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}