001    /*
002      GRANITE DATA SERVICES
003      Copyright (C) 2012 GRANITE DATA SERVICES S.A.S.
004    
005      This file is part of Granite Data Services.
006    
007      Granite Data Services is free software; you can redistribute it and/or modify
008      it under the terms of the GNU Library General Public License as published by
009      the Free Software Foundation; either version 2 of the License, or (at your
010      option) any later version.
011    
012      Granite Data Services is distributed in the hope that it will be useful, but
013      WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
014      FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
015      for more details.
016    
017      You should have received a copy of the GNU Library General Public License
018      along with this library; if not, see <http://www.gnu.org/licenses/>.
019    */
020    
021    package org.granite.client.tide.collections;
022    
023    import java.lang.reflect.Array;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.ListIterator;
030    import java.util.Set;
031    
032    import javax.annotation.PreDestroy;
033    
034    import org.granite.client.tide.data.EntityManager.UpdateKind;
035    import org.granite.client.tide.events.TideEvent;
036    import org.granite.client.tide.events.TideEventObserver;
037    import org.granite.client.tide.server.TideFaultEvent;
038    import org.granite.client.tide.server.TideResponder;
039    import org.granite.client.tide.server.TideResultEvent;
040    import org.granite.client.tide.server.TideRpcEvent;
041    import org.granite.logging.Logger;
042    import org.granite.tide.data.model.Page;
043    
044    /**
045     * @author William DRAI
046     */
047    public abstract class AbstractPagedCollection<E> implements List<E>, TideEventObserver {
048            
049        private static final Logger log = Logger.getLogger(AbstractPagedCollection.class);
050        
051            
052            /**
053             *      @private
054             */
055        protected boolean initializing = false;
056        private boolean initSent = false;
057        
058            /**
059             *      @private
060             */
061            protected int first;
062            /**
063             *      @private
064             */
065        protected int last;                 // Current last index of local data
066            /**
067             *      @private
068             */
069        protected int max;           // Page size
070            /**
071             *      @private
072             */
073        protected int count;         // Result count
074        private E[] localIndex = null;
075        
076            /**
077             *      @private
078             */
079            protected boolean fullRefresh = false;
080            /**
081             *      @private
082             */
083            protected boolean filterRefresh = false;
084            
085    
086            public AbstractPagedCollection() {
087                    super();
088                log.debug("create collection");
089                    first = 0;
090                    last = 0;
091                    count = 0;
092                    initializing = true;
093            }
094            
095            
096            /**
097             *      Get total number of elements
098             *  
099             *  @return collection total length
100             */
101            @Override
102            public int size() {
103                    if (initialFind())
104                    return 0;
105                else if (localIndex == null)
106                    return 0;
107                    return count;
108            }
109            
110            /**
111             *  Set the page size. The collection will store in memory twice this page size, and each server call
112             *  will return at most the page size.
113             * 
114             *  @param max maximum number of requested elements
115             */
116            public void setMaxResults(int max) {
117                    this.max = max;
118            }
119            
120            
121            private Class<? extends E> elementClass;
122            private String elementName;     
123            private Set<String> entityNames = new HashSet<String>();
124            
125            public void setElementClass(Class<? extends E> elementClass) {
126                    this.elementClass = elementClass;
127                    
128                    if (this.elementName != null)
129                            entityNames.remove(elementName);
130                    
131                    elementName = elementClass != null ? elementClass.getSimpleName() : null;
132                    
133                    if (this.elementName != null)
134                            entityNames.add(this.elementName);
135            }
136    
137            @Override
138            public void handleEvent(TideEvent event) {
139                    if (event.getType().startsWith(UpdateKind.REFRESH.eventName() + ".")) {
140                            String entityName = event.getType().substring(UpdateKind.REFRESH.eventName().length()+1);
141                            if (entityNames.contains(entityName))
142                                    fullRefresh();
143                    }
144            }       
145            
146            
147            /**
148             *      Clear collection content
149             */
150            @Override
151            @PreDestroy
152            public void clear() {
153                    initializing = true;
154                    initSent = false;
155                    getInternalWrappedList().clear();
156                    clearLocalIndex();
157                    first = 0;
158                    last = first+max;
159                    fullRefresh = false;
160                    filterRefresh = false;
161            }
162            
163            
164            private List<Integer[]> pendingRanges = new ArrayList<Integer[]>();
165            
166            /**
167             *      Abstract method: trigger a results query for the current filter
168             *      @param first    : index of first required result
169             *  @param last     : index of last required result
170             */
171            protected void find(int first, int last) {
172                    log.debug("find from %d to %d", first, last);
173                    
174                    pendingRanges.add(new Integer[] { first, last });
175            }
176            
177            
178            /**
179             *      Force refresh of collection when filter/sort have been changed
180             * 
181             *  @return always false
182             */
183            public boolean fullRefresh() {
184                this.fullRefresh = true;
185                return refresh();
186            }
187            
188            /**
189             *      Refresh collection with new filter/sort parameters
190             * 
191             *  @return always false
192             */
193            public boolean refresh() {
194                    // Recheck sort fields to listen for asc/desc change events
195                    pendingRanges.clear();
196                    
197                    if (fullRefresh) {
198                            log.debug("full refresh");
199                            
200                            clearLocalIndex();
201                            
202                            fullRefresh = false;
203                            if (filterRefresh) {
204                                first = 0;
205                                last = first+max;
206                                filterRefresh = false;
207                            }
208            }
209            else
210                            log.debug("refresh");                   
211            
212                    if (!initialFind())
213                            find(first, last);
214                    return true;
215            }
216            
217            private boolean initialFind() {
218                    if (max > 0 && !initializing)
219                            return false;
220                    
221                    if (!initSent) {
222                            log.debug("initial find");
223                            find(0, max);
224                            initSent = true;
225                    }
226                    return true;
227            }
228            
229            private void clearLocalIndex() {
230                    localIndex = null;
231            }
232            
233            /**
234             *  Build a result object from the result event
235             *  
236             *  @param event the result event
237             *  @param first first index requested
238             *  @param max max elements requested
239             *   
240             *  @return an object containing data from the collection
241             *      resultList   : the retrieved data
242             *      resultCount  : the total count of elements (non paged)
243             *      firstResult  : the index of the first retrieved element
244             *      maxResults   : the maximum count of retrieved elements 
245             */
246            protected abstract Page<E> getResult(TideResultEvent<?> event, int first, int max);
247            
248            
249            /**
250             *  Notify listeners of remote page result
251             *  
252             *  @param event the remote event (ResultEvent or FaultEvent)
253             */
254            protected abstract void firePageChange(TideRpcEvent event);
255            
256            
257            /**
258             *      @private
259             *  Initialize collection after first find
260             *   
261             *  @param the result event of the first find
262             */
263            protected void initialize(TideResultEvent<?> event) {
264            }
265            
266            /**
267             *      @private
268             *      Event handler for results query
269             * 
270             *  @param event the result event
271             *  @param first first requested index
272             *  @param max max elements requested
273             */
274            protected void findResult(TideResultEvent<?> event, int first, int max) {
275                Page<E> page = getResult(event, first, max);
276            
277                handleResult(page, event, first, max);
278            }
279            
280            /**
281             *      @private
282             *      Event handler for results query
283             * 
284             *  @param result the result object
285             *  @param event the result event
286             */
287            @SuppressWarnings("unchecked")
288            protected void handleResult(Page<E> page, TideResultEvent<?> event, int first, int max) {
289                    List<E> list = (List<E>)page.getResultList();
290    
291                    for (Iterator<Integer[]> ipr = pendingRanges.iterator(); ipr.hasNext(); ) {
292                            Integer[] pr = ipr.next();
293                            if (pr[0] == first && pr[1] == first+max) {
294                                    ipr.remove();
295                                    break;
296                            }
297                    }
298                    
299                    if (initializing && event != null) {
300                            if (this.max == 0 && page.getMaxResults() > 0)
301                            this.max = page.getMaxResults();
302                        initialize(event);
303                    }
304                    
305                    int nextFirst = (Integer)page.getFirstResult();
306                    int nextLast = nextFirst + (Integer)page.getMaxResults();
307                    
308                    int pageNum = max > 0 ? nextFirst / max : 0;
309                    log.debug("handle result page %d (%d - %d)", pageNum, nextFirst, nextLast);
310    
311            if (!initializing) {
312                    log.debug("Adjusting from %d-%d to %d-%d size %d", AbstractPagedCollection.this.first, AbstractPagedCollection.this.last, nextFirst, nextLast, list.size());
313                    // Adjust internal list to expected results without triggering events
314                    if (nextFirst > AbstractPagedCollection.this.first && nextFirst < AbstractPagedCollection.this.last) {
315                            getInternalWrappedList().subList(0, nextFirst - AbstractPagedCollection.this.first).clear();
316                            for (int i = 0; i < nextFirst - AbstractPagedCollection.this.first && AbstractPagedCollection.this.last - nextFirst + i < list.size(); i++) {
317                                    E elt = list.get(AbstractPagedCollection.this.last - nextFirst + i);
318                                    getInternalWrappedList().add(elt);
319                            }
320                    }
321                    else if (nextFirst == AbstractPagedCollection.this.first && nextLast > AbstractPagedCollection.this.last) {
322                            for (int i = 0; i < (nextLast-nextFirst)-(AbstractPagedCollection.this.last-AbstractPagedCollection.this.first) && AbstractPagedCollection.this.last + i < list.size(); i++) {
323                                    E elt = list.get(AbstractPagedCollection.this.last + i);
324                                    getInternalWrappedList().add(elt);
325                            }
326                    }
327                    else if (nextLast > AbstractPagedCollection.this.first && nextLast < AbstractPagedCollection.this.last) {
328                            if (nextLast-AbstractPagedCollection.this.first < getInternalWrappedList().size())
329                                    getInternalWrappedList().subList(nextLast-AbstractPagedCollection.this.first, getInternalWrappedList().size()).clear();
330                            else
331                                    getInternalWrappedList().clear();
332                            for (int i = 0; i < AbstractPagedCollection.this.first - nextFirst && i < list.size(); i++) {
333                                    E elt = list.get(i);
334                                    getInternalWrappedList().add(i, elt);
335                            }
336                    }
337                    else if (nextFirst >= AbstractPagedCollection.this.last || nextLast <= AbstractPagedCollection.this.first) {
338                            getInternalWrappedList().clear();
339                            for (int i = 0; i < list.size(); i++) {
340                                    E elt = list.get(i);
341                                    getInternalWrappedList().add(i, elt);
342                            }
343                    }
344            }
345            else
346                    getWrappedList().addAll(list);
347                    
348                    count = page.getResultCount();
349                    
350                initializing = false;
351                    
352                if (localIndex != null) {
353                    List<String> entityNames = new ArrayList<String>();
354                    for (int i = 0; i < localIndex.length; i++) {
355                                    String entityName = localIndex[i].getClass().getSimpleName();
356                                    if (!entityName.equals(elementName))
357                                            entityNames.remove(entityName);
358                    }
359                }
360                for (Object o : list) {
361                    if (elementClass == null || (o != null && o.getClass().isAssignableFrom(elementClass)))
362                            elementClass = (Class<? extends E>)o.getClass();
363                }
364                localIndex = (E[])Array.newInstance(elementClass, list.size());
365                    localIndex = list.toArray(localIndex);
366                if (localIndex != null) {
367                    for (int i = 0; i < localIndex.length; i++) {
368                                    String entityName = localIndex[i].getClass().getSimpleName();
369                                    if (!entityName.equals(elementName))
370                                            entityNames.add(entityName);
371                    }
372                }
373                
374                    this.first = nextFirst;
375                    this.last = nextLast;
376                
377                    pendingRanges.clear();
378                    
379                    firePageChange(event);
380            }
381            
382            /**
383             *  @private
384             *      Event handler for results fault
385             *  
386             *  @param event the fault event
387             *  @param first first requested index
388             *  @param max max elements requested
389             */
390            protected void findFault(TideFaultEvent event, int first, int max) {
391                    handleFault(event);
392            }
393            
394            /**
395             *      @private
396             *      Event handler for results query fault
397             * 
398             *  @param event the fault event
399             */
400            protected void handleFault(TideFaultEvent event) {
401                    log.debug("findFault: %s", event);
402                    
403                    for (Iterator<Integer[]> ipr = pendingRanges.iterator(); ipr.hasNext(); ) {
404                            Integer[] pr = ipr.next();
405                            if (pr[0] == first && pr[1] == first+max) {
406                                    ipr.remove();
407                                    break;
408                            }
409                    }
410                
411                    if (initializing)
412                            initSent = false;
413                    
414                    firePageChange(event);
415            }
416            
417            
418            protected abstract List<E> getInternalWrappedList();
419            
420            protected abstract List<E> getWrappedList();
421            
422            
423            /**
424             *      Override of getItemAt with ItemPendingError management
425             * 
426             *      @param index index of requested item
427             *      @param prefetch not used
428             *  @return object at specified index
429             */
430            @Override
431            public E get(int index) {
432                    if (index < 0)
433                            return null;
434            
435                    if (initialFind())
436                            return null;
437    
438                    if (localIndex != null && index >= first && index < last) {       // Local data available for index
439                        int j = index-first;
440                        if (j >= 0 && j < localIndex.length)
441                            return localIndex[j];
442                        // Index not in current loaded range, max is more than last page size
443                        return null;
444                    }
445                    
446                    // If already in a pending range, return null
447                    for (Integer[] pendingRange : pendingRanges) {
448                            if (index >= pendingRange[0] && index < pendingRange[1])
449                                    return null;
450                    }
451                
452                int page = index / max;
453                
454                    // Trigger a results query for requested page
455                    int nfi = 0;
456                    int nla = 0;
457                    @SuppressWarnings("unused")
458                    int idx = page * max;
459                    if (index >= last && index < last + max) {
460                            nfi = first;
461                            nla = last + max;
462                            if (nla > nfi + 2*max)
463                                nfi = nla - 2*max;
464                            if (nfi < 0)
465                                nfi = 0;
466                            if (nla > count)
467                                nla = count;
468                    }
469                    else if (index < first && index >= first - max) {
470                            nfi = first - max;
471                            if (nfi < 0)
472                                    nfi = 0;
473                            nla = last;
474                            if (nla > nfi + 2*max)
475                                nla = nfi + 2*max;
476                            if (nla > count)
477                                nla = count;
478                    }
479                    else {
480                            nfi = index - max;
481                            nla = nfi + 2 * max;
482                            if (nfi < 0)
483                                    nfi = 0;
484                            if (nla > count)
485                                nla = count;
486                    }
487                    log.debug("request find for index " + index);
488                    find(nfi, nla);
489                    return null;
490            }
491            
492            
493            @Override
494            public boolean isEmpty() {
495                    return size() == 0;
496            }
497    
498    
499            @Override
500            public boolean contains(Object o) {
501                    if (o == null)
502                            return false;
503                    
504                    if (localIndex != null) {
505                            for (Object obj : localIndex) {
506                                    if (o.equals(obj))
507                                            return true;
508                            }
509                    }
510                    return false;
511            }
512    
513            @Override
514            public boolean containsAll(Collection<?> c) {
515                    return false;
516            }
517    
518            @Override
519            public int indexOf(Object o) {
520                    if (o == null)
521                            return -1;
522                    
523                    if (localIndex != null) {
524                            for (int i = 0; i < localIndex.length; i++) {
525                                    if (o.equals(localIndex[i]))
526                                            return first+i;;
527                            }
528                    }
529                    return -1;
530            }
531    
532            @Override
533            public int lastIndexOf(Object o) {
534                    if (o == null)
535                            return -1;
536                                    
537                    if (localIndex != null) {
538                            int index = -1;
539                            for (int i = 0; i < localIndex.length; i++) {
540                                    if (o.equals(localIndex[i]))
541                                            index = first+i;;
542                            }
543                            return index;
544                    }
545                    return -1;
546            }
547    
548            @Override
549            public Iterator<E> iterator() {
550                    return new PagedCollectionIterator();
551            }
552    
553            @Override
554            public ListIterator<E> listIterator() {
555                    return new PagedCollectionIterator();
556            }
557    
558            @Override
559            public ListIterator<E> listIterator(int index) {
560                    return new PagedCollectionIterator();
561            }
562            
563            
564            @Override
565            public boolean add(E e) {
566                    throw new UnsupportedOperationException();
567            }
568    
569            @Override
570            public void add(int index, E element) {
571                    throw new UnsupportedOperationException();
572            }
573    
574            @Override
575            public boolean addAll(Collection<? extends E> c) {
576                    throw new UnsupportedOperationException();
577            }
578    
579            @Override
580            public boolean addAll(int index, Collection<? extends E> c) {
581                    throw new UnsupportedOperationException();
582            }
583            
584            @Override
585            public boolean remove(Object o) {
586                    throw new UnsupportedOperationException();
587            }
588    
589            @Override
590            public E remove(int index) {
591                    throw new UnsupportedOperationException();
592            }
593    
594            @Override
595            public boolean removeAll(Collection<?> c) {
596                    throw new UnsupportedOperationException();
597            }
598    
599            @Override
600            public boolean retainAll(Collection<?> c) {
601                    throw new UnsupportedOperationException();
602            }
603    
604            @Override
605            public E set(int index, E element) {
606                    throw new UnsupportedOperationException();
607            }
608    
609            @Override
610            public List<E> subList(int fromIndex, int toIndex) {
611                    throw new UnsupportedOperationException();
612            }
613        
614    //    protected void itemUpdateHandler(PropertyChangeEvent event) {
615    //              if (hasEventListener(CollectionEvent.COLLECTION_CHANGE)) {
616    //              var ce:CollectionEvent = new CollectionEvent(CollectionEvent.COLLECTION_CHANGE);
617    //              ce.kind = CollectionEventKind.UPDATE;
618    //              ce.items.push(event);
619    //              ce.location = -1;
620    //              dispatchEvent(ce);
621    //          }
622    //    }
623    
624            
625            public class PagedCollectionIterator implements ListIterator<E> {
626                    
627                    private ListIterator<E> wrappedListIterator;
628                    
629                    public PagedCollectionIterator() {
630                            wrappedListIterator = getWrappedList().listIterator();
631                    }
632    
633                    public PagedCollectionIterator(int index) {
634                            wrappedListIterator = getWrappedList().listIterator(index);
635                    }
636    
637                    @Override
638                    public boolean hasNext() {
639                            return wrappedListIterator.hasNext();
640                    }
641            
642                    @Override
643                    public E next() {
644                            return wrappedListIterator.next();
645                    }
646            
647                    @Override
648                    public boolean hasPrevious() {
649                            return wrappedListIterator.hasPrevious();
650                    }
651            
652                    @Override
653                    public E previous() {
654                            return wrappedListIterator.previous();
655                    }
656            
657                    @Override
658                    public int nextIndex() {
659                            return wrappedListIterator.nextIndex();
660                    }
661            
662                    @Override
663                    public int previousIndex() {
664                            return wrappedListIterator.previousIndex();
665                    }
666            
667                    @Override
668                    public void remove() {
669                            throw new UnsupportedOperationException();
670                    }
671            
672                    @Override
673                    public void set(E e) {
674                            throw new UnsupportedOperationException();
675                    }
676            
677                    @Override
678                    public void add(E e) {
679                            throw new UnsupportedOperationException();
680                    }
681                    
682            }
683            
684            
685            public class PagedCollectionResponder implements TideResponder<Object> {
686                
687                private int first;
688                private int max;
689                
690                
691                public PagedCollectionResponder(int first, int max) {
692                    this.first = first;
693                    this.max = max;
694                }
695                
696                @Override
697                public void result(TideResultEvent<Object> event) {
698                findResult(event, first, max);
699                }
700                
701                public void fault(TideFaultEvent event) {
702                findFault(event, first, max);
703                } 
704            }
705    }