001package io.ebean.common; 002 003import io.ebean.bean.BeanCollection; 004import io.ebean.bean.BeanCollectionAdd; 005import io.ebean.bean.BeanCollectionLoader; 006import io.ebean.bean.EntityBean; 007 008import java.io.Serializable; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.Iterator; 013import java.util.List; 014import java.util.ListIterator; 015 016/** 017 * List capable of lazy loading. 018 */ 019public final class BeanList<E> extends AbstractBeanCollection<E> implements List<E>, BeanCollectionAdd { 020 021 private static final long serialVersionUID = 1L; 022 023 /** 024 * The underlying List implementation. 025 */ 026 private List<E> list; 027 028 /** 029 * Specify the underlying List implementation. 030 */ 031 public BeanList(List<E> list) { 032 super(); 033 this.list = list; 034 } 035 036 /** 037 * Uses an ArrayList as the underlying List implementation. 038 */ 039 public BeanList() { 040 this(new ArrayList<>()); 041 } 042 043 /** 044 * Used to create deferred fetch proxy. 045 */ 046 public BeanList(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) { 047 super(loader, ownerBean, propertyName); 048 } 049 050 @Override 051 public void reset(EntityBean ownerBean, String propertyName) { 052 this.ownerBean = ownerBean; 053 this.propertyName = propertyName; 054 this.list = null; 055 } 056 057 @Override 058 public boolean isSkipSave() { 059 return list == null || (list.isEmpty() && !holdsModifications()); 060 } 061 062 @Override 063 @SuppressWarnings("unchecked") 064 public void addEntityBean(EntityBean bean) { 065 list.add((E) bean); 066 } 067 068 @Override 069 @SuppressWarnings("unchecked") 070 public void loadFrom(BeanCollection<?> other) { 071 if (list == null) { 072 list = new ArrayList<>(); 073 } 074 list.addAll((Collection<? extends E>) other.getActualDetails()); 075 } 076 077 @Override 078 @SuppressWarnings("unchecked") 079 public void internalAdd(Object bean) { 080 if (list == null) { 081 list = new ArrayList<>(); 082 } 083 if (bean != null) { 084 list.add((E) bean); 085 } 086 } 087 088 @Override 089 public void internalAddWithCheck(Object bean) { 090 if (list == null || !containsInstance(bean)) { 091 internalAdd(bean); 092 } 093 } 094 095 /** 096 * Contains using instance equality for List (specifically not .equals() based). 097 */ 098 private boolean containsInstance(Object bean) { 099 for (Object element : list) { 100 if (element == bean) { 101 return true; 102 } 103 } 104 return false; 105 } 106 107 @Override 108 public boolean checkEmptyLazyLoad() { 109 if (list == null) { 110 list = new ArrayList<>(); 111 return true; 112 } else { 113 return false; 114 } 115 } 116 117 private void initClear() { 118 synchronized (this) { 119 if (list == null) { 120 if (!disableLazyLoad && modifyListening) { 121 lazyLoadCollection(true); 122 } else { 123 list = new ArrayList<>(); 124 } 125 } 126 } 127 } 128 129 private void init() { 130 synchronized (this) { 131 if (list == null) { 132 if (disableLazyLoad) { 133 list = new ArrayList<>(); 134 } else { 135 lazyLoadCollection(false); 136 } 137 } 138 } 139 } 140 141 /** 142 * Set the actual underlying list. 143 * <p> 144 * This is primarily for the deferred fetching function. 145 * </p> 146 */ 147 @SuppressWarnings("unchecked") 148 public void setActualList(List<?> list) { 149 this.list = (List<E>) list; 150 } 151 152 /** 153 * Return the actual underlying list. 154 */ 155 public List<E> getActualList() { 156 return list; 157 } 158 159 @Override 160 public Collection<E> getActualDetails() { 161 return list; 162 } 163 164 @Override 165 public Collection<?> getActualEntries() { 166 return list; 167 } 168 169 /** 170 * Return true if the underlying list is populated. 171 */ 172 @Override 173 public boolean isPopulated() { 174 return list != null; 175 } 176 177 /** 178 * Return true if this is a reference (lazy loading) bean collection. This is 179 * the same as !isPopulated(); 180 */ 181 @Override 182 public boolean isReference() { 183 return list == null; 184 } 185 186 @Override 187 public String toString() { 188 StringBuilder sb = new StringBuilder(50); 189 sb.append("BeanList "); 190 if (isReadOnly()) { 191 sb.append("readOnly "); 192 } 193 if (list == null) { 194 sb.append("deferred "); 195 196 } else { 197 sb.append("size[").append(list.size()).append("] "); 198 sb.append("list").append(list).append(""); 199 } 200 return sb.toString(); 201 } 202 203 /** 204 * Equal if obj is a List and equal in a list sense. 205 * <p> 206 * Specifically obj does not need to be a BeanList but any list. This does not 207 * use the FindMany, fetchedMaxRows or finishedFetch properties in the equals 208 * test. 209 * </p> 210 */ 211 @Override 212 public boolean equals(Object obj) { 213 init(); 214 return list.equals(obj); 215 } 216 217 @Override 218 public int hashCode() { 219 init(); 220 return list.hashCode(); 221 } 222 223 // -----------------------------------------------------// 224 // The additional methods are here 225 // -----------------------------------------------------// 226 227 // -----------------------------------------------------// 228 // proxy method for List 229 // -----------------------------------------------------// 230 231 @Override 232 public void add(int index, E element) { 233 checkReadOnly(); 234 init(); 235 if (modifyListening) { 236 modifyAddition(element); 237 } 238 list.add(index, element); 239 } 240 241 @Override 242 public void addBean(E bean) { 243 add(bean); 244 } 245 246 @Override 247 public boolean add(E o) { 248 checkReadOnly(); 249 init(); 250 if (modifyListening) { 251 if (list.add(o)) { 252 modifyAddition(o); 253 return true; 254 } else { 255 return false; 256 } 257 } 258 return list.add(o); 259 } 260 261 @Override 262 public boolean addAll(Collection<? extends E> c) { 263 checkReadOnly(); 264 init(); 265 if (modifyListening) { 266 // all elements in c are added (no contains checking) 267 getModifyHolder().modifyAdditionAll(c); 268 } 269 return list.addAll(c); 270 } 271 272 @Override 273 public boolean addAll(int index, Collection<? extends E> c) { 274 checkReadOnly(); 275 init(); 276 if (modifyListening) { 277 // all elements in c are added (no contains checking) 278 getModifyHolder().modifyAdditionAll(c); 279 } 280 return list.addAll(index, c); 281 } 282 283 @Override 284 public void clear() { 285 checkReadOnly(); 286 // TODO: when clear() and not initialised could be more clever 287 // and fetch just the Id's 288 initClear(); 289 if (modifyListening) { 290 for (E aList : list) { 291 getModifyHolder().modifyRemoval(aList); 292 } 293 } 294 list.clear(); 295 } 296 297 @Override 298 public boolean contains(Object o) { 299 init(); 300 return list.contains(o); 301 } 302 303 @Override 304 public boolean containsAll(Collection<?> c) { 305 init(); 306 return list.containsAll(c); 307 } 308 309 @Override 310 public E get(int index) { 311 init(); 312 return list.get(index); 313 } 314 315 @Override 316 public int indexOf(Object o) { 317 init(); 318 return list.indexOf(o); 319 } 320 321 @Override 322 public boolean isEmpty() { 323 init(); 324 return list.isEmpty(); 325 } 326 327 @Override 328 public Iterator<E> iterator() { 329 init(); 330 if (isReadOnly()) { 331 return new ReadOnlyListIterator<>(list.listIterator()); 332 } 333 if (modifyListening) { 334 Iterator<E> it = list.iterator(); 335 return new ModifyIterator<>(this, it); 336 } 337 return list.iterator(); 338 } 339 340 @Override 341 public int lastIndexOf(Object o) { 342 init(); 343 return list.lastIndexOf(o); 344 } 345 346 @Override 347 public ListIterator<E> listIterator() { 348 init(); 349 if (isReadOnly()) { 350 return new ReadOnlyListIterator<>(list.listIterator()); 351 } 352 if (modifyListening) { 353 ListIterator<E> it = list.listIterator(); 354 return new ModifyListIterator<>(this, it); 355 } 356 return list.listIterator(); 357 } 358 359 @Override 360 public ListIterator<E> listIterator(int index) { 361 init(); 362 if (isReadOnly()) { 363 return new ReadOnlyListIterator<>(list.listIterator(index)); 364 } 365 if (modifyListening) { 366 ListIterator<E> it = list.listIterator(index); 367 return new ModifyListIterator<>(this, it); 368 } 369 return list.listIterator(index); 370 } 371 372 @Override 373 public void removeBean(E bean) { 374 if (list.remove(bean)) { 375 getModifyHolder().modifyRemoval(bean); 376 } 377 } 378 379 @Override 380 public E remove(int index) { 381 checkReadOnly(); 382 init(); 383 if (modifyListening) { 384 E o = list.remove(index); 385 modifyRemoval(o); 386 return o; 387 } 388 return list.remove(index); 389 } 390 391 @Override 392 public boolean remove(Object o) { 393 checkReadOnly(); 394 init(); 395 if (modifyListening) { 396 boolean isRemove = list.remove(o); 397 if (isRemove) { 398 modifyRemoval(o); 399 } 400 return isRemove; 401 } 402 return list.remove(o); 403 } 404 405 @Override 406 public boolean removeAll(Collection<?> beans) { 407 checkReadOnly(); 408 init(); 409 if (modifyListening) { 410 boolean changed = false; 411 for (Object bean : beans) { 412 if (list.remove(bean)) { 413 // register this bean as having been removed 414 modifyRemoval(bean); 415 changed = true; 416 } 417 } 418 return changed; 419 } 420 return list.removeAll(beans); 421 } 422 423 @Override 424 public boolean retainAll(Collection<?> retainBeans) { 425 checkReadOnly(); 426 init(); 427 if (modifyListening) { 428 boolean changed = false; 429 Iterator<E> it = list.iterator(); 430 while (it.hasNext()) { 431 Object bean = it.next(); 432 if (!retainBeans.contains(bean)) { 433 // removing this bean 434 it.remove(); 435 modifyRemoval(bean); 436 changed = true; 437 } 438 } 439 return changed; 440 } 441 return list.retainAll(retainBeans); 442 } 443 444 @Override 445 public E set(int index, E element) { 446 checkReadOnly(); 447 init(); 448 if (modifyListening) { 449 E o = list.set(index, element); 450 modifyAddition(element); 451 modifyRemoval(o); 452 return o; 453 } 454 return list.set(index, element); 455 } 456 457 @Override 458 public int size() { 459 init(); 460 return list.size(); 461 } 462 463 @Override 464 public List<E> subList(int fromIndex, int toIndex) { 465 init(); 466 if (isReadOnly()) { 467 return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); 468 } 469 if (modifyListening) { 470 return new ModifyList<>(this, list.subList(fromIndex, toIndex)); 471 } 472 return list.subList(fromIndex, toIndex); 473 } 474 475 @Override 476 public Object[] toArray() { 477 init(); 478 return list.toArray(); 479 } 480 481 @Override 482 public <T> T[] toArray(T[] a) { 483 init(); 484 //noinspection SuspiciousToArrayCall 485 return list.toArray(a); 486 } 487 488 private static class ReadOnlyListIterator<E> implements ListIterator<E>, Serializable { 489 490 private static final long serialVersionUID = 3097271091406323699L; 491 492 private final ListIterator<E> i; 493 494 ReadOnlyListIterator(ListIterator<E> i) { 495 this.i = i; 496 } 497 498 @Override 499 public void add(E o) { 500 throw new IllegalStateException("This collection is in ReadOnly mode"); 501 } 502 503 @Override 504 public void remove() { 505 throw new IllegalStateException("This collection is in ReadOnly mode"); 506 } 507 508 @Override 509 public void set(E o) { 510 throw new IllegalStateException("This collection is in ReadOnly mode"); 511 } 512 513 @Override 514 public boolean hasNext() { 515 return i.hasNext(); 516 } 517 518 @Override 519 public boolean hasPrevious() { 520 return i.hasPrevious(); 521 } 522 523 @Override 524 public E next() { 525 return i.next(); 526 } 527 528 @Override 529 public int nextIndex() { 530 return i.nextIndex(); 531 } 532 533 @Override 534 public E previous() { 535 return i.previous(); 536 } 537 538 @Override 539 public int previousIndex() { 540 return i.previousIndex(); 541 } 542 543 } 544 545 @Override 546 public BeanCollection<E> getShallowCopy() { 547 BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list)); 548 copy.setFromOriginal(this); 549 return copy; 550 } 551}