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 || bean == 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    lock.lock();
119    try {
120      if (list == null) {
121        if (!disableLazyLoad && modifyListening) {
122          lazyLoadCollection(true);
123        } else {
124          list = new ArrayList<>();
125        }
126      }
127    } finally {
128      lock.unlock();
129    }
130  }
131
132  private void init() {
133    lock.lock();
134    try {
135      if (list == null) {
136        if (disableLazyLoad) {
137          list = new ArrayList<>();
138        } else {
139          lazyLoadCollection(false);
140        }
141      }
142    } finally {
143      lock.unlock();
144    }
145  }
146
147  /**
148   * Set the actual underlying list.
149   * <p>
150   * This is primarily for the deferred fetching function.
151   * </p>
152   */
153  @SuppressWarnings("unchecked")
154  public void setActualList(List<?> list) {
155    this.list = (List<E>) list;
156  }
157
158  /**
159   * Return the actual underlying list.
160   */
161  public List<E> getActualList() {
162    return list;
163  }
164
165  @Override
166  public Collection<E> getActualDetails() {
167    return list;
168  }
169
170  @Override
171  public Collection<?> getActualEntries() {
172    return list;
173  }
174
175  /**
176   * Return true if the underlying list is populated.
177   */
178  @Override
179  public boolean isPopulated() {
180    return list != null;
181  }
182
183  /**
184   * Return true if this is a reference (lazy loading) bean collection. This is
185   * the same as !isPopulated();
186   */
187  @Override
188  public boolean isReference() {
189    return list == null;
190  }
191
192  @Override
193  public String toString() {
194    StringBuilder sb = new StringBuilder(50);
195    sb.append("BeanList ");
196    if (isReadOnly()) {
197      sb.append("readOnly ");
198    }
199    if (list == null) {
200      sb.append("deferred ");
201    } else {
202      sb.append("size[").append(list.size()).append("] ");
203      sb.append("list").append(list);
204    }
205    return sb.toString();
206  }
207
208  /**
209   * Equal if obj is a List and equal in a list sense.
210   * <p>
211   * Specifically obj does not need to be a BeanList but any list. This does not
212   * use the FindMany, fetchedMaxRows or finishedFetch properties in the equals
213   * test.
214   * </p>
215   */
216  @Override
217  public boolean equals(Object obj) {
218    init();
219    return list.equals(obj);
220  }
221
222  @Override
223  public int hashCode() {
224    init();
225    return list.hashCode();
226  }
227
228  // -----------------------------------------------------//
229  // The additional methods are here
230  // -----------------------------------------------------//
231
232  // -----------------------------------------------------//
233  // proxy method for List
234  // -----------------------------------------------------//
235
236  @Override
237  public void add(int index, E element) {
238    checkReadOnly();
239    init();
240    if (modifyListening) {
241      modifyAddition(element);
242    }
243    list.add(index, element);
244  }
245
246  @Override
247  public void addBean(E bean) {
248    add(bean);
249  }
250
251  @Override
252  public boolean add(E o) {
253    checkReadOnly();
254    init();
255    if (modifyListening) {
256      if (list.add(o)) {
257        modifyAddition(o);
258        return true;
259      } else {
260        return false;
261      }
262    }
263    return list.add(o);
264  }
265
266  @Override
267  public boolean addAll(Collection<? extends E> c) {
268    checkReadOnly();
269    init();
270    if (modifyListening) {
271      // all elements in c are added (no contains checking)
272      getModifyHolder().modifyAdditionAll(c);
273    }
274    return list.addAll(c);
275  }
276
277  @Override
278  public boolean addAll(int index, Collection<? extends E> c) {
279    checkReadOnly();
280    init();
281    if (modifyListening) {
282      // all elements in c are added (no contains checking)
283      getModifyHolder().modifyAdditionAll(c);
284    }
285    return list.addAll(index, c);
286  }
287
288  @Override
289  public void clear() {
290    checkReadOnly();
291    // TODO: when clear() and not initialised could be more clever
292    // and fetch just the Id's
293    initClear();
294    if (modifyListening) {
295      for (E aList : list) {
296        getModifyHolder().modifyRemoval(aList);
297      }
298    }
299    list.clear();
300  }
301
302  @Override
303  public boolean contains(Object o) {
304    init();
305    return list.contains(o);
306  }
307
308  @Override
309  public boolean containsAll(Collection<?> c) {
310    init();
311    return list.containsAll(c);
312  }
313
314  @Override
315  public E get(int index) {
316    init();
317    return list.get(index);
318  }
319
320  @Override
321  public int indexOf(Object o) {
322    init();
323    return list.indexOf(o);
324  }
325
326  @Override
327  public boolean isEmpty() {
328    init();
329    return list.isEmpty();
330  }
331
332  @Override
333  public Iterator<E> iterator() {
334    init();
335    if (isReadOnly()) {
336      return new ReadOnlyListIterator<>(list.listIterator());
337    }
338    if (modifyListening) {
339      Iterator<E> it = list.iterator();
340      return new ModifyIterator<>(this, it);
341    }
342    return list.iterator();
343  }
344
345  @Override
346  public int lastIndexOf(Object o) {
347    init();
348    return list.lastIndexOf(o);
349  }
350
351  @Override
352  public ListIterator<E> listIterator() {
353    init();
354    if (isReadOnly()) {
355      return new ReadOnlyListIterator<>(list.listIterator());
356    }
357    if (modifyListening) {
358      ListIterator<E> it = list.listIterator();
359      return new ModifyListIterator<>(this, it);
360    }
361    return list.listIterator();
362  }
363
364  @Override
365  public ListIterator<E> listIterator(int index) {
366    init();
367    if (isReadOnly()) {
368      return new ReadOnlyListIterator<>(list.listIterator(index));
369    }
370    if (modifyListening) {
371      ListIterator<E> it = list.listIterator(index);
372      return new ModifyListIterator<>(this, it);
373    }
374    return list.listIterator(index);
375  }
376
377  @Override
378  public void removeBean(E bean) {
379    if (list.remove(bean)) {
380      getModifyHolder().modifyRemoval(bean);
381    }
382  }
383
384  @Override
385  public E remove(int index) {
386    checkReadOnly();
387    init();
388    if (modifyListening) {
389      E o = list.remove(index);
390      modifyRemoval(o);
391      return o;
392    }
393    return list.remove(index);
394  }
395
396  @Override
397  public boolean remove(Object o) {
398    checkReadOnly();
399    init();
400    if (modifyListening) {
401      boolean isRemove = list.remove(o);
402      if (isRemove) {
403        modifyRemoval(o);
404      }
405      return isRemove;
406    }
407    return list.remove(o);
408  }
409
410  @Override
411  public boolean removeAll(Collection<?> beans) {
412    checkReadOnly();
413    init();
414    if (modifyListening) {
415      boolean changed = false;
416      for (Object bean : beans) {
417        if (list.remove(bean)) {
418          // register this bean as having been removed
419          modifyRemoval(bean);
420          changed = true;
421        }
422      }
423      return changed;
424    }
425    return list.removeAll(beans);
426  }
427
428  @Override
429  public boolean retainAll(Collection<?> retainBeans) {
430    checkReadOnly();
431    init();
432    if (modifyListening) {
433      boolean changed = false;
434      Iterator<E> it = list.iterator();
435      while (it.hasNext()) {
436        Object bean = it.next();
437        if (!retainBeans.contains(bean)) {
438          // removing this bean
439          it.remove();
440          modifyRemoval(bean);
441          changed = true;
442        }
443      }
444      return changed;
445    }
446    return list.retainAll(retainBeans);
447  }
448
449  @Override
450  public E set(int index, E element) {
451    checkReadOnly();
452    init();
453    if (modifyListening) {
454      E o = list.set(index, element);
455      modifyAddition(element);
456      modifyRemoval(o);
457      return o;
458    }
459    return list.set(index, element);
460  }
461
462  @Override
463  public int size() {
464    init();
465    return list.size();
466  }
467
468  @Override
469  public List<E> subList(int fromIndex, int toIndex) {
470    init();
471    if (isReadOnly()) {
472      return Collections.unmodifiableList(list.subList(fromIndex, toIndex));
473    }
474    if (modifyListening) {
475      return new ModifyList<>(this, list.subList(fromIndex, toIndex));
476    }
477    return list.subList(fromIndex, toIndex);
478  }
479
480  @Override
481  public Object[] toArray() {
482    init();
483    return list.toArray();
484  }
485
486  @Override
487  public <T> T[] toArray(T[] a) {
488    init();
489    //noinspection SuspiciousToArrayCall
490    return list.toArray(a);
491  }
492
493  private static class ReadOnlyListIterator<E> implements ListIterator<E>, Serializable {
494
495    private static final long serialVersionUID = 3097271091406323699L;
496
497    private final ListIterator<E> i;
498
499    ReadOnlyListIterator(ListIterator<E> i) {
500      this.i = i;
501    }
502
503    @Override
504    public void add(E o) {
505      throw new IllegalStateException("This collection is in ReadOnly mode");
506    }
507
508    @Override
509    public void remove() {
510      throw new IllegalStateException("This collection is in ReadOnly mode");
511    }
512
513    @Override
514    public void set(E o) {
515      throw new IllegalStateException("This collection is in ReadOnly mode");
516    }
517
518    @Override
519    public boolean hasNext() {
520      return i.hasNext();
521    }
522
523    @Override
524    public boolean hasPrevious() {
525      return i.hasPrevious();
526    }
527
528    @Override
529    public E next() {
530      return i.next();
531    }
532
533    @Override
534    public int nextIndex() {
535      return i.nextIndex();
536    }
537
538    @Override
539    public E previous() {
540      return i.previous();
541    }
542
543    @Override
544    public int previousIndex() {
545      return i.previousIndex();
546    }
547
548  }
549
550  @Override
551  public BeanCollection<E> getShallowCopy() {
552    BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
553    copy.setFromOriginal(this);
554    return copy;
555  }
556}