// // Copyright (C) 2012 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.io.PrintStream; import java.util.Iterator; import java.util.NoSuchElementException; /** * Persistent (immutable) associative array that maps integer keys to generic reference values. * <p> * PSIntMap is implemented as a bitwise trie which processes key bits in msb order * (from left to right) and has the same depth along all paths (i.e. values are only kept at the * terminal node level, which corresponds to the rightmost bit block in the key). * * This particular implementation was chosen to optimize performance for dense key value domains, * e.g. keys that are computed from counters. More specifically, PSIntMap was designed to be a * suitable basis for JPF Heap implementations with their characteristic usage pattern: * .. * transition{ ..alloc( n),..alloc(n+1),..alloc(n+2), ..}, garbage-collection{ remove(x),remove(y),..} * .. * * The 32bit keys are broken up into 5bit blocks that represent the trie levels, each 5bit block * (0..31) being the index for the respective child node or value. * For instance, a key/value pair of 12345->'x' is stored as * <blockquote><pre> * level: 6 5 4 3 2 1 0 * key: 00.00000.00000.00000.01100.00001.11001 = 12345 * block-val: 0 0 0 0 12 1 25 * * Node0 (level 2 : nodes) * ... * [12] -> Node1 (level 1 : nodes) * ... * [1] -> Node2 (level 0 : values) * ... * [25] -> 'x' *</pre></blockquote> * The main benefit of using this representation is that existing maps are never modified (are * persistent) and hence a previous state can be restored by simply keeping the reference of * the respective map. The main drawback is that not only the changed value has to be stored * upon add/remove, but everything from the node that contains this value up to the root node. * * This implementation partitions keys from left (msb) to right, which has the major property that * consecutive keys are stored in the same node, which in turn allows for efficient caching of * the last modified node. Keeping track of this 'stagingNode' avoids copying anything * but the affected node until the next staging node miss, at which point the old stagingNode * has to be merged. This merge only requires copying of old stagingNode parents up to the * level that already has been copied due to the new key insertion that caused the stagingNode miss). * * The internal trie representation uses a protected Node type, which uses the bit block values (0..31) * as index into an array that stores either child node references (in case this is not a * terminal block), or value objects (if this is the terminal level). There are three Node * subtypes that get promoted upon population in the following order: * <ul> * <li>OneNode - store only a single value/child element. Every node starts as a OneNode * <li>BitmapNode - stores up to 31 elements (compressed) * <li>FullNode - stores 32 elements * </ul> * Removal of keys leads to symmetric demotion of node types. * * The five major public operations for PersistentIntMaps are * * <ol> * <li>set(int key, V value) -> PersistentIntMap : return a new map with an additional value * <li>get(int key) -> V : retrieve value * <li>remove(int key) -> PersistentIntMap : return a new map without the specified key/value * <li>removeAllSatisfying(Predicate<V> predicate) -> PersistentIntMap : return a new map * without all values satisfying the specified predicate * <li>process(Processor<V> processor) : iterate over all values with specified processor * </ol> * * Being a persistent data structure, the main property of PersistentIntMaps is that all * add/remove operations (set,remove,removeAllSatisfying) have to return new PersistenIntMap * instances, no destructive update is allowed. Normal usage patterns therefore look like this: * * <blockquote><pre> * PSIntMap<String> map = PSIntMap<String>(); * .. * map = map.set(42, "fortytwo"); // returns a new map * .. * map = map.remove(42); // returns a new map * .. * map = map.removeAllSatisfying( new Predicate<String>(){ // returns a new map * public boolean isTrue (String val){ * return val.endsWith("two"); * }); * * map.process( new Processor<String>(){ * public void process (String val){ * System.out.println(val); * }); * </pre></blockquote> * * NOTE: bitwise tries are inherently recursive data structures, which would naturally lend * itself to implementations using recursive methods (over nodes). However, the recursion * is always bounded (finite number of key bits), and we need to keep track of the terminal * (value) node that was modified, which means we would have to return two values from * every recursion level (new current level node and new (terminal) stagingNode), thus * requiring additional allocation per map operation ( e.g. 'result' object to keep track * of transient state, as in "node = node.assoc(..key, value, result)") or per recursive call * ( result: {node,stagingNode}, as in "result = node.assoc( ..key, value)"). The first solution * would allow to create/store a result object on the caller site, but this could compromise * map consistency in case of concurrent map operations. Both solutions are counter-productive * in a sense that PSIntMap is optimized to minimize allocation count, which is the crux of * persistent data structures. * * The approach that is taken here is to manually unroll the recursion by means of explicit * operand stacks, which leads to methods with large number of local variables (to avoid * array allocation) and large switch statements to set respective fields. The resulting * programming style should only be acceptable for critical runtime optimizations. */ @SuppressWarnings({ "rawtypes", "unchecked" }) public class PSIntMap <V> implements Iterable<V> { //--- auxiliary types /** * Abstract root class for all node types. This type needs to be internal, no instances * are allowed to be visible outside the PersistentIntMap class hierarchy in order to guarantee * invariant data. * * NOTE - since this is an internal type, we forego a lot of argument range checks in * the Node subclasses, assuming that all internal use has been tested and bugs will not * cause silent corruption of node data but will lead to follow-on exceptions such as * ArrayIndexOutOfBounds etc. */ protected abstract static class Node<E> { abstract E getElementAtLevelIndex (int i); abstract int getNumberOfElements(); abstract E getElementAtStorageIndex (int i); abstract int storageToLevelIndex (int i); //--- those clone abstract Node cloneWithAdded (int idx, E e); abstract Node cloneWithReplaced (int idx, E e); abstract Node cloneWithRemoved (int idx); abstract Node removeAllSatisfying (Predicate<E> pred); //--- no clone abstract void set (int idx, E e); abstract void process (int level, Node<E> targetNode, Node<E> stagingNode, Processor<E> p); boolean isEmptyNode(){ return false; } //--- debugging void printIndentOn (PrintStream ps, int level) { for (int i=0; i<level; i++) { ps.print(" "); } } void printNodeInfoOn (PrintStream ps, Node targetNode, Node stagingNode) { String clsName = getClass().getSimpleName(); int idx = clsName.indexOf('$'); if (idx > 0) { clsName = clsName.substring(idx+1); } ps.print(clsName); if (this == targetNode){ ps.print( " (target)"); } } abstract void printOn(PrintStream ps, int level, Node targetNode, Node stagingNode); } /** * Node that has only one element and hence does not need an array. * If a new element is added, this OneNode gets promoted into a BitmapNode */ protected static class OneNode<E> extends Node<E> { E e; int idx; OneNode (int idx, E e){ this.idx = idx; this.e = e; } @Override int getNumberOfElements(){ return 1; } @Override E getElementAtStorageIndex (int i){ assert i == 0; return e; } @Override E getElementAtLevelIndex(int i) { if (i == idx){ return e; } else { return null; } } @Override int storageToLevelIndex (int i){ if (i == 0){ return idx; } return -1; } /** * this assumes the index is not set */ @Override Node cloneWithAdded(int i, E newElement) { assert i != idx; Object[] a = new Object[2]; if (i < idx){ a[0] = newElement; a[1] = e; } else { a[0] = e; a[1] = newElement; } int bitmap = (1 << idx) | (1 << i); return new BitmapNode(bitmap, a); } /** * this assumes the index is set */ @Override Node cloneWithReplaced(int i, E e) { assert i == idx; return new OneNode( i, e); } /** * this assumes the index is set */ @Override Node cloneWithRemoved(int i){ assert (i == idx); return null; } @Override Node removeAllSatisfying (Predicate<E> pred){ if (pred.isTrue(e)){ return null; } else { return this; } } @Override void set (int i, E e){ assert i == idx; this.e = e; } @Override boolean isEmptyNode(){ return idx == 0; } @Override void process (int level, Node<E> targetNode, Node<E> stagingNode, Processor<E> p){ if (level == 0){ if (this == targetNode){ stagingNode.process( 0, null, null, p); } else { p.process(e); } } else { ((Node)e).process( level-1, targetNode, stagingNode, p); } } public void printOn (PrintStream ps, int depth, Node targetNode, Node stagingNode) { printIndentOn(ps, depth); ps.printf("%2d: ", idx); if (e instanceof Node) { Node<E> n = (Node<E>) e; n.printNodeInfoOn(ps, targetNode, stagingNode); ps.println(); n.printOn(ps, depth+1, targetNode, stagingNode); } else { ps.print("value="); ps.println(e); } } } /** * A node that holds between 2 and 31 elements. * * We use bitmap based element array compaction - the corresponding bit block of the key * [0..31] is used as an index into a bitmap. The elements are stored in a dense * array at indices corresponding to the number of set bitmap bits to the right of the * respective index in the bitmap, e.g. for * * <blockquote><pre> * key = 289 = 0b01001.00001, shift = 5, assuming node already contains key 97 = 0b00011.00001 => * idx = (key >>> shift) & 0x1f = 0b01001 = 9 * bitmap = 1000001000 : bit 9 from key 289 (0b01001.), bit 3 from key 97 (0b00011.) * node element index for key 289 (level index 9) = 1 (one set bit to the right of bit 9) * </pre></blockquote> * * While storage index computation seems complicated and expensive, there are efficient algorithms to * count leading/trailing bits by means of binary operations and minimal branching, which is * suitable for JIT compilation (see http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup) * * <p> * If the bit count of a BitmapNode is 2 and an element is removed, this gets demoted into q OneNode. * If the bit count of a BitmapNode is 31 and an element is added, this gets promoted into a FullNode */ protected static class BitmapNode<E> extends Node<E> { final E[] elements; final int bitmap; BitmapNode (int idx, E e, E e0){ bitmap = (1 << idx) | 1; elements = (E[]) new Object[2]; elements[0] = e0; elements[1] = e; } BitmapNode (int bitmap, E[] elements){ this.bitmap = bitmap; this.elements = elements; } @Override int getNumberOfElements(){ return elements.length; } @Override E getElementAtStorageIndex (int i){ return elements[i]; } @Override E getElementAtLevelIndex (int i) { int bit = 1 << i; if ((bitmap & bit) != 0) { int idx = Integer.bitCount( bitmap & (bit-1)); return elements[idx]; } else { return null; } } /** * get the position of the (n+1)'th set bit in bitmap */ @Override int storageToLevelIndex (int n){ int v = bitmap; /**/ switch (n){ case 30: v &= v-1; case 29: v &= v-1; case 28: v &= v-1; case 27: v &= v-1; case 26: v &= v-1; case 25: v &= v-1; case 24: v &= v-1; case 23: v &= v-1; case 22: v &= v-1; case 21: v &= v-1; case 20: v &= v-1; case 19: v &= v-1; case 18: v &= v-1; case 17: v &= v-1; case 16: v &= v-1; case 15: v &= v-1; case 14: v &= v-1; case 13: v &= v-1; case 12: v &= v-1; case 11: v &= v-1; case 10: v &= v-1; case 9: v &= v-1; case 8: v &= v-1; case 7: v &= v-1; case 6: v &= v-1; case 5: v &= v-1; case 4: v &= v-1; case 3: v &= v-1; case 2: v &= v-1; case 1: v &= v-1; } /**/ /** for (int i=n; i>0; i--){ v &= v-1; // remove n-1 least significant bits } **/ v = v & ~(v-1); // reduce to the least significant bit return TrailingMultiplyDeBruijnBitPosition[((v & -v) * 0x077CB531) >>> 27]; } @Override Node cloneWithAdded(int i, E e) { int bit = 1 << i; int idx = Integer.bitCount( bitmap & (bit -1)); if (elements.length == 31){ Object[] a = new Object[32]; if (idx > 0) { System.arraycopy(elements, 0, a, 0, idx); } if (idx < 31) { System.arraycopy(elements, idx, a, idx + 1, 31 - idx); } a[idx] = e; return new FullNode(a); } else { int n = elements.length; Object[] a = new Object[n + 1]; if (idx > 0) { System.arraycopy(elements, 0, a, 0, idx); } a[idx] = e; if (n > idx) { System.arraycopy(elements, idx, a, idx + 1, (n - idx)); } return new BitmapNode( bitmap | bit, a); } } @Override Node cloneWithReplaced(int i, E e) { int idx = Integer.bitCount( bitmap & ((1<<i) -1)); E[] a = elements.clone(); a[idx] = e; return new BitmapNode( bitmap, a); } @Override Node cloneWithRemoved(int i){ int bit = (1<<i); int idx = Integer.bitCount( bitmap & (bit-1)); int n = elements.length; if (n == 2){ E e = (idx == 0) ? elements[1] : elements[0]; // the remaining value int i0 = Integer.numberOfTrailingZeros(bitmap ^ bit); return new OneNode( i0, e); } else { Object[] a = new Object[n - 1]; if (idx > 0) { System.arraycopy(elements, 0, a, 0, idx); } n--; if (n > idx) { System.arraycopy(elements, idx + 1, a, idx, (n - idx)); } return new BitmapNode(bitmap ^ bit, a); } } @Override Node removeAllSatisfying (Predicate<E> pred){ int newBitmap = bitmap; int len = elements.length; int newLen = len; E[] elem = elements; int removed = 0; for (int i=0, bit=1; i<len; i++, bit<<=1){ while ((newBitmap & bit) == 0){ bit <<= 1; } if (pred.isTrue(elem[i])){ newBitmap ^= bit; newLen--; removed |= (1 << i); } } if (newLen == 0){ // nothing left return null; } else if (newLen == len){ // nothing removed return this; } else if (newLen == 1) { // just one value left - reduce to OneNode int i = Integer.bitCount( bitmap & (newBitmap -1)); int idx = Integer.numberOfTrailingZeros(newBitmap); return new OneNode<E>( idx, elem[i]); } else { // some values removed - reduced BitmapNode E[] newElements = (E[]) new Object[newLen]; for (int i=0, j=0; j<newLen; i++){ if ((removed & (1<<i)) == 0){ newElements[j++] = elem[i]; } } return new BitmapNode( newBitmap, newElements); } } @Override void set (int i, E e){ int idx = Integer.bitCount( bitmap & ((1<<i) -1)); elements[idx] = e; } @Override void process (int level, Node<E> targetNode, Node<E> stagingNode, Processor<E> p){ if (level == 0){ if (this == targetNode){ stagingNode.process(0, null, null, p); } else { for (int i = 0; i < elements.length; i++) { p.process(elements[i]); } } } else { for (int i=0; i<elements.length; i++){ ((Node)elements[i]).process(level-1, targetNode, stagingNode, p); } } } void printOn (PrintStream ps, int depth, Node targetNode, Node stagingNode) { int j=0; for (int i=0; i<32; i++) { if ((bitmap & (1<<i)) != 0) { printIndentOn(ps, depth); ps.printf("%2d: ", i); E e = elements[j++]; if (e instanceof Node) { Node<E> n = (Node<E>)e; n.printNodeInfoOn(ps, targetNode, stagingNode); ps.println(); n.printOn(ps, depth+1, targetNode, stagingNode); } else { ps.print("value="); ps.println(e); } } } } } /** * newElements node with 32 elements, for which we don't need newElements bitmap. * No element can be added since this means we just promote an existing element * If an element is removed, this FullNode gets demoted int newElements BitmapNode */ protected static class FullNode<E> extends Node<E> { final E[] elements; FullNode (E[] elements){ this.elements = elements; } @Override int getNumberOfElements(){ return 32; } @Override E getElementAtStorageIndex (int i){ return elements[i]; } @Override E getElementAtLevelIndex (int i) { return elements[i]; } @Override int storageToLevelIndex (int i){ return i; } @Override Node cloneWithAdded (int idx, E e){ throw new RuntimeException("can't add a new element to a FullNode"); } @Override Node cloneWithReplaced (int idx, E e){ E[] newElements = elements.clone(); newElements[idx] = e; return new FullNode(newElements); } @Override Node cloneWithRemoved(int idx){ Object[] a = new Object[31]; int bitmap = 0xffffffff ^ (1 << idx); if (idx > 0){ System.arraycopy(elements, 0, a, 0, idx); } if (idx < 31){ System.arraycopy(elements, idx+1, a, idx, 31-idx); } return new BitmapNode( bitmap, a); } @Override Node removeAllSatisfying (Predicate<E> pred){ int newBitmap = 0xffffffff; int newLen = 32; E[] elem = elements; int removed = 0; for (int i=0, bit=1; i<32; i++, bit<<=1){ if (pred.isTrue(elem[i])){ newBitmap ^= bit; newLen--; removed |= (1 << i); } } if (newLen == 0){ // nothing left return null; } else if (newLen == 32){ // nothing removed return this; } else if (newLen == 1) { // just one value left - reduce to OneNode int idx = Integer.numberOfTrailingZeros(newBitmap); return new OneNode<E>( idx, elem[idx]); } else { // some values removed - reduced BitmapNode E[] newElements = (E[]) new Object[newLen]; for (int i=0, j=0; j<newLen; i++){ if ((removed & (1<<i)) == 0){ newElements[j++] = elem[i]; } } return new BitmapNode( newBitmap, newElements); } } @Override void set (int i, E e){ elements[i] = e; } @Override void process (int level, Node<E> targetNode, Node<E> stagingNode, Processor<E> p){ if (level == 0){ if (this == targetNode){ stagingNode.process(0, null, null, p); } else { for (int i = 0; i < elements.length; i++) { p.process(elements[i]); } } } else { for (int i=0; i<elements.length; i++){ ((Node)elements[i]).process(level-1, targetNode, stagingNode, p); } } } void printOn (PrintStream ps, int depth, Node targetNode, Node stagingNode) { for (int i=0; i<32; i++) { printIndentOn(ps, depth); ps.printf("%2d: ", i); E e = elements[i]; if (e instanceof Node) { Node<E> n = (Node<E>) e; n.printNodeInfoOn(ps, targetNode, stagingNode); ps.println(); n.printOn(ps, depth+1, targetNode, stagingNode); } else { ps.print("value="); ps.println(e); } } } } public Iterator<V> iterator(){ return new ValueIterator(); } /** * this is less efficient than using map.process(processor), but required to use PSIntMaps in lieu of ordinary containers * Since PSIntMaps are bounded recursive data structures, we have to model newElements stack explicitly, but at least we know it is * not exceeding newElements depth of 6 (5 bit index blocks) * * Note - there are no empty nodes. Each one has at least newElements single child node or value */ protected class ValueIterator implements Iterator<V> { Node node; int nodeIdx, maxNodeIdx; Node[] parentNodeStack; int[] parentIdxStack; int top; int nVisited, nTotal; public ValueIterator (){ node = PSIntMap.this.rootNode; if (node != null) { if (node == PSIntMap.this.targetNode){ node = PSIntMap.this.stagingNode; } maxNodeIdx = node.getNumberOfElements(); // nodeIdx = 0; // nVisited = 0; // top = 0; int depth = PSIntMap.this.rootLevel; parentNodeStack = new Node[depth]; parentIdxStack = new int[depth]; nTotal = PSIntMap.this.size; } } @Override public boolean hasNext() { return nVisited < nTotal; } @Override public V next() { if (nVisited >= nTotal) { throw new NoSuchElementException(); } int idx = nodeIdx; Object nv = node.getElementAtStorageIndex( idx); //--- descend while (top < PSIntMap.this.rootLevel) { parentNodeStack[top] = node; // push current node on stack parentIdxStack[top] = idx; top++; if (nv == PSIntMap.this.targetNode){ node = PSIntMap.this.stagingNode; } else { node = (Node)nv; } idx = nodeIdx = 0; maxNodeIdx = node.getNumberOfElements(); nv = node.getElementAtStorageIndex(0); } //--- newElements value, finally nVisited++; idx++; if (idx == maxNodeIdx) { // done, no more child nodes/values for this node while (top > 0) { // go up top--; node = parentNodeStack[top]; nodeIdx = parentIdxStack[top] + 1; maxNodeIdx = node.getNumberOfElements(); if (nodeIdx < maxNodeIdx) break; } } else { nodeIdx = idx; } //assert (nVisited == nTotal) || (nodeIdx < maxNodeIdx); return (V) nv; } @Override public void remove() { throw new UnsupportedOperationException("PersistentIntMap iterators don't support removal"); } } //--- auxiliary data and functions static final int BASE_MASK = ~0x1f; static final int TrailingMultiplyDeBruijnBitPosition[] = { 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 }; static int getNumberOfTrailingZeros (int v){ return TrailingMultiplyDeBruijnBitPosition[((v & -v) * 0x077CB531) >>> 27]; } // the values are the respective block levels static final int LeadingMultiplyDeBruijnBitPosition[] = { 0, 1, 0, 2, 2, 4, 0, 5, 2, 2, 3, 3, 4, 5, 0, 6, 1, 2, 4, 5, 3, 3, 4, 1, 3, 5, 4, 1, 5, 1, 0, 6 }; /** * get the start level [0..7] for the highest bit index (bit block). This is essentially counting the number of leading zero bits, * which we can derive from http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup */ static int getStartLevel (int v){ v |= v >>> 1; v |= v >>> 2; v |= v >>> 4; v |= v >>> 8; v |= v >>> 16; return LeadingMultiplyDeBruijnBitPosition[(v * 0x07C4ACDD) >>> 27]; } //--- instance data final protected int size; // number of values in this map final protected int rootLevel; // bit block level of the root node (highest non-0 bit block of all keys in map) final protected Node rootNode; // topmost node of trie /* * the following fields are used to cache consecutive key operations with the goal of avoiding * path copies from the modified value node all the way up to the root node. As long as the same value * node is modified (hence msb key block traversal) we just need to keep track which position in the * trie the stagingNode refers to (stagingNodeMask), and only have to create a new stagingNode with the * updated values. Once we have a key operation that refers to a different value node position (staging miss), * we merge the old stagingNode back into the trie. If we do this after inserting the new key, only * nodes from the old stagingNode parent up to the first node that is on the new key path have to be copied, * the merge node (on the new stagingNode path) can be safely modified since it has only been created during * the ongoing map operation. Example: * key value * last mod key/value (old stagingNode) : a.c.e -> Y => stagingNodeMask = a.c.FF * new key/value (new stagingNode) : a.b.d -> X * * a * n0: [...n1...] root node (level 2) * / * b c / * n1: [.n2...n3.] * / \ * d / \ e * n2: [.X..] n3: [.....] value nodes (level 0) [...Y...] * new stagingNode old targetNode <-------------------------- old stagingNode * (= new targetNode) * * In this case, the sequence of operations is as follows: * <ol> * <li> insert new key/value pair (a.b.d)->X into the trie, which is a stagingNode miss since * stagingNodeMasks are different (a.b.FF != a.c.FF). This leads to copied/new nodes n2,n1,n0 * <li> check if old stagingNode differs from targetNode (had several consecutive modifications), if * targetNode != stagingNode then merge old stagingNode <em>after</em> n2,n1,n0 creation * <li> since n1 is already a new node that is not shared with any prior version of this map, * its [c] element can be simply set to the old stagingNode, i.e. the merge does not require * any additional allocation. Note that n1 has to contain a [c] element since we always link * new stagingNodes into the trie upon creation. This means the number of elements in n1 * (and hence the node type) does not change, i.e. setting the new [c] element involves * just a single AASTORE instruction * <li> set stagingNode = targetNode = n2 * </ol> */ final protected Node<V> stagingNode; // last modified value node (not linked into the trie upon subsequent modification) final protected int stagingNodeMask; // key mask for stagingNode (key | 0x1f) final protected Node targetNode; // original stagingNode state that is linked into the trie /** * the only public constructor */ public PSIntMap(){ this.size = 0; this.rootLevel = 0; this.rootNode = null; this.targetNode = null; this.stagingNode = null; this.stagingNodeMask = 0; } protected PSIntMap (int size, int rootLevel, Node rootNode, Node<V> stagingNode, Node<V> targetNode, int stagingNodeMask){ this.size = size; this.rootLevel = rootLevel; this.rootNode = rootNode; this.stagingNode = stagingNode; this.targetNode = targetNode; this.stagingNodeMask = stagingNodeMask; } //--- public API public int size(){ return size; } public V get (int key){ if (stagingNodeMask == (key | 0x1f)){ int idx = key & 0x1f; return stagingNode.getElementAtLevelIndex(idx); } else { if (rootNode == null) return null; int l = getStartLevel(key); if (l > rootLevel) return null; Node<Node> n = rootNode; switch (rootLevel){ case 6: n = n.getElementAtLevelIndex( key >>> 30); if (n == null) return null; case 5: n = n.getElementAtLevelIndex( (key >>> 25) & 0x1f); if (n == null) return null; case 4: n = n.getElementAtLevelIndex( (key >>> 20) & 0x1f); if (n == null) return null; case 3: n = n.getElementAtLevelIndex( (key >>> 15) & 0x1f); if (n == null) return null; case 2: n = n.getElementAtLevelIndex( (key >>> 10) & 0x1f); if (n == null) return null; case 1: n = n.getElementAtLevelIndex( (key >>> 5) & 0x1f); if (n == null) return null; case 0: return ((Node<V>)n).getElementAtLevelIndex(key & 0x1f); } return null; // can't get here } } protected Node mergeStagingNode (){ Node<Node> n2=null, n3=null, n4=null, n5=null, n6=null; int i1, i2=0, i3=0, i4=0, i5=0, i6=0; int k = stagingNodeMask; Node<Node> n = rootNode; switch (rootLevel){ case 6: i6 = (k >>> 30); n6 = n; n = n.getElementAtLevelIndex(i6); case 5: i5 = (k >>> 25) & 0x1f; n5 = n; n = n.getElementAtLevelIndex(i5); case 4: i4 = (k >>> 20) & 0x1f; n4 = n; n = n.getElementAtLevelIndex(i4); case 3: i3 = (k >>> 15) & 0x1f; n3 = n; n = n.getElementAtLevelIndex(i3); case 2: i2 = (k >>> 10) & 0x1f; n2 = n; n = n.getElementAtLevelIndex(i2); case 1: i1 = (k >>> 5) & 0x1f; n = n.cloneWithReplaced(i1, stagingNode); if (n2 != null){ n = n2.cloneWithReplaced(i2, n); if (n3 != null){ n = n3.cloneWithReplaced(i3, n); if (n4 != null){ n = n4.cloneWithReplaced(i4, n); if (n5 != null){ n = n5.cloneWithReplaced(i5, n); if (n6 != null){ n = n6.cloneWithReplaced(i6, n); } } } } } return n; case 0: // special case - only node in the trie is the targetNode return stagingNode; } return null; // can't get here } /** * this relies on that all nodes from the new staging node to the newRootNode have been copied * and can be modified without cloning. * The modification does not change the node type since the old staging/target node was in the trie * The first node where new and old staging indices differ is the mergeNode that needs to be * modified (old staging path node replaced). This has to be level 1..6 * Everything above the mergeNode is not modified (the newRootNode does not have to be copied * as it is new) * All nodes between the old stagingNode and the mergeNode have to be copied * The old stagingNode itself does not need to be cloned. */ protected void mergeStagingNode (int key, int newRootLevel, Node newRootNode){ int k = stagingNodeMask; int mergeLevel = getStartLevel( key ^ k); // block of first differing bit Node<Node> mergeNode = newRootNode; int shift = newRootLevel*5; //--- get the mergeNode for (int l=newRootLevel; l>mergeLevel; l--){ int idx = (k >>> shift) & 0x1f; mergeNode = mergeNode.getElementAtLevelIndex(idx); shift -= 5; } int mergeIdx = (k >>> shift) & 0x1f; //--- copy from old staging up to mergeNode Node<Node> n5=null, n4=null, n3=null, n2=null, n1=null; int i5=0, i4=0, i3=0, i2=0, i1=0; Node<Node> n = mergeNode.getElementAtLevelIndex(mergeIdx); switch (mergeLevel-1){ case 5: i5 = (k >>> 25) & 0x1f; n5 = n; n = n.getElementAtLevelIndex(i5); case 4: i4 = (k >>> 20) & 0x1f; n4 = n; n = n.getElementAtLevelIndex(i4); case 3: i3 = (k >>> 15) & 0x1f; n3 = n; n = n.getElementAtLevelIndex(i3); case 2: i2 = (k >>> 10) & 0x1f; n2 = n; n = n.getElementAtLevelIndex(i2); case 1: i1 = (k >>> 5) & 0x1f; n1 = n; case 0: n = (Node)stagingNode; if (n1 != null){ n = n1.cloneWithReplaced(i1, n); if (n2 != null) { n = n2.cloneWithReplaced(i2, n); if (n3 != null) { n = n3.cloneWithReplaced(i3, n); if (n4 != null) { n = n4.cloneWithReplaced(i4, n); if (n5 != null) { n = n5.cloneWithReplaced(i5, n); } } } } } } //--- modify mergeNode mergeNode.set(mergeIdx, n); } PSIntMap<V> remove (int key, boolean isTargetNode){ Node<Node> n6=null, n5=null, n4=null, n3=null, n2=null, n1=null; Node<V> n0; int i6=0, i5=0, i4=0, i3=0, i2=0, i1=0, i0; Node<Node> n = rootNode; switch (rootLevel){ case 6: i6 = (key >>> 30); n5 = n.getElementAtLevelIndex(i6); if (n5 == null){ return this; // key not in map } else { n6 = n; n = n5; } case 5: i5 = (key >>> 25) & 0x1f; n4 = n.getElementAtLevelIndex(i5); if (n4 == null){ return this; // key not in map } else { n5 = n; n = n4; } case 4: i4 = (key >>> 20) & 0x1f; n3 = n.getElementAtLevelIndex(i4); if (n3 == null){ return this; // key not in map } else { n4 = n; n = n3; } case 3: i3 = (key >>> 15) & 0x1f; n2 = n.getElementAtLevelIndex(i3); if (n2 == null){ return this; // key not in map } else { n3 = n; n = n2; } case 2: i2 = (key >>> 10) & 0x1f; n1 = n.getElementAtLevelIndex(i2); if (n1 == null){ return this; // key not in map } else { n2 = n; n = n1; } case 1: i1 = (key >>> 5) & 0x1f; n0 = n.getElementAtLevelIndex(i1); if (n0 == null){ return null; } else { n1 = n; n = (Node)n0; } case 0: n0 = (Node<V>)n; if (isTargetNode){ n0 = null; } else { i0 = key & 0x1f; if (n0 == null || n0.getElementAtLevelIndex(i0) == null){ return this; // key not in map } else { n0 = n0.cloneWithRemoved(i0); } } n = (Node)n0; if (n1 != null){ n = (n == null) ? n1.cloneWithRemoved(i1) : n1.cloneWithReplaced(i1, n); if (n2 != null){ n = (n == null) ? n2.cloneWithRemoved(i2) : n2.cloneWithReplaced(i2, n); if (n3 != null){ n = (n == null) ? n3.cloneWithRemoved(i3) : n3.cloneWithReplaced(i3, n); if (n4 != null){ n = (n == null) ? n4.cloneWithRemoved(i4) : n4.cloneWithReplaced(i4, n); if (n5 != null){ n = (n == null) ? n5.cloneWithRemoved(i5) : n5.cloneWithReplaced(i5, n); if (n6 != null){ n = (n == null) ? n6.cloneWithRemoved(i6) : n6.cloneWithReplaced(i6, n); } } } } } } if (n == null){ return new PSIntMap<V>(); } else { int newRootLevel = rootLevel; int newSb = (n0 == null) ? 0 : (key | 0x1f); while ((newRootLevel > 0) && n.isEmptyNode()){ newRootLevel--; n = n.getElementAtLevelIndex(0); } if (!isTargetNode && (stagingNode != targetNode)){ mergeStagingNode(key, newRootLevel, n); } return new PSIntMap<V>( size-1, newRootLevel, n, n0, n0, newSb); } } return null; // can't get here } public PSIntMap<V> remove (int key){ int newSm = key | 0x1f; if (newSm == stagingNodeMask){ // staging node hit - this should be the dominant case int i = key & 0x1f; Node<V> n = stagingNode; if ((n.getElementAtLevelIndex(i)) != null) { // key is in the stagingNode n = n.cloneWithRemoved(i); if (n == null){ // staging node is empty, remove target node return remove(newSm, true); } else { // non-empty staging node, just replace it return new PSIntMap<V>( size-1, rootLevel, rootNode, n, targetNode, newSm); } } else { // key wasn't in the stagingNode return this; } } else { // staging node miss return remove( key, false); } } /** * this either replaces or adds newElements new value */ public PSIntMap<V> set (int key, V value){ if (value == null){ // we don't store null values, this is a remove in disguise return remove(key); } int newSm = key | 0x1f; if (newSm == stagingNodeMask){ // staging node hit - this should be the dominant case int i = key & 0x1f; Node<V> n = stagingNode; int newSize = size; if ((n.getElementAtLevelIndex(i)) == null) { n = n.cloneWithAdded(i, value); newSize = size+1; } else { n = n.cloneWithReplaced(i, value); } return new PSIntMap<V>( newSize, rootLevel, rootNode, n, targetNode, newSm); } else { // staging node miss int newRootLevel = getStartLevel(key); if (newRootLevel > rootLevel){ // old trie has to be merged in return setInNewRootLevel( newRootLevel, key, value); } else { // new value can be added to old trie (stagingNode change) return setInCurrentRootLevel( key, value); } } } protected PSIntMap<V> setInNewRootLevel (int newRootLevel, int key, V value){ int newSm = key | 0x1f; Node<Node> nOld; if (stagingNode != targetNode){ nOld = mergeStagingNode(); } else { nOld = rootNode; } //--- expand old root upwards if (nOld != null){ for (int l = rootLevel + 1; l < newRootLevel; l++) { nOld = new OneNode(0, nOld); } } //--- create chain of new value nodes int i = key & 0x1f; Node nNew = new OneNode(i, value); int shift = 5; Node newStagingNode = nNew; for (int l = 1; l < newRootLevel; l++) { i = (key >>> shift) & 0x1f; nNew = new OneNode(i, nNew); shift += 5; } //--- create new root i = (key >>> shift); // no remainBmp needed, top level Node<Node> newRootNode = (nOld == null) ? new OneNode( i, nNew) : new BitmapNode<Node>(i, nNew, nOld); return new PSIntMap<V>(size + 1, newRootLevel, newRootNode, newStagingNode, newStagingNode, newSm); } /** * that's ugly, but if we use recursion we need newElements result object to obtain the new stagingNode and * the size change, which means there would be an additional allocation per set() or newElements non-persistent, * transient object that would need synchronization */ protected PSIntMap<V> setInCurrentRootLevel (int key, V value){ Node<Node> n6=null, n5=null, n4=null, n3=null, n2=null, n1=null; Node<V> n0; int i6=0, i5=0, i4=0, i3=0, i2=0, i1=0, i0; int newSb = key | 0x1f; boolean needsMerge = (targetNode != stagingNode); int newSize = size+1; //--- new stagingNode Node<Node> n = rootNode; switch(rootLevel){ case 6: i6 = key >>> 30; n5 = (Node)n.getElementAtLevelIndex(i6); if (n5 == null) { n0 = new OneNode( (key & 0x1f), value); n1 = new OneNode( (key >>> 5) & 0x1f, n0); n2 = new OneNode( (key >>> 10) & 0x1f, n1); n3 = new OneNode( (key >>> 15) & 0x1f, n2); n4 = new OneNode( (key >>> 20) & 0x1f, n3); n5 = new OneNode( (key >>> 25) & 0x1f, n4); n = n.cloneWithAdded( i6, n5); if (needsMerge) mergeStagingNode(key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } else { n6 = n; n = n5; } case 5: i5 = (key >>> 25) & 0x1f; n4 = n.getElementAtLevelIndex(i5); if (n4 == null) { n0 = new OneNode( (key & 0x1f), value); n1 = new OneNode( (key >>> 5) & 0x1f, n0); n2 = new OneNode( (key >>> 10) & 0x1f, n1); n3 = new OneNode( (key >>> 15) & 0x1f, n2); n4 = new OneNode( (key >>> 20) & 0x1f, n3); n = n.cloneWithAdded( i5, n4); if (n6 != null){ n = n6.cloneWithReplaced( i6, n); } if (needsMerge) mergeStagingNode(key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } else { n5 = n; n = n4; } case 4: i4 = (key >>> 20) & 0x1f; n3 = n.getElementAtLevelIndex(i4); if (n3 == null) { n0 = new OneNode( (key & 0x1f), value); n1 = new OneNode( (key >>> 5) & 0x1f, n0); n2 = new OneNode( (key >>> 10) & 0x1f, n1); n3 = new OneNode( (key >>> 15) & 0x1f, n2); n = n.cloneWithAdded( i4, n3); if (n5 != null){ n = n5.cloneWithReplaced( i5, n); if (n6 != null){ n = n6.cloneWithReplaced( i6, n); } } if (needsMerge) mergeStagingNode(key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } else { n4 = n; n = n3; } case 3: i3 = (key >>> 15) & 0x1f; n2 = n.getElementAtLevelIndex(i3); if (n2 == null) { n0 = new OneNode( (key & 0x1f), value); n1 = new OneNode( (key >>> 5) & 0x1f, n0); n2 = new OneNode( (key >>> 10) & 0x1f, n1); n = n.cloneWithAdded( i3, n2); if (n4 != null){ n = n4.cloneWithReplaced( i4, n); if (n5 != null){ n = n5.cloneWithReplaced( i5, n); if (n6 != null){ n = n6.cloneWithReplaced( i6, n); } } } if (needsMerge) mergeStagingNode(key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } else { n3 = n; n = n2; } case 2: i2 = (key >>> 10) & 0x1f; n1 = n.getElementAtLevelIndex(i2); if (n1 == null) { n0 = new OneNode( (key & 0x1f), value); n1 = new OneNode( (key >>> 5) & 0x1f, n0); n = n.cloneWithAdded( i2, n1); if (n3 != null){ n = n3.cloneWithReplaced( i3, n); if (n4 != null){ n = n4.cloneWithReplaced( i4, n); if (n5 != null){ n = n5.cloneWithReplaced( i5, n); if (n6 != null){ n = n6.cloneWithReplaced( i6, n); } } } } if (needsMerge) mergeStagingNode(key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } else { n2 = n; n = n1; } case 1: i1 = (key >>> 5) & 0x1f; n0 = n.getElementAtLevelIndex(i1); if (n0 == null) { n0 = new OneNode( (key & 0x1f), value); n = n.cloneWithAdded( i1, n0); if (n2 != null){ n = n2.cloneWithReplaced( i2, n); if (n3 != null){ n = n3.cloneWithReplaced( i3, n); if (n4 != null){ n = n4.cloneWithReplaced( i4, n); if (n5 != null){ n = n5.cloneWithReplaced( i5, n); if (n6 != null){ n = n6.cloneWithReplaced( i6, n); } } } } } if (needsMerge) mergeStagingNode(key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } else { n1 = n; n = (Node)n0; } case 0: // finally the value level i0 = key & 0x1f; n0 = (Node<V>)n; if (n0 != null){ if (n0.getElementAtLevelIndex(i0) == null) { n0 = n0.cloneWithAdded(i0, value); } else { n0 = n0.cloneWithReplaced(i0, value); newSize = size; } } else { // first node n0 = new OneNode( i0, value); newSize = 1; } n = (Node)n0; if (n1 != null){ n = n1.cloneWithReplaced( i1, n); if (n2 != null){ n = n2.cloneWithReplaced( i2, n); if (n3 != null){ n = n3.cloneWithReplaced( i3, n); if (n4 != null){ n = n4.cloneWithReplaced( i4, n); if (n5 != null){ n = n5.cloneWithReplaced( i5, n); if (n6 != null){ n = n6.cloneWithReplaced( i6, n); } } } } } } if (needsMerge) mergeStagingNode( key, rootLevel, n); return new PSIntMap<V>( newSize, rootLevel, n, n0, n0, newSb); } return null; // can't get here } public void process (Processor<V> p){ if (rootNode != null){ if (targetNode == stagingNode){ rootNode.process( rootLevel, null, null, p); } else { rootNode.process( rootLevel, targetNode, stagingNode, p); } } } final protected Node removeAllSatisfying (int level, Node node, Predicate<V> pred){ if (level == 0){ // value level return ((Node<V>)node).removeAllSatisfying(pred); } else { // node level // it sucks not having stack arrays but we don't want to allocate for temporary results Node n0=null,n1=null,n2=null,n3=null,n4=null,n5=null,n6=null,n7=null,n8=null,n9=null,n10=null, n11=null,n12=null,n13=null,n14=null,n15=null,n16=null,n17=null,n18=null,n19=null,n20=null, n21=null,n22=null,n23=null,n24=null,n25=null,n26=null,n27=null,n28=null,n29=null,n30=null,n31=null; int nRemaining = 0, nChanged = 0; int len = node.getNumberOfElements(); //--- collect the remaining nodes for (int i=0; i<len; i++){ Node e = (Node)node.getElementAtStorageIndex(i); Node n = removeAllSatisfying( level-1, e, pred); if (n != null){ nRemaining++; if (n != e){ nChanged++; } switch (i){ case 0: n0=n; break; case 1: n1=n; break; case 2: n2=n; break; case 3: n3=n; break; case 4: n4=n; break; case 5: n5=n; break; case 6: n6=n; break; case 7: n7=n; break; case 8: n8=n; break; case 9: n9=n; break; case 10: n10=n; break; case 11: n11=n; break; case 12: n12=n; break; case 13: n13=n; break; case 14: n14=n; break; case 15: n15=n; break; case 16: n16=n; break; case 17: n17=n; break; case 18: n18=n; break; case 19: n19=n; break; case 20: n20=n; break; case 21: n21=n; break; case 22: n22=n; break; case 23: n23=n; break; case 24: n24=n; break; case 25: n25=n; break; case 26: n26=n; break; case 27: n27=n; break; case 28: n28=n; break; case 29: n29=n; break; case 30: n30=n; break; case 31: n31=n; break; } } } //--- construct the returned node if (nRemaining == 0){ return null; } else if ((nRemaining == len) && (nChanged == 0)){ return node; } else { if (nRemaining == 1){ // becomes a OneNode for (int i=0; i<32; i++){ switch (i){ case 0: if (n0!=null) return new OneNode( node.storageToLevelIndex(0), n0); break; case 1: if (n1!=null) return new OneNode( node.storageToLevelIndex(1), n1); break; case 2: if (n2!=null) return new OneNode( node.storageToLevelIndex(2), n2); break; case 3: if (n3!=null) return new OneNode( node.storageToLevelIndex(3), n3); break; case 4: if (n4!=null) return new OneNode( node.storageToLevelIndex(4), n4); break; case 5: if (n5!=null) return new OneNode( node.storageToLevelIndex(5), n5); break; case 6: if (n6!=null) return new OneNode( node.storageToLevelIndex(6), n6); break; case 7: if (n7!=null) return new OneNode( node.storageToLevelIndex(7), n7); break; case 8: if (n8!=null) return new OneNode( node.storageToLevelIndex(8), n8); break; case 9: if (n9!=null) return new OneNode( node.storageToLevelIndex(9), n9); break; case 10: if (n10!=null) return new OneNode( node.storageToLevelIndex(10),n10); break; case 11: if (n11!=null) return new OneNode( node.storageToLevelIndex(11),n11); break; case 12: if (n12!=null) return new OneNode( node.storageToLevelIndex(12),n12); break; case 13: if (n13!=null) return new OneNode( node.storageToLevelIndex(13),n13); break; case 14: if (n14!=null) return new OneNode( node.storageToLevelIndex(14),n14); break; case 15: if (n15!=null) return new OneNode( node.storageToLevelIndex(15),n15); break; case 16: if (n16!=null) return new OneNode( node.storageToLevelIndex(16),n16); break; case 17: if (n17!=null) return new OneNode( node.storageToLevelIndex(17),n17); break; case 18: if (n18!=null) return new OneNode( node.storageToLevelIndex(18),n18); break; case 19: if (n19!=null) return new OneNode( node.storageToLevelIndex(19),n19); break; case 20: if (n20!=null) return new OneNode( node.storageToLevelIndex(20),n20); break; case 21: if (n21!=null) return new OneNode( node.storageToLevelIndex(21),n21); break; case 22: if (n22!=null) return new OneNode( node.storageToLevelIndex(22),n22); break; case 23: if (n23!=null) return new OneNode( node.storageToLevelIndex(23),n23); break; case 24: if (n24!=null) return new OneNode( node.storageToLevelIndex(24),n24); break; case 25: if (n25!=null) return new OneNode( node.storageToLevelIndex(25),n25); break; case 26: if (n26!=null) return new OneNode( node.storageToLevelIndex(26),n26); break; case 27: if (n27!=null) return new OneNode( node.storageToLevelIndex(27),n27); break; case 28: if (n28!=null) return new OneNode( node.storageToLevelIndex(28),n28); break; case 29: if (n29!=null) return new OneNode( node.storageToLevelIndex(29),n29); break; case 30: if (n30!=null) return new OneNode( node.storageToLevelIndex(30),n30); break; case 31: if (n31!=null) return new OneNode( node.storageToLevelIndex(31),n31); break; } } } else if (nRemaining == 32) { // still a FullNode, but elements might have changed Node[] a = {n0,n1,n2,n3,n4,n5,n6,n7,n8,n9,n10, n11,n12,n13,n14,n15,n16,n17,n18,n19,n20, n21,n22,n23,n24,n25,n26,n27,n28,n29,n30,n31}; return new FullNode(a); } else { int bitmap = 0; Node[] a = new Node[nRemaining]; int j=0; // <2do> this is bad - there has to be a more efficient way to generate the bitmap for (int i=0; j < nRemaining; i++){ switch (i){ case 0: if (n0!=null) { a[j++] = n0; bitmap |= (1<<node.storageToLevelIndex(0)); } break; case 1: if (n1!=null) { a[j++] = n1; bitmap |= (1<<node.storageToLevelIndex(1)); } break; case 2: if (n2!=null) { a[j++] = n2; bitmap |= (1<<node.storageToLevelIndex(2)); } break; case 3: if (n3!=null) { a[j++] = n3; bitmap |= (1<<node.storageToLevelIndex(3)); } break; case 4: if (n4!=null) { a[j++] = n4; bitmap |= (1<<node.storageToLevelIndex(4)); } break; case 5: if (n5!=null) { a[j++] = n5; bitmap |= (1<<node.storageToLevelIndex(5)); } break; case 6: if (n6!=null) { a[j++] = n6; bitmap |= (1<<node.storageToLevelIndex(6)); } break; case 7: if (n7!=null) { a[j++] = n7; bitmap |= (1<<node.storageToLevelIndex(7)); } break; case 8: if (n8!=null) { a[j++] = n8; bitmap |= (1<<node.storageToLevelIndex(8)); } break; case 9: if (n9!=null) { a[j++] = n9; bitmap |= (1<<node.storageToLevelIndex(9)); } break; case 10: if (n10!=null) { a[j++] = n10; bitmap |= (1<<node.storageToLevelIndex(10)); } break; case 11: if (n11!=null) { a[j++] = n11; bitmap |= (1<<node.storageToLevelIndex(11)); } break; case 12: if (n12!=null) { a[j++] = n12; bitmap |= (1<<node.storageToLevelIndex(12)); } break; case 13: if (n13!=null) { a[j++] = n13; bitmap |= (1<<node.storageToLevelIndex(13)); } break; case 14: if (n14!=null) { a[j++] = n14; bitmap |= (1<<node.storageToLevelIndex(14)); } break; case 15: if (n15!=null) { a[j++] = n15; bitmap |= (1<<node.storageToLevelIndex(15)); } break; case 16: if (n16!=null) { a[j++] = n16; bitmap |= (1<<node.storageToLevelIndex(16)); } break; case 17: if (n17!=null) { a[j++] = n17; bitmap |= (1<<node.storageToLevelIndex(17)); } break; case 18: if (n18!=null) { a[j++] = n18; bitmap |= (1<<node.storageToLevelIndex(18)); } break; case 19: if (n19!=null) { a[j++] = n19; bitmap |= (1<<node.storageToLevelIndex(19)); } break; case 20: if (n20!=null) { a[j++] = n20; bitmap |= (1<<node.storageToLevelIndex(20)); } break; case 21: if (n21!=null) { a[j++] = n21; bitmap |= (1<<node.storageToLevelIndex(21)); } break; case 22: if (n22!=null) { a[j++] = n22; bitmap |= (1<<node.storageToLevelIndex(22)); } break; case 23: if (n23!=null) { a[j++] = n23; bitmap |= (1<<node.storageToLevelIndex(23)); } break; case 24: if (n24!=null) { a[j++] = n24; bitmap |= (1<<node.storageToLevelIndex(24)); } break; case 25: if (n25!=null) { a[j++] = n25; bitmap |= (1<<node.storageToLevelIndex(25)); } break; case 26: if (n26!=null) { a[j++] = n26; bitmap |= (1<<node.storageToLevelIndex(26)); } break; case 27: if (n27!=null) { a[j++] = n27; bitmap |= (1<<node.storageToLevelIndex(27)); } break; case 28: if (n28!=null) { a[j++] = n28; bitmap |= (1<<node.storageToLevelIndex(28)); } break; case 29: if (n29!=null) { a[j++] = n29; bitmap |= (1<<node.storageToLevelIndex(29)); } break; case 30: if (n30!=null) { a[j++] = n30; bitmap |= (1<<node.storageToLevelIndex(30)); } break; case 31: if (n31!=null) { a[j++] = n31; bitmap |= (1<<node.storageToLevelIndex(31)); } break; } } return new BitmapNode( bitmap, a); } } } throw new RuntimeException("can't get here"); } public PSIntMap<V> removeAllSatisfying( Predicate<V> pred){ Node<Node> node = rootNode; if (stagingNode != targetNode){ // we need to merge first since the target node might be gone after bulk removal node = mergeStagingNode(); } node = removeAllSatisfying( rootLevel, node, pred); // reduce depth int newRootLevel = rootLevel; int newSize = countSize( newRootLevel, node); return new PSIntMap<V>( newSize, newRootLevel, node, null, null, 0); } protected final int countSize (int level, Node node){ if (node == null){ return 0; } else { if (level == 0) { return node.getNumberOfElements(); } else { int nValues = 0; int len = node.getNumberOfElements(); for (int i = 0; i < len; i++) { nValues += countSize(level - 1, (Node) node.getElementAtStorageIndex(i)); } return nValues; } } } public V[] values (){ final Object[] values = new Object[size]; Processor<V> flattener = new Processor<V>(){ int i=0; public void process (V v){ values[i] = v; } }; process(flattener); return (V[])values; } //--- debugging public void printOn(PrintStream ps) { if (rootNode != null) { rootNode.printNodeInfoOn(ps, targetNode, stagingNode); ps.println(); rootNode.printOn(ps, rootLevel, targetNode, stagingNode); } else { ps.println( "empty"); } if (stagingNode != null) { ps.println("--------------- staging"); stagingNode.printNodeInfoOn(ps, targetNode, stagingNode); ps.println(); stagingNode.printOn(ps, 0, targetNode, stagingNode); } } public String keyDescription (int key) { StringBuilder sb = new StringBuilder(); int ish = getStartLevel(key); sb.append(key); sb.append(" (0x"); sb.append(Integer.toHexString(key)); sb.append(") => "); for (int shift=ish*5; shift>=0; shift-=5) { sb.append((key>>shift) & 0x1f); if (shift > 0) { sb.append('.'); } } return sb.toString(); } }