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