001package io.ebean.common;
002
003import io.ebean.bean.BeanCollection;
004import io.ebean.bean.BeanCollectionLoader;
005import io.ebean.bean.EntityBean;
006
007import java.util.Collection;
008import java.util.Collections;
009import java.util.LinkedHashMap;
010import java.util.Map;
011import java.util.Set;
012
013/**
014 * Map capable of lazy loading.
015 */
016public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Map<K, E> {
017
018  private static final long serialVersionUID = 1L;
019
020  /**
021   * The underlying map implementation.
022   */
023  private Map<K, E> map;
024
025  /**
026   * Create with a given Map.
027   */
028  public BeanMap(Map<K, E> map) {
029    this.map = map;
030  }
031
032  /**
033   * Create using a underlying LinkedHashMap.
034   */
035  public BeanMap() {
036    this(new LinkedHashMap<>());
037  }
038
039  public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) {
040    super(ebeanServer, ownerBean, propertyName);
041  }
042
043  @Override
044  public void reset(EntityBean ownerBean, String propertyName) {
045    this.ownerBean = ownerBean;
046    this.propertyName = propertyName;
047    this.map = null;
048  }
049
050  @Override
051  public boolean isSkipSave() {
052    return map == null || (map.isEmpty() && !holdsModifications());
053  }
054
055  @Override
056  @SuppressWarnings("unchecked")
057  public void loadFrom(BeanCollection<?> other) {
058    BeanMap<K, E> otherMap = (BeanMap<K, E>) other;
059    internalPutNull();
060    map.putAll(otherMap.getActualMap());
061  }
062
063  public void internalPutNull() {
064    if (map == null) {
065      map = new LinkedHashMap<>();
066    }
067  }
068
069  @SuppressWarnings("unchecked")
070  public void internalPut(Object key, Object bean) {
071    if (map == null) {
072      map = new LinkedHashMap<>();
073    }
074    if (key != null) {
075      map.put((K) key, (E) bean);
076    }
077  }
078
079  public void internalPutWithCheck(Object key, Object bean) {
080    if (map == null || !map.containsKey(key)) {
081      internalPut(key, bean);
082    }
083  }
084
085  @Override
086  public void internalAddWithCheck(Object bean) {
087    throw new RuntimeException("Not allowed for map");
088  }
089
090  @Override
091  public void internalAdd(Object bean) {
092    throw new RuntimeException("Not allowed for map");
093  }
094
095  /**
096   * Return true if the underlying map has been populated. Returns false if it
097   * has a deferred fetch pending.
098   */
099  @Override
100  public boolean isPopulated() {
101    return map != null;
102  }
103
104  /**
105   * Return true if this is a reference (lazy loading) bean collection. This is
106   * the same as !isPopulated();
107   */
108  @Override
109  public boolean isReference() {
110    return map == null;
111  }
112
113  @Override
114  public boolean checkEmptyLazyLoad() {
115    if (map == null) {
116      map = new LinkedHashMap<>();
117      return true;
118    } else {
119      return false;
120    }
121  }
122
123  private void initClear() {
124    synchronized (this) {
125      if (map == null) {
126        if (!disableLazyLoad && modifyListening) {
127          lazyLoadCollection(true);
128        } else {
129          map = new LinkedHashMap<>();
130        }
131      }
132    }
133  }
134
135  private void init() {
136    synchronized (this) {
137      if (map == null) {
138        if (disableLazyLoad) {
139          map = new LinkedHashMap<>();
140        } else {
141          lazyLoadCollection(false);
142        }
143      }
144    }
145  }
146
147  /**
148   * Set the actual underlying map. Used for performing lazy fetch.
149   */
150  @SuppressWarnings("unchecked")
151  public void setActualMap(Map<?, ?> map) {
152    this.map = (Map<K, E>) map;
153  }
154
155  /**
156   * Return the actual underlying map.
157   */
158  public Map<K, E> getActualMap() {
159    return map;
160  }
161
162  /**
163   * Returns the collection of beans (map values).
164   */
165  @Override
166  public Collection<E> getActualDetails() {
167    return map.values();
168  }
169
170  /**
171   * Returns the map entrySet.
172   * <p>
173   * This is because the key values may need to be set against the details (so
174   * they don't need to be set twice).
175   * </p>
176   */
177  @Override
178  public Collection<?> getActualEntries() {
179    return map.entrySet();
180  }
181
182  @Override
183  public String toString() {
184    StringBuilder sb = new StringBuilder(50);
185    sb.append("BeanMap ");
186    if (isReadOnly()) {
187      sb.append("readOnly ");
188    }
189    if (map == null) {
190      sb.append("deferred ");
191
192    } else {
193      sb.append("size[").append(map.size()).append("]");
194      sb.append(" map").append(map);
195    }
196    return sb.toString();
197  }
198
199  /**
200   * Equal if obj is a Map and equal in a Map sense.
201   */
202  @Override
203  public boolean equals(Object obj) {
204    init();
205    return map.equals(obj);
206  }
207
208  @Override
209  public int hashCode() {
210    init();
211    return map.hashCode();
212  }
213
214  @Override
215  public void clear() {
216    checkReadOnly();
217    initClear();
218    if (modifyListening) {
219      // add all beans to the removal list
220      for (E bean : map.values()) {
221        modifyRemoval(bean);
222      }
223    }
224    map.clear();
225  }
226
227  @Override
228  public boolean containsKey(Object key) {
229    init();
230    return map.containsKey(key);
231  }
232
233  @Override
234  public boolean containsValue(Object value) {
235    init();
236    return map.containsValue(value);
237  }
238
239  @Override
240  @SuppressWarnings({"unchecked", "rawtypes"})
241  public Set<Entry<K, E>> entrySet() {
242    init();
243    if (isReadOnly()) {
244      return Collections.unmodifiableSet(map.entrySet());
245    }
246    if (modifyListening) {
247      Set<Entry<K, E>> s = map.entrySet();
248      return new ModifySet(this, s);
249    }
250    return map.entrySet();
251  }
252
253  @Override
254  public E get(Object key) {
255    init();
256    return map.get(key);
257  }
258
259  @Override
260  public boolean isEmpty() {
261    init();
262    return map.isEmpty();
263  }
264
265  @Override
266  public Set<K> keySet() {
267    init();
268    if (isReadOnly()) {
269      return Collections.unmodifiableSet(map.keySet());
270    }
271    // we don't really care about modifications to the ketSet?
272    return map.keySet();
273  }
274
275  @Override
276  public E put(K key, E value) {
277    checkReadOnly();
278    init();
279    if (modifyListening) {
280      E oldBean = map.put(key, value);
281      if (value != oldBean) {
282        // register the add of the new and the removal of the old
283        modifyAddition(value);
284        modifyRemoval(oldBean);
285      }
286      return oldBean;
287    } else {
288      return map.put(key, value);
289    }
290  }
291
292  @Override
293  public void putAll(Map<? extends K, ? extends E> puts) {
294    checkReadOnly();
295    init();
296    if (modifyListening) {
297      for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
298        Object oldBean = map.put(entry.getKey(), entry.getValue());
299        if (entry.getValue() != oldBean) {
300          modifyAddition(entry.getValue());
301          modifyRemoval(oldBean);
302        }
303      }
304    } else {
305      map.putAll(puts);
306    }
307  }
308
309  @Override
310  public void addBean(E bean) {
311    throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
312  }
313
314  @Override
315  public void removeBean(E bean) {
316    throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
317  }
318
319  @Override
320  public E remove(Object key) {
321    checkReadOnly();
322    init();
323    if (modifyListening) {
324      E o = map.remove(key);
325      modifyRemoval(o);
326      return o;
327    }
328    return map.remove(key);
329  }
330
331  @Override
332  public int size() {
333    init();
334    return map.size();
335  }
336
337  @Override
338  public Collection<E> values() {
339    init();
340    if (isReadOnly()) {
341      return Collections.unmodifiableCollection(map.values());
342    }
343    if (modifyListening) {
344      Collection<E> c = map.values();
345      return new ModifyCollection<>(this, c);
346    }
347    return map.values();
348  }
349
350  @Override
351  public BeanCollection<E> getShallowCopy() {
352    BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
353    copy.setFromOriginal(this);
354    return copy;
355  }
356}