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