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