Ranides Atterwim 11 rokov pred
rodič
commit
864e84497d

+ 927 - 0
assira/src/main/java/net/ranides/assira/collection/lookups/AHashLookup.java

@@ -0,0 +1,927 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.collection.lookups;
+
+import net.ranides.assira.collection.IntCollection;
+import net.ranides.assira.collection.iterators.IntIterator;
+import net.ranides.assira.collection.sets.ASet;
+import net.ranides.assira.collection.arrays.ArrayUtils;
+import net.ranides.assira.collection.utils.HashUtils;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import net.ranides.assira.collection.utils.HashCollection;
+import net.ranides.assira.math.MathUtils;
+
+/**
+ * A type-specific hash map with primitive int values.
+ * @todo (assira # 7) review.hash.lookup
+ */
+@SuppressWarnings({
+    "PMD.AvoidReassigningParameters",
+    "EqualsAndHashcode"
+})
+public abstract class AHashLookup<K> extends Lookup<K> implements java.io.Serializable {
+
+    private static final long serialVersionUID = 0L;
+    /**
+     * The array of mkeys.
+     */
+    protected transient K[] keys;
+    /**
+     * The array of mvalues.
+     */
+    protected transient int[] values;
+    /**
+     * The array telling whether a position is used.
+     */
+    protected transient boolean used[];
+    /**
+     * The acceptable load factor.
+     */
+    protected float factor;
+    /**
+     * The current table size.
+     */
+    protected transient int n;
+    /**
+     * Threshold after which we rehash. It must be the table size times
+     * {@link #factor}.
+     */
+    protected transient int maxFill;
+    /**
+     * The mask for wrapping a position counter.
+     */
+    protected transient int mask;
+    /**
+     * Number of mentries in the set.
+     */
+    protected int size;
+    /**
+     * Cached set of mentries.
+     */
+    protected transient volatile Set<LookupEntry<K>> mentries;
+    /**
+     * Cached set of mkeys.
+     */
+    protected transient volatile Set<K> mkeys;
+    /**
+     * Cached collection of mvalues.
+     */
+    protected transient volatile IntCollection mvalues;
+
+    /**
+     * Creates a new hash map.
+     *
+     * <p>The actual table size will be the least power of two greater than
+     * <code>expected</code>/
+     * <code>factor</code>.
+     *
+     * @param expected the expected number of elements in the hash set.
+     * @param f the load factor.
+     * @param strategy the strategy.
+     */
+    @SuppressWarnings("unchecked")
+    public AHashLookup(int expected, float f) {
+        if (f <= 0 || f > 1) {
+            throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1");
+        }
+        if (expected < 0) {
+            throw new IllegalArgumentException("The expected number of elements must be nonnegative");
+        }
+        this.factor = f;
+        n = HashCollection.arraysize(expected, f);
+        mask = n - 1;
+        maxFill = HashCollection.arrayfill(n, f);
+        keys = (K[]) new Object[n];
+        values = new int[n];
+        used = new boolean[n];
+    }
+    
+    protected abstract int hashkey(K key);
+    
+    protected abstract boolean cmpkey(K key1, K key2);
+
+    @Override
+    public final int put(K key, int value) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey(key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], key)) {
+                int oldValue = values[pos];
+                values[ pos] = value;
+                return oldValue;
+            }
+            pos = (pos + 1) & mask;
+        }
+        used[ pos] = true;
+        keys[ pos] = key;
+        values[ pos] = value;
+        if (++size >= maxFill) {
+            rehash(HashCollection.arraysize(size + 1, factor));
+        }
+        return defRetValue;
+    }
+
+    @Override
+    public final Integer put(K key, Integer value) {
+        int v = value;
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey(key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[ pos], key)) {
+                Integer oldValue = values[pos];
+                values[pos] = v;
+                return oldValue;
+            }
+            pos = (pos + 1) & mask;
+        }
+        used[ pos] = true;
+        keys[ pos] = key;
+        values[ pos] = v;
+        if (++size >= maxFill) {
+            rehash(HashCollection.arraysize(size + 1, factor));
+        }
+        return null;
+    }
+
+    /**
+     * Adds an increment to values currently associated with a keys.
+     *
+     * <P>Note that this method respects the
+     * {@link #defaultReturnValue() default return values} semantics: when
+ called with a keys that does not currently appears in the map, the keys
+ will be associated with the default return values plus the given
+ increment.
+     *
+     * @param key the keys.
+     * @param delta the increment.
+     * @return the old values, or the
+    {@link #defaultReturnValue() default return values} if no values was
+ present for the given keys.
+     */
+    public final int addTo(K key, int delta) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[ pos], key)) {
+                int oldValue = values[ pos];
+                values[ pos] += delta;
+                return oldValue;
+            }
+            pos = (pos + 1) & mask;
+        }
+        used[ pos] = true;
+        keys[ pos] = key;
+        values[ pos] = defRetValue + delta;
+        if (++size >= maxFill) {
+            rehash(HashCollection.arraysize(size + 1, factor));
+        }
+        return defRetValue;
+    }
+
+    /**
+     * Shifts left mentries with the specified hash code, starting at the
+ specified position, and empties the resulting free entry.
+     *
+     * @param pos a starting position.
+     * @return the position cleared by the shifting process.
+     */
+    private final int shiftKeys(int pos) {
+        // Shift mentries with the same hash.
+        int last, slot;
+        for (;;) {
+            pos = ((last = pos) + 1) & mask;
+            while (used[ pos]) {
+                slot = HashUtils.murmurHash3(hashkey((K)keys[ pos])) & mask;
+                if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
+                    break;
+                }
+                pos = (pos + 1) & mask;
+            }
+            if (!used[ pos]) {
+                break;
+            }
+            keys[ last] = keys[ pos];
+            values[ last] = values[ pos];
+        }
+        used[ last] = false;
+        keys[ last] = null;
+        return last;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public final int removeInt(Object key) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[ pos], (K)key)) {
+                size--;
+                int v = values[ pos];
+                shiftKeys(pos);
+                return v;
+            }
+            pos = (pos + 1) & mask;
+        }
+        return defRetValue;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public final Integer remove(Object key) {
+        K kkey = (K)key;
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)kkey)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], kkey)) {
+                size--;
+                int v = values[ pos];
+                shiftKeys(pos);
+                return v;
+            }
+            pos = (pos + 1) & mask;
+        }
+        return null;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public final int getInt(Object key) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], (K)key)) {
+                return values[pos];
+            }
+            pos = (pos + 1) & mask;
+        }
+        return defRetValue;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public boolean containsKey(Object key) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], (K)key)) {
+                return true;
+            }
+            pos = (pos + 1) & mask;
+        }
+        return false;
+    }
+
+    @Override
+    public final boolean containsValue(int v) {
+        int nvalues[] = this.values;
+        boolean nused[] = this.used;
+        for (int i = n; i-- != 0;) {
+            if (nused[i] && (nvalues[i] == v)) {
+                return true;
+            }
+        }
+        return false;
+    }
+    /* Removes all elements from this map.
+     *
+     * <P>To increase object reuse, this method does not change the table size.
+     * If you want to reduce the table size, you must use {@link #trim()}.
+     *
+     */
+
+    @Override
+    public final void clear() {
+        if (size == 0) {
+            return;
+        }
+        size = 0;
+        ArrayUtils.fill(used, false);
+        // We null all object mentries so that the garbage collector can do its work.
+        ArrayUtils.fill(keys, null);
+    }
+
+    @Override
+    public final int size() {
+        return size;
+    }
+
+    @Override
+    public final boolean isEmpty() {
+        return size == 0;
+    }
+
+    /**
+     * The entry class for a hash map does not record keys and values, but rather
+ the position in the hash table of the corresponding entry. This is
+     * necessary so that calls to {@link java.util.Map.Entry#setValue(Object)}
+     * are reflected in the map
+     */
+    private final class MapEntry implements LookupEntry<K>, Map.Entry<K, Integer> {
+        // The table index this entry refers to, or -1 if this entry has been deleted.
+
+        int index;
+
+        MapEntry(int index) {
+            this.index = index;
+        }
+
+        @Override
+        public K getKey() {
+            return keys[ index];
+        }
+
+        @Override
+        public Integer getValue() {
+            return values[ index];
+        }
+
+        @Override
+        public int getIntValue() {
+            return values[ index];
+        }
+
+        @Override
+        public int setValue(int v) {
+            int oldValue = values[ index];
+            values[ index] = v;
+            return oldValue;
+        }
+
+        @Override
+        public Integer setValue(Integer value) {
+            return setValue(value.intValue());
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean equals(Object object) {
+            if (!(object instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
+            K key = entry.getKey();
+            int v = entry.getValue();
+            return cmpkey(keys[index], key) && (values[index] == v);
+        }
+
+        @Override
+        public int hashCode() {
+            return (hashkey((K)keys[ index])) ^ (values[ index]);
+        }
+
+        @Override
+        public String toString() {
+            return keys[ index] + "=>" + values[ index];
+        }
+    }
+
+    /**
+     * An iterator over a hash map.
+     */
+    private class MapIterator {
+        
+        /**
+         * The index of the next entry to be returned, if positive or zero. If
+         * negative, the next entry to be returned, if any, is that of index
+         * -pos -2 from the {@link #wrapped} list.
+         */
+        int pos;
+        /**
+         * The index of the last entry that has been returned. It is -1 if
+         * either we did not return an entry yet, or the last returned entry has
+         * been removed.
+         */
+        int last;
+        /**
+         * A downward counter measuring how many mentries must still be returned.
+         */
+        int counter;
+        /**
+         * A lazily allocated list containing the mkeys of elements that have
+ wrapped around the table because of removals; such elements would not
+ be enumerated (other elements would be usually enumerated twice in
+ their place).
+         */
+        ArrayList<K> wrapped;
+       
+        public MapIterator() {
+            this.pos = AHashLookup.this.n;
+            this.last = -1;
+            this.counter = size;
+            this.wrapped = null;
+            
+            boolean used[] = AHashLookup.this.used;
+            if (counter != 0) {
+                while (!used[ --pos]) { } // NOPMD
+            }
+        }
+
+        public boolean hasNext() {
+            return counter != 0;
+        }
+
+        public int nextEntry() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            counter--;
+            // We are just enumerating elements from the wrapped list.
+            if (pos < 0) {
+                K ckey = wrapped.get(-(last = --pos) - 2);
+                // The starting point.
+                int cpos = HashUtils.murmurHash3(hashkey(ckey)) & mask;
+                // There's always an unused entry.
+                while (used[ cpos]) {
+                    if (cmpkey(keys[ cpos], ckey)) {
+                        return cpos;
+                    }
+                    cpos = (cpos + 1) & mask;
+                }
+            }
+            last = pos;
+            //System.err.println( "Count: " + c );
+            if (counter != 0) {
+                boolean used[] = AHashLookup.this.used;
+                while (pos-- != 0 && !used[ pos]) { } // NOPMD
+                // When here pos < 0 there are no more elements to be enumerated by scanning, but wrapped might be nonempty.
+            }
+            return last;
+        }
+
+        /**
+         * Shifts left mentries with the specified hash code, starting at the
+ specified position, and empties the resulting free entry. If any
+         * entry wraps around the table, instantiates lazily {@link #wrapped}
+ and stores the entry keys.
+         *
+         * @param pos a starting position.
+         * @return the position cleared by the shifting process.
+         */
+        protected final int shiftKeys(int pos) {
+            // Shift mentries with the same hash.
+            int ilast, islot;
+            for (;;) {
+                pos = ((ilast = pos) + 1) & mask;
+                while (used[ pos]) {
+                    islot = HashUtils.murmurHash3(hashkey((K)keys[ pos])) & mask;
+                    if (ilast <= pos ? ilast >= islot || islot > pos : ilast >= islot && islot > pos) {
+                        break;
+                    }
+                    pos = (pos + 1) & mask;
+                }
+                if (!used[ pos]) {
+                    break;
+                }
+                if (pos < ilast) {
+                    // Wrapped entry.
+                    if (wrapped == null) {
+                        wrapped = new ArrayList<>();
+                    }
+                    wrapped.add(keys[ pos]);
+                }
+                keys[ ilast] = keys[ pos];
+                values[ ilast] = values[ pos];
+            }
+            used[ ilast] = false;
+            keys[ ilast] = null;
+            return ilast;
+        }
+
+        @SuppressWarnings("unchecked")
+        public void remove() {
+            if (last == -1) {
+                throw new IllegalStateException();
+            }
+            if (pos < -1) {
+                // We're removing wrapped mentries.
+                AHashLookup.this.remove(wrapped.set(-pos - 2, null));
+                last = -1;
+                return;
+            }
+            size--;
+            if (shiftKeys(last) == pos && counter > 0) {
+                counter++;
+                nextEntry();
+            }
+            last = -1; // You can no longer remove this entry.
+        }
+
+        public int skip(int n) {
+            int i = n;
+            while (i-- != 0 && hasNext()) {
+                nextEntry();
+            }
+            return n - i - 1;
+        }
+    }
+
+    private class EntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
+
+        private MapEntry entry;
+        
+        public EntryIterator() { }
+
+        @Override
+        public LookupEntry<K> next() {
+            return entry = new MapEntry(nextEntry());
+        }
+
+        @Override
+        public void remove() {
+            super.remove();
+            entry.index = -1; // You cannot use a deleted entry.
+        }
+    }
+
+    private class FastEntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
+
+        final BasicEntry<K> entry = new BasicEntry<>(null, 0);
+        
+        public FastEntryIterator() { }
+
+        @Override
+        public BasicEntry<K> next() {
+            int e = nextEntry();
+            entry.key = keys[ e];
+            entry.value = values[ e];
+            return entry;
+        }
+    }
+
+	private final class MapEntrySet extends ASet<LookupEntry<K>> {
+
+        @Override
+        public Iterator<LookupEntry<K>> iterator() {
+            return new EntryIterator();
+        }
+
+        public Iterator<LookupEntry<K>> fastIterator() {
+            return new FastEntryIterator();
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean contains(Object object) {
+            if (!(object instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
+            K ckey = entry.getKey();
+            // The starting point.
+            int pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
+            // There's always an unused entry.
+            while (used[ pos]) {
+                if (cmpkey(keys[ pos], ckey)) {
+                    int v = entry.getValue();
+                    return values[pos] == v;
+                }
+                pos = (pos + 1) & mask;
+            }
+            return false;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean remove(Object object) {
+            if (!(object instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
+            K ckey = entry.getKey();
+            // The starting point.
+            int pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
+            // There's always an unused entry.
+            while (used[ pos]) {
+                if (cmpkey(keys[pos], (K)ckey)) {
+                    AHashLookup.this.remove(entry.getKey());
+                    return true;
+                }
+                pos = (pos + 1) & mask;
+            }
+            return false;
+        }
+
+        @Override
+        public int size() {
+            return size;
+        }
+
+        @Override
+        public void clear() {
+            AHashLookup.this.clear();
+        }
+    }
+
+    @Override
+    public final Set<LookupEntry<K>> fastEntrySet() {
+        if (mentries == null) {
+            mentries = new MapEntrySet();
+        }
+        return mentries;
+    }
+
+    /**
+     * An iterator on mkeys.
+     *
+     * <P>We simply override the
+     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
+ methods (and possibly their type-specific counterparts) so that they
+ return mkeys instead of mentries.
+     */
+    private final class KeyIterator extends MapIterator implements Iterator<K> {
+
+        public KeyIterator() {
+            super();
+        }
+
+        @Override
+        public K next() {
+            return keys[ nextEntry()];
+        }
+    }
+
+    private final class KeySet extends ASet<K> {
+
+        @Override
+        public Iterator<K> iterator() {
+            return new KeyIterator();
+        }
+
+        @Override
+        public int size() {
+            return size;
+        }
+
+        @Override
+        @SuppressWarnings("element-type-mismatch")
+        public boolean contains(Object key) {
+            return containsKey(key);
+        }
+
+        @Override
+        @SuppressWarnings("element-type-mismatch")
+        public boolean remove(Object key) {
+            int oldSize = size;
+            AHashLookup.this.remove(key);
+            return size != oldSize;
+        }
+
+        @Override
+        public void clear() {
+            AHashLookup.this.clear();
+        }
+    }
+
+    @Override
+    public final Set<K> keySet() {
+        if (mkeys == null) {
+            mkeys = new KeySet();
+        }
+        return mkeys;
+    }
+
+    /**
+     * An iterator on mvalues.
+     *
+     * <P>We simply override the
+     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
+ methods (and possibly their type-specific counterparts) so that they
+ return mvalues instead of mentries.
+     */
+    private final class ValueIterator extends IntIterator {
+
+        private final MapIterator delegate = new MapIterator();
+
+        public ValueIterator() { }
+        
+        @Override
+        public int nextInt() {
+            return values[delegate.nextEntry()];
+        }
+
+        @Override
+        public Integer next() {
+            return values[delegate.nextEntry()];
+        }
+
+        @Override
+        public boolean hasNext() {
+            return delegate.hasNext();
+        }
+    }
+
+    @Override
+    public final IntCollection values() {
+        if (mvalues == null) {
+            mvalues = new IntCollection() {
+                @Override
+                public IntIterator iterator() {
+                    return new ValueIterator();
+                }
+
+                @Override
+                public int size() {
+                    return size;
+                }
+
+                @Override
+                public boolean contains(int v) {
+                    return containsValue(v);
+                }
+
+                @Override
+                public void clear() {
+                    AHashLookup.this.clear();
+                }
+            };
+        }
+        return mvalues;
+    }
+
+    /**
+     * Rehashes the map, making the table as small as possible.
+     *
+     * <P>This method rehashes the table to the smallest size satisfying the
+     * load factor. It can be used when the set will not be changed anymore, so
+     * to optimize access speed and size.
+     *
+     * <P>If the table size is already the minimum possible, this method does
+     * nothing.
+     *
+     * @return true if there was enough memory to trim the map.
+     * @see #trim(int)
+     */
+    public final boolean trim() {
+        int l = HashCollection.arraysize(size, factor);
+        if (l >= n) {
+            return true;
+        }
+        try {
+            rehash(l);
+        } catch (OutOfMemoryError cantDoIt) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Rehashes this map if the table is too large.
+     *
+     * <P>Let <var>N</var> be the smallest table size that can hold
+     * <code>max(n,{@link #size()})</code> mentries, still satisfying the load
+ factor. If the current table size is smaller than or equal to
+ <var>N</var>, this method does nothing. Otherwise, it rehashes this map
+     * in a table of size
+     * <var>N</var>.
+     *
+     * <P>This method is useful when reusing maps. null null null null null     {@linkplain #clear() Clearing a
+	 * map} leaves the table size untouched. If you are reusing a map many
+     * times, you can call this method with a typical size to avoid keeping
+     * around a very large table just because of a few large transient maps.
+     *
+     * @param n the threshold for the trimming.
+     * @return true if there was enough memory to trim the map.
+     * @see #trim()
+     */
+    public final boolean trim(int n) {
+        int l = MathUtils.pow2next((int) Math.ceil(n / factor));
+        if (this.n <= l) {
+            return true;
+        }
+        try {
+            rehash(l);
+        } catch (OutOfMemoryError cantDoIt) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Resizes the map.
+     *
+     * <P>This method implements the basic rehashing strategy, and may be
+     * overriden by subclasses implementing different rehashing strategies
+     * (e.g., disk-based rehashing). However, you should not override this
+     * method unless you understand the internal workings of this class.
+     *
+     * @param newN the new size
+     */
+    @SuppressWarnings("unchecked")
+    protected final void rehash(int newN) {
+        int i = 0, pos;
+        boolean nused[] = this.used;
+        K ckey;
+        K ikeys[] = this.keys;
+        int ivalues[] = this.values;
+        int newMask = newN - 1;
+        K newKey[] = (K[]) new Object[newN];
+        int newValue[] = new int[newN];
+        boolean newUsed[] = new boolean[newN];
+        for (int j = size; j-- != 0;) {
+            while (!nused[ i]) {
+                i++;
+            }
+            ckey = ikeys[ i];
+            pos = HashUtils.murmurHash3(hashkey((K)ckey)) & newMask;
+            while (newUsed[ pos]) {
+                pos = (pos + 1) & newMask;
+            }
+            newUsed[ pos] = true;
+            newKey[ pos] = ckey;
+            newValue[ pos] = ivalues[ i];
+            i++;
+        }
+        n = newN;
+        mask = newMask;
+        maxFill = HashCollection.arrayfill(n, factor);
+        this.keys = newKey;
+        this.values = newValue;
+        this.used = newUsed;
+    }
+
+    /**
+     * Returns a hash code for this map.
+     *
+     * This method overrides the generic method provided by the superclass.
+     * Since
+     * <code>equals()</code> is not overriden, it is important that the values
+ returned by this method is the same values as the one returned by the
+ overriden method.
+     *
+     * @return a hash code for this map.
+     */
+    @Override
+    public final int hashCode() {
+        int h = 0;
+        for (int j = size, i = 0, t = 0; j-- != 0;) {
+            while (!used[ i]) {
+                i++;
+            }
+            if (this != keys[ i]) {
+                t = hashkey((K)keys[i]);
+            }
+            t ^= values[i];
+            h += t;
+            i++;
+        }
+        return h;
+    }
+
+    @Override
+    public final boolean equals(Object object) {
+        return super.equals(object);
+    }
+    
+    private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
+        K ikeys[] = this.keys;
+        int ivalues[] = this.values;
+        MapIterator iterator = new MapIterator();
+        ostream.defaultWriteObject();
+        for (int j = size, element; j-- != 0;) {
+            element = iterator.nextEntry();
+            ostream.writeObject(ikeys[ element]);
+            ostream.writeInt(ivalues[ element]);
+        }
+    }
+
+    @SuppressWarnings({
+        "unchecked",
+        "MismatchedReadAndWriteOfArray"
+    })
+    private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
+        istream.defaultReadObject();
+        n = HashCollection.arraysize(size, factor);
+        maxFill = HashCollection.arrayfill(n, factor);
+        mask = n - 1;
+        K ikeys[] = this.keys = (K[]) new Object[n];
+        int ivalues[] = this.values = new int[n];
+        boolean iused[] = this.used = new boolean[n];
+        for (int i = size, pos; i-- != 0;) {
+            K ckey = (K) istream.readObject();
+            int cval = istream.readInt();
+            pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
+            while (iused[ pos]) {
+                pos = (pos + 1) & mask;
+            }
+            iused[pos] = true;
+            ikeys[pos] = ckey;
+            ivalues[pos] = cval;
+        }
+    }
+
+}

+ 1 - 1
assira/src/main/java/net/ranides/assira/collection/lookups/AVLTreeLookup.java

@@ -1195,7 +1195,7 @@ public final class AVLTreeLookup<K> extends SortedLookup<K> implements java.io.S
 
     }
 
-    private class TreeIterator extends AListIterator {
+    private class TreeIterator extends AListIterator<LookupEntry<K>> {
 
         /**
          * The entry that will be returned by the next call to

+ 16 - 910
assira/src/main/java/net/ranides/assira/collection/lookups/CustomLookup.java

@@ -1,106 +1,29 @@
 /*
- * @author Paolo Boldi
- * @author Sebastiano Vigna
- * @author Ranides Atterwim <contact@ranides.net>
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
  */
-
 package net.ranides.assira.collection.lookups;
 
-import net.ranides.assira.collection.IntCollection;
-import net.ranides.assira.collection.iterators.IntIterator;
-import net.ranides.assira.collection.sets.ASet;
-import net.ranides.assira.collection.arrays.ArrayUtils;
 import net.ranides.assira.collection.utils.HashUtils;
 import net.ranides.assira.collection.HashComparator;
-import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
 import net.ranides.assira.collection.utils.HashCollection;
-import net.ranides.assira.math.MathUtils;
 
 /**
- * A type-specific hash map with a fast, small-footprint implementation whose
- * {@linkplain net.ranides.assira.collection.HashComparator hashing strategy} is
- * specified at creation time.
- *
- * <P>Instances of this class use a hash table to represent a map. The table is
- * enlarged as needed by doubling its size when new entries are created, but it
- * is <em>never</em> made smaller (even on a {@link #clear()}). A family of {@linkplain #trim() trimming
- * methods} lets you control the size of the table; this is particularly useful
- * if you reuse instances of this class.
- * 
- * @todo (assira # 6) review.hash.custom  
+ * A type-specific hash map with primitive int values, whose
+ * {@linkplain HashComparator hashing strategy} is specified at creation time.
  */
 @SuppressWarnings({
     "PMD.AvoidReassigningParameters",
     "PMD.OverrideBothEqualsAndHashcode",
     "EqualsAndHashcode"
 })
-public final class CustomLookup<K> extends Lookup<K> implements java.io.Serializable {
+public final class CustomLookup<K> extends AHashLookup<K> {
 
-    private static final long serialVersionUID = 0L;
-    /**
-     * The array of keys.
-     */
-    protected transient K[] akeys;
-    /**
-     * The array of values.
-     */
-    protected transient int[] avalues;
-    /**
-     * The array telling whether a position is used.
-     */
-    protected transient boolean used[];
-    /**
-     * The acceptable load factor.
-     */
-    protected float f;
-    /**
-     * The current table size.
-     */
-    protected transient int n;
-    /**
-     * Threshold after which we rehash. It must be the table size times
-     * {@link #f}.
-     */
-    protected transient int maxFill;
-    /**
-     * The mask for wrapping a position counter.
-     */
-    protected transient int mask;
-    /**
-     * Number of entries in the set.
-     */
-    protected int size;
-    /**
-     * Cached set of entries.
-     */
-    protected transient volatile Set<LookupEntry<K>> entries;
-    /**
-     * Cached set of keys.
-     */
-    protected transient volatile Set<K> keys;
-    /**
-     * Cached collection of values.
-     */
-    protected transient volatile IntCollection values;
-    /**
-     * The hash strategy of this custom map.
-     */
+    private static final long serialVersionUID = 2L;
+    
     protected HashComparator<K> strategy;
 
     /**
@@ -111,25 +34,13 @@ public final class CustomLookup<K> extends Lookup<K> implements java.io.Serializ
      * <code>f</code>.
      *
      * @param expected the expected number of elements in the hash set.
-     * @param f the load factor.
+     * @param factor the load factor.
      * @param strategy the strategy.
      */
     @SuppressWarnings("unchecked")
-    public CustomLookup(int expected, float f, HashComparator<K> strategy) {
+    public CustomLookup(int expected, float factor, HashComparator<K> strategy) {
+        super(expected, factor);
         this.strategy = strategy;
-        if (f <= 0 || f > 1) {
-            throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1");
-        }
-        if (expected < 0) {
-            throw new IllegalArgumentException("The expected number of elements must be nonnegative");
-        }
-        this.f = f;
-        n = HashCollection.arraysize(expected, f);
-        mask = n - 1;
-        maxFill = HashCollection.arrayfill(n, f);
-        akeys = (K[]) new Object[n];
-        avalues = new int[n];
-        used = new boolean[n];
     }
 
     /**
@@ -242,820 +153,15 @@ public final class CustomLookup<K> extends Lookup<K> implements java.io.Serializ
     public HashComparator<K> strategy() {
         return strategy;
     }
-    /*
-     * The following methods implements some basic building blocks used by
-     * all accessors. They are (and should be maintained) identical to those used in OpenHashSet.drv.
-     */
-
-    @Override
-    public int put(K key, int value) {
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode(key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], key)) {
-                int oldValue = avalues[pos];
-                avalues[ pos] = value;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = value;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return defRetValue;
-    }
-
-    @Override
-    public Integer put(K key, Integer value) {
-        int v = value;
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode(key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[ pos], key)) {
-                Integer oldValue = avalues[pos];
-                avalues[pos] = v;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = v;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return null;
-    }
-
-    /**
-     * Adds an increment to avalues currently associated with a akeys.
-     *
-     * <P>Note that this method respects the
-     * {@link #defaultReturnValue() default return avalues} semantics: when
- called with a akeys that does not currently appears in the map, the akeys
- will be associated with the default return avalues plus the given
- increment.
-     *
-     * @param key the akeys.
-     * @param delta the increment.
-     * @return the old avalues, or the
-    {@link #defaultReturnValue() default return avalues} if no avalues was
- present for the given akeys.
-     */
-    public int addTo(K key, int delta) {
-        // The starting point.
-        int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (key)))) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[ pos], key)) {
-                int oldValue = avalues[ pos];
-                avalues[ pos] += delta;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = defRetValue + delta;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return defRetValue;
-    }
-
-    /**
-     * Shifts left entries with the specified hash code, starting at the
-     * specified position, and empties the resulting free entry.
-     *
-     * @param pos a starting position.
-     * @return the position cleared by the shifting process.
-     */
-    private int shiftKeys(int pos) {
-        // Shift entries with the same hash.
-        int last, slot;
-        for (;;) {
-            pos = ((last = pos) + 1) & mask;
-            while (used[ pos]) {
-                slot = (HashUtils.murmurHash3(strategy.hashCode((K) (akeys[ pos])))) & mask;
-                if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
-                    break;
-                }
-                pos = (pos + 1) & mask;
-            }
-            if (!used[ pos]) {
-                break;
-            }
-            akeys[ last] = akeys[ pos];
-            avalues[ last] = avalues[ pos];
-        }
-        used[ last] = false;
-        akeys[ last] = null;
-        return last;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public int removeInt(Object key) {
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[ pos], (K)key)) {
-                size--;
-                int v = avalues[ pos];
-                shiftKeys(pos);
-                return v;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return defRetValue;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public Integer remove(Object key) {
-        K kkey = (K)key;
-        // The starting point.
-        int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (kkey)))) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], kkey)) {
-                size--;
-                int v = avalues[ pos];
-                shiftKeys(pos);
-                return v;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return null;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public int getInt(Object key) {
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], (K)key)) {
-                return avalues[pos];
-            }
-            pos = (pos + 1) & mask;
-        }
-        return defRetValue;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public boolean containsKey(Object key) {
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], (K)key)) {
-                return true;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean containsValue(int v) {
-        int nvalues[] = this.avalues;
-        boolean nused[] = this.used;
-        for (int i = n; i-- != 0;) {
-            if (nused[i] && (nvalues[i] == v)) {
-                return true;
-            }
-        }
-        return false;
-    }
-    /* Removes all elements from this map.
-     *
-     * <P>To increase object reuse, this method does not change the table size.
-     * If you want to reduce the table size, you must use {@link #trim()}.
-     *
-     */
-
-    @Override
-    public void clear() {
-        if (size == 0) {
-            return;
-        }
-        size = 0;
-        ArrayUtils.fill(used, false);
-        // We null all object entries so that the garbage collector can do its work.
-        ArrayUtils.fill(akeys, null);
-    }
-
-    @Override
-    public int size() {
-        return size;
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return size == 0;
-    }
-
-    /**
-     * The entry class for a hash map does not record akeys and avalues, but rather
-     * the position in the hash table of the corresponding entry. This is
-     * necessary so that calls to {@link java.util.Map.Entry#setValue(Object)}
-     * are reflected in the map
-     */
-    private final class MapEntry implements LookupEntry<K>, Map.Entry<K, Integer> {
-        // The table index this entry refers to, or -1 if this entry has been deleted.
-
-        int index;
-
-        MapEntry(int index) {
-            this.index = index;
-        }
-
-        @Override
-        public K getKey() {
-            return akeys[ index];
-        }
-
-        @Override
-        public Integer getValue() {
-            return avalues[ index];
-        }
-
-        @Override
-        public int getIntValue() {
-            return avalues[ index];
-        }
-
-        @Override
-        public int setValue(int v) {
-            int oldValue = avalues[ index];
-            avalues[ index] = v;
-            return oldValue;
-        }
-
-        @Override
-        public Integer setValue(Integer value) {
-            return setValue(value.intValue());
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean equals(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
-            K key = entry.getKey();
-            int v = entry.getValue();
-            return strategy.equals(akeys[index], key) && (avalues[index] == v);
-        }
-
-        @Override
-        public int hashCode() {
-            return (strategy.hashCode((K) (akeys[ index]))) ^ (avalues[ index]);
-        }
-
-        @Override
-        public String toString() {
-            return akeys[ index] + "=>" + avalues[ index];
-        }
-    }
-
-    /**
-     * An iterator over a hash map.
-     */
-    private class MapIterator {
-        
-        /**
-         * The index of the next entry to be returned, if positive or zero. If
-         * negative, the next entry to be returned, if any, is that of index
-         * -pos -2 from the {@link #wrapped} list.
-         */
-        int pos;
-        /**
-         * The index of the last entry that has been returned. It is -1 if
-         * either we did not return an entry yet, or the last returned entry has
-         * been removed.
-         */
-        int last;
-        /**
-         * A downward counter measuring how many entries must still be returned.
-         */
-        int counter;
-        /**
-         * A lazily allocated list containing the keys of elements that have
-         * wrapped around the table because of removals; such elements would not
-         * be enumerated (other elements would be usually enumerated twice in
-         * their place).
-         */
-        ArrayList<K> wrapped;
-       
-        public MapIterator() {
-            this.pos = CustomLookup.this.n;
-            this.last = -1;
-            this.counter = size;
-            this.wrapped = null;
-            
-            boolean used[] = CustomLookup.this.used;
-            if (counter != 0) {
-                while (!used[ --pos]) { } // NOPMD
-            }
-        }
-
-        public boolean hasNext() {
-            return counter != 0;
-        }
-
-        public int nextEntry() {
-            if (!hasNext()) {
-                throw new NoSuchElementException();
-            }
-            counter--;
-            // We are just enumerating elements from the wrapped list.
-            if (pos < 0) {
-                K ckey = wrapped.get(-(last = --pos) - 2);
-                // The starting point.
-                int cpos = HashUtils.murmurHash3(strategy.hashCode(ckey)) & mask;
-                // There's always an unused entry.
-                while (used[ cpos]) {
-                    if (strategy.equals(akeys[ cpos], ckey)) {
-                        return cpos;
-                    }
-                    cpos = (cpos + 1) & mask;
-                }
-            }
-            last = pos;
-            //System.err.println( "Count: " + c );
-            if (counter != 0) {
-                boolean used[] = CustomLookup.this.used;
-                while (pos-- != 0 && !used[ pos]) { } // NOPMD
-                // When here pos < 0 there are no more elements to be enumerated by scanning, but wrapped might be nonempty.
-            }
-            return last;
-        }
-
-        /**
-         * Shifts left entries with the specified hash code, starting at the
-         * specified position, and empties the resulting free entry. If any
-         * entry wraps around the table, instantiates lazily {@link #wrapped}
-         * and stores the entry akeys.
-         *
-         * @param pos a starting position.
-         * @return the position cleared by the shifting process.
-         */
-        protected final int shiftKeys(int pos) {
-            // Shift entries with the same hash.
-            int ilast, islot;
-            for (;;) {
-                pos = ((ilast = pos) + 1) & mask;
-                while (used[ pos]) {
-                    islot = (HashUtils.murmurHash3(strategy.hashCode((K) (akeys[ pos])))) & mask;
-                    if (ilast <= pos ? ilast >= islot || islot > pos : ilast >= islot && islot > pos) {
-                        break;
-                    }
-                    pos = (pos + 1) & mask;
-                }
-                if (!used[ pos]) {
-                    break;
-                }
-                if (pos < ilast) {
-                    // Wrapped entry.
-                    if (wrapped == null) {
-                        wrapped = new ArrayList<>();
-                    }
-                    wrapped.add(akeys[ pos]);
-                }
-                akeys[ ilast] = akeys[ pos];
-                avalues[ ilast] = avalues[ pos];
-            }
-            used[ ilast] = false;
-            akeys[ ilast] = null;
-            return ilast;
-        }
-
-        @SuppressWarnings("unchecked")
-        public void remove() {
-            if (last == -1) {
-                throw new IllegalStateException();
-            }
-            if (pos < -1) {
-                // We're removing wrapped entries.
-                CustomLookup.this.remove(wrapped.set(-pos - 2, null));
-                last = -1;
-                return;
-            }
-            size--;
-            if (shiftKeys(last) == pos && counter > 0) {
-                counter++;
-                nextEntry();
-            }
-            last = -1; // You can no longer remove this entry.
-        }
-
-        public int skip(int n) {
-            int i = n;
-            while (i-- != 0 && hasNext()) {
-                nextEntry();
-            }
-            return n - i - 1;
-        }
-    }
-
-    private class EntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
-
-        private MapEntry entry;
-        
-        public EntryIterator() { }
-
-        @Override
-        public LookupEntry<K> next() {
-            return entry = new MapEntry(nextEntry());
-        }
-
-        @Override
-        public void remove() {
-            super.remove();
-            entry.index = -1; // You cannot use a deleted entry.
-        }
-    }
-
-    private class FastEntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
-
-        final BasicEntry<K> entry = new BasicEntry<>(null, 0);
-        
-        public FastEntryIterator() { }
-
-        @Override
-        public BasicEntry<K> next() {
-            int e = nextEntry();
-            entry.key = akeys[ e];
-            entry.value = avalues[ e];
-            return entry;
-        }
-    }
-
-	private final class MapEntrySet extends ASet<LookupEntry<K>> {
-
-        @Override
-        public Iterator<LookupEntry<K>> iterator() {
-            return new EntryIterator();
-        }
-
-        public Iterator<LookupEntry<K>> fastIterator() {
-            return new FastEntryIterator();
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean contains(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
-            K ckey = entry.getKey();
-            // The starting point.
-            int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
-            // There's always an unused entry.
-            while (used[ pos]) {
-                if (strategy.equals(akeys[ pos], ckey)) {
-                    int v = entry.getValue();
-                    return avalues[pos] == v;
-                }
-                pos = (pos + 1) & mask;
-            }
-            return false;
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean remove(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
-            K ckey = entry.getKey();
-            // The starting point.
-            int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
-            // There's always an unused entry.
-            while (used[ pos]) {
-                if (strategy.equals(akeys[pos], (K)ckey)) {
-                    CustomLookup.this.remove(entry.getKey());
-                    return true;
-                }
-                pos = (pos + 1) & mask;
-            }
-            return false;
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        public void clear() {
-            CustomLookup.this.clear();
-        }
-    }
-
-    @Override
-    public Set<LookupEntry<K>> fastEntrySet() {
-        if (entries == null) {
-            entries = new MapEntrySet();
-        }
-        return entries;
-    }
-
-    /**
-     * An iterator on keys.
-     *
-     * <P>We simply override the
-     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
-     * methods (and possibly their type-specific counterparts) so that they
-     * return keys instead of entries.
-     */
-    private final class KeyIterator extends MapIterator implements Iterator<K> {
-
-        public KeyIterator() {
-            super();
-        }
-
-        @Override
-        public K next() {
-            return akeys[ nextEntry()];
-        }
-    }
-
-    private final class KeySet extends ASet<K> {
-
-        @Override
-        public Iterator<K> iterator() {
-            return new KeyIterator();
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        @SuppressWarnings("element-type-mismatch")
-        public boolean contains(Object key) {
-            return containsKey(key);
-        }
-
-        @Override
-        @SuppressWarnings("element-type-mismatch")
-        public boolean remove(Object key) {
-            int oldSize = size;
-            CustomLookup.this.remove(key);
-            return size != oldSize;
-        }
-
-        @Override
-        public void clear() {
-            CustomLookup.this.clear();
-        }
-    }
 
     @Override
-    public Set<K> keySet() {
-        if (keys == null) {
-            keys = new KeySet();
-        }
-        return keys;
-    }
-
-    /**
-     * An iterator on values.
-     *
-     * <P>We simply override the
-     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
-     * methods (and possibly their type-specific counterparts) so that they
-     * return values instead of entries.
-     */
-    private final class ValueIterator extends IntIterator {
-
-        private final MapIterator delegate = new MapIterator();
-
-        public ValueIterator() { }
-        
-        @Override
-        public int nextInt() {
-            return avalues[delegate.nextEntry()];
-        }
-
-        @Override
-        public Integer next() {
-            return avalues[delegate.nextEntry()];
-        }
-
-        @Override
-        public boolean hasNext() {
-            return delegate.hasNext();
-        }
+    protected int hashkey(K key) {
+        return HashUtils.murmurHash3(strategy.hashCode(key));
     }
 
     @Override
-    public IntCollection values() {
-        if (values == null) {
-            values = new IntCollection() {
-                @Override
-                public IntIterator iterator() {
-                    return new ValueIterator();
-                }
-
-                @Override
-                public int size() {
-                    return size;
-                }
-
-                @Override
-                public boolean contains(int v) {
-                    return containsValue(v);
-                }
-
-                @Override
-                public void clear() {
-                    CustomLookup.this.clear();
-                }
-            };
-        }
-        return values;
-    }
-
-    /**
-     * Rehashes the map, making the table as small as possible.
-     *
-     * <P>This method rehashes the table to the smallest size satisfying the
-     * load factor. It can be used when the set will not be changed anymore, so
-     * to optimize access speed and size.
-     *
-     * <P>If the table size is already the minimum possible, this method does
-     * nothing.
-     *
-     * @return true if there was enough memory to trim the map.
-     * @see #trim(int)
-     */
-    public boolean trim() {
-        int l = HashCollection.arraysize(size, f);
-        if (l >= n) {
-            return true;
-        }
-        try {
-            rehash(l);
-        } catch (OutOfMemoryError cantDoIt) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Rehashes this map if the table is too large.
-     *
-     * <P>Let <var>N</var> be the smallest table size that can hold
-     * <code>max(n,{@link #size()})</code> entries, still satisfying the load
-     * factor. If the current table size is smaller than or equal to
-     * <var>N</var>, this method does nothing. Otherwise, it rehashes this map
-     * in a table of size
-     * <var>N</var>.
-     *
-     * <P>This method is useful when reusing maps. null null null null null     {@linkplain #clear() Clearing a
-	 * map} leaves the table size untouched. If you are reusing a map many
-     * times, you can call this method with a typical size to avoid keeping
-     * around a very large table just because of a few large transient maps.
-     *
-     * @param n the threshold for the trimming.
-     * @return true if there was enough memory to trim the map.
-     * @see #trim()
-     */
-    public boolean trim(int n) {
-        int l = MathUtils.pow2next((int) Math.ceil(n / f));
-        if (this.n <= l) {
-            return true;
-        }
-        try {
-            rehash(l);
-        } catch (OutOfMemoryError cantDoIt) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Resizes the map.
-     *
-     * <P>This method implements the basic rehashing strategy, and may be
-     * overriden by subclasses implementing different rehashing strategies
-     * (e.g., disk-based rehashing). However, you should not override this
-     * method unless you understand the internal workings of this class.
-     *
-     * @param newN the new size
-     */
-    @SuppressWarnings("unchecked")
-    protected void rehash(int newN) {
-        int i = 0, pos;
-        boolean nused[] = this.used;
-        K ckey;
-        K ikeys[] = this.akeys;
-        int ivalues[] = this.avalues;
-        int newMask = newN - 1;
-        K newKey[] = (K[]) new Object[newN];
-        int newValue[] = new int[newN];
-        boolean newUsed[] = new boolean[newN];
-        for (int j = size; j-- != 0;) {
-            while (!nused[ i]) {
-                i++;
-            }
-            ckey = ikeys[ i];
-            pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & newMask;
-            while (newUsed[ pos]) {
-                pos = (pos + 1) & newMask;
-            }
-            newUsed[ pos] = true;
-            newKey[ pos] = ckey;
-            newValue[ pos] = ivalues[ i];
-            i++;
-        }
-        n = newN;
-        mask = newMask;
-        maxFill = HashCollection.arrayfill(n, f);
-        this.akeys = newKey;
-        this.avalues = newValue;
-        this.used = newUsed;
-    }
-
-    /**
-     * Returns a hash code for this map.
-     *
-     * This method overrides the generic method provided by the superclass.
-     * Since
-     * <code>equals()</code> is not overriden, it is important that the avalues
-     * returned by this method is the same avalues as the one returned by the
-     * overriden method.
-     *
-     * @return a hash code for this map.
-     */
-    @Override
-    public int hashCode() {
-        int h = 0;
-        for (int j = size, i = 0, t = 0; j-- != 0;) {
-            while (!used[ i]) {
-                i++;
-            }
-            if (this != akeys[ i]) {
-                t = strategy.hashCode((K)akeys[i]);
-            }
-            t ^= avalues[i];
-            h += t;
-            i++;
-        }
-        return h;
-    }
-
-    private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
-        K ikeys[] = this.akeys;
-        int ivalues[] = this.avalues;
-        MapIterator iterator = new MapIterator();
-        ostream.defaultWriteObject();
-        for (int j = size, element; j-- != 0;) {
-            element = iterator.nextEntry();
-            ostream.writeObject(ikeys[ element]);
-            ostream.writeInt(ivalues[ element]);
-        }
-    }
-
-    @SuppressWarnings({
-        "unchecked",
-        "MismatchedReadAndWriteOfArray"
-    })
-    private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
-        istream.defaultReadObject();
-        n = HashCollection.arraysize(size, f);
-        maxFill = HashCollection.arrayfill(n, f);
-        mask = n - 1;
-        K ikeys[] = this.akeys = (K[]) new Object[n];
-        int ivalues[] = this.avalues = new int[n];
-        boolean iused[] = this.used = new boolean[n];
-        for (int i = size, pos; i-- != 0;) {
-            K ckey = (K) istream.readObject();
-            int cval = istream.readInt();
-            pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
-            while (iused[ pos]) {
-                pos = (pos + 1) & mask;
-            }
-            iused[pos] = true;
-            ikeys[pos] = ckey;
-            ivalues[pos] = cval;
-        }
+    protected boolean cmpkey(K key1, K key2) {
+        return strategy.equals(key1, key2);
     }
 
 }

+ 14 - 922
assira/src/main/java/net/ranides/assira/collection/lookups/HashLookup.java

@@ -1,109 +1,22 @@
 /*
- * @author Paolo Boldi
- * @author Sebastiano Vigna
- * @author Ranides Atterwim <contact@ranides.net>
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
  */
-
 package net.ranides.assira.collection.lookups;
 
-import net.ranides.assira.collection.IntCollection;
-import net.ranides.assira.collection.iterators.IntIterator;
-import net.ranides.assira.collection.sets.ASet;
-import net.ranides.assira.collection.arrays.ArrayUtils;
 import net.ranides.assira.collection.utils.HashUtils;
-import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
 import net.ranides.assira.collection.utils.HashCollection;
 import net.ranides.assira.generic.ValueUtils;
-import net.ranides.assira.math.MathUtils;
 
 /**
- * A type-specific hash map with a fast, small-footprint implementation.
- *
- * <P>Instances of this class use a hash table to represent a map. The table is
- * enlarged as needed by doubling its size when new entries are created, but it
- * is <em>never</em> made smaller (even on a {@link #clear()}). A family of {@linkplain #trim() trimming
- * methods} lets you control the size of the table; this is particularly useful
- * if you reuse instances of this class.
- *
- * <p><strong>Warning:</strong> The implementation of this class has
- * significantly changed in
- * <code>fastutil</code> 6.1.0. Please read the comments about this issue in the
- * section &ldquo;Faster Hash Tables&rdquo; of the <a
- * href="../../../../../overview-summary.html">overview</a>.
- *
- * @see Hash
- * @see HashUtils
- * @todo (assira # 6) review.hash 
+ * A type-specific hash map with primitive int values.
  */
-@SuppressWarnings({
-    "PMD.AvoidReassigningParameters",
-    "EqualsAndHashcode"
-})
-public final class HashLookup<K> extends Lookup<K> implements java.io.Serializable {
+public final class HashLookup<K> extends AHashLookup<K> {
 
-    private static final long serialVersionUID = 0L;
-
-    /**
-     * The array of keys.
-     */
-    protected transient K[] akeys;
-    /**
-     * The array of values.
-     */
-    protected transient int[] avalues;
-    /**
-     * The array telling whether a position is used.
-     */
-    protected transient boolean used[];
-    /**
-     * The acceptable load factor.
-     */
-    protected final float f;
-    /**
-     * The current table size.
-     */
-    protected transient int n;
-    /**
-     * Threshold after which we rehash. It must be the table size times
-     * {@link #f}.
-     */
-    protected transient int maxFill;
-    /**
-     * The mask for wrapping a position counter.
-     */
-    protected transient int mask;
-    /**
-     * Number of entries in the set.
-     */
-    protected int size;
-    /**
-     * Cached set of entries.
-     */
-    protected transient volatile Set<LookupEntry<K>> entries;
-    /**
-     * Cached set of keys.
-     */
-    protected transient volatile Set<K> keys;
-    /**
-     * Cached collection of values.
-     */
-    protected transient volatile IntCollection values;
+    private static final long serialVersionUID = 3L;
 
     /**
      * Creates a new hash map.
@@ -116,20 +29,8 @@ public final class HashLookup<K> extends Lookup<K> implements java.io.Serializab
      * @param f the load factor.
      */
     @SuppressWarnings("unchecked")
-    public HashLookup(int expected, float f) {
-        if (f <= 0 || f > 1) {
-            throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1");
-        }
-        if (expected < 0) {
-            throw new IllegalArgumentException("The expected number of elements must be nonnegative");
-        }
-        this.f = f;
-        n = HashCollection.arraysize(expected, f);
-        mask = n - 1;
-        maxFill = HashCollection.arrayfill(n, f);
-        akeys = (K[]) new Object[n];
-        avalues = new int[n];
-        used = new boolean[n];
+    public HashLookup(int expected, float factor) {
+        super(expected, factor);
     }
 
     /**
@@ -224,824 +125,15 @@ public final class HashLookup<K> extends Lookup<K> implements java.io.Serializab
     public HashLookup(K[] keys, int values[]) {
         this(keys, values, HashCollection.LOAD_FACTOR);
     }
-    /*
-     * The following methods implements some basic building blocks used by
-     * all accessors. They are (and should be maintained) identical to those used in OpenHashSet.drv.
-     */
-
-    @Override
-    public int put(K key, int value) {
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (((akeys[ pos]) == null ? (key) == null : (akeys[ pos]).equals(key))) {
-                int oldValue = avalues[ pos];
-                avalues[ pos] = value;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = value;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return defRetValue;
-    }
-
-    @Override
-    public Integer put(K key, Integer value) {
-        int ival = value;
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (((akeys[ pos]) == null ? (key) == null : (akeys[ pos]).equals(key))) {
-                Integer oldValue = avalues[ pos];
-                avalues[ pos] = ival;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = ival;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return null;
-    }
-
-    /**
-     * Adds an increment to avalues currently associated with a akeys.
-     *
-     * <P>Note that this method respects the
-     * {@link #defaultReturnValue() default return avalues} semantics: when
- called with a akeys that does not currently appears in the map, the akeys
- will be associated with the default return avalues plus the given
- increment.
-     *
-     * @param k the akeys.
-     * @param key the increment.
-     * @return the old avalues, or the
-    {@link #defaultReturnValue() default return avalues} if no avalues was
- present for the given akeys.
-     */
-    public int addTo(K key, int delta) {
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if ((akeys[ pos] == null ? key == null : akeys[ pos].equals(key))) {
-                int oldValue = avalues[ pos];
-                avalues[ pos] += delta;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = defRetValue + delta;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return defRetValue;
-    }
-
-    /**
-     * Shifts left entries with the specified hash code, starting at the
-     * specified position, and empties the resulting free entry.
-     *
-     * @param pos a starting position.
-     * @return the position cleared by the shifting process.
-     */
-    protected int shiftKeys(int pos) {
-        // Shift entries with the same hash.
-        int last, slot;
-        for (;;) {
-            pos = ((last = pos) + 1) & mask;
-            while (used[ pos]) {
-                slot = hashkey(akeys[pos]);
-                if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
-                    break;
-                }
-                pos = (pos + 1) & mask;
-            }
-            if (!used[ pos]) {
-                break;
-            }
-            akeys[ last] = akeys[ pos];
-            avalues[ last] = avalues[ pos];
-        }
-        used[ last] = false;
-        akeys[ last] = null;
-        return last;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public int removeInt(Object key) {
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (((akeys[ pos]) == null ? (key) == null : akeys[pos].equals(key))) {
-                size--;
-                int v = avalues[ pos];
-                shiftKeys(pos);
-                return v;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return defRetValue;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public Integer remove(Object key) {
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (((akeys[ pos]) == null ? (key) == null : (akeys[ pos]).equals(key))) {
-                size--;
-                int v = avalues[ pos];
-                shiftKeys(pos);
-                return v;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return null;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public int getInt(Object key) {
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (((akeys[ pos]) == null ? (key) == null : (akeys[ pos]).equals(key))) {
-                return avalues[ pos];
-            }
-            pos = (pos + 1) & mask;
-        }
-        return defRetValue;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public boolean containsKey(Object key) {
-        // The starting point.
-        int pos = hashkey(key);
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (((akeys[ pos]) == null ? (key) == null : (akeys[ pos]).equals(key))) {
-                return true;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean containsValue(int v) {
-        int value[] = this.avalues;
-        boolean iused[] = this.used;
-        for (int i = n; i-- != 0;) {
-            if (iused[ i] && ((value[ i]) == (v))) {
-                return true;
-            }
-        }
-        return false;
-    }
-    /* Removes all elements from this map.
-     *
-     * <P>To increase object reuse, this method does not change the table size.
-     * If you want to reduce the table size, you must use {@link #trim()}.
-     *
-     */
-
-    @Override
-    public void clear() {
-        if (size == 0) {
-            return;
-        }
-        size = 0;
-        ArrayUtils.fill(used, false);
-        // We null all object entries so that the garbage collector can do its work.
-        ArrayUtils.fill(akeys, null);
-    }
-
-    @Override
-    public int size() {
-        return size;
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return size == 0;
-    }
-
-    /**
-     * The entry class for a hash map does not record akeys and avalues, but rather
- the position in the hash table of the corresponding entry. This is
-     * necessary so that calls to {@link java.util.Map.Entry#setValue(Object)}
-     * are reflected in the map
-     */
-    private final class MapEntry implements LookupEntry<K>, Map.Entry<K, Integer> {
-        // The table index this entry refers to, or -1 if this entry has been deleted.
-
-        int index;
-
-        MapEntry(int index) {
-            this.index = index;
-        }
-
-        @Override
-        public K getKey() {
-            return akeys[ index];
-        }
-
-        @Override
-        public Integer getValue() {
-            return avalues[ index];
-        }
-
-        @Override
-        public int getIntValue() {
-            return avalues[ index];
-        }
-
-        @Override
-        public int setValue(int v) {
-            int oldValue = avalues[ index];
-            avalues[ index] = v;
-            return oldValue;
-        }
-
-        @Override
-        public Integer setValue(Integer value) {
-            return setValue(value.intValue());
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean equals(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
-            return ValueUtils.equals(akeys[index], entry.getKey()) && ValueUtils.equals(avalues[index], entry.getValue());
-        }
-
-        @Override
-        public int hashCode() {
-            return ((akeys[ index]) == null ? 0 : (akeys[ index]).hashCode()) ^ (avalues[ index]);
-        }
-
-        @Override
-        public String toString() {
-            return akeys[ index] + "=>" + avalues[ index];
-        }
-    }
-
-    /**
-     * An iterator over a hash map.
-     */
-    private class MapIterator {
-
-        /**
-         * The index of the next entry to be returned, if positive or zero. If
-         * negative, the next entry to be returned, if any, is that of index
-         * -pos -2 from the {@link #wrapped} list.
-         */
-        int pos = HashLookup.this.n;
-        /**
-         * The index of the last entry that has been returned. It is -1 if
-         * either we did not return an entry yet, or the last returned entry has
-         * been removed.
-         */
-        int last = -1;
-        /**
-         * A downward counter measuring how many entries must still be returned.
-         */
-        int c = size;
-        /**
-         * A lazily allocated list containing the keys of elements that have
-         * wrapped around the table because of removals; such elements would not
-         * be enumerated (other elements would be usually enumerated twice in
-         * their place).
-         */
-        ArrayList<K> wrapped;
-
-        {
-            boolean used[] = HashLookup.this.used;
-            if (c != 0) {
-                while (!used[ --pos]) { } // NOPMD
-            }
-        }
-
-        public MapIterator() {
-            this.pos = HashLookup.this.n;
-            this.last = -1;
-            this.c = size;
-            this.wrapped = null;
-
-            boolean used[] = HashLookup.this.used;
-            if (c != 0) {
-                while (!used[ --pos]) { } // NOPMD
-            }
-        }
-        
-        public boolean hasNext() {
-            return c != 0;
-        }
-
-        public int nextEntry() {
-            if (!hasNext()) {
-                throw new NoSuchElementException();
-            }
-            c--;
-            // We are just enumerating elements from the wrapped list.
-            if (pos < 0) {
-                K ckey = wrapped.get(-(last = --pos) - 2);
-                // The starting point.
-                int ret = hashkey(ckey);
-                // There's always an unused entry.
-                while (used[ ret]) {
-                    if (((akeys[ ret]) == null ? (ckey) == null : (akeys[ ret]).equals(ckey))) {
-                        return ret;
-                    }
-                    ret = (ret + 1) & mask;
-                }
-            }
-            last = pos;
-            //System.err.println( "Count: " + c );
-            if (c != 0) {
-                boolean used[] = HashLookup.this.used;
-                while (pos-- != 0 && !used[ pos]) { } // NOPMD
-                // When here pos < 0 there are no more elements to be enumerated by scanning, but wrapped might be nonempty.
-            }
-            return last;
-        }
-
-        /**
-         * Shifts left entries with the specified hash code, starting at the
-         * specified position, and empties the resulting free entry. If any
-         * entry wraps around the table, instantiates lazily {@link #wrapped}
- and stores the entry akeys.
-         *
-         * @param pos a starting position.
-         * @return the position cleared by the shifting process.
-         */
-        protected final int shiftKeys(int pos) {
-            // Shift entries with the same hash.
-            int ilast, slot;
-            for (;;) {
-                pos = ((ilast = pos) + 1) & mask;
-                while (used[ pos]) {
-                    slot = hashkey(akeys[ pos]);
-                    if (ilast <= pos ? ilast >= slot || slot > pos : ilast >= slot && slot > pos) {
-                        break;
-                    }
-                    pos = (pos + 1) & mask;
-                }
-                if (!used[ pos]) {
-                    break;
-                }
-                if (pos < ilast) {
-                    // Wrapped entry.
-                    if (wrapped == null) {
-                        wrapped = new ArrayList<>();
-                    }
-                    wrapped.add(akeys[ pos]);
-                }
-                akeys[ ilast] = akeys[ pos];
-                avalues[ ilast] = avalues[ pos];
-            }
-            used[ ilast] = false;
-            akeys[ ilast] = null;
-            return ilast;
-        }
-
-        @SuppressWarnings("unchecked")
-        public void remove() {
-            if (last == -1) {
-                throw new IllegalStateException();
-            }
-            if (pos < -1) {
-                // We're removing wrapped entries.
-                HashLookup.this.remove(wrapped.set(-pos - 2, null));
-                last = -1;
-                return;
-            }
-            size--;
-            if (shiftKeys(last) == pos && c > 0) {
-                c++;
-                nextEntry();
-            }
-            last = -1; // You can no longer remove this entry.
-        }
-
-        public int skip(int n) {
-            int i = n;
-            while (i-- != 0 && hasNext()) {
-                nextEntry();
-            }
-            return n - i - 1;
-        }
-    }
-
-    private class EntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
-
-        private MapEntry entry;
-
-        @Override
-        public LookupEntry<K> next() {
-            return entry = new MapEntry(nextEntry());
-        }
-
-        @Override
-        public void remove() {
-            super.remove();
-            entry.index = -1; // You cannot use a deleted entry.
-        }
-    }
-
-    private class FastEntryIterator extends MapIterator implements Iterator<LookupEntry<K>> {
-
-        final BasicEntry<K> entry = new BasicEntry<>(null, 0);
-
-        @Override
-        public BasicEntry<K> next() {
-            int e = nextEntry();
-            entry.key = akeys[ e];
-            entry.value = avalues[ e];
-            return entry;
-        }
-    }
-
-    private final class MapEntrySet extends ASet<LookupEntry<K>> implements Set<LookupEntry<K>> {
-
-        @Override
-        public Iterator<LookupEntry<K>> iterator() {
-            return new EntryIterator();
-        }
-
-        public Iterator<LookupEntry<K>> fastIterator() {
-            return new FastEntryIterator();
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean contains(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
-            K ckey = entry.getKey();
-            // The starting point.
-            int pos = hashkey(ckey);
-            // There's always an unused entry.
-            while (used[ pos]) {
-                if (((akeys[ pos]) == null ? (ckey) == null : (akeys[ pos]).equals(ckey))) {
-                    return avalues[pos] == entry.getValue();
-                }
-                pos = (pos + 1) & mask;
-            }
-            return false;
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean remove(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, Integer> entry = (Map.Entry<K, Integer>) object;
-            K ckey = entry.getKey();
-            // The starting point.
-            int pos = hashkey(ckey);
-            // There's always an unused entry.
-            while (used[ pos]) {
-                if (((akeys[ pos]) == null ? (ckey) == null : (akeys[ pos]).equals(ckey))) {
-                    HashLookup.this.remove(entry.getKey());
-                    return true;
-                }
-                pos = (pos + 1) & mask;
-            }
-            return false;
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        public void clear() {
-            HashLookup.this.clear();
-        }
-    }
-
-    @Override
-    public Set<LookupEntry<K>> fastEntrySet() {
-        if (entries == null) {
-            entries = new MapEntrySet();
-        }
-        return entries;
-    }
-
-    /**
-     * An iterator on keys.
-     *
-     * <P>We simply override the
-     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
-     * methods (and possibly their type-specific counterparts) so that they
-     * return keys instead of entries.
-     */
-    private final class KeyIterator extends MapIterator implements Iterator<K> {
-
-        public KeyIterator() {
-            super();
-        }
-
-        @Override
-        public K next() {
-            return akeys[ nextEntry()];
-        }
-    }
-
-    private final class KeySet extends ASet<K> {
-
-        @Override
-        public Iterator<K> iterator() {
-            return new KeyIterator();
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        @SuppressWarnings("element-type-mismatch")
-        public boolean contains(Object key) {
-            return containsKey(key);
-        }
-
-        @Override
-        @SuppressWarnings("element-type-mismatch")
-        public boolean remove(Object key) {
-            int oldSize = size;
-            HashLookup.this.remove(key);
-            return size != oldSize;
-        }
-
-        @Override
-        public void clear() {
-            HashLookup.this.clear();
-        }
-    }
-
+    
     @Override
-    public Set<K> keySet() {
-        if (keys == null) {
-            keys = new KeySet();
-        }
-        return keys;
-    }
-
-    /**
-     * An iterator on values.
-     *
-     * <P>We simply override the
-     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
-     * methods (and possibly their type-specific counterparts) so that they
-     * return values instead of entries.
-     */
-    private final class ValueIterator extends IntIterator {
-
-        private final MapIterator delegate = new MapIterator();
-
-        @Override
-        public int nextInt() {
-            return avalues[ delegate.nextEntry()];
-        }
-
-        @Override
-        public Integer next() {
-            return avalues[delegate.nextEntry()];
-        }
-
-        @Override
-        public boolean hasNext() {
-            return delegate.hasNext();
-        }
+    protected int hashkey(K key) {
+        return key == null ? 0x87fcd5c : HashUtils.murmurHash3(key.hashCode());
     }
 
     @Override
-    public IntCollection values() {
-        if (values == null) {
-            values = new IntCollection() {
-                @Override
-                public IntIterator iterator() {
-                    return new ValueIterator();
-                }
-
-                @Override
-                public int size() {
-                    return size;
-                }
-
-                @Override
-                public boolean contains(int v) {
-                    return containsValue(v);
-                }
-
-                @Override
-                public void clear() {
-                    HashLookup.this.clear();
-                }
-            };
-        }
-        return values;
-    }
-
-    /**
-     * Rehashes the map, making the table as small as possible.
-     *
-     * <P>This method rehashes the table to the smallest size satisfying the
-     * load factor. It can be used when the set will not be changed anymore, so
-     * to optimize access speed and size.
-     *
-     * <P>If the table size is already the minimum possible, this method does
-     * nothing.
-     *
-     * @return true if there was enough memory to trim the map.
-     * @see #trim(int)
-     */
-    public boolean trim() {
-        int l = HashCollection.arraysize(size, f);
-        if (l >= n) {
-            return true;
-        }
-        try {
-            rehash(l);
-        } catch (OutOfMemoryError cantDoIt) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Rehashes this map if the table is too large.
-     *
-     * <P>Let <var>N</var> be the smallest table size that can hold
-     * <code>max(n,{@link #size()})</code> entries, still satisfying the load
-     * factor. If the current table size is smaller than or equal to
-     * <var>N</var>, this method does nothing. Otherwise, it rehashes this map
-     * in a table of size
-     * <var>N</var>.
-     *
-     * <P>This method is useful when reusing maps. null null null null     {@linkplain #clear() Clearing a
-	 * map} leaves the table size untouched. If you are reusing a map many
-     * times, you can call this method with a typical size to avoid keeping
-     * around a very large table just because of a few large transient maps.
-     *
-     * @param n the threshold for the trimming.
-     * @return true if there was enough memory to trim the map.
-     * @see #trim()
-     */
-    public boolean trim(int n) {
-        int l = MathUtils.pow2next((int) Math.ceil(n / f));
-        if (this.n <= l) {
-            return true;
-        }
-        try {
-            rehash(l);
-        } catch (OutOfMemoryError cantDoIt) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Resizes the map.
-     *
-     * <P>This method implements the basic rehashing strategy, and may be
-     * overriden by subclasses implementing different rehashing strategies
-     * (e.g., disk-based rehashing). However, you should not override this
-     * method unless you understand the internal workings of this class.
-     *
-     * @param newN the new size
-     */
-    @SuppressWarnings("unchecked")
-    protected void rehash(int newN) {
-        int i = 0, pos;
-        boolean iused[] = this.used;
-        K key;
-        K ikeys[] = this.akeys;
-        int ivalues[] = this.avalues;
-        int newMask = newN - 1;
-        K newKey[] = (K[]) new Object[newN];
-        int newValue[] = new int[newN];
-        boolean newUsed[] = new boolean[newN];
-        for (int j = size; j-- != 0;) {
-            while (!iused[ i]) {
-                i++;
-            }
-            key = ikeys[ i];
-            pos = (key == null ? 0x87fcd5c : HashUtils.murmurHash3(key.hashCode())) & newMask;
-            while (newUsed[ pos]) {
-                pos = (pos + 1) & newMask;
-            }
-            newUsed[ pos] = true;
-            newKey[ pos] = key;
-            newValue[ pos] = ivalues[ i];
-            i++;
-        }
-        n = newN;
-        mask = newMask;
-        maxFill = HashCollection.arrayfill(n, f);
-        this.akeys = newKey;
-        this.avalues = newValue;
-        this.used = newUsed;
+    protected boolean cmpkey(K key1, K key2) {
+        return ValueUtils.equals(key1, key2);
     }
     
-    private int hashkey(Object key) {
-        return (key == null ? 0x87fcd5c : HashUtils.murmurHash3(key.hashCode())) & mask;
-    }
-
-    /**
-     * Returns a hash code for this map.
-     *
-     * This method overrides the generic method provided by the superclass.
-     * Since
-     * <code>equals()</code> is not overriden, it is important that the avalues
- returned by this method is the same avalues as the one returned by the
- overriden method.
-     *
-     * @return a hash code for this map.
-     */
-    @SuppressWarnings("PMD.OverrideBothEqualsAndHashcode")
-    @Override
-    public int hashCode() {
-        int h = 0;
-        for (int j = size, i = 0, t = 0; j-- != 0;) {
-            while (!used[ i]) {
-                i++;
-            }
-            if (this != akeys[ i]) {
-                t = ((akeys[ i]) == null ? 0 : (akeys[ i]).hashCode());
-            }
-            t ^= avalues[i];
-            h += t;
-            i++;
-        }
-        return h;
-    }
-
-    private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
-        K key[] = this.akeys;
-        int value[] = this.avalues;
-        MapIterator iterator = new MapIterator();
-        ostream.defaultWriteObject();
-        for (int j = size, e; j-- != 0;) {
-            e = iterator.nextEntry();
-            ostream.writeObject(key[ e]);
-            ostream.writeInt(value[ e]);
-        }
-    }
-
-    @SuppressWarnings({
-        "unchecked",
-        "MismatchedReadAndWriteOfArray"
-    })
-    private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
-        istream.defaultReadObject();
-        n = HashCollection.arraysize(size, f);
-        maxFill = HashCollection.arrayfill(n, f);
-        mask = n - 1;
-        K ikeys[] = this.akeys = (K[]) new Object[n];
-        int ivalues[] = this.avalues = new int[n];
-        boolean iused[] = this.used = new boolean[n];
-        K ckey;
-        int cval;
-        for (int i = size, pos; i-- != 0;) {
-            ckey = (K) istream.readObject();
-            cval = istream.readInt();
-            pos = hashkey(ckey);
-            while (iused[ pos]) {
-                pos = (pos + 1) & mask;
-            }
-            iused[ pos] = true;
-            ikeys[ pos] = ckey;
-            ivalues[ pos] = cval;
-        }
-    }
-
 }

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 140 - 1214
assira/src/main/java/net/ranides/assira/collection/lookups/IdentLookup.java


+ 818 - 0
assira/src/main/java/net/ranides/assira/collection/maps/AHashMap.java

@@ -0,0 +1,818 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.collection.maps;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import net.ranides.assira.collection.ACollection;
+import net.ranides.assira.collection.arrays.ArrayUtils;
+import net.ranides.assira.collection.sets.ASet;
+import net.ranides.assira.collection.utils.HashCollection;
+import net.ranides.assira.collection.utils.HashUtils;
+import net.ranides.assira.generic.ValueUtils;
+import net.ranides.assira.math.MathUtils;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @todo (assira # 7) review.hash.map
+ */
+public abstract class AHashMap<K,V> extends AMap<K, V> implements java.io.Serializable {
+   
+    private static final long serialVersionUID = 0L;
+    
+    /**
+     * The array of mkeys.
+     */
+    protected transient K[] keys;
+    /**
+     * The array of mvalues.
+     */
+    protected transient V[] values;
+    /**
+     * The array telling whether a position is used.
+     */
+    protected transient boolean used[];
+    /**
+     * The acceptable load factor.
+     */
+    protected final float factor;
+    /**
+     * The current table size.
+     */
+    protected transient int n;
+    /**
+     * Threshold after which we rehash. It must be the table size times
+     * {@link #factor}.
+     */
+    protected transient int maxFill;
+    /**
+     * The mask for wrapping a position counter.
+     */
+    protected transient int mask;
+    /**
+     * Number of mentries in the set.
+     */
+    protected int size;
+    /**
+     * Cached set of mentries.
+     */
+    protected transient volatile Set<Entry<K, V>> mentries;
+    /**
+     * Cached set of mkeys.
+     */
+    protected transient volatile Set<K> mkeys;
+    /**
+     * Cached collection of mvalues.
+     */
+    protected transient volatile Collection<V> mvalues;
+
+    @SuppressWarnings("unchecked")
+    public AHashMap(int expected, float factor) {
+        if (factor <= 0 || factor > 1) {
+            throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1");
+        }
+        if (expected < 0) {
+            throw new IllegalArgumentException("The expected number of elements must be nonnegative");
+        }
+        this.factor = factor;
+        n = HashCollection.arraysize(expected, factor);
+        mask = n - 1;
+        maxFill = HashCollection.arrayfill(n, factor);
+        keys = (K[]) new Object[n];
+        values = (V[]) new Object[n];
+        used = new boolean[n];
+    }
+    
+    protected abstract int hashkey(K key);
+    
+    protected abstract boolean cmpkey(K key1, K key2);
+    
+    @Override
+    public final V put(K key, V value) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], key)) {
+                final V oldValue = values[ pos];
+                values[ pos] = value;
+                return oldValue;
+            }
+            pos = (pos + 1) & mask;
+        }
+        used[ pos] = true;
+        keys[ pos] = key;
+        values[ pos] = value;
+        if (++size >= maxFill) {
+            rehash(HashCollection.arraysize(size + 1, factor));
+        }
+        return defRetValue;
+    }
+
+    /**
+     * Shifts left mentries with the specified hash code, starting at the
+ specified position, and empties the resulting free entry.
+     *
+     * @param pos a starting position.
+     * @return the position cleared by the shifting process.
+     */
+    protected final int shiftKeys(int pos) {
+        // Shift mentries with the same hash.
+        int last, slot;
+        for (;;) {
+            pos = ((last = pos) + 1) & mask;
+            while (used[ pos]) {
+                slot = HashUtils.murmurHash3(hashkey((K)keys[ pos])) & mask;
+                if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
+                    break;
+                }
+                pos = (pos + 1) & mask;
+            }
+            if (!used[ pos]) {
+                break;
+            }
+            keys[ last] = keys[ pos];
+            values[ last] = values[ pos];
+        }
+        used[ last] = false;
+        keys[ last] = null;
+        values[ last] = null;
+        return last;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public final V remove(Object key) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], (K)key)) {
+                size--;
+                final V prev = values[ pos];
+                shiftKeys(pos);
+                return prev;
+            }
+            pos = (pos + 1) & mask;
+        }
+        return defRetValue;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public final V get(Object key) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[pos], (K)key)) {
+                return values[ pos];
+            }
+            pos = (pos + 1) & mask;
+        }
+        return defRetValue;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public final boolean containsKey(Object key) {
+        // The starting point.
+        int pos = HashUtils.murmurHash3(hashkey((K)key)) & mask;
+        // There's always an unused entry.
+        while (used[ pos]) {
+            if (cmpkey(keys[ pos], (K)key)) {
+                return true;
+            }
+            pos = (pos + 1) & mask;
+        }
+        return false;
+    }
+
+    @Override
+    public final boolean containsValue(Object value) {
+        final V ivalues[] = this.values;
+        final boolean iused[] = this.used;
+        for (int i = n; i-- != 0;) {
+            if (iused[i] && (ivalues[i] == null ? value == null : ivalues[i].equals(value))) {
+                return true;
+            }
+        }
+        return false;
+    }
+    /* Removes all elements from this map.
+     *
+     * <P>To increase object reuse, this method does not change the table size.
+     * If you want to reduce the table size, you must use {@link #trim()}.
+     *
+     */
+
+    @Override
+    public final void clear() {
+        if (size == 0) {
+            return;
+        }
+        size = 0;
+        ArrayUtils.fill(used, false);
+        // We null all object mentries so that the garbage collector can do its work.
+        ArrayUtils.fill(keys, null);
+        ArrayUtils.fill(values, null);
+    }
+
+    @Override
+    public final int size() {
+        return size;
+    }
+
+    @Override
+    public final boolean isEmpty() {
+        return size == 0;
+    }
+
+    /**
+     * The entry class for a hash map does not record keys and values, but rather
+ the position in the hash table of the corresponding entry. This is
+     * necessary so that calls to {@link java.util.Map.Entry#setValue(Object)}
+     * are reflected in the map
+     */
+    private final class MapEntry implements Map.Entry<K, V> {
+        // The table index this entry refers to, or -1 if this entry has been deleted.
+
+        int index;
+
+        MapEntry(int index) {
+            this.index = index;
+        }
+
+        @Override
+        public K getKey() {
+            return keys[ index];
+        }
+
+        @Override
+        public V getValue() {
+            return values[ index];
+        }
+
+        @Override
+        public V setValue(V value) {
+            final V prev = values[ index];
+            values[ index] = value;
+            return prev;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean equals(Object object) {
+            if (!(object instanceof Map.Entry)) {
+                return false;
+            }
+            Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
+            return cmpkey(keys[index], entry.getKey()) && ValueUtils.equals(values[index], entry.getValue());
+        }
+
+        @Override
+        public int hashCode() {
+            return (hashkey((K)keys[ index])) ^ ((values[ index]) == null ? 0 : (values[ index]).hashCode());
+        }
+
+        @Override
+        public String toString() {
+            return keys[ index] + "=>" + values[ index];
+        }
+    }
+
+    /**
+     * An iterator over a hash map.
+     */
+    private class MapIterator {
+
+        /**
+         * The index of the next entry to be returned, if positive or zero. If
+         * negative, the next entry to be returned, if any, is that of index
+         * -pos -2 from the {@link #wrapped} list.
+         */
+        int mpos;
+        /**
+         * The index of the last entry that has been returned. It is -1 if
+         * either we did not return an entry yet, or the last returned entry has
+         * been removed.
+         */
+        int mlast;
+        /**
+         * A downward counter measuring how many mentries must still be returned.
+         */
+        int counter;
+        /**
+         * A lazily allocated list containing the mkeys of elements that have
+ wrapped around the table because of removals; such elements would not
+ be enumerated (other elements would be usually enumerated twice in
+ their place).
+         */
+        ArrayList<K> wrapped;
+
+        public MapIterator() {
+            this.mpos = AHashMap.this.n;
+            this.mlast = -1;
+            this.counter = size;
+            this.wrapped = null;
+
+            boolean used[] = AHashMap.this.used;
+            if (counter != 0) {
+                while (!used[ --mpos]) { } // NOPMD
+            }
+        }
+
+        public boolean hasNext() {
+            return counter != 0;
+        }
+
+        public int nextEntry() {
+            if (!hasNext()) {
+                throw new NoSuchElementException();
+            }
+            counter--;
+            // We are just enumerating elements from the wrapped list.
+            if (mpos < 0) {
+                final K ckey = wrapped.get(-(mlast = --mpos) - 2);
+                // The starting point.
+                int pos = HashUtils.murmurHash3(hashkey(ckey)) & mask;
+                // There's always an unused entry.
+                while (used[ pos]) {
+                    if (cmpkey(keys[ pos], ckey)) {
+                        return pos;
+                    }
+                    pos = (pos + 1) & mask;
+                }
+            }
+            mlast = mpos;
+            //System.err.println( "Count: " + c );
+            if (counter != 0) {
+                final boolean used[] = AHashMap.this.used;
+                while (mpos-- != 0 && !used[ mpos]) { } // NOPMD
+                // When here pos < 0 there are no more elements to be enumerated by scanning, but wrapped might be nonempty.
+            }
+            return mlast;
+        }
+
+        /**
+         * Shifts left mentries with the specified hash code, starting at the
+ specified position, and empties the resulting free entry. If any
+         * entry wraps around the table, instantiates lazily {@link #wrapped}
+ and stores the entry keys.
+         *
+         * @param pos a starting position.
+         * @return the position cleared by the shifting process.
+         */
+        protected final int shiftKeys(int pos) {
+            // Shift mentries with the same hash.
+            int last, slot;
+            for (;;) {
+                pos = ((last = pos) + 1) & mask;
+                while (used[ pos]) {
+                    slot = HashUtils.murmurHash3(hashkey((K)keys[ pos])) & mask;
+                    if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
+                        break;
+                    }
+                    pos = (pos + 1) & mask;
+                }
+                if (!used[ pos]) {
+                    break;
+                }
+                if (pos < last) {
+                    // Wrapped entry.
+                    if (wrapped == null) {
+                        wrapped = new ArrayList<>();
+                    }
+                    wrapped.add(keys[ pos]);
+                }
+                keys[ last] = keys[ pos];
+                values[ last] = values[ pos];
+            }
+            used[ last] = false;
+            keys[ last] = null;
+            values[ last] = null;
+            return last;
+        }
+
+        @SuppressWarnings("unchecked")
+        public void remove() {
+            if (mlast == -1) {
+                throw new IllegalStateException();
+            }
+            if (mpos < -1) {
+                // We're removing wrapped mentries.
+                AHashMap.this.remove(wrapped.set(-mpos - 2, null));
+                mlast = -1;
+                return;
+            }
+            size--;
+            if (shiftKeys(mlast) == mpos && counter > 0) {
+                counter++;
+                nextEntry();
+            }
+            mlast = -1; // You can no longer remove this entry.
+        }
+
+        public int skip(int n) {
+            int i = n;
+            while (i-- != 0 && hasNext()) {
+                nextEntry();
+            }
+            return n - i - 1;
+        }
+    }
+
+    private final class EntryIterator extends MapIterator implements Iterator<Entry<K, V>> {
+
+        private MapEntry entry;
+
+        @Override
+        public Entry<K, V> next() {
+            return entry = new MapEntry(nextEntry());
+        }
+
+        @Override
+        public void remove() {
+            super.remove();
+            entry.index = -1; // You cannot use a deleted entry.
+        }
+    }
+
+    private final class FastEntryIterator extends MapIterator implements Iterator<Entry<K, V>> {
+
+        final BasicEntry<K, V> entry = new BasicEntry<>(null, null);
+
+        @Override
+        public BasicEntry<K, V> next() {
+            final int e = nextEntry();
+            entry.key = keys[ e];
+            entry.value = values[ e];
+            return entry;
+        }
+    }
+
+    private final class MapEntrySet extends ASet<Entry<K, V>> {
+
+        @Override
+        public Iterator<Entry<K, V>> iterator() {
+            return new EntryIterator();
+        }
+
+        public Iterator<Entry<K, V>> fastIterator() {
+            return new FastEntryIterator();
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean contains(Object object) {
+            if (!(object instanceof Map.Entry)) {
+                return false;
+            }
+            final Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
+            final K ckey = entry.getKey();
+            // The starting point.
+            int pos = HashUtils.murmurHash3(hashkey(ckey)) & mask;
+            // There's always an unused entry.
+            while (used[ pos]) {
+                if (cmpkey(keys[ pos], ckey)) {
+                    return (values[ pos] == null ? entry.getValue() == null : values[ pos].equals(entry.getValue()));
+                }
+                pos = (pos + 1) & mask;
+            }
+            return false;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public boolean remove(Object object) {
+            if (!(object instanceof Map.Entry)) {
+                return false;
+            }
+            final Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
+            final K ckey = entry.getKey();
+            // The starting point.
+            int pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
+            // There's always an unused entry.
+            while (used[ pos]) {
+                if (cmpkey(keys[ pos], ckey)) {
+                    AHashMap.this.remove(entry.getKey());
+                    return true;
+                }
+                pos = (pos + 1) & mask;
+            }
+            return false;
+        }
+
+        @Override
+        public int size() {
+            return size;
+        }
+
+        @Override
+        public void clear() {
+            AHashMap.this.clear();
+        }
+    }
+
+    @Override
+    public final Set<Entry<K, V>> entrySet() {
+        if (mentries == null) {
+            mentries = new MapEntrySet();
+        }
+        return mentries;
+    }
+
+    /**
+     * An iterator on mkeys.
+     *
+     * <P>We simply override the
+     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
+ methods (and possibly their type-specific counterparts) so that they
+ return mkeys instead of mentries.
+     */
+    private final class KeyIterator extends MapIterator implements Iterator<K> {
+
+        public KeyIterator() {
+            super();
+        }
+
+        @Override
+        public K next() {
+            return keys[ nextEntry()];
+        }
+    }
+
+    private final class KeySet extends ASet<K> {
+
+        @Override
+        public Iterator<K> iterator() {
+            return new KeyIterator();
+        }
+
+        @Override
+        public int size() {
+            return size;
+        }
+
+        @Override
+        @SuppressWarnings("element-type-mismatch")
+        public boolean contains(Object key) {
+            return containsKey(key);
+        }
+
+        @Override
+        @SuppressWarnings("element-type-mismatch")
+        public boolean remove(Object key) {
+            final int oldSize = size;
+            AHashMap.this.remove(key);
+            return size != oldSize;
+        }
+
+        @Override
+        public void clear() {
+            AHashMap.this.clear();
+        }
+    }
+
+    @Override
+    public final Set<K> keySet() {
+        if (mkeys == null) {
+            mkeys = new KeySet();
+        }
+        return mkeys;
+    }
+
+    /**
+     * An iterator on mvalues.
+     *
+     * <P>We simply override the
+     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
+ methods (and possibly their type-specific counterparts) so that they
+ return mvalues instead of mentries.
+     */
+    private final class ValueIterator extends MapIterator implements Iterator<V> {
+
+        public ValueIterator() {
+            super();
+        }
+
+        @Override
+        public V next() {
+            return values[ nextEntry()];
+        }
+    }
+
+    @Override
+    public final Collection<V> values() {
+        if (mvalues == null) {
+            mvalues = new ACollection<V>() {
+                @Override
+                public Iterator<V> iterator() {
+                    return new ValueIterator();
+                }
+
+                @Override
+                public int size() {
+                    return size;
+                }
+
+                @Override
+                @SuppressWarnings("element-type-mismatch")
+                public boolean contains(Object value) {
+                    return containsValue(value);
+                }
+
+                @Override
+                public void clear() {
+                    AHashMap.this.clear();
+                }
+            };
+        }
+        return mvalues;
+    }
+
+    /**
+     * Rehashes the map, making the table as small as possible.
+     *
+     * <P>This method rehashes the table to the smallest size satisfying the
+     * load factor. It can be used when the set will not be changed anymore, so
+     * to optimize access speed and size.
+     *
+     * <P>If the table size is already the minimum possible, this method does
+     * nothing.
+     *
+     * @return true if there was enough memory to trim the map.
+     * @see #trim(int)
+     */
+    public final boolean trim() {
+        final int l = HashCollection.arraysize(size, factor);
+        if (l >= n) {
+            return true;
+        }
+        try {
+            rehash(l);
+        } catch (OutOfMemoryError cantDoIt) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Rehashes this map if the table is too large.
+     *
+     * <P>Let <var>N</var> be the smallest table size that can hold
+     * <code>max(n,{@link #size()})</code> mentries, still satisfying the load
+ factor. If the current table size is smaller than or equal to
+ <var>N</var>, this method does nothing. Otherwise, it rehashes this map
+     * in a table of size
+     * <var>N</var>.
+     *
+     * <P>This method is useful when reusing maps. null null null     {@linkplain #clear() Clearing a
+	 * map} leaves the table size untouched. If you are reusing a map many
+     * times, you can call this method with a typical size to avoid keeping
+     * around a very large table just because of a few large transient maps.
+     *
+     * @param n the threshold for the trimming.
+     * @return true if there was enough memory to trim the map.
+     * @see #trim()
+     */
+    public final boolean trim(int n) {
+        final int l = MathUtils.pow2next((int) Math.ceil(n / factor));
+        if (this.n <= l) {
+            return true;
+        }
+        try {
+            rehash(l);
+        } catch (OutOfMemoryError cantDoIt) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Resizes the map.
+     *
+     * <P>This method implements the basic rehashing strategy, and may be
+     * overriden by subclasses implementing different rehashing strategies
+     * (e.g., disk-based rehashing). However, you should not override this
+     * method unless you understand the internal workings of this class.
+     *
+     * @param newN the new size
+     */
+    @SuppressWarnings("unchecked")
+    protected final void rehash(int newN) {
+        int i = 0, pos;
+        final boolean iused[] = this.used;
+        K ckey;
+        final K ikeys[] = this.keys;
+        final V ivalues[] = this.values;
+        final int newMask = newN - 1;
+        final K newKey[] = (K[]) new Object[newN];
+        final V newValue[] = (V[]) new Object[newN];
+        final boolean newUsed[] = new boolean[newN];
+        for (int j = size; j-- != 0;) {
+            while (!iused[ i]) {
+                i++;
+            }
+            ckey = ikeys[ i];
+            pos = HashUtils.murmurHash3(hashkey((K)ckey)) & newMask;
+            while (newUsed[ pos]) {
+                pos = (pos + 1) & newMask;
+            }
+            newUsed[ pos] = true;
+            newKey[ pos] = ckey;
+            newValue[ pos] = ivalues[ i];
+            i++;
+        }
+        n = newN;
+        mask = newMask;
+        maxFill = HashCollection.arrayfill(n, factor);
+        this.keys = newKey;
+        this.values = newValue;
+        this.used = newUsed;
+    }
+
+    /**
+     * Returns a hash code for this map.
+     *
+     * This method overrides the generic method provided by the superclass.
+     * Since
+     * <code>equals()</code> is not overriden, it is important that the values
+ returned by this method is the same values as the one returned by the
+ overriden method.
+     *
+     * @return a hash code for this map.
+     */
+    @Override
+    public final int hashCode() {
+        int h = 0;
+        for (int j = size, i = 0, t = 0; j-- != 0;) {
+            while (!used[ i]) {
+                i++;
+            }
+            if (this != keys[ i]) {
+                t = hashkey(keys[i]);
+            }
+            if (this != values[ i]) {
+                t ^= (values[ i] == null ? 0 : values[ i]).hashCode();
+            }
+            h += t;
+            i++;
+        }
+        return h;
+    }
+    
+    @Override
+    public final boolean equals(Object object) {
+        return super.equals(object);
+    }
+
+    private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
+        final K key[] = this.keys;
+        final V value[] = this.values;
+        final MapIterator iterator = new MapIterator();
+        ostream.defaultWriteObject();
+        for (int j = size, e; j-- != 0;) {
+            e = iterator.nextEntry();
+            ostream.writeObject(key[ e]);
+            ostream.writeObject(value[ e]);
+        }
+    }
+
+    @SuppressWarnings({
+        "unchecked",
+        "MismatchedReadAndWriteOfArray"
+    })
+    private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
+        istream.defaultReadObject();
+        n = HashCollection.arraysize(size, factor);
+        maxFill = HashCollection.arrayfill(n, factor);
+        mask = n - 1;
+        final K key[] = this.keys = (K[]) new Object[n];
+        final V value[] = this.values = (V[]) new Object[n];
+        final boolean iused[] = this.used = new boolean[n];
+        K ckey;
+        V cval;
+        for (int i = size, pos; i-- != 0;) {
+            ckey = (K) istream.readObject();
+            cval = (V) istream.readObject();
+            pos = HashUtils.murmurHash3(hashkey((K)ckey)) & mask;
+            while (iused[ pos]) {
+                pos = (pos + 1) & mask;
+            }
+            iused[ pos] = true;
+            key[ pos] = ckey;
+            value[ pos] = cval;
+        }
+    }
+    
+}

+ 14 - 817
assira/src/main/java/net/ranides/assira/collection/maps/CustomMap.java

@@ -1,41 +1,18 @@
 /*
- * @author Paolo Boldi
- * @author Sebastiano Vigna
- * @author Ranides Atterwim <contact@ranides.net>
- *
- * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
  */
-
 package net.ranides.assira.collection.maps;
 
-import net.ranides.assira.collection.ACollection;
-import net.ranides.assira.collection.sets.ASet;
-import net.ranides.assira.collection.arrays.ArrayUtils;
 import net.ranides.assira.collection.utils.HashUtils;
 import net.ranides.assira.collection.HashComparator;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
 import net.ranides.assira.collection.utils.HashCollection;
-import net.ranides.assira.generic.ValueUtils;
-import net.ranides.assira.math.MathUtils;
 
 /**
- * A type-specific hash map with a fast, small-footprint implementation whose
- * {@linkplain it.unimi.dsi.fastutil.Hash.Strategy hashing strategy} is
+ * A generic hash map implementation with {@linkplain HashComparator hashing strategy} 
  * specified at creation time.
  *
  * <P>Instances of this class use a hash table to represent a map. The table is
@@ -43,71 +20,11 @@ import net.ranides.assira.math.MathUtils;
  * is <em>never</em> made smaller (even on a {@link #clear()}). A family of {@linkplain #trim() trimming
  * methods} lets you control the size of the table; this is particularly useful
  * if you reuse instances of this class.
- *
- * <p><strong>Warning:</strong> The implementation of this class has
- * significantly changed in
- * <code>fastutil</code> 6.1.0. Please read the comments about this issue in the
- * section &ldquo;Faster Hash Tables&rdquo; of the <a
- * href="../../../../../overview-summary.html">overview</a>.
- *
- * @see Hash
- * @see HashUtils
- * @todo (assira # 6) review.hash.custom 
  */
-@SuppressWarnings({
-    "PMD.AvoidReassigningParameters",
-    "PMD.OverrideBothEqualsAndHashcode",
-    "EqualsAndHashcode"
-})
-public final class CustomMap<K, V> extends AMap<K, V> implements java.io.Serializable {
+public final class CustomMap<K, V> extends AHashMap<K, V> {
 
     private static final long serialVersionUID = 0L;
-    
-    /**
-     * The array of keys.
-     */
-    protected transient K[] akeys;
-    /**
-     * The array of values.
-     */
-    protected transient V[] avalues;
-    /**
-     * The array telling whether a position is used.
-     */
-    protected transient boolean used[];
-    /**
-     * The acceptable load factor.
-     */
-    protected final float f;
-    /**
-     * The current table size.
-     */
-    protected transient int n;
-    /**
-     * Threshold after which we rehash. It must be the table size times
-     * {@link #f}.
-     */
-    protected transient int maxFill;
-    /**
-     * The mask for wrapping a position counter.
-     */
-    protected transient int mask;
-    /**
-     * Number of entries in the set.
-     */
-    protected int size;
-    /**
-     * Cached set of entries.
-     */
-    protected transient volatile Set<Entry<K, V>> entries;
-    /**
-     * Cached set of keys.
-     */
-    protected transient volatile Set<K> keys;
-    /**
-     * Cached collection of values.
-     */
-    protected transient volatile Collection<V> values;
+
     /**
      * The hash strategy of this custom map.
      */
@@ -121,25 +38,13 @@ public final class CustomMap<K, V> extends AMap<K, V> implements java.io.Seriali
      * <code>f</code>.
      *
      * @param expected the expected number of elements in the hash set.
-     * @param f the load factor.
+     * @param factor the load factor.
      * @param strategy the strategy.
      */
     @SuppressWarnings("unchecked")
-    public CustomMap(int expected, float f, HashComparator<K> strategy) {
+    public CustomMap(int expected, float factor, HashComparator<K> strategy) {
+        super(expected, factor);
         this.strategy = strategy;
-        if (f <= 0 || f > 1) {
-            throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1");
-        }
-        if (expected < 0) {
-            throw new IllegalArgumentException("The expected number of elements must be nonnegative");
-        }
-        this.f = f;
-        n = HashCollection.arraysize(expected, f);
-        mask = n - 1;
-        maxFill = HashCollection.arrayfill(n, f);
-        akeys = (K[]) new Object[n];
-        avalues = (V[]) new Object[n];
-        used = new boolean[n];
     }
 
     /**
@@ -229,723 +134,15 @@ public final class CustomMap<K, V> extends AMap<K, V> implements java.io.Seriali
     public HashComparator<K> strategy() {
         return strategy;
     }
-    /*
-     * The following methods implements some basic building blocks used by
-     * all accessors. They are (and should be maintained) identical to those used in OpenHashSet.drv.
-     */
-
-    @Override
-    public V put(K key, V value) {
-        // The starting point.
-        int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (key)))) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], key)) {
-                final V oldValue = avalues[ pos];
-                avalues[ pos] = value;
-                return oldValue;
-            }
-            pos = (pos + 1) & mask;
-        }
-        used[ pos] = true;
-        akeys[ pos] = key;
-        avalues[ pos] = value;
-        if (++size >= maxFill) {
-            rehash(HashCollection.arraysize(size + 1, f));
-        }
-        return defRetValue;
-    }
-
-    /**
-     * Shifts left entries with the specified hash code, starting at the
-     * specified position, and empties the resulting free entry.
-     *
-     * @param pos a starting position.
-     * @return the position cleared by the shifting process.
-     */
-    protected int shiftKeys(int pos) {
-        // Shift entries with the same hash.
-        int last, slot;
-        for (;;) {
-            pos = ((last = pos) + 1) & mask;
-            while (used[ pos]) {
-                slot = (HashUtils.murmurHash3(strategy.hashCode((K) (akeys[ pos])))) & mask;
-                if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
-                    break;
-                }
-                pos = (pos + 1) & mask;
-            }
-            if (!used[ pos]) {
-                break;
-            }
-            akeys[ last] = akeys[ pos];
-            avalues[ last] = avalues[ pos];
-        }
-        used[ last] = false;
-        akeys[ last] = null;
-        avalues[ last] = null;
-        return last;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public V remove(Object key) {
-        // The starting point.
-        int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (key)))) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], (K)key)) {
-                size--;
-                final V prev = avalues[ pos];
-                shiftKeys(pos);
-                return prev;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return defRetValue;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public V get(Object key) {
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[pos], (K)key)) {
-                return avalues[ pos];
-            }
-            pos = (pos + 1) & mask;
-        }
-        return defRetValue;
-    }
-
-    @SuppressWarnings("unchecked")
-    @Override
-    public boolean containsKey(Object key) {
-        // The starting point.
-        int pos = HashUtils.murmurHash3(strategy.hashCode((K)key)) & mask;
-        // There's always an unused entry.
-        while (used[ pos]) {
-            if (strategy.equals(akeys[ pos], (K)key)) {
-                return true;
-            }
-            pos = (pos + 1) & mask;
-        }
-        return false;
-    }
-
-    @Override
-    public boolean containsValue(Object value) {
-        final V ivalues[] = this.avalues;
-        final boolean iused[] = this.used;
-        for (int i = n; i-- != 0;) {
-            if (iused[i] && (ivalues[i] == null ? value == null : ivalues[i].equals(value))) {
-                return true;
-            }
-        }
-        return false;
-    }
-    /* Removes all elements from this map.
-     *
-     * <P>To increase object reuse, this method does not change the table size.
-     * If you want to reduce the table size, you must use {@link #trim()}.
-     *
-     */
-
-    @Override
-    public void clear() {
-        if (size == 0) {
-            return;
-        }
-        size = 0;
-        ArrayUtils.fill(used, false);
-        // We null all object entries so that the garbage collector can do its work.
-        ArrayUtils.fill(akeys, null);
-        ArrayUtils.fill(avalues, null);
-    }
-
-    @Override
-    public int size() {
-        return size;
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return size == 0;
-    }
-
-    /**
-     * The entry class for a hash map does not record akeys and avalues, but rather
- the position in the hash table of the corresponding entry. This is
-     * necessary so that calls to {@link java.util.Map.Entry#setValue(Object)}
-     * are reflected in the map
-     */
-    private final class MapEntry implements Map.Entry<K, V> {
-        // The table index this entry refers to, or -1 if this entry has been deleted.
-
-        int index;
-
-        MapEntry(int index) {
-            this.index = index;
-        }
-
-        @Override
-        public K getKey() {
-            return akeys[ index];
-        }
-
-        @Override
-        public V getValue() {
-            return avalues[ index];
-        }
-
-        @Override
-        public V setValue(V value) {
-            final V prev = avalues[ index];
-            avalues[ index] = value;
-            return prev;
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean equals(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
-            return strategy.equals(akeys[index], entry.getKey()) && ValueUtils.equals(avalues[index], entry.getValue());
-        }
-
-        @Override
-        public int hashCode() {
-            return (strategy.hashCode((K) (akeys[ index]))) ^ ((avalues[ index]) == null ? 0 : (avalues[ index]).hashCode());
-        }
-
-        @Override
-        public String toString() {
-            return akeys[ index] + "=>" + avalues[ index];
-        }
-    }
-
-    /**
-     * An iterator over a hash map.
-     */
-    private class MapIterator {
-
-        /**
-         * The index of the next entry to be returned, if positive or zero. If
-         * negative, the next entry to be returned, if any, is that of index
-         * -pos -2 from the {@link #wrapped} list.
-         */
-        int mpos;
-        /**
-         * The index of the last entry that has been returned. It is -1 if
-         * either we did not return an entry yet, or the last returned entry has
-         * been removed.
-         */
-        int mlast;
-        /**
-         * A downward counter measuring how many entries must still be returned.
-         */
-        int counter;
-        /**
-         * A lazily allocated list containing the keys of elements that have
-         * wrapped around the table because of removals; such elements would not
-         * be enumerated (other elements would be usually enumerated twice in
-         * their place).
-         */
-        ArrayList<K> wrapped;
-
-        public MapIterator() {
-            this.mpos = CustomMap.this.n;
-            this.mlast = -1;
-            this.counter = size;
-            this.wrapped = null;
-
-            boolean used[] = CustomMap.this.used;
-            if (counter != 0) {
-                while (!used[ --mpos]) { } // NOPMD
-            }
-        }
-
-        public boolean hasNext() {
-            return counter != 0;
-        }
-
-        public int nextEntry() {
-            if (!hasNext()) {
-                throw new NoSuchElementException();
-            }
-            counter--;
-            // We are just enumerating elements from the wrapped list.
-            if (mpos < 0) {
-                final K ckey = wrapped.get(-(mlast = --mpos) - 2);
-                // The starting point.
-                int pos = HashUtils.murmurHash3(strategy.hashCode(ckey)) & mask;
-                // There's always an unused entry.
-                while (used[ pos]) {
-                    if (strategy.equals(akeys[ pos], ckey)) {
-                        return pos;
-                    }
-                    pos = (pos + 1) & mask;
-                }
-            }
-            mlast = mpos;
-            //System.err.println( "Count: " + c );
-            if (counter != 0) {
-                final boolean used[] = CustomMap.this.used;
-                while (mpos-- != 0 && !used[ mpos]) { } // NOPMD
-                // When here pos < 0 there are no more elements to be enumerated by scanning, but wrapped might be nonempty.
-            }
-            return mlast;
-        }
-
-        /**
-         * Shifts left entries with the specified hash code, starting at the
-         * specified position, and empties the resulting free entry. If any
-         * entry wraps around the table, instantiates lazily {@link #wrapped}
- and stores the entry akeys.
-         *
-         * @param pos a starting position.
-         * @return the position cleared by the shifting process.
-         */
-        protected final int shiftKeys(int pos) {
-            // Shift entries with the same hash.
-            int last, slot;
-            for (;;) {
-                pos = ((last = pos) + 1) & mask;
-                while (used[ pos]) {
-                    slot = (HashUtils.murmurHash3(strategy.hashCode((K) (akeys[ pos])))) & mask;
-                    if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) {
-                        break;
-                    }
-                    pos = (pos + 1) & mask;
-                }
-                if (!used[ pos]) {
-                    break;
-                }
-                if (pos < last) {
-                    // Wrapped entry.
-                    if (wrapped == null) {
-                        wrapped = new ArrayList<>();
-                    }
-                    wrapped.add(akeys[ pos]);
-                }
-                akeys[ last] = akeys[ pos];
-                avalues[ last] = avalues[ pos];
-            }
-            used[ last] = false;
-            akeys[ last] = null;
-            avalues[ last] = null;
-            return last;
-        }
-
-        @SuppressWarnings("unchecked")
-        public void remove() {
-            if (mlast == -1) {
-                throw new IllegalStateException();
-            }
-            if (mpos < -1) {
-                // We're removing wrapped entries.
-                CustomMap.this.remove(wrapped.set(-mpos - 2, null));
-                mlast = -1;
-                return;
-            }
-            size--;
-            if (shiftKeys(mlast) == mpos && counter > 0) {
-                counter++;
-                nextEntry();
-            }
-            mlast = -1; // You can no longer remove this entry.
-        }
-
-        public int skip(int n) {
-            int i = n;
-            while (i-- != 0 && hasNext()) {
-                nextEntry();
-            }
-            return n - i - 1;
-        }
-    }
-
-    private class EntryIterator extends MapIterator implements Iterator<Entry<K, V>> {
-
-        private MapEntry entry;
-
-        @Override
-        public Entry<K, V> next() {
-            return entry = new MapEntry(nextEntry());
-        }
-
-        @Override
-        public void remove() {
-            super.remove();
-            entry.index = -1; // You cannot use a deleted entry.
-        }
-    }
-
-    private class FastEntryIterator extends MapIterator implements Iterator<Entry<K, V>> {
-
-        final BasicEntry<K, V> entry = new BasicEntry<>(null, null);
-
-        @Override
-        public BasicEntry<K, V> next() {
-            final int e = nextEntry();
-            entry.key = akeys[ e];
-            entry.value = avalues[ e];
-            return entry;
-        }
-    }
-
-    private final class MapEntrySet extends ASet<Entry<K, V>> {
-
-        @Override
-        public Iterator<Entry<K, V>> iterator() {
-            return new EntryIterator();
-        }
-
-        public Iterator<Entry<K, V>> fastIterator() {
-            return new FastEntryIterator();
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean contains(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            final Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
-            final K ckey = entry.getKey();
-            // The starting point.
-            int pos = HashUtils.murmurHash3(strategy.hashCode(ckey)) & mask;
-            // There's always an unused entry.
-            while (used[ pos]) {
-                if (strategy.equals(akeys[ pos], ckey)) {
-                    return (avalues[ pos] == null ? entry.getValue() == null : avalues[ pos].equals(entry.getValue()));
-                }
-                pos = (pos + 1) & mask;
-            }
-            return false;
-        }
-
-        @SuppressWarnings("unchecked")
-        @Override
-        public boolean remove(Object object) {
-            if (!(object instanceof Map.Entry)) {
-                return false;
-            }
-            final Map.Entry<K, V> entry = (Map.Entry<K, V>) object;
-            final K ckey = entry.getKey();
-            // The starting point.
-            int pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
-            // There's always an unused entry.
-            while (used[ pos]) {
-                if (strategy.equals(akeys[ pos], ckey)) {
-                    CustomMap.this.remove(entry.getKey());
-                    return true;
-                }
-                pos = (pos + 1) & mask;
-            }
-            return false;
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        public void clear() {
-            CustomMap.this.clear();
-        }
-    }
 
     @Override
-    public Set<Entry<K, V>> entrySet() {
-        if (entries == null) {
-            entries = new MapEntrySet();
-        }
-        return entries;
-    }
-
-    /**
-     * An iterator on keys.
-     *
-     * <P>We simply override the
-     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
-     * methods (and possibly their type-specific counterparts) so that they
-     * return keys instead of entries.
-     */
-    private final class KeyIterator extends MapIterator implements Iterator<K> {
-
-        public KeyIterator() {
-            super();
-        }
-
-        @Override
-        public K next() {
-            return akeys[ nextEntry()];
-        }
-    }
-
-    private final class KeySet extends ASet<K> {
-
-        @Override
-        public Iterator<K> iterator() {
-            return new KeyIterator();
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        @SuppressWarnings("element-type-mismatch")
-        public boolean contains(Object key) {
-            return containsKey(key);
-        }
-
-        @Override
-        @SuppressWarnings("element-type-mismatch")
-        public boolean remove(Object key) {
-            final int oldSize = size;
-            CustomMap.this.remove(key);
-            return size != oldSize;
-        }
-
-        @Override
-        public void clear() {
-            CustomMap.this.clear();
-        }
+    protected int hashkey(K key) {
+        return HashUtils.murmurHash3(strategy.hashCode(key));
     }
 
     @Override
-    public Set<K> keySet() {
-        if (keys == null) {
-            keys = new KeySet();
-        }
-        return keys;
-    }
-
-    /**
-     * An iterator on values.
-     *
-     * <P>We simply override the
-     * {@link java.util.ListIterator#next()}/{@link java.util.ListIterator#previous()}
-     * methods (and possibly their type-specific counterparts) so that they
-     * return values instead of entries.
-     */
-    private final class ValueIterator extends MapIterator implements Iterator<V> {
-
-        public ValueIterator() {
-            super();
-        }
-
-        @Override
-        public V next() {
-            return avalues[ nextEntry()];
-        }
-    }
-
-    @Override
-    public Collection<V> values() {
-        if (values == null) {
-            values = new ACollection<V>() {
-                @Override
-                public Iterator<V> iterator() {
-                    return new ValueIterator();
-                }
-
-                @Override
-                public int size() {
-                    return size;
-                }
-
-                @Override
-                @SuppressWarnings("element-type-mismatch")
-                public boolean contains(Object value) {
-                    return containsValue(value);
-                }
-
-                @Override
-                public void clear() {
-                    CustomMap.this.clear();
-                }
-            };
-        }
-        return values;
-    }
-
-    /**
-     * Rehashes the map, making the table as small as possible.
-     *
-     * <P>This method rehashes the table to the smallest size satisfying the
-     * load factor. It can be used when the set will not be changed anymore, so
-     * to optimize access speed and size.
-     *
-     * <P>If the table size is already the minimum possible, this method does
-     * nothing.
-     *
-     * @return true if there was enough memory to trim the map.
-     * @see #trim(int)
-     */
-    public boolean trim() {
-        final int l = HashCollection.arraysize(size, f);
-        if (l >= n) {
-            return true;
-        }
-        try {
-            rehash(l);
-        } catch (OutOfMemoryError cantDoIt) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Rehashes this map if the table is too large.
-     *
-     * <P>Let <var>N</var> be the smallest table size that can hold
-     * <code>max(n,{@link #size()})</code> entries, still satisfying the load
-     * factor. If the current table size is smaller than or equal to
-     * <var>N</var>, this method does nothing. Otherwise, it rehashes this map
-     * in a table of size
-     * <var>N</var>.
-     *
-     * <P>This method is useful when reusing maps. null null null     {@linkplain #clear() Clearing a
-	 * map} leaves the table size untouched. If you are reusing a map many
-     * times, you can call this method with a typical size to avoid keeping
-     * around a very large table just because of a few large transient maps.
-     *
-     * @param n the threshold for the trimming.
-     * @return true if there was enough memory to trim the map.
-     * @see #trim()
-     */
-    public boolean trim(int n) {
-        final int l = MathUtils.pow2next((int) Math.ceil(n / f));
-        if (this.n <= l) {
-            return true;
-        }
-        try {
-            rehash(l);
-        } catch (OutOfMemoryError cantDoIt) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Resizes the map.
-     *
-     * <P>This method implements the basic rehashing strategy, and may be
-     * overriden by subclasses implementing different rehashing strategies
-     * (e.g., disk-based rehashing). However, you should not override this
-     * method unless you understand the internal workings of this class.
-     *
-     * @param newN the new size
-     */
-    @SuppressWarnings("unchecked")
-    protected void rehash(int newN) {
-        int i = 0, pos;
-        final boolean iused[] = this.used;
-        K ckey;
-        final K ikeys[] = this.akeys;
-        final V ivalues[] = this.avalues;
-        final int newMask = newN - 1;
-        final K newKey[] = (K[]) new Object[newN];
-        final V newValue[] = (V[]) new Object[newN];
-        final boolean newUsed[] = new boolean[newN];
-        for (int j = size; j-- != 0;) {
-            while (!iused[ i]) {
-                i++;
-            }
-            ckey = ikeys[ i];
-            pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & newMask;
-            while (newUsed[ pos]) {
-                pos = (pos + 1) & newMask;
-            }
-            newUsed[ pos] = true;
-            newKey[ pos] = ckey;
-            newValue[ pos] = ivalues[ i];
-            i++;
-        }
-        n = newN;
-        mask = newMask;
-        maxFill = HashCollection.arrayfill(n, f);
-        this.akeys = newKey;
-        this.avalues = newValue;
-        this.used = newUsed;
-    }
-
-    /**
-     * Returns a hash code for this map.
-     *
-     * This method overrides the generic method provided by the superclass.
-     * Since
-     * <code>equals()</code> is not overriden, it is important that the avalues
- returned by this method is the same avalues as the one returned by the
- overriden method.
-     *
-     * @return a hash code for this map.
-     */
-    @Override
-    public int hashCode() {
-        int h = 0;
-        for (int j = size, i = 0, t = 0; j-- != 0;) {
-            while (!used[ i]) {
-                i++;
-            }
-            if (this != akeys[ i]) {
-                t = strategy.hashCode(akeys[i]);
-            }
-            if (this != avalues[ i]) {
-                t ^= (avalues[ i] == null ? 0 : avalues[ i]).hashCode();
-            }
-            h += t;
-            i++;
-        }
-        return h;
-    }
-
-    private void writeObject(java.io.ObjectOutputStream ostream) throws java.io.IOException {
-        final K key[] = this.akeys;
-        final V value[] = this.avalues;
-        final MapIterator iterator = new MapIterator();
-        ostream.defaultWriteObject();
-        for (int j = size, e; j-- != 0;) {
-            e = iterator.nextEntry();
-            ostream.writeObject(key[ e]);
-            ostream.writeObject(value[ e]);
-        }
-    }
-
-    @SuppressWarnings({
-        "unchecked",
-        "MismatchedReadAndWriteOfArray"
-    })
-    private void readObject(java.io.ObjectInputStream istream) throws java.io.IOException, ClassNotFoundException {
-        istream.defaultReadObject();
-        n = HashCollection.arraysize(size, f);
-        maxFill = HashCollection.arrayfill(n, f);
-        mask = n - 1;
-        final K key[] = this.akeys = (K[]) new Object[n];
-        final V value[] = this.avalues = (V[]) new Object[n];
-        final boolean iused[] = this.used = new boolean[n];
-        K ckey;
-        V cval;
-        for (int i = size, pos; i-- != 0;) {
-            ckey = (K) istream.readObject();
-            cval = (V) istream.readObject();
-            pos = (HashUtils.murmurHash3(strategy.hashCode((K) (ckey)))) & mask;
-            while (iused[ pos]) {
-                pos = (pos + 1) & mask;
-            }
-            iused[ pos] = true;
-            key[ pos] = ckey;
-            value[ pos] = cval;
-        }
+    protected boolean cmpkey(K key1, K key2) {
+        return strategy.equals(key1, key2);
     }
 
 }

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 125 - 922
assira/src/main/java/net/ranides/assira/collection/maps/HashMap.java


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 156 - 1078
assira/src/main/java/net/ranides/assira/collection/maps/IdentMap.java


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 634 - 638
assira/src/main/java/net/ranides/assira/collection/sets/HashSet.java