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