/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil -*- * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Grendel mail/news client. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1997 * Netscape Communications Corporation. All Rights Reserved. */ package calypso.util; /** * An identifier mapper. This provides a 1-1 map between strings and * integers where the strings are unique within the map. */ public class IDMap { private Object[] fKeys; private int[] fHashCodes; private int[] fValues; private String[] fStrings; private int fNumStrings; private int fShift; private int fIndexMask; private int fCount; private int fCapacity; public IDMap() { fShift = 32 - 3 + 1; } /** * Given a string, find an id for it. If the string is already * present return the old id. Otherwise allocate a new one. */ public synchronized int stringToID(String aStr) { if (fHashCodes == null) { grow(); } int hash = hashCodeForString(aStr); // hashCodeFor(aStr); int slot = tableIndexFor(aStr, hash); if (fHashCodes[slot] == EMPTY) { // If the total number of occupied slots gets too big, grow the // table. if (fCount >= fCapacity) { grow(); return stringToID(aStr); } fCount++; fHashCodes[slot] = hash; fKeys[slot] = aStr; fValues[slot] = addString(aStr); } return fValues[slot]; } /** * Given a StringBuf, find an id for it. If the string is already * present return the old id. Otherwise allocate a new one. */ public synchronized int stringBufToID(StringBuf aStrBuf) { if (fHashCodes == null) { return( stringToID( aStrBuf.toString() ) ); } int hash = hashCodeForStringBuf(aStrBuf); int slot = tableIndexFor(aStrBuf, hash); if (fHashCodes[slot] == EMPTY) { // not found, insert it. return( stringToID( aStrBuf.toString() ) ); } return fValues[slot]; } public synchronized String stringBufToString(StringBuf aStrBuf) { int id = stringBufToID(aStrBuf); return fStrings[id]; } /** * If the argument string is already present in the map return the * original string object. Otherwise, allocate a new id and return * the argument string. */ public synchronized String stringToString(String aStr) { int id = stringToID(aStr); return fStrings[id]; } /** * Given an id, return the string it maps to. */ public String idToString(int aID) { if ((aID < 0) || (aID >= fNumStrings)) { throw new IllegalArgumentException("invalid id: " + aID); } return fStrings[aID]; } /** * Add string to the end of the internal strings array. Return the * index in the array. */ private int addString(String aString) { int rv = fNumStrings; if (fStrings == null) { fStrings = new String[4]; } else if (rv == fStrings.length) { int newLen = rv; if (newLen < 32) { // Double the size of small tables newLen = newLen * 2; } else { // Grow by 20% for larger tables newLen = (int) (newLen * 1.2f); } String[] ns = new String[newLen]; System.arraycopy(fStrings, 0, ns, 0, rv); fStrings = ns; } fStrings[fNumStrings++] = aString; return rv; } //---------------------------------------------------------------------- // Hashtable code lifted from the netscape.util package; retargeted // to use int's as the value's; also removed the "REMOVE" code. /** For the multiplicative hash, choose the golden ratio: *
* A = ((sqrt(5) - 1) / 2) * (1 << 32) ** ala Knuth... */ private static final int A = 0x9e3779b9; /** * We use EMPTY and REMOVED as special markers in the table. If some * poor object returns one of these two values as their hashCode, it * is wacked to DEFAULT. */ private static final int EMPTY = 0; private static final int DEFAULT = 2; /** * Provide hashcode for a given key */ /* private final int hashCodeFor(Object aKey) { int hash = aKey.hashCode(); if (hash == EMPTY) hash = DEFAULT; return hash; } */ // We want StringBuf and String have the same hash code, if // they contain the same string. Therefor, we need to do our // own hash code for String. private final int hashCodeForString(String str ) { int h = 0; int len = str.length(); if (len < 16) { for (int i = 0; i < len; i++) { h = (h * 37) + str.charAt(i); } } else { // only sample some characters int skip = len / 8; for (int i = len, off = 0 ; i > 0; i -= skip, off += skip) { h = (h * 39) + str.charAt(off); } } if (h == EMPTY) h = DEFAULT; return h; } private final int hashCodeForStringBuf(StringBuf strBuf) { int h = 0; int len = strBuf.length(); if (len < 16) { for (int i = 0; i < len; i++) { h = (h * 37) + strBuf.charAt(i); } } else { // only sample some characters int skip = len / 8; for (int i = len, off = 0 ; i > 0; i -= skip, off += skip) { h = (h * 39) + strBuf.charAt(off); } } if (h == EMPTY) h = DEFAULT; return h; } private final boolean keysAreEqual(Object aKey1, Object aKey2) { // It is emportant to use aKey1's equals because aKey1 can be // a String or StringBuf. return aKey1.equals (aKey2); } /** * Primitive method used internally to find slots in the table. If * the key is present in the table, this method will return the * index under which it is stored. If the key is not present, then * this method will return the index under which it can be put. The * caller must look at the hashCode at that index to differentiate * between the two possibilities. */ private int tableIndexFor(Object aKey, int aHash) { // Probe the first slot in the table. We keep track of the first // index where we found a REMOVED marker so we can return that index // as the first available slot if the key is not already in the table. int product = aHash * A; int index = product >>> fShift; int testHash = fHashCodes[index]; if (testHash == aHash) { if (keysAreEqual(aKey, fKeys[index])) return index; } else if (testHash == EMPTY) return index; // Our first probe has failed, so now we need to start looking // elsewhere in the table. int step = ((product >>> (2 * fShift - 32)) & fIndexMask) | 1; int probeCount = 1; do { probeCount++; index = (index + step) & fIndexMask; testHash = fHashCodes[index]; if (testHash == aHash) { if (keysAreEqual(aKey, fKeys[index])) return index; } else if (testHash == EMPTY) { return index; } } while (probeCount <= fCount); // Something very bad has happened. throw new Error ("Hashtable overflow"); } /** * Grows the table by a factor of 2 (or creates it if necessary). All * the REMOVED markers go away and the elements are rehashed into the * bigger table. */ private void grow() { // The table size needs to be a power of two, and it should double // when it grows. We grow when we are more than 3/4 full. fShift--; int power = 32 - fShift; fIndexMask = (1 << power) - 1; fCapacity = (3 * (1 << power)) / 4; int[] oldHashCodes = fHashCodes; Object[] oldKeys = fKeys; int[] oldValues = fValues; fHashCodes = new int[1 << power]; fKeys = new Object[1 << power]; fValues = new int[1 << power]; // Reinsert the old elements into the new table if there are any. Be // sure to reset the counts and increment them as the old entries are // put back in the table. if (fCount > 0) { fCount = 0; for (int i = 0; i < oldHashCodes.length; i++) { Object key = oldKeys[i]; if (key != null) { int hash = oldHashCodes[i]; int index = tableIndexFor(key, hash); fHashCodes[index] = hash; fKeys[index] = key; fValues[index] = oldValues[i]; fCount++; } } } } }