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}