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