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