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