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