001package com.hammurapi.store.local; 002 003import java.lang.ref.Reference; 004import java.lang.ref.SoftReference; 005import java.lang.ref.WeakReference; 006import java.util.Map; 007import java.util.concurrent.atomic.AtomicBoolean; 008 009import com.hammurapi.common.Observable; 010import com.hammurapi.common.Observer; 011import com.hammurapi.common.Util; 012import com.hammurapi.extract.Extractor; 013import com.hammurapi.extract.Predicate; 014import com.hammurapi.store.Store; 015import com.hammurapi.store.Store.Handle; 016import com.hammurapi.store.StoreException; 017 018public class LocalHandle<T,PK, S extends Store<T,PK,S>> implements Handle<T, PK, S> { 019 020 private final Observer<T> observer = new Observer<T>() { 021 022 @Override 023 public void update(T obj, Object... args) { 024 LocalHandle.this.update(); 025 } 026 027 }; 028 029 public enum HandleStrength { 030 STRONG, SOFT, WEAK 031 } 032 033 @Override 034 public int hashCode() { 035 if (isValid()) { 036 return get().hashCode(); 037 } 038 return 0; 039 } 040 041 @Override 042 public boolean equals(Object obj) { 043 if (this == obj) 044 return true; 045 if (obj == null) 046 return false; 047 if (getClass() != obj.getClass()) 048 return false; 049 050 @SuppressWarnings("unchecked") 051 LocalHandle<T,PK,S> other = (LocalHandle<T,PK,S>) obj; 052 if (isValid() && other.isValid()) { 053 return get().equals(other.get()); 054 } 055 return !other.isValid(); 056 } 057 058 private Reference<T> ref; 059 private T obj; 060 private AtomicBoolean valid = new AtomicBoolean(true); 061 private PK primaryKey; 062 063 private Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> cache; 064 private Reference<Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>>> cacheRef; 065 private HandleStrength handleStrength; 066 private LocalStoreBase<T,PK,S> store; 067 private boolean cacheExtracted; 068 private Predicate<T, S>[] validators; 069 070 public LocalHandle( 071 LocalStoreBase<T,PK,S> store, 072 T obj, 073 PK primaryKey, 074 Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> cache, 075 Predicate<T, S>[] validators, 076 HandleStrength handleStrength, 077 boolean cacheExtracted) { 078 079 this.store = store; 080 this.handleStrength = handleStrength; 081 this.cacheExtracted = cacheExtracted; 082 this.validators = validators; 083 084 com.hammurapi.common.Observable<T> observable = toObservable(obj); 085 if (observable!=null) { 086 observable.addObserver(observer); 087 } 088 089 switch (handleStrength) { 090 case STRONG: 091 this.obj = obj; 092 this.cache = cache; 093 break; 094 case SOFT: 095 ref = new SoftReference<T>(obj); 096 if (cache!=null) { 097 cacheRef = new SoftReference<Map<S,Map<Extractor<T,? super PK,S>,? super PK>>>(cache); 098 } 099 break; 100 case WEAK: 101 ref = new WeakReference<T>(obj); 102 if (cache!=null) { 103 cacheRef = new WeakReference<Map<S,Map<Extractor<T,? super PK,S>,? super PK>>>(cache); 104 } 105 break; 106 } 107 this.primaryKey = primaryKey; 108 valid.set(true); 109 } 110 111 @Override 112 public void update(T obj) { 113 Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> theCache = getCache(); 114 if (cacheExtracted) { 115 if (theCache!=null) { 116 theCache.clear(); 117 } 118 } 119 120 if (store.getPrimaryKeyExtractor()!=null) { 121 @SuppressWarnings("unchecked") 122 PK newPrimaryKey = store.getPrimaryKeyExtractor().extract((S) store, theCache, Util.wrap(obj)); 123 if (!primaryKey.equals(newPrimaryKey)) { 124 throw new StoreException("Primary key of the update object is different from the original primary key, use Store.put() instead of Handle.update()"); 125 } 126 } 127 128 store.updateIndices(this, false); 129 130 switch (handleStrength) { 131 case STRONG: 132 deleteObserver(this.obj); 133 this.obj = obj; 134 break; 135 case SOFT: 136 deleteObserver(ref==null ? null : ref.get()); 137 ref = new SoftReference<T>(obj); 138 break; 139 case WEAK: 140 deleteObserver(ref==null ? null : ref.get()); 141 ref = new WeakReference<T>(obj); 142 break; 143 } 144 145 com.hammurapi.common.Observable observable = toObservable(obj); 146 if (observable!=null) { 147 observable.addObserver(observer); 148 } 149 150 valid.set(true); 151 } 152 153 @Override 154 public void update() { 155 if (cacheExtracted) { 156 getCache().clear(); 157 } 158 159 if (store.getPrimaryKeyExtractor()!=null) { 160 @SuppressWarnings("unchecked") 161 PK newPrimaryKey = store.getPrimaryKeyExtractor().extract((S) store, getCache(), Util.wrap(obj)); 162 if (!primaryKey.equals(newPrimaryKey)) { 163 throw new StoreException("Primary key of the updated object is different from the original primary key, use Store.put() instead of Handle.update()"); 164 } 165 } 166 167 store.updateIndices(this, false); 168 valid.set(true); 169 } 170 171 @Override 172 public void remove() { 173 if (HandleStrength.STRONG.equals(handleStrength)) { 174 deleteObserver(this.obj); 175 } else { 176 deleteObserver(ref==null ? null : ref.get()); 177 } 178 179 valid.set(false); 180 obj = null; 181 ref = null; 182 cache = null; 183 cacheRef = null; 184 } 185 186 private void deleteObserver(T obj) { 187 com.hammurapi.common.Observable<T> observable = toObservable(obj); 188 if (observable!=null) { 189 observable.deleteObserver(observer); 190 } 191 } 192 193 protected Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> getCache() { 194 if (valid.get()) { 195 if (handleStrength==HandleStrength.STRONG) { 196 return cache; 197 } 198 Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> ret = cacheRef==null ? null : cacheRef.get(); 199 if (ret==null && cacheExtracted) { 200 ret = store.createCache(); 201 switch (handleStrength) { 202 case SOFT: 203 cacheRef = new SoftReference<Map<S,Map<Extractor<T,? super PK,S>,? super PK>>>(ret); 204 break; 205 case WEAK: 206 cacheRef = new WeakReference<Map<S,Map<Extractor<T,? super PK,S>,? super PK>>>(ret); 207 break; 208 } 209 } 210 211 return ret; 212 } 213 return null; 214 } 215 216 @Override 217 public T get() { 218 if (isValid()) { 219 if (HandleStrength.STRONG.equals(handleStrength)) { 220 return obj; 221 } 222 return ref==null ? null : ref.get(); 223 } 224 return null; 225 } 226 227 @Override 228 public PK getPrimaryKey() { 229 return primaryKey; 230 } 231 232 public boolean isValid() { 233 if (!valid.get()) { 234 return false; 235 } 236 237 T theObj; 238 if (HandleStrength.STRONG.equals(handleStrength)) { 239 theObj = obj; 240 } else { 241 theObj = ref==null ? null : ref.get(); 242 } 243 if (theObj==null) { 244 return false; 245 } 246 if (validators!=null && validators.length>0) { 247// S deputy = store.createDeputy(); // Do we need deputy here??? 248 for (Predicate<T, S> v: validators) { 249 if (!v.extract((S) store /* deputy */, null, Util.wrap(theObj))) { 250 return false; 251 } 252 } 253 } 254 return true; 255 } 256 257 @Override 258 public <V> V extract(Extractor<T, V, S> extractor) { 259 if (isValid()) { 260 // Rude cast because cache is intentionally of wrong value type (PK) instead of Object for convenience. 261 Map<S, Map<Extractor<T, ? super V, S>, ? super V>> theCache = (Map) getCache(); 262 return extractor.extract(store.createDeputy(), theCache, Util.wrap(get())); 263 } 264 265 return null; 266 } 267 268 @Override 269 public String toString() { 270 return "HandleImpl [get()=" + get() + ", getPrimaryKey()=" 271 + getPrimaryKey() + ", isValid()=" + isValid() + "]"; 272 } 273 274 // TODO equals() and hashCode() for unique stores. 275 276 private Observable<T> toObservable(T obj) { 277 return store.config.getObservableConverter()==null ? null : store.config.getObservableConverter().convert(obj); 278 } 279 280}