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