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