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}