// // Copyright (C) 2006 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.util; import java.util.Iterator; import gov.nasa.jpf.JPFException; /** * A hash map that holds int values associated with generic key objects. * This is a straight forward linked list hashmap. * * Key objects have to be invariant, lookup uses equality but checks for * identity as an optimization. * * note: this does deep copy clones, which can be quite expensive */ @SuppressWarnings("unchecked") public final class IntTable<E> implements Iterable<IntTable.Entry<E>>, Cloneable{ static final int INIT_TBL_POW = 7; static final double MAX_LOAD = 0.80; //--- inner types /** * encapsulates an Entry in the table. changes to val will be reflected * in the table. */ public static class Entry<E> implements Cloneable { public final E key; public final int val; protected Entry<E> next; protected Entry(E k, int v) { key = k; val = v; next = null; } protected Entry(E k, int v, Entry<E> n) { key = k; val = v; next = n; } public Entry<E> clone() { try { return (Entry<E>)super.clone(); } catch (CloneNotSupportedException x){ throw new JPFException("clone failed"); } } public String toString() { return key.toString() + " => " + val; } //--- methods required to use IntTable entries itself as HashMap keys // but beware - val can be modified since we expose it (never modify // key objects of HashMaps) public int hashCode (){ return OATHash.hash(key.hashCode(), val); } public boolean equals (Object o){ if (o instanceof Entry){ Entry<E> other = (Entry<E>)o; if (val == other.val){ E k = other.key; if (key == k || key.equals(k)){ return true; } } } return false; } } /** * helper class to store a compact, invariant representation of this table */ public static class Snapshot<E> { protected final int tblSize; protected final int tblPow; protected final int[] indices; protected final E[] keys; protected final int[] vals; protected Snapshot (IntTable<E> t){ Entry<E>[] tbl = t.table; int nEntries = t.size; tblSize = tbl.length; tblPow = t.tblPow; indices = new int[nEntries]; keys = (E[]) new Object[nEntries]; vals = new int[nEntries]; int j = 0; for (int i=0; i<tbl.length && j<nEntries; i++){ Entry<E> e = tbl[i]; if (e != null){ if (e.next == null){ // just one entry under this head indices[j] = i; keys[j] = e.key; vals[j] = e.val; j++; } else { // we have to store in reverse order so that restore preserves it // we do the revert here because storing happens once, whereas restore can happen many times int n = 1; for (Entry<E> ee = e.next; ee != null; ee = ee.next){ n++; } int k = j+n-1; j += n; for (; e != null; e = e.next){ indices[k] = i; keys[k] = e.key; vals[k] = e.val; k--; } } } } } } //--- instance fields protected Entry<E>[] table; // array of entry heads protected int tblPow; // = log_2(table.length) protected int mask; // = table.length - 1 protected int nextRehash; // = ceil(MAX_LOAD * table.length); protected int size; // number of Entry<E> objects reachable from table protected Entry<E> nullEntry = null; Snapshot<E> lastSnapshot; // cache for the last snapshot (nulled once the IntTable is changed) public IntTable() { this(INIT_TBL_POW); } public IntTable(int pow) { newTable(pow); size = 0; } public Snapshot<E> getSnapshot(){ if (lastSnapshot == null) { lastSnapshot = new Snapshot<E>(this); } return lastSnapshot; } public void restore (Snapshot<E> snapshot){ Entry<E>[] tbl = (Entry<E>[]) new Entry[snapshot.tblSize]; int[] indices = snapshot.indices; E[] keys = snapshot.keys; int[] vals = snapshot.vals; int nEntries = vals.length; for (int i=0; i<nEntries; i++){ int idx = indices[i]; tbl[idx] = new Entry<>( keys[i], vals[i], tbl[idx]); } table = tbl; size = nEntries; mask = table.length -1; nextRehash = (int) Math.ceil(MAX_LOAD * table.length); tblPow = snapshot.tblPow; lastSnapshot = snapshot; } // this is a deep copy (needs to be because entries are reused when growing the table) public IntTable<E> clone() { try { IntTable<E> t = (IntTable<E>)super.clone(); Entry<E>[] tbl = (Entry<E>[])table.clone(); t.table = tbl; // clone entries int len = table.length; for (int i=0; i<len; i++){ Entry<E> eFirst = tbl[i]; if (eFirst != null){ eFirst = eFirst.clone(); Entry<E> ePrev = eFirst; for (Entry<E> e = eFirst.next; e != null; e = e.next){ e = e.clone(); ePrev.next = e; ePrev = e; } tbl[i] = eFirst; } } return t; } catch (CloneNotSupportedException cnsx){ throw new JPFException("clone failed"); } } protected void newTable(int pow) { tblPow = pow; table = (Entry<E>[]) new Entry[1 << tblPow]; mask = table.length - 1; nextRehash = (int) Math.ceil(MAX_LOAD * table.length); } protected int getTableIndex(E key) { int hc = key.hashCode(); int ret = hc ^ 786668707; ret += (hc >>> tblPow); return (ret ^ 1558394450) & mask; } protected boolean maybeRehash() { if (size < nextRehash){ return false; } else { lastSnapshot = null; size = 0; Entry<E>[] old = table; int oldTblLength = old.length; newTable(tblPow + 1); int len = oldTblLength; for (int i = 0; i < len; i++) { addList(old[i]); } return true; } } private void addList(Entry<E> e) { Entry<E> cur = e; while (cur != null) { Entry<E> tmp = cur; cur = cur.next; addEntry(tmp, getTableIndex(tmp.key)); } } //--- the methods traversing the entry lists // helper for adding protected void addEntry(Entry<E> e, int idx) { e.next = table[idx]; table[idx] = e; size++; lastSnapshot = null; } // helper for searching protected Entry<E> getEntry(E key, int idx) { Entry<E> cur = table[idx]; while (cur != null) { E k = cur.key; // note - this assumes invariant keys !! if (k == key || (k.equals(key))){ return cur; } cur = cur.next; } return null; // not found } // helper for value update protected void replaceEntryValue( int idx, Entry<E> oldEntry, int newValue) { Entry<E> last = null; for (Entry<E> e = table[idx]; e != null; e = e.next, last = e) { if (e == oldEntry) { Entry<E> newEntry = new Entry<E>(oldEntry.key, newValue); newEntry.next = e.next; lastSnapshot = null; if (last == null) { table[idx] = newEntry; } else { last.next = newEntry; } } } } //--- public methods /** returns number of bindings in the table. */ public int size() { return size; } /** ONLY USE IF YOU ARE SURE NO PREVIOUS BINDING FOR key EXISTS. */ public Entry<E> add (E key, int val) { Entry<E> e = new Entry<E>(key,val); if (key == null) { nullEntry = e; } else { maybeRehash(); addEntry(e, getTableIndex(key)); } return e; } /** lookup, returning null if no binding. */ public Entry<E> get (E key) { return getEntry(key, getTableIndex(key)); } /** * a little optimization to speed up counter increments */ public Entry<E> getInc (E key){ int idx = getTableIndex(key); Entry<E> last = null; for (Entry<E> e = table[idx]; e != null; e = e.next) { if (e.key == key || e.key.equals(key)) { // found it, replace entry Entry<E> newEntry = new Entry<E>(key, e.val+1, e.next); lastSnapshot = null; if (last == null) { table[idx] = newEntry; } else { last.next = newEntry; } return newEntry; } else { last = e; } } // it wasn't there, add a new entry with value 1 Entry<E> newEntry = new Entry<E>( key, 1); if (maybeRehash()) { idx = getTableIndex(key); } addEntry( newEntry, idx); return newEntry; } /** just like HashMap put. */ public void put(E key, int val) { if (key == null) { if (nullEntry == null) { nullEntry = new Entry<E>(null,val); size++; } else { nullEntry = new Entry<E>(null, val); } return; } int idx = getTableIndex(key); Entry<E> e = getEntry(key, idx); if (e == null) { // wasn't there if (maybeRehash()){ idx = getTableIndex(key); } addEntry(new Entry<E>(key,val), idx); } else { replaceEntryValue( idx, e, val); lastSnapshot = null; } } /** removes a binding/entry from the table. */ public Entry<E> remove(E key) { int idx = getTableIndex(key); Entry<E> prev = null; Entry<E> cur = table[idx]; while (cur != null) { E k = cur.key; if (k == key || k.equals(key)) { if (prev == null) { table[idx] = cur.next; } else { prev.next = cur.next; } cur.next = null; size--; lastSnapshot = null; return cur; } prev = cur; cur = cur.next; } return null; // not found } /** empties the table, leaving it capacity the same. */ public void clear() { table = (Entry<E>[]) new Entry[table.length]; nullEntry = null; size = 0; lastSnapshot = null; } /** returns the next val to be assigned by a call to pool() on a fresh key. */ public int nextPoolVal() { return size; } /** gets the Entry associated with key, adding previous `size' if not yet bound. */ public Entry<E> pool(E key) { if (key == null) { if (nullEntry == null) { nullEntry = new Entry<E>(null,size); size++; } return nullEntry; } int idx = getTableIndex(key); Entry<E> e = getEntry(key, idx); if (e == null) { if (maybeRehash()) { idx = getTableIndex(key); } e = new Entry<E>(key,size); addEntry(e, idx); } return e; } /** shorthand for <code>pool(key).val</code>. */ public int poolIndex(E key) { return pool(key).val; } /** shorthand for <code>pool(key).key</code>. */ public E poolKey(E key) { return pool(key).key; } /** shorthand for <code>get(key) != null</code>. */ public boolean hasEntry(E key) { return get(key) != null; } /** * returns an iterator over the entries. unpredictable behavior could result if * using iterator after table is altered. */ public Iterator<Entry<E>> iterator () { return new TblIterator(); } protected class TblIterator implements Iterator<Entry<E>> { int idx; Entry<E> cur; public TblIterator() { idx = -1; cur = null; advance(); } void advance() { if (cur != null) { cur = cur.next; } int len = table.length; while (idx < len && cur == null) { idx++; if (idx < len) { cur = table[idx]; } } } public boolean hasNext () { return idx < table.length; } public Entry<E> next () { Entry<E> e = cur; advance(); return e; } public void remove () { throw new UnsupportedOperationException(); } } /** * for debugging purposes */ public void dump(){ System.out.print('{'); int n=0; for (int i=0; i<table.length; i++){ for (Entry<E> e = table[i]; e != null; e = e.next){ if (n++>0){ System.out.print(','); } System.out.print('('); System.out.print(e.key); System.out.print("=>"); System.out.print(e.val); System.out.print(')'); } } System.out.println('}'); } public int computeSize() { int n=0; for (int i=0; i<table.length; i++){ for (Entry<E> e = table[i]; e != null; e = e.next){ n++; } } return n; } }