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 internalAdd(Object bean) { 071 throw new RuntimeException("Not allowed for map"); 072 } 073 074 /** 075 * Return true if the underlying map has been populated. Returns false if it 076 * has a deferred fetch pending. 077 */ 078 public boolean isPopulated() { 079 return map != null; 080 } 081 082 /** 083 * Return true if this is a reference (lazy loading) bean collection. This is 084 * the same as !isPopulated(); 085 */ 086 public boolean isReference() { 087 return map == null; 088 } 089 090 public boolean checkEmptyLazyLoad() { 091 if (map == null) { 092 map = new LinkedHashMap<K, E>(); 093 return true; 094 } else { 095 return false; 096 } 097 } 098 099 private void initClear() { 100 synchronized (this) { 101 if (map == null) { 102 if (modifyListening) { 103 lazyLoadCollection(true); 104 } else { 105 map = new LinkedHashMap<K, E>(); 106 } 107 } 108 touched(true); 109 } 110 } 111 112 private void initAsUntouched() { 113 init(false); 114 } 115 116 private void init() { 117 init(true); 118 } 119 120 private void init(boolean setTouched) { 121 synchronized (this) { 122 if (map == null) { 123 lazyLoadCollection(false); 124 } 125 touched(setTouched); 126 } 127 } 128 129 /** 130 * Set the actual underlying map. Used for performing lazy fetch. 131 */ 132 @SuppressWarnings("unchecked") 133 public void setActualMap(Map<?, ?> map) { 134 this.map = (Map<K, E>) map; 135 } 136 137 /** 138 * Return the actual underlying map. 139 */ 140 public Map<K, E> getActualMap() { 141 return map; 142 } 143 144 /** 145 * Returns the collection of beans (map values). 146 */ 147 public Collection<E> getActualDetails() { 148 return map.values(); 149 } 150 151 /** 152 * Returns the map entrySet. 153 * <p> 154 * This is because the key values may need to be set against the details (so 155 * they don't need to be set twice). 156 * </p> 157 */ 158 public Collection<?> getActualEntries() { 159 return map.entrySet(); 160 } 161 162 public String toString() { 163 StringBuilder sb = new StringBuilder(50); 164 sb.append("BeanMap "); 165 if (isReadOnly()) { 166 sb.append("readOnly "); 167 } 168 if (map == null) { 169 sb.append("deferred "); 170 171 } else { 172 sb.append("size[").append(map.size()).append("]"); 173 sb.append(" map").append(map); 174 } 175 return sb.toString(); 176 } 177 178 /** 179 * Equal if obj is a Map and equal in a Map sense. 180 */ 181 public boolean equals(Object obj) { 182 init(); 183 return map.equals(obj); 184 } 185 186 public int hashCode() { 187 init(); 188 return map.hashCode(); 189 } 190 191 public void clear() { 192 checkReadOnly(); 193 initClear(); 194 if (modifyRemoveListening) { 195 // add all beans to the removal list 196 for (E bean : map.values()) { 197 modifyRemoval(bean); 198 } 199 } 200 map.clear(); 201 } 202 203 public boolean containsKey(Object key) { 204 init(); 205 return map.containsKey(key); 206 } 207 208 public boolean containsValue(Object value) { 209 init(); 210 return map.containsValue(value); 211 } 212 213 @SuppressWarnings({ "unchecked", "rawtypes" }) 214 public Set<Entry<K, E>> entrySet() { 215 init(); 216 if (isReadOnly()) { 217 return Collections.unmodifiableSet(map.entrySet()); 218 } 219 if (modifyListening) { 220 Set<Entry<K, E>> s = map.entrySet(); 221 return new ModifySet(this, s); 222 } 223 return map.entrySet(); 224 } 225 226 public E get(Object key) { 227 init(); 228 return map.get(key); 229 } 230 231 public boolean isEmpty() { 232 initAsUntouched(); 233 return map.isEmpty(); 234 } 235 236 public Set<K> keySet() { 237 init(); 238 if (isReadOnly()) { 239 return Collections.unmodifiableSet(map.keySet()); 240 } 241 // we don't really care about modifications to the ketSet? 242 return map.keySet(); 243 } 244 245 public E put(K key, E value) { 246 checkReadOnly(); 247 init(); 248 if (modifyListening) { 249 Object oldBean = map.put(key, value); 250 if (value != oldBean) { 251 // register the add of the new and the removal of the old 252 modifyAddition(value); 253 modifyRemoval(oldBean); 254 } 255 } 256 return map.put(key, value); 257 } 258 259 @SuppressWarnings({ "unchecked", "rawtypes" }) 260 public void putAll(Map<? extends K, ? extends E> puts) { 261 checkReadOnly(); 262 init(); 263 if (modifyListening) { 264 for (Entry<? extends K, ? extends E> entry : puts.entrySet()) { 265 Object oldBean = map.put(entry.getKey(), entry.getValue()); 266 if (entry.getValue() != oldBean) { 267 modifyAddition(entry.getValue()); 268 modifyRemoval(oldBean); 269 } 270 } 271 } 272 map.putAll(puts); 273 } 274 275 public E remove(Object key) { 276 checkReadOnly(); 277 init(); 278 if (modifyRemoveListening) { 279 E o = map.remove(key); 280 modifyRemoval(o); 281 return o; 282 } 283 return map.remove(key); 284 } 285 286 public int size() { 287 init(); 288 return map.size(); 289 } 290 291 public Collection<E> values() { 292 init(); 293 if (isReadOnly()) { 294 return Collections.unmodifiableCollection(map.values()); 295 } 296 if (modifyListening) { 297 Collection<E> c = map.values(); 298 return new ModifyCollection<E>(this, c); 299 } 300 return map.values(); 301 } 302 303}