001package org.cache2k.impl;
002
003/*
004 * #%L
005 * cache2k core package
006 * %%
007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich
008 * %%
009 * This program is free software: you can redistribute it and/or modify
010 * it under the terms of the GNU General Public License as
011 * published by the Free Software Foundation, either version 3 of the 
012 * License, or (at your option) any later version.
013 * 
014 * This program is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 * 
019 * You should have received a copy of the GNU General Public 
020 * License along with this program.  If not, see
021 * <http://www.gnu.org/licenses/gpl-3.0.html>.
022 * #L%
023 */
024
025import java.lang.reflect.Array;
026
027/**
028 * Fast hash table implementation. Why we don't want to use the Java HashMap implementation:
029 *
030 * We need to move the entries back and forth between different hashes.
031 * If we do this, each time an entry object needs to be allocated and deallocated.
032 *
033 * Second, we need to do countermeasures, if a key changes its value and hashcode
034 * during its use in the cache. Although it is a user error and therefore not
035 * our primary concern, the effects can be very curious and hard to find.
036 *
037 * Third, we want to have use the hash partly unsynchronized, so we should know the
038 * implementation details.
039 *
040 * Fourth, we can leave out "details" of a general hash table, like shrinking. Our hash table
041 * only expands.
042 *
043 * Access needs to be public, since we want to access the hash primitives from classes
044 * in another package.
045 */
046public class Hash<E extends Entry> {
047
048  public int size = 0;
049  public int maxFill = 0;
050  private int suppressExpandCount;
051
052  public static int index(Entry[] _hashTable, int _hashCode) {
053    return _hashCode & (_hashTable.length - 1);
054  }
055
056
057  public static <E extends Entry> E lookup(E[] _hashTable, Object key, int _hashCode) {
058    int i = index(_hashTable, _hashCode);
059    E e = _hashTable[i];
060    while (e != null) {
061      if (e.hashCode == _hashCode &&
062          key.equals(e.key)) {
063        return e;
064      }
065      e = (E) e.another;
066    }
067    return null;
068  }
069
070  public static boolean contains(Entry[] _hashTable, Object key, int _hashCode) {
071    int i = index(_hashTable, _hashCode);
072    Entry e = _hashTable[i];
073    while (e != null) {
074      if (e.hashCode == _hashCode &&
075          (key == e.key || key.equals(e.key))) {
076        return true;
077      }
078      e = e.another;
079    }
080    return false;
081  }
082
083
084  public static void insertWoExpand(Entry[] _hashTable, Entry e) {
085    int i = index(_hashTable, e.hashCode);
086    e.another = _hashTable[i];
087    _hashTable[i] = e;
088  }
089
090  private static void rehash(Entry[] a1, Entry[] a2) {
091    for (Entry e : a1) {
092      while (e != null) {
093        Entry _next = e.another;
094        insertWoExpand(a2, e);
095        e = _next;
096      }
097    }
098  }
099
100  private static <E extends Entry> E[] expandHash(E[] _hashTable) {
101    E[] a2 = (E[]) Array.newInstance(
102        _hashTable.getClass().getComponentType(),
103        _hashTable.length * 2);
104    rehash(_hashTable, a2);
105    return a2;
106  }
107
108  public static void calcHashCollisionInfo(BaseCache.CollisionInfo inf, Entry[] _hashTable) {
109    for (Entry e : _hashTable) {
110      if (e != null) {
111        e = e.another;
112        if (e != null) {
113          inf.collisionSlotCnt++;
114          int _size = 1;
115          while (e != null) {
116            inf.collisionCnt++;
117            e = e.another;
118            _size++;
119          }
120          if (inf.longestCollisionSize < _size) {
121            inf.longestCollisionSize = _size;
122          }
123        }
124      }
125    }
126
127  }
128
129  /**
130   * Count the entries in the hash table, by scanning through the hash table.
131   * This is used for integrity checks.
132   */
133  public static int calcEntryCount(Entry[] _hashTable) {
134    int _entryCount = 0;
135    for (Entry e : _hashTable) {
136      while (e != null) {
137        _entryCount++;
138        e = e.another;
139      }
140    }
141    return _entryCount;
142  }
143
144  public boolean remove(Entry[] _hashTable, Entry _entry) {
145    int i = index(_hashTable, _entry.hashCode);
146    Entry e = _hashTable[i];
147    if (e == _entry) {
148      _hashTable[i] = e.another;
149      size--;
150      return true;
151    }
152    while (e != null) {
153      Entry _another = e.another;
154      if (_another == _entry) {
155        e.another = _another.another;
156        size--;
157        return true;
158      }
159      e = _another;
160    }
161    return false;
162  }
163
164  /**
165   * Remove entry from the hash. We never shrink the hash table, so
166   * the array keeps identical. After this remove operation the entry
167   * object may be inserted in another hash.
168   */
169  public E remove(E[] _hashTable, Object key, int hc) {
170    int i = index(_hashTable, hc);
171    Entry e = _hashTable[i];
172    if (e == null) {
173      return null;
174    }
175    if (e.hashCode == hc && key.equals(e.key)) {
176      _hashTable[i] = (E) e.another;
177      size--;
178      return (E) e;
179    }
180    Entry _another = e.another;
181    while (_another != null) {
182      if (_another.hashCode == hc && key.equals(_another.key)) {
183        e.another = _another.another;
184        size--;
185        return (E) _another;
186      }
187      e = _another;
188      _another = _another.another;
189    }
190    return null;
191  }
192
193
194  public E[] insert(E[] _hashTable, Entry _entry) {
195    size++;
196    insertWoExpand(_hashTable, _entry);
197    synchronized (this) {
198      if (size >= maxFill && suppressExpandCount == 0) {
199        maxFill = maxFill * 2;
200        return expandHash(_hashTable);
201      }
202      return _hashTable;
203    }
204  }
205
206  /**
207   * Usage/reference counter for iterations to suspend expand
208   * until the iteration finished. This is needed for correctness
209   * of the iteration, if an expand is done during the iteration
210   * process, the iterations returns duplicate entries or not
211   * all entries.
212   *
213   * <p>Failing to operate the increment/decrement in balance will
214   * mean that hash table expands are blocked forever, which is a
215   * serious error condition. Typical problems arise by thrown
216   * exceptions during an iteration.
217   */
218  public synchronized void incrementSuppressExpandCount() {
219    suppressExpandCount++;
220  }
221
222  public synchronized void decrementSuppressExpandCount() {
223    suppressExpandCount--;
224  }
225
226  /**
227   * The cache with this hash was cleared and the hash table is no longer
228   * in used. Signal to iterations to abort.
229   */
230  public void cleared() {
231    if (size >= 0) {
232      size = -1;
233    }
234  }
235
236  /**
237   * Cache was closed. Inform operations/iterators on the hash.
238   */
239  public void close() { size = -2; }
240
241  /**
242   * Operations should terminate
243   */
244  public boolean isCleared() { return size == -1; }
245
246  /**
247   * Operations should terminate and throw a {@link CacheClosedException}
248   */
249  public boolean isClosed() { return size == -2; }
250
251  public boolean shouldAbort() { return size < 0; }
252
253  public E[] init(Class<E> _entryType) {
254    size = 0;
255    maxFill = BaseCache.TUNABLE.initialHashSize * BaseCache.TUNABLE.hashLoadPercent / 100;
256    return (E[]) Array.newInstance(_entryType, BaseCache.TUNABLE.initialHashSize);
257  }
258
259}