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 }