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