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