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 || key == 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    lock.lock();
125    try {
126      if (map == null) {
127        if (!disableLazyLoad && modifyListening) {
128          lazyLoadCollection(true);
129        } else {
130          map = new LinkedHashMap<>();
131        }
132      }
133    } finally {
134      lock.unlock();
135    }
136  }
137
138  private void init() {
139    lock.lock();
140    try {
141      if (map == null) {
142        if (disableLazyLoad) {
143          map = new LinkedHashMap<>();
144        } else {
145          lazyLoadCollection(false);
146        }
147      }
148    } finally {
149      lock.unlock();
150    }
151  }
152
153  /**
154   * Set the actual underlying map. Used for performing lazy fetch.
155   */
156  @SuppressWarnings("unchecked")
157  public void setActualMap(Map<?, ?> map) {
158    this.map = (Map<K, E>) map;
159  }
160
161  /**
162   * Return the actual underlying map.
163   */
164  public Map<K, E> getActualMap() {
165    return map;
166  }
167
168  /**
169   * Returns the collection of beans (map values).
170   */
171  @Override
172  public Collection<E> getActualDetails() {
173    return map.values();
174  }
175
176  /**
177   * Returns the map entrySet.
178   */
179  @Override
180  public Collection<?> getActualEntries() {
181    return map.entrySet();
182  }
183
184  @Override
185  public String toString() {
186    StringBuilder sb = new StringBuilder(50);
187    sb.append("BeanMap ");
188    if (isReadOnly()) {
189      sb.append("readOnly ");
190    }
191    if (map == null) {
192      sb.append("deferred ");
193    } else {
194      sb.append("size[").append(map.size()).append("]");
195      sb.append(" map").append(map);
196    }
197    return sb.toString();
198  }
199
200  /**
201   * Equal if obj is a Map and equal in a Map sense.
202   */
203  @Override
204  public boolean equals(Object obj) {
205    init();
206    return map.equals(obj);
207  }
208
209  @Override
210  public int hashCode() {
211    init();
212    return map.hashCode();
213  }
214
215  @Override
216  public void clear() {
217    checkReadOnly();
218    initClear();
219    if (modifyListening) {
220      // add all beans to the removal list
221      for (E bean : map.values()) {
222        modifyRemoval(bean);
223      }
224    }
225    map.clear();
226  }
227
228  @Override
229  public boolean containsKey(Object key) {
230    init();
231    return map.containsKey(key);
232  }
233
234  @Override
235  public boolean containsValue(Object value) {
236    init();
237    return map.containsValue(value);
238  }
239
240  @Override
241  public Set<Entry<K, E>> entrySet() {
242    init();
243    if (isReadOnly()) {
244      return Collections.unmodifiableSet(map.entrySet());
245    }
246    return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
247  }
248
249  @Override
250  public E get(Object key) {
251    init();
252    return map.get(key);
253  }
254
255  @Override
256  public boolean isEmpty() {
257    init();
258    return map.isEmpty();
259  }
260
261  @Override
262  public Set<K> keySet() {
263    init();
264    if (isReadOnly()) {
265      return Collections.unmodifiableSet(map.keySet());
266    }
267    return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
268  }
269
270  @Override
271  public E put(K key, E value) {
272    checkReadOnly();
273    init();
274    if (modifyListening) {
275      E oldBean = map.put(key, value);
276      if (value != oldBean) {
277        // register the add of the new and the removal of the old
278        modifyAddition(value);
279        modifyRemoval(oldBean);
280      }
281      return oldBean;
282    } else {
283      return map.put(key, value);
284    }
285  }
286
287  @Override
288  public void putAll(Map<? extends K, ? extends E> puts) {
289    checkReadOnly();
290    init();
291    if (modifyListening) {
292      for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
293        Object oldBean = map.put(entry.getKey(), entry.getValue());
294        if (entry.getValue() != oldBean) {
295          modifyAddition(entry.getValue());
296          modifyRemoval(oldBean);
297        }
298      }
299    } else {
300      map.putAll(puts);
301    }
302  }
303
304  @Override
305  public void addBean(E bean) {
306    throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
307  }
308
309  @Override
310  public void removeBean(E bean) {
311    throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
312  }
313
314  @Override
315  public E remove(Object key) {
316    checkReadOnly();
317    init();
318    if (modifyListening) {
319      E o = map.remove(key);
320      modifyRemoval(o);
321      return o;
322    }
323    return map.remove(key);
324  }
325
326  @Override
327  public int size() {
328    init();
329    return map.size();
330  }
331
332  @Override
333  public Collection<E> values() {
334    init();
335    if (isReadOnly()) {
336      return Collections.unmodifiableCollection(map.values());
337    }
338    return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
339  }
340
341  @Override
342  public BeanCollection<E> getShallowCopy() {
343    BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
344    copy.setFromOriginal(this);
345    return copy;
346  }
347}