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   * <p>
179   * This is because the key values may need to be set against the details (so
180   * they don't need to be set twice).
181   * </p>
182   */
183  @Override
184  public Collection<?> getActualEntries() {
185    return map.entrySet();
186  }
187
188  @Override
189  public String toString() {
190    StringBuilder sb = new StringBuilder(50);
191    sb.append("BeanMap ");
192    if (isReadOnly()) {
193      sb.append("readOnly ");
194    }
195    if (map == null) {
196      sb.append("deferred ");
197
198    } else {
199      sb.append("size[").append(map.size()).append("]");
200      sb.append(" map").append(map);
201    }
202    return sb.toString();
203  }
204
205  /**
206   * Equal if obj is a Map and equal in a Map sense.
207   */
208  @Override
209  public boolean equals(Object obj) {
210    init();
211    return map.equals(obj);
212  }
213
214  @Override
215  public int hashCode() {
216    init();
217    return map.hashCode();
218  }
219
220  @Override
221  public void clear() {
222    checkReadOnly();
223    initClear();
224    if (modifyListening) {
225      // add all beans to the removal list
226      for (E bean : map.values()) {
227        modifyRemoval(bean);
228      }
229    }
230    map.clear();
231  }
232
233  @Override
234  public boolean containsKey(Object key) {
235    init();
236    return map.containsKey(key);
237  }
238
239  @Override
240  public boolean containsValue(Object value) {
241    init();
242    return map.containsValue(value);
243  }
244
245  @Override
246  @SuppressWarnings({"unchecked"})
247  public Set<Entry<K, E>> entrySet() {
248    init();
249    if (isReadOnly()) {
250      return Collections.unmodifiableSet(map.entrySet());
251    }
252    if (modifyListening) {
253      Set<Entry<K, E>> s = map.entrySet();
254      return new ModifySet(this, s);
255    }
256    return map.entrySet();
257  }
258
259  @Override
260  public E get(Object key) {
261    init();
262    return map.get(key);
263  }
264
265  @Override
266  public boolean isEmpty() {
267    init();
268    return map.isEmpty();
269  }
270
271  @Override
272  public Set<K> keySet() {
273    init();
274    if (isReadOnly()) {
275      return Collections.unmodifiableSet(map.keySet());
276    }
277    // we don't really care about modifications to the ketSet?
278    return map.keySet();
279  }
280
281  @Override
282  public E put(K key, E value) {
283    checkReadOnly();
284    init();
285    if (modifyListening) {
286      E oldBean = map.put(key, value);
287      if (value != oldBean) {
288        // register the add of the new and the removal of the old
289        modifyAddition(value);
290        modifyRemoval(oldBean);
291      }
292      return oldBean;
293    } else {
294      return map.put(key, value);
295    }
296  }
297
298  @Override
299  public void putAll(Map<? extends K, ? extends E> puts) {
300    checkReadOnly();
301    init();
302    if (modifyListening) {
303      for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
304        Object oldBean = map.put(entry.getKey(), entry.getValue());
305        if (entry.getValue() != oldBean) {
306          modifyAddition(entry.getValue());
307          modifyRemoval(oldBean);
308        }
309      }
310    } else {
311      map.putAll(puts);
312    }
313  }
314
315  @Override
316  public void addBean(E bean) {
317    throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
318  }
319
320  @Override
321  public void removeBean(E bean) {
322    throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
323  }
324
325  @Override
326  public E remove(Object key) {
327    checkReadOnly();
328    init();
329    if (modifyListening) {
330      E o = map.remove(key);
331      modifyRemoval(o);
332      return o;
333    }
334    return map.remove(key);
335  }
336
337  @Override
338  public int size() {
339    init();
340    return map.size();
341  }
342
343  @Override
344  public Collection<E> values() {
345    init();
346    if (isReadOnly()) {
347      return Collections.unmodifiableCollection(map.values());
348    }
349    if (modifyListening) {
350      Collection<E> c = map.values();
351      return new ModifyCollection<>(this, c);
352    }
353    return map.values();
354  }
355
356  @Override
357  public BeanCollection<E> getShallowCopy() {
358    BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
359    copy.setFromOriginal(this);
360    return copy;
361  }
362}