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}