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}