    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}