001package com.avaje.ebean.common;
002
003import java.io.Serializable;
004import java.util.Collection;
005import java.util.Iterator;
006import java.util.LinkedHashSet;
007import java.util.Set;
008
009import com.avaje.ebean.bean.BeanCollectionAdd;
010import com.avaje.ebean.bean.BeanCollectionLoader;
011import com.avaje.ebean.bean.EntityBean;
012
013/**
014 * Set capable of lazy loading.
015 */
016public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E>, BeanCollectionAdd {
017
018  private static final long serialVersionUID = 1L;
019
020  /**
021   * The underlying Set implementation.
022   */
023  private Set<E> set;
024
025  /**
026   * Create with a specific Set implementation.
027   */
028  public BeanSet(Set<E> set) {
029    this.set = set;
030  }
031
032  /**
033   * Create using an underlying LinkedHashSet.
034   */
035  public BeanSet() {
036    this(new LinkedHashSet<E>());
037  }
038
039  public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) {
040    super(loader, ownerBean, propertyName);
041  }
042
043  @Override
044  public void reset(EntityBean ownerBean, String propertyName) {
045    this.ownerBean = ownerBean;
046    this.propertyName = propertyName;
047    this.set = null;
048    this.touched = false;
049  }
050
051  public boolean isEmptyAndUntouched() {
052    return !touched && (set == null || set.isEmpty());
053  }
054
055  @SuppressWarnings("unchecked")
056  public void addBean(EntityBean bean) {
057    set.add((E) bean);
058  }
059
060  @SuppressWarnings("unchecked")
061  public void internalAdd(Object bean) {
062    if (set == null) {
063      set = new LinkedHashSet<E>();
064    }
065    if (bean != null) {
066      set.add((E) bean);
067    }
068  }
069
070  /**
071   * Returns true if the underlying set has its data.
072   */
073  public boolean isPopulated() {
074    return set != null;
075  }
076
077  /**
078   * Return true if this is a reference (lazy loading) bean collection. This is
079   * the same as !isPopulated();
080   */
081  public boolean isReference() {
082    return set == null;
083  }
084
085  public boolean checkEmptyLazyLoad() {
086    if (set == null) {
087      set = new LinkedHashSet<E>();
088      return true;
089    } else {
090      return false;
091    }
092  }
093
094  private void initClear() {
095    synchronized (this) {
096      if (set == null) {
097        if (modifyListening) {
098          lazyLoadCollection(true);
099        } else {
100          set = new LinkedHashSet<E>();
101        }
102      }
103      touched(true);
104    }
105  }
106
107  private void initAsUntouched() {
108    init(false);
109  }
110  
111  private void init() {
112    init(true);
113  }
114  
115  private void init(boolean setTouched) {
116    synchronized (this) {
117      if (set == null) {
118        lazyLoadCollection(true);
119      }
120      touched(setTouched);
121    }
122  }
123
124  /**
125   * Set the underlying set (used for lazy fetch).
126   */
127  @SuppressWarnings("unchecked")
128  public void setActualSet(Set<?> set) {
129    this.set = (Set<E>) set;
130  }
131
132  /**
133   * Return the actual underlying set.
134   */
135  public Set<E> getActualSet() {
136    return set;
137  }
138
139  public Collection<E> getActualDetails() {
140    return set;
141  }
142
143  @Override
144  public Collection<?> getActualEntries() {
145    return set;
146  }
147
148  public String toString() {
149    StringBuilder sb = new StringBuilder(50);
150    sb.append("BeanSet ");
151    if (isReadOnly()) {
152      sb.append("readOnly ");
153    }
154    if (set == null) {
155      sb.append("deferred ");
156
157    } else {
158      sb.append("size[").append(set.size()).append("]");
159      sb.append(" set").append(set);
160    }
161    return sb.toString();
162  }
163
164  /**
165   * Equal if obj is a Set and equal in a Set sense.
166   */
167  public boolean equals(Object obj) {
168    init();
169    return set.equals(obj);
170  }
171
172  public int hashCode() {
173    init();
174    return set.hashCode();
175  }
176
177  // -----------------------------------------------------//
178  // proxy method for map
179  // -----------------------------------------------------//
180
181  public boolean add(E o) {
182    checkReadOnly();
183    init();
184    if (modifyAddListening) {
185      if (set.add(o)) {
186        modifyAddition(o);
187        return true;
188      } else {
189        return false;
190      }
191    }
192    return set.add(o);
193  }
194
195  public boolean addAll(Collection<? extends E> addCollection) {
196    checkReadOnly();
197    init();
198    if (modifyAddListening) {
199      boolean changed = false;
200      for (E bean : addCollection) {
201        if (set.add(bean)) {
202          // register the addition of the bean
203          modifyAddition(bean);
204          changed = true;
205        }
206      }
207      return changed;
208    }
209    return set.addAll(addCollection);
210  }
211
212  public void clear() {
213    checkReadOnly();
214    initClear();
215    if (modifyRemoveListening) {
216      for (E bean : set) {
217        modifyRemoval(bean);
218      }
219    }
220    set.clear();
221  }
222
223  public boolean contains(Object o) {
224    init();
225    return set.contains(o);
226  }
227
228  public boolean containsAll(Collection<?> c) {
229    init();
230    return set.containsAll(c);
231  }
232
233  public boolean isEmpty() {
234    initAsUntouched();
235    return set.isEmpty();
236  }
237
238  public Iterator<E> iterator() {
239    init();
240    if (isReadOnly()) {
241      return new ReadOnlyIterator<E>(set.iterator());
242    }
243    if (modifyListening) {
244      return new ModifyIterator<E>(this, set.iterator());
245    }
246    return set.iterator();
247  }
248
249  public boolean remove(Object o) {
250    checkReadOnly();
251    init();
252    if (modifyRemoveListening) {
253      if (set.remove(o)) {
254        modifyRemoval(o);
255        return true;
256      }
257      return false;
258    }
259    return set.remove(o);
260  }
261
262  public boolean removeAll(Collection<?> beans) {
263    checkReadOnly();
264    init();
265    if (modifyRemoveListening) {
266      boolean changed = false;
267      for (Object bean : beans) {
268        if (set.remove(bean)) {
269          modifyRemoval(bean);
270          changed = true;
271        }
272      }
273      return changed;
274    }
275    return set.removeAll(beans);
276  }
277
278  public boolean retainAll(Collection<?> beans) {
279    checkReadOnly();
280    init();
281    if (modifyRemoveListening) {
282      boolean changed = false;
283      Iterator<?> it = set.iterator();
284      while (it.hasNext()) {
285        Object bean = it.next();
286        if (!beans.contains(bean)) {
287          // not retaining this bean so add it to the removal list
288          it.remove();
289          modifyRemoval(bean);
290          changed = true;
291        }
292      }
293      return changed;
294    }
295    return set.retainAll(beans);
296  }
297
298  public int size() {
299    init();
300    return set.size();
301  }
302
303  public Object[] toArray() {
304    init();
305    return set.toArray();
306  }
307
308  public <T> T[] toArray(T[] a) {
309    init();
310    //noinspection SuspiciousToArrayCall
311    return set.toArray(a);
312  }
313
314  private static class ReadOnlyIterator<E> implements Iterator<E>, Serializable {
315
316    private static final long serialVersionUID = 2577697326745352605L;
317
318    private final Iterator<E> it;
319
320    ReadOnlyIterator(Iterator<E> it) {
321      this.it = it;
322    }
323
324    public boolean hasNext() {
325      return it.hasNext();
326    }
327
328    public E next() {
329      return it.next();
330    }
331
332    public void remove() {
333      throw new IllegalStateException("This collection is in ReadOnly mode");
334    }
335  }
336
337}