001package com.hammurapi.extract;
002
003import java.util.HashMap;
004import java.util.Map;
005import java.util.concurrent.TimeUnit;
006
007import com.hammurapi.extract.ComparisonResult.Type;
008
009
010/**
011 * Base class for extractors, implements value caching.
012 * @author Pavel Vlasov.
013 *
014 * @param <T>
015 * @param <V>
016 */
017public abstract class ExtractorBase<T, V, C> implements Extractor<T, V, C> {
018        
019        protected TimeUnit costUnit;
020        protected long invocations;
021        protected double totalCost;
022        protected boolean nanos;
023        protected double cost;
024        protected double initialCost;
025
026        /**
027         * @param initialCost Initial cost. If positive, this value is added to the calculated cost. If costUnit is null,
028         * then cost is always equal to the initial cost. If negative and time unit is not null, then absolute value is used as first measurement of cost.
029         * @param costUnit If this argument is not null, then extractor cost is 
030         * calculated as average time to execute extractInternal() method in specified time unit.
031         * If time unit is nanoseconds or microseconds, then System.nanoTime() method is used for 
032         * measurement, otherwise Systsm.currentTimeMillis() is used.
033         */
034        protected ExtractorBase(double initialCost, TimeUnit costUnit) {                
035                this.costUnit = costUnit;
036                this.initialCost = initialCost;
037                if (initialCost<0 && costUnit!=null) {
038                        invocations = 1;
039                        totalCost = initialCost;
040                } else {
041                        this.cost = initialCost;                        
042                }
043                nanos = costUnit!=null && TimeUnit.MILLISECONDS.compareTo(costUnit)>0;
044        }
045        
046        @SuppressWarnings("unchecked")
047        public V extract(C context, Map<C, Map<Extractor<T, ? super V, C>, ? super V>> cache, T... obj) {
048                long start = 0;
049                if (costUnit!=null) {
050                        start = nanos ? System.nanoTime() : System.currentTimeMillis();
051                }
052                try {
053                        if (cache==null) {
054                                return extractInternal(context, cache, obj);
055                        }
056                        
057                        synchronized (cache) {
058                                Map<Extractor<T, ? super V, C>, ? super V> ce = cache.get(context);
059                                if (ce==null) {
060                                        ce = createCacheEntry();
061                                        cache.put(context, ce);
062                                }
063                                
064                                V ret = (V) ce.get(this);
065                                if (ret==null) {
066                                        ret = extractInternal(context, cache, obj);
067                                        ce.put(this, ret);
068                                }
069                                return ret;
070                        }
071                } finally {
072                        if (costUnit!=null) {
073                                long end = nanos ? System.nanoTime() : System.currentTimeMillis();
074                                totalCost += costUnit.convert(end-start, nanos ? TimeUnit.NANOSECONDS : TimeUnit.MICROSECONDS);
075                                ++invocations;
076                        }
077                }
078        }
079        
080        @Override
081        public double getCost() {
082                
083                if (invocations==0) {
084                        return cost;
085                }
086                
087                return cost + totalCost/invocations;
088        }
089
090        /**
091         * Basic comparisons with True and False.
092         * @param otherPredicate
093         * @return
094         */
095        public ComparisonResult compareTo(Extractor<T, V, C> other) {
096                if (this==other) {
097                        return ComparisonResult.EQUAL_NM;
098                }
099                
100                // True is less restrictive than anything but self.
101                if (other instanceof True) {
102                        return ComparisonResult.MORE_RESTRICTIVE_NM;
103                }
104                
105                // False is more restrictive than anything but self.
106                if (other instanceof False) {
107                        return ComparisonResult.LESS_RESTRICTIVE_NM;
108                }
109                
110                if (other instanceof FacadeExtractor && !(this instanceof FacadeExtractor)) {
111                        ComparisonResult cr = other.compareTo(this);
112                        if (cr==null) {
113                                return cr;
114                        }
115                        switch (cr.getType()) {
116                        case EQUAL:
117                        case NOT_EQUAL:
118                        case OPPOSITE:
119                                return cr;
120                        case LESS_RESTRICTIVE:
121                                return new ComparisonResult(Type.MORE_RESTRICTIVE, ComparisonResult.inverse(cr.getIndexMap(), parameterIndices()));
122                        case MORE_RESTRICTIVE:
123                                return new ComparisonResult(Type.LESS_RESTRICTIVE, ComparisonResult.inverse(cr.getIndexMap(), parameterIndices()));
124                        case OPPOSITE_LESS_RESTRICTIVE:
125                                return new ComparisonResult(Type.OPPOSITE_LESS_RESTRICTIVE, ComparisonResult.inverse(cr.getIndexMap(), parameterIndices()));
126                        case OPPOSITE_MORE_RESTRICTIVE:
127                                return new ComparisonResult(Type.OPPOSITE_MORE_RESTRICTIVE, ComparisonResult.inverse(cr.getIndexMap(), parameterIndices()));
128                        }
129                }
130                
131                return ComparisonResult.NOT_EQUAL_NM;
132        }
133        
134
135        protected abstract V extractInternal(C context, Map<C, Map<Extractor<T, ? super V, C>, ? super V>> cache, T... obj);
136        
137        protected Map<Extractor<T, ? super V, C>, ? super V> createCacheEntry() {
138                return new HashMap<Extractor<T,? super V,C>, V>();
139        }
140        
141        @Override
142        public boolean equals(Object obj) {
143                if (this == obj) {
144                        return true;
145                }
146                if (obj instanceof Extractor) {
147                        ComparisonResult cr = compareTo((Extractor) obj);
148                        if (ComparisonResult.Type.EQUAL.equals(cr.getType()) && cr.isOneToOneMapping()) {
149                                return true;
150                        }
151                }
152                return false;
153        }
154        
155}