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}