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