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    } else {
180      sb.append("size[").append(set.size()).append("]");
181      sb.append(" set").append(set);
182    }
183    return sb.toString();
184  }
185
186  /**
187   * Equal if obj is a Set and equal in a Set sense.
188   */
189  @Override
190  public boolean equals(Object obj) {
191    init();
192    return set.equals(obj);
193  }
194
195  @Override
196  public int hashCode() {
197    init();
198    return set.hashCode();
199  }
200
201  @Override
202  public void addBean(E bean) {
203    add(bean);
204  }
205
206  @Override
207  public void removeBean(E bean) {
208    if (set.remove(bean)) {
209      getModifyHolder().modifyRemoval(bean);
210    }
211  }
212
213  // -----------------------------------------------------//
214  // proxy method for map
215  // -----------------------------------------------------//
216
217  @Override
218  public boolean add(E o) {
219    checkReadOnly();
220    init();
221    if (modifyListening) {
222      if (set.add(o)) {
223        modifyAddition(o);
224        return true;
225      } else {
226        return false;
227      }
228    }
229    return set.add(o);
230  }
231
232  @Override
233  public boolean addAll(Collection<? extends E> addCollection) {
234    checkReadOnly();
235    init();
236    if (modifyListening) {
237      boolean changed = false;
238      for (E bean : addCollection) {
239        if (set.add(bean)) {
240          // register the addition of the bean
241          modifyAddition(bean);
242          changed = true;
243        }
244      }
245      return changed;
246    }
247    return set.addAll(addCollection);
248  }
249
250  @Override
251  public void clear() {
252    checkReadOnly();
253    initClear();
254    if (modifyListening) {
255      for (E bean : set) {
256        modifyRemoval(bean);
257      }
258    }
259    set.clear();
260  }
261
262  @Override
263  public boolean contains(Object o) {
264    init();
265    return set.contains(o);
266  }
267
268  @Override
269  public boolean containsAll(Collection<?> c) {
270    init();
271    return set.containsAll(c);
272  }
273
274  @Override
275  public boolean isEmpty() {
276    init();
277    return set.isEmpty();
278  }
279
280  @Override
281  public Iterator<E> iterator() {
282    init();
283    if (isReadOnly()) {
284      return new ReadOnlyIterator<>(set.iterator());
285    }
286    if (modifyListening) {
287      return new ModifyIterator<>(this, set.iterator());
288    }
289    return set.iterator();
290  }
291
292  @Override
293  public boolean remove(Object o) {
294    checkReadOnly();
295    init();
296    if (modifyListening) {
297      if (set.remove(o)) {
298        modifyRemoval(o);
299        return true;
300      }
301      return false;
302    }
303    return set.remove(o);
304  }
305
306  @Override
307  public boolean removeAll(Collection<?> beans) {
308    checkReadOnly();
309    init();
310    if (modifyListening) {
311      boolean changed = false;
312      for (Object bean : beans) {
313        if (set.remove(bean)) {
314          modifyRemoval(bean);
315          changed = true;
316        }
317      }
318      return changed;
319    }
320    return set.removeAll(beans);
321  }
322
323  @Override
324  public boolean retainAll(Collection<?> beans) {
325    checkReadOnly();
326    init();
327    if (modifyListening) {
328      boolean changed = false;
329      Iterator<?> it = set.iterator();
330      while (it.hasNext()) {
331        Object bean = it.next();
332        if (!beans.contains(bean)) {
333          // not retaining this bean so add it to the removal list
334          it.remove();
335          modifyRemoval(bean);
336          changed = true;
337        }
338      }
339      return changed;
340    }
341    return set.retainAll(beans);
342  }
343
344  @Override
345  public int size() {
346    init();
347    return set.size();
348  }
349
350  @Override
351  public Object[] toArray() {
352    init();
353    return set.toArray();
354  }
355
356  @Override
357  public <T> T[] toArray(T[] a) {
358    init();
359    //noinspection SuspiciousToArrayCall
360    return set.toArray(a);
361  }
362
363  private static class ReadOnlyIterator<E> implements Iterator<E>, Serializable {
364
365    private static final long serialVersionUID = 2577697326745352605L;
366
367    private final Iterator<E> it;
368
369    ReadOnlyIterator(Iterator<E> it) {
370      this.it = it;
371    }
372
373    @Override
374    public boolean hasNext() {
375      return it.hasNext();
376    }
377
378    @Override
379    public E next() {
380      return it.next();
381    }
382
383    @Override
384    public void remove() {
385      throw new IllegalStateException("This collection is in ReadOnly mode");
386    }
387  }
388
389  @Override
390  public BeanCollection<E> getShallowCopy() {
391    BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
392    copy.setFromOriginal(this);
393    return copy;
394  }
395}