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 || !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 synchronized (this) { 125 if (map == null) { 126 if (!disableLazyLoad && modifyListening) { 127 lazyLoadCollection(true); 128 } else { 129 map = new LinkedHashMap<>(); 130 } 131 } 132 } 133 } 134 135 private void init() { 136 synchronized (this) { 137 if (map == null) { 138 if (disableLazyLoad) { 139 map = new LinkedHashMap<>(); 140 } else { 141 lazyLoadCollection(false); 142 } 143 } 144 } 145 } 146 147 /** 148 * Set the actual underlying map. Used for performing lazy fetch. 149 */ 150 @SuppressWarnings("unchecked") 151 public void setActualMap(Map<?, ?> map) { 152 this.map = (Map<K, E>) map; 153 } 154 155 /** 156 * Return the actual underlying map. 157 */ 158 public Map<K, E> getActualMap() { 159 return map; 160 } 161 162 /** 163 * Returns the collection of beans (map values). 164 */ 165 @Override 166 public Collection<E> getActualDetails() { 167 return map.values(); 168 } 169 170 /** 171 * Returns the map entrySet. 172 * <p> 173 * This is because the key values may need to be set against the details (so 174 * they don't need to be set twice). 175 * </p> 176 */ 177 @Override 178 public Collection<?> getActualEntries() { 179 return map.entrySet(); 180 } 181 182 @Override 183 public String toString() { 184 StringBuilder sb = new StringBuilder(50); 185 sb.append("BeanMap "); 186 if (isReadOnly()) { 187 sb.append("readOnly "); 188 } 189 if (map == null) { 190 sb.append("deferred "); 191 192 } else { 193 sb.append("size[").append(map.size()).append("]"); 194 sb.append(" map").append(map); 195 } 196 return sb.toString(); 197 } 198 199 /** 200 * Equal if obj is a Map and equal in a Map sense. 201 */ 202 @Override 203 public boolean equals(Object obj) { 204 init(); 205 return map.equals(obj); 206 } 207 208 @Override 209 public int hashCode() { 210 init(); 211 return map.hashCode(); 212 } 213 214 @Override 215 public void clear() { 216 checkReadOnly(); 217 initClear(); 218 if (modifyListening) { 219 // add all beans to the removal list 220 for (E bean : map.values()) { 221 modifyRemoval(bean); 222 } 223 } 224 map.clear(); 225 } 226 227 @Override 228 public boolean containsKey(Object key) { 229 init(); 230 return map.containsKey(key); 231 } 232 233 @Override 234 public boolean containsValue(Object value) { 235 init(); 236 return map.containsValue(value); 237 } 238 239 @Override 240 @SuppressWarnings({"unchecked", "rawtypes"}) 241 public Set<Entry<K, E>> entrySet() { 242 init(); 243 if (isReadOnly()) { 244 return Collections.unmodifiableSet(map.entrySet()); 245 } 246 if (modifyListening) { 247 Set<Entry<K, E>> s = map.entrySet(); 248 return new ModifySet(this, s); 249 } 250 return map.entrySet(); 251 } 252 253 @Override 254 public E get(Object key) { 255 init(); 256 return map.get(key); 257 } 258 259 @Override 260 public boolean isEmpty() { 261 init(); 262 return map.isEmpty(); 263 } 264 265 @Override 266 public Set<K> keySet() { 267 init(); 268 if (isReadOnly()) { 269 return Collections.unmodifiableSet(map.keySet()); 270 } 271 // we don't really care about modifications to the ketSet? 272 return map.keySet(); 273 } 274 275 @Override 276 public E put(K key, E value) { 277 checkReadOnly(); 278 init(); 279 if (modifyListening) { 280 E oldBean = map.put(key, value); 281 if (value != oldBean) { 282 // register the add of the new and the removal of the old 283 modifyAddition(value); 284 modifyRemoval(oldBean); 285 } 286 return oldBean; 287 } else { 288 return map.put(key, value); 289 } 290 } 291 292 @Override 293 public void putAll(Map<? extends K, ? extends E> puts) { 294 checkReadOnly(); 295 init(); 296 if (modifyListening) { 297 for (Entry<? extends K, ? extends E> entry : puts.entrySet()) { 298 Object oldBean = map.put(entry.getKey(), entry.getValue()); 299 if (entry.getValue() != oldBean) { 300 modifyAddition(entry.getValue()); 301 modifyRemoval(oldBean); 302 } 303 } 304 } else { 305 map.putAll(puts); 306 } 307 } 308 309 @Override 310 public void addBean(E bean) { 311 throw new IllegalStateException("Method not allowed on Map. Please use List instead."); 312 } 313 314 @Override 315 public void removeBean(E bean) { 316 throw new IllegalStateException("Method not allowed on Map. Please use List instead."); 317 } 318 319 @Override 320 public E remove(Object key) { 321 checkReadOnly(); 322 init(); 323 if (modifyListening) { 324 E o = map.remove(key); 325 modifyRemoval(o); 326 return o; 327 } 328 return map.remove(key); 329 } 330 331 @Override 332 public int size() { 333 init(); 334 return map.size(); 335 } 336 337 @Override 338 public Collection<E> values() { 339 init(); 340 if (isReadOnly()) { 341 return Collections.unmodifiableCollection(map.values()); 342 } 343 if (modifyListening) { 344 Collection<E> c = map.values(); 345 return new ModifyCollection<>(this, c); 346 } 347 return map.values(); 348 } 349 350 @Override 351 public BeanCollection<E> getShallowCopy() { 352 BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map)); 353 copy.setFromOriginal(this); 354 return copy; 355 } 356}