| 1 | package com.hammurapi.store.local; |
| 2 | |
| 3 | import java.lang.ref.Reference; |
| 4 | import java.lang.ref.SoftReference; |
| 5 | import java.lang.ref.WeakReference; |
| 6 | import java.util.Map; |
| 7 | import java.util.concurrent.atomic.AtomicBoolean; |
| 8 | |
| 9 | import com.hammurapi.common.Observable; |
| 10 | import com.hammurapi.common.Observer; |
| 11 | import com.hammurapi.common.Util; |
| 12 | import com.hammurapi.extract.Extractor; |
| 13 | import com.hammurapi.extract.Predicate; |
| 14 | import com.hammurapi.store.Store; |
| 15 | import com.hammurapi.store.Store.Handle; |
| 16 | import com.hammurapi.store.StoreException; |
| 17 | |
| 18 | public class LocalHandle<T,PK, S extends Store<T,PK,S>> implements Handle<T, PK, S> { |
| 19 | |
| 20 | private final Observer<T> observer = new Observer<T>() { |
| 21 | |
| 22 | @Override |
| 23 | public void update(T obj, Object... args) { |
| 24 | LocalHandle.this.update(); |
| 25 | } |
| 26 | |
| 27 | }; |
| 28 | |
| 29 | public enum HandleStrength { |
| 30 | STRONG, SOFT, WEAK |
| 31 | } |
| 32 | |
| 33 | @Override |
| 34 | public int hashCode() { |
| 35 | if (isValid()) { |
| 36 | return get().hashCode(); |
| 37 | } |
| 38 | return 0; |
| 39 | } |
| 40 | |
| 41 | @Override |
| 42 | public boolean equals(Object obj) { |
| 43 | if (this == obj) |
| 44 | return true; |
| 45 | if (obj == null) |
| 46 | return false; |
| 47 | if (getClass() != obj.getClass()) |
| 48 | return false; |
| 49 | |
| 50 | @SuppressWarnings("unchecked") |
| 51 | LocalHandle<T,PK,S> other = (LocalHandle<T,PK,S>) obj; |
| 52 | if (isValid() && other.isValid()) { |
| 53 | return get().equals(other.get()); |
| 54 | } |
| 55 | return !other.isValid(); |
| 56 | } |
| 57 | |
| 58 | private Reference<T> ref; |
| 59 | private T obj; |
| 60 | private AtomicBoolean valid = new AtomicBoolean(true); |
| 61 | private PK primaryKey; |
| 62 | |
| 63 | private Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> cache; |
| 64 | private Reference<Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>>> cacheRef; |
| 65 | private HandleStrength handleStrength; |
| 66 | private LocalStoreBase<T,PK,S> store; |
| 67 | private boolean cacheExtracted; |
| 68 | private Predicate<T, S>[] validators; |
| 69 | |
| 70 | public LocalHandle( |
| 71 | LocalStoreBase<T,PK,S> store, |
| 72 | T obj, |
| 73 | PK primaryKey, |
| 74 | Map<S, Map<Extractor<T, ? super PK, S>, ? super PK>> cache, |
| 75 | Predicate<T, S>[] validators, |
| 76 | HandleStrength handleStrength, |
| 77 | boolean cacheExtracted) { |
| 78 | |
| 79 | this.store = store; |
| 80 | this.handleStrength = handleStrength; |
| 81 | this.cacheExtracted = cacheExtracted; |
| 82 | this.validators = validators; |
| 83 | |
| 84 | com.hammurapi.common.Observable<T> observable = toObservable(obj); |
| 85 | if (observable!=null) { |
| 86 | observable.addObserver(observer); |
| 87 | } |
| 88 | |
| 89 | switch (handleStrength) { |
| 90 | case STRONG: |
| 91 | this.obj = obj; |
| 92 | this.cache = cache; |
| 93 | break; |
| 94 | case SOFT: |
| 95 | ref = new SoftReference<T>(obj); |
| 96 | if (cache!=null) { |
| 97 | cacheRef = new SoftReference<Map<S,Map<Extractor<T,? super PK,S>,? super PK>>>(cache); |
| 98 | } |
| 99 | 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 | } |