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