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