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