001    /*
002    // $Id: //open/mondrian/src/main/mondrian/util/ObjectPool.java#8 $
003    // This software is subject to the terms of the Common Public License
004    // Agreement, available at the following URL:
005    // http://www.opensource.org/licenses/cpl.html.
006    // Copyright (C) 2007-2008 Julian Hyde and others
007    // All Rights Reserved.
008    // You must accept the terms of that agreement to use this software.
009    */
010    
011    //   Copyright (c) 1999-2007 CERN - European Organization for Nuclear Research.
012    //   Permission to use, copy, modify, distribute and sell this software
013    //   and its documentation for any purpose is hereby granted without fee,
014    //   provided that the above copyright notice appear in all copies and
015    //   that both that copyright notice and this permission notice appear in
016    //   supporting documentation. CERN makes no representations about the
017    //   suitability of this software for any purpose. It is provided "as is"
018    //   without expressed or implied warranty.
019    
020    // Created from package cern.colt.map by Richard Emberson, 2007/1/23.
021    // For the source to the Colt project, go to:
022    // http://dsd.lbl.gov/~hoschek/colt/
023    
024    package mondrian.util;
025    
026    import java.util.Iterator;
027    import java.util.NoSuchElementException;
028    
029    /**
030     * An <code>ObjectPool</code> is a low-memory replacement for a
031     * {@link java.util.HashSet}. A HashSet contains a {@link java.util.HashMap}
032     * which in turn has
033     * an array of Entry objects, the Entry objects themselves, and the
034     * key and value objects. An ObjectPool has simply an array of
035     * objects and the objects themselves which server as both key and value.
036     * <p>
037     * This is like the String <code>intern</code> method, but works for
038     * an Object type and whereas the String <code>intern</code> method is global
039     * an ObjectPool can be used within a context and then garbage collected.
040     * Objects can not removed from an ObjectPool except by calling the
041     * <code>clear</code> method which removes all objects.
042     * <p>
043     * Just as with a HashSet's key objects, objects to be placed into
044     * an ObjectPool must implement the <code>equals</code> and
045     * <code>hashCode</code> methods.
046     * <p>
047     * This implementation is NOT thread safe.
048     *
049     * @author Richard Emberson
050     * @version $Id: //open/mondrian/src/main/mondrian/util/ObjectPool.java#8 $
051     */
052    public class ObjectPool<T> {
053        // TODO: Use bits, the state byte array could be a bit array.
054        // The Cern code has to use bytes because they also support
055        // a REMOVE (== 2) state value but for the ObjectPool we only
056        // have FREE or FULL so the use of bits is possible; the
057        // state byte array could be a bit vector which would save
058        // some memory.
059        protected static final byte FREE = 0;
060        protected static final byte FULL = 1;
061    
062        protected static final int DEFAULT_CAPACITY = 277;
063        protected static final double DEFAULT_MIN_LOAD_FACTOR = 0.2;
064        protected static final double DEFAULT_MAX_LOAD_FACTOR = 0.5;
065    
066    
067        /**
068         *  The number of distinct associations in the map; its "size()".
069         */
070        protected int distinct;
071    
072        protected int highWaterMark;
073    
074        /**
075         * The minimum load factor for the hashtable.
076         */
077        protected double minLoadFactor;
078    
079        /**
080         * The maximum load factor for the hashtable.
081         */
082        protected double maxLoadFactor;
083    
084        protected T[] values;
085    
086        /**
087         * The number of table entries in state==FREE.
088         */
089        protected int freeEntries;
090    
091        public ObjectPool() {
092            this(DEFAULT_CAPACITY);
093        }
094        public ObjectPool(int initialCapacity) {
095            this(initialCapacity, DEFAULT_MIN_LOAD_FACTOR, DEFAULT_MAX_LOAD_FACTOR);
096        }
097        public ObjectPool(int initialCapacity,
098                          double minLoadFactor,
099                          double maxLoadFactor) {
100            setUp(initialCapacity,minLoadFactor,maxLoadFactor);
101        }
102    
103        /**
104         * Return the number of entries in the ObjectPool.
105         *
106         * @return number of entries.
107         */
108        public int size() {
109            return distinct;
110        }
111    
112        /**
113         * Reduce the size of the internal arrays to a size just big
114         * enough to hold the current set of entries. Generally, this
115         * should only be called after all entries have been added.
116         * Calling this causes a new, smaller array to be allocated, the
117         * objects are copied to the new array and then the old array is
118         * free to be garbage collected; there is a small time when both
119         * arrays are in memory.
120         */
121        public void trimToSize() {
122            // * 1.2 because open addressing's performance
123            // exponentially degrades beyond that point
124            // so that even rehashing the table can take very long
125            int newCapacity = nextPrime((int)(1 + 1.2 * size()));
126            if (values.length > newCapacity) {
127                rehash(newCapacity);
128            }
129        }
130    
131        /**
132         * Returns true it the Object is already in the ObjectPool and false
133         * otherwise.
134         *
135         * @param key Object to test if member already or not.
136         * @return true is already member
137         */
138        public boolean contains(T key) {
139            int i = indexOfInsertion(key);
140            return (i < 0);
141        }
142    
143        /**
144         * Adds an object to the ObjectPool if it is not
145         * already in the pool or returns the object that is already in the
146         * pool that matches the object being added.
147         *
148         * @param key Object to add to pool
149         * @return Equivalent object, if it exists, otherwise key
150         */
151        public T add(T key) {
152            int i = indexOfInsertion(key);
153            if (i < 0) {
154                //already contained
155                i = -i -1;
156                return this.values[i];
157            }
158    
159            if (this.distinct > this.highWaterMark) {
160                int newCapacity = chooseGrowCapacity(this.distinct + 1,
161                                                     this.minLoadFactor,
162                                                     this.maxLoadFactor);
163                rehash(newCapacity);
164                return add(key);
165            }
166    
167            T v = this.values[i];
168            this.values[i] = key;
169    
170            if (v == null) {
171                this.freeEntries--;
172            }
173            this.distinct++;
174    
175            if (this.freeEntries < 1) {
176                //delta
177                int newCapacity = chooseGrowCapacity(this.distinct + 1,
178                                                     this.minLoadFactor,
179                                                     this.maxLoadFactor);
180                 rehash(newCapacity);
181            }
182    
183            return key;
184        }
185    
186        /**
187         * Removes all objects from the pool but keeps the current size of
188         * the internal storage.
189         */
190        public void clear() {
191            values = (T[]) new Object[values.length];
192    
193            this.distinct = 0;
194            this.freeEntries = values.length; // delta
195            trimToSize();
196        }
197    
198        /**
199         * Returns an Iterator of this <code>ObjectPool</code>. The order of
200         * the Objects returned by the <code>Iterator</code> can not be
201         * counted on to be in the same order as they were inserted
202         * into the <code>ObjectPool</code>.  The
203         * <code>Iterator</code> returned does not
204         * support the removal of <code>ObjectPool</code> members.
205         */
206        public Iterator<T> iterator() {
207            return new Itr();
208        }
209    
210    
211    
212        protected int chooseGrowCapacity(int size, double minLoad, double maxLoad) {
213            return nextPrime(Math.max(size + 1,
214                (int) ((4 * size / (3 * minLoad + maxLoad)))));
215        }
216        protected final int chooseHighWaterMark(int capacity, double maxLoad) {
217            //makes sure there is always at least one FREE slot
218            return Math.min(capacity - 2, (int) (capacity * maxLoad));
219        }
220        protected final int chooseLowWaterMark(int capacity, double minLoad) {
221            return (int) (capacity * minLoad);
222        }
223    /*
224        protected int chooseMeanCapacity(int size, double minLoad, double maxLoad) {
225            return nextPrime(Math.max(size + 1, (int) ((2*size/(minLoad+maxLoad)))));
226        }
227        protected int chooseShrinkCapacity(int size, double minLoad, double maxLoad) {
228            return nextPrime(Math.max(size + 1, (int) ((4*size/(minLoad+3*maxLoad)))));
229        }
230    */
231    
232        protected int nextPrime(int desiredCapacity) {
233            return PrimeFinder.nextPrime(desiredCapacity);
234        }
235    
236        protected void setUp(int initialCapacity,
237                             double minLoadFactor,
238                             double maxLoadFactor) {
239            int capacity = initialCapacity;
240    
241            if (initialCapacity < 0) {
242                throw new IllegalArgumentException(
243                    "Initial Capacity must not be less than zero: "+
244                    initialCapacity);
245            }
246            if (minLoadFactor < 0.0 || minLoadFactor >= 1.0) {
247                throw new IllegalArgumentException(
248                    "Illegal minLoadFactor: " + minLoadFactor);
249            }
250            if (maxLoadFactor <= 0.0 || maxLoadFactor >= 1.0) {
251                throw new IllegalArgumentException(
252                    "Illegal maxLoadFactor: " + maxLoadFactor);
253            }
254            if (minLoadFactor >= maxLoadFactor) {
255                throw new IllegalArgumentException(
256                    "Illegal minLoadFactor: " + minLoadFactor +
257                    " and maxLoadFactor: " + maxLoadFactor);
258            }
259            capacity = nextPrime(capacity);
260    
261            // open addressing needs at least one FREE slot at any time.
262            if (capacity == 0) {
263                capacity = 1;
264            }
265    
266            //this.table = new long[capacity];
267            this.values = (T[]) new Object[capacity];
268            //this.state = new byte[capacity];
269    
270            // memory will be exhausted long before this
271            // pathological case happens, anyway.
272            this.minLoadFactor = minLoadFactor;
273            if (capacity == PrimeFinder.largestPrime) {
274                this.maxLoadFactor = 1.0;
275            } else {
276                this.maxLoadFactor = maxLoadFactor;
277            }
278    
279            this.distinct = 0;
280            this.freeEntries = capacity; // delta
281            this.highWaterMark = chooseHighWaterMark(capacity, this.maxLoadFactor);
282    
283        }
284    
285    
286        protected int hash(T key) {
287            return (key == null) ? 0 : key.hashCode();
288        }
289        protected boolean equals(T t, T key) {
290            return (t != null) && t.equals(key);
291        }
292    
293        protected int indexOfInsertion(T key) {
294            final T[] tab = values;
295            final int length = tab.length;
296    
297            final int hash = key.hashCode() & 0x7FFFFFFF;
298            int i = hash % length;
299    
300            // double hashing,
301            // see http://www.eece.unm.edu/faculty/heileman/hash/node4.html
302            int decrement = hash % (length - 2);
303    
304            //int decrement = (hash / length) % length;
305            if (decrement == 0) {
306                decrement = 1;
307            }
308    
309            // stop if we find a free slot, or if we find the key itself
310            T v = tab[i];
311            while (v != null && !v.equals(key)) {
312                //hashCollisions++;
313                i -= decrement;
314                if (i < 0) {
315                    i += length;
316                }
317                v = tab[i];
318            }
319    
320            // key already contained at slot i.
321            // return a negative number identifying the slot.
322            // not already contained, should be inserted at slot i.
323            // return a number >= 0 identifying the slot.
324            return (v != null) ? -i - 1 : i;
325        }
326    
327        protected void rehash(int newCapacity) {
328            int oldCapacity = values.length;
329    
330            T[] oldValues = values;
331    
332            T[] newValues = (T[]) new Object[newCapacity];
333    
334            this.highWaterMark = chooseHighWaterMark(newCapacity,this.maxLoadFactor);
335    
336            this.values = newValues;
337            this.freeEntries = newCapacity - this.distinct; // delta
338            for (int i = oldCapacity ; i-- > 0 ;) {
339                T v = oldValues[i];
340                if (v != null) {
341                    int index = indexOfInsertion(v);
342                    newValues[index] = v;
343                }
344            }
345        }
346    
347        private class Itr implements Iterator<T> {
348            int index = 0;
349            Itr() {
350            }
351    
352            public boolean hasNext() {
353                if (index == ObjectPool.this.values.length) {
354                    return false;
355                }
356                while (ObjectPool.this.values[index] == null) {
357                    index++;
358                    if (index == ObjectPool.this.values.length) {
359                        return false;
360                    }
361                }
362                return (ObjectPool.this.values[index] != null);
363            }
364    
365            public T next() {
366                if (index >= ObjectPool.this.values.length) {
367                    throw new NoSuchElementException();
368                }
369                return ObjectPool.this.values[index++];
370            }
371    
372            public void remove() {
373                throw new UnsupportedOperationException("ObjectPool.Itr.remove");
374            }
375        }
376    }
377    
378    // End ObjectPool.java