001package io.ebean.common; 002 003import io.ebean.bean.BeanCollection; 004import io.ebean.bean.BeanCollectionLoader; 005import io.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<>()); 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 @Override 051 public boolean isSkipSave() { 052 return map == null || (map.isEmpty() && !holdsModifications()); 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<>(); 066 } 067 } 068 069 @SuppressWarnings("unchecked") 070 public void internalPut(Object key, Object bean) { 071 if (map == null) { 072 map = new LinkedHashMap<>(); 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 || key == 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 @Override 091 public void internalAdd(Object bean) { 092 throw new RuntimeException("Not allowed for map"); 093 } 094 095 /** 096 * Return true if the underlying map has been populated. Returns false if it 097 * has a deferred fetch pending. 098 */ 099 @Override 100 public boolean isPopulated() { 101 return map != null; 102 } 103 104 /** 105 * Return true if this is a reference (lazy loading) bean collection. This is 106 * the same as !isPopulated(); 107 */ 108 @Override 109 public boolean isReference() { 110 return map == null; 111 } 112 113 @Override 114 public boolean checkEmptyLazyLoad() { 115 if (map == null) { 116 map = new LinkedHashMap<>(); 117 return true; 118 } else { 119 return false; 120 } 121 } 122 123 private void initClear() { 124 lock.lock(); 125 try { 126 if (map == null) { 127 if (!disableLazyLoad && modifyListening) { 128 lazyLoadCollection(true); 129 } else { 130 map = new LinkedHashMap<>(); 131 } 132 } 133 } finally { 134 lock.unlock(); 135 } 136 } 137 138 private void init() { 139 lock.lock(); 140 try { 141 if (map == null) { 142 if (disableLazyLoad) { 143 map = new LinkedHashMap<>(); 144 } else { 145 lazyLoadCollection(false); 146 } 147 } 148 } finally { 149 lock.unlock(); 150 } 151 } 152 153 /** 154 * Set the actual underlying map. Used for performing lazy fetch. 155 */ 156 @SuppressWarnings("unchecked") 157 public void setActualMap(Map<?, ?> map) { 158 this.map = (Map<K, E>) map; 159 } 160 161 /** 162 * Return the actual underlying map. 163 */ 164 public Map<K, E> getActualMap() { 165 return map; 166 } 167 168 /** 169 * Returns the collection of beans (map values). 170 */ 171 @Override 172 public Collection<E> getActualDetails() { 173 return map.values(); 174 } 175 176 /** 177 * Returns the map entrySet. 178 */ 179 @Override 180 public Collection<?> getActualEntries() { 181 return map.entrySet(); 182 } 183 184 @Override 185 public String toString() { 186 StringBuilder sb = new StringBuilder(50); 187 sb.append("BeanMap "); 188 if (isReadOnly()) { 189 sb.append("readOnly "); 190 } 191 if (map == null) { 192 sb.append("deferred "); 193 } else { 194 sb.append("size[").append(map.size()).append("]"); 195 sb.append(" map").append(map); 196 } 197 return sb.toString(); 198 } 199 200 /** 201 * Equal if obj is a Map and equal in a Map sense. 202 */ 203 @Override 204 public boolean equals(Object obj) { 205 init(); 206 return map.equals(obj); 207 } 208 209 @Override 210 public int hashCode() { 211 init(); 212 return map.hashCode(); 213 } 214 215 @Override 216 public void clear() { 217 checkReadOnly(); 218 initClear(); 219 if (modifyListening) { 220 // add all beans to the removal list 221 for (E bean : map.values()) { 222 modifyRemoval(bean); 223 } 224 } 225 map.clear(); 226 } 227 228 @Override 229 public boolean containsKey(Object key) { 230 init(); 231 return map.containsKey(key); 232 } 233 234 @Override 235 public boolean containsValue(Object value) { 236 init(); 237 return map.containsValue(value); 238 } 239 240 @Override 241 public Set<Entry<K, E>> entrySet() { 242 init(); 243 if (isReadOnly()) { 244 return Collections.unmodifiableSet(map.entrySet()); 245 } 246 return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet(); 247 } 248 249 @Override 250 public E get(Object key) { 251 init(); 252 return map.get(key); 253 } 254 255 @Override 256 public boolean isEmpty() { 257 init(); 258 return map.isEmpty(); 259 } 260 261 @Override 262 public Set<K> keySet() { 263 init(); 264 if (isReadOnly()) { 265 return Collections.unmodifiableSet(map.keySet()); 266 } 267 return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet(); 268 } 269 270 @Override 271 public E put(K key, E value) { 272 checkReadOnly(); 273 init(); 274 if (modifyListening) { 275 E oldBean = map.put(key, value); 276 if (value != oldBean) { 277 // register the add of the new and the removal of the old 278 modifyAddition(value); 279 modifyRemoval(oldBean); 280 } 281 return oldBean; 282 } else { 283 return map.put(key, value); 284 } 285 } 286 287 @Override 288 public void putAll(Map<? extends K, ? extends E> puts) { 289 checkReadOnly(); 290 init(); 291 if (modifyListening) { 292 for (Entry<? extends K, ? extends E> entry : puts.entrySet()) { 293 Object oldBean = map.put(entry.getKey(), entry.getValue()); 294 if (entry.getValue() != oldBean) { 295 modifyAddition(entry.getValue()); 296 modifyRemoval(oldBean); 297 } 298 } 299 } else { 300 map.putAll(puts); 301 } 302 } 303 304 @Override 305 public void addBean(E bean) { 306 throw new IllegalStateException("Method not allowed on Map. Please use List instead."); 307 } 308 309 @Override 310 public void removeBean(E bean) { 311 throw new IllegalStateException("Method not allowed on Map. Please use List instead."); 312 } 313 314 @Override 315 public E remove(Object key) { 316 checkReadOnly(); 317 init(); 318 if (modifyListening) { 319 E o = map.remove(key); 320 modifyRemoval(o); 321 return o; 322 } 323 return map.remove(key); 324 } 325 326 @Override 327 public int size() { 328 init(); 329 return map.size(); 330 } 331 332 @Override 333 public Collection<E> values() { 334 init(); 335 if (isReadOnly()) { 336 return Collections.unmodifiableCollection(map.values()); 337 } 338 return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values(); 339 } 340 341 @Override 342 public BeanCollection<E> getShallowCopy() { 343 BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map)); 344 copy.setFromOriginal(this); 345 return copy; 346 } 347}