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 }