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