All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jboss.jandex.StrongInternPool Maven / Gradle / Ivy

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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.
 */

package org.jboss.jandex;

import java.io.IOException;
import java.io.Serializable;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * A strong intern pool. The pool acts as a set where the first stored entry can be retrieved.
 * This can be used to conserve memory by eliminating duplicate objects (those that are equal
 * but have different identity). It however holds strong references to every item in the pool,
 * so it must be cleared to allow for GC.
 *
 * Note: It is very important to use a smaller load factor than you normally
 * would for HashSet, since the implementation is open-addressed with linear
 * probing. With a 50% load-factor a get is expected to return in only 2 probes.
 * However, a 90% load-factor is expected to return in around 50 probes.
 *
 * @author Jason T. Greene
 */
class StrongInternPool implements Cloneable, Serializable {
    /**
     * Marks null keys.
     */
    private static final Object NULL = new Object();

    /**
     * Serialization ID
     */
    private static final long serialVersionUID = 10929568968762L;

    /**
     * Same default as HashMap, must be a power of 2
     */
    private static final int DEFAULT_CAPACITY = 8;

    /**
     * MAX_INT - 1
     */
    private static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * 67%, just like IdentityHashMap
     */
    private static final float DEFAULT_LOAD_FACTOR = 0.67f;

    /**
     * The open-addressed table
     */
    private transient Object[] table;

    /**
     * The current number of key-value pairs
     */
    private transient int size;

    /**
     * The next resize
     */
    private transient int threshold;

    /**
     * The user defined load factor which defines when to resize
     */
    private final float loadFactor;

    /**
     * Counter used to detect changes made outside of an iterator
     */
    private transient int modCount;

    /**
     * Cache for an index
     */
    private transient Index index;

    public StrongInternPool(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Can not have a negative size table!");

        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;

        if (!(loadFactor > 0F && loadFactor <= 1F))
            throw new IllegalArgumentException("Load factor must be greater than 0 and less than or equal to 1");

        this.loadFactor = loadFactor;
        init(initialCapacity, loadFactor);
    }


    private void init(int initialCapacity, float loadFactor) {
        int c = 1;
        for (; c < initialCapacity; c <<= 1);
        threshold = (int) (c * loadFactor);

        // Include the load factor when sizing the table for the first time
        if (initialCapacity > threshold && c < MAXIMUM_CAPACITY) {
            c <<= 1;
            threshold = (int) (c * loadFactor);
        }

        this.table = new Object[c];
    }

    private static boolean eq(Object o1, Object o2) {
        if (o1 == o2) {
            return true;
        }

        if (o1 instanceof Object[] && o2 instanceof Object[]) {
            return Arrays.equals((Object[])o1, (Object[])o2);
        }

        if (o1 instanceof byte[] && o2 instanceof byte[]) {
            return Arrays.equals((byte[])o1, (byte[])o2);
        }

        return o1 != null && o1.equals(o2);
    }

    public StrongInternPool(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    public StrongInternPool() {
        this(DEFAULT_CAPACITY);
    }

    // The normal bit spreader...
    private static int hash(Object o) {
        int h = o instanceof Object[] ? Arrays.hashCode((Object[])o) : o instanceof byte[] ? Arrays.hashCode((byte[])o) : o.hashCode();
        return ((h << 1 ) - (h << 8));
    }

    @SuppressWarnings("unchecked")
    private static final  K maskNull(K key) {
        return key == null ? (K) NULL : key;
    }

    private static final  K unmaskNull(K key) {
        return key == NULL ? null : key;
    }

    private int nextIndex(int index, int length) {
        index = (index >= length - 1) ? 0 : index + 1;
        return index;
    }

    private static final int index(int hashCode, int length) {
        return hashCode & (length - 1);
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean contains(Object entry) {
        entry = maskNull(entry);

        int hash = hash(entry);
        int length = table.length;
        int index = index(hash, length);

        for (int start = index;;) {
            Object e = table[index];
            if (e == null)
                return false;

            if (eq(entry,e))
                return true;

            index = nextIndex(index, length);
            if (index == start) // Full table
                return false;
        }
    }

    private int offset(Object entry) {
        entry = maskNull(entry);

        int hash = hash(entry);
        int length = table.length;
        int index = index(hash, length);

        for (int start = index;;) {
            Object e = table[index];
            if (e == null)
                return -1;

            if (eq(entry,e))
                return index;

            index = nextIndex(index, length);
            if (index == start) // Full table
                return -1;
        }
    }

    /***
     * Internalizes the specified object by always returning the first ever stored.
     * Equivalent objects (via .equals) but with different identity (aka duplicates)
     * can be eliminated with this method.
     *
     * @param entry the object to internalize
     * @return the one true unique (and equivalent) object
     */
    @SuppressWarnings("unchecked")
    public E intern(E entry) {
        entry = maskNull(entry);

        Object[] table = this.table;
        int hash = hash(entry);
        int length = table.length;
        int index = index(hash, length);

        for (int start = index;;) {
            Object e = table[index];
            if (e == null)
                break;

            if (eq(entry, e))
                return (E)unmaskNull(e);

            index = nextIndex(index, length);
            if (index == start)
                throw new IllegalStateException("Table is full!");
        }

        modCount++;
        table[index] = entry;
        if (++size >= threshold)
            resize(length);

        return unmaskNull(entry);
    }

    private void resize(int from) {
        int newLength = from << 1;

        // Can't get any bigger
        if (newLength > MAXIMUM_CAPACITY || newLength <= from)
            return;

        Object[] newTable = new Object[newLength];
        Object[] old = table;

        for (Object e : old) {
            if (e == null)
                continue;

            int index = index(hash(e), newLength);
            while (newTable[index] != null)
                index = nextIndex(index, newLength);

            newTable[index] = e;
        }

        threshold = (int) (loadFactor * newLength);
        table = newTable;
    }

    public boolean remove(Object o) {
        o = maskNull(o);

        Object[] table = this.table;
        int length = table.length;
        int hash = hash(o);
        int start = index(hash, length);

        for (int index = start;;) {
            Object e = table[index];
            if (e == null)
                return false;

            if (eq(e, o)) {
                table[index] = null;
                relocate(index);
                modCount++;
                size--;
                return true;
            }

            index = nextIndex(index, length);
            if (index == start)
                return false;
        }
    }

    private void relocate(int start) {
        Object[] table = this.table;
        int length = table.length;
        int current = nextIndex(start, length);

        for (;;) {
            Object e = table[current];
            if (e == null)
                return;

            // A Doug Lea variant of Knuth's Section 6.4 Algorithm R.
            // This provides a non-recursive method of relocating
            // entries to their optimal positions once a gap is created.
            int prefer = index(hash(e), length);
            if ((current < prefer && (prefer <= start || start <= current)) || (prefer <= start && start <= current)) {
                table[start] = e;
                table[current] = null;
                start = current;
            }

            current = nextIndex(current, length);
        }
    }

    public void clear() {
        modCount++;
        Object[] table = this.table;
        for (int i = 0; i < table.length; i++)
            table[i] = null;

        size = 0;
    }

    @SuppressWarnings("unchecked")
    public StrongInternPool clone() {
        try {
            StrongInternPool clone = (StrongInternPool) super.clone();
            clone.table = table.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            // should never happen
            throw new IllegalStateException(e);
        }
    }

    /**
     * Advanced method that returns the internal table. The resulting
     * array will contain nulls at random places that must be skipped. In
     * addition, it will not operate correctly if a null was inserted into the
     * set. Use at your own risk....
     *
     * @return an array containing elements in this set along with randomly
     *         placed nulls,
     */
    public Object[] toInternalArray() {
        return table;
    }

    public void printDebugStats() {
        int optimal = 0;
        int total = 0;
        int totalSkew = 0;
        int maxSkew = 0;
        for (int i = 0; i < table.length; i++) {
            Object e = table[i];
            if (e != null) {

                total++;
                int target = index(hash(e), table.length);
                if (i == target)
                    optimal++;
                else {
                    int skew = Math.abs(i - target);
                    if (skew > maxSkew)
                        maxSkew = skew;
                    totalSkew += skew;
                }

            }
        }

        System.out.println(" Size:            " + size);
        System.out.println(" Real Size:       " + total);
        System.out.println(" Optimal:         " + optimal + " (" + (float) optimal * 100 / total + "%)");
        System.out.println(" Average Distnce: " + ((float) totalSkew / (total - optimal)));
        System.out.println(" Max Distance:    " + maxSkew);
    }

    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();

        int size = s.readInt();

        init(size, loadFactor);

        for (int i = 0; i < size; i++) {
            putForCreate((E) s.readObject());
        }

        this.size = size;
    }

    private void putForCreate(E entry) {
        entry = maskNull(entry);

        Object[] table = this.table;
        int hash = hash(entry);
        int length = table.length;
        int index = index(hash, length);

        Object e = table[index];
        while (e != null) {
            index = nextIndex(index, length);
            e = table[index];
        }

        table[index] = entry;
    }

    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeInt(size);

        for (Object e : table) {
            if (e != null) {
                s.writeObject(unmaskNull(e));
            }
        }
    }

    public Iterator iterator() {
        return new IdentityHashSetIterator();
    }

    public Index index() {
        if (index == null || index.modCount != modCount) {
            index = new Index();
        }

        return index;
    }

    public String toString() {
        Iterator i = iterator();
        if (! i.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = i.next();
            sb.append(e);
            if (! i.hasNext())
                return sb.append(']').toString();
            sb.append(", ");
        }
    }

    public class Index {
        private int[] offsets;
        private int modCount;

        Index() {
            offsets = new int[table.length];
            for (int i = 0, c = 1; i < offsets.length; i++) {
                if (table[i] != null)
                    offsets[i] = c++;
            }
            modCount = StrongInternPool.this.modCount;
        }

        public int positionOf(E e)
        {
            int offset = offset(e);
            return offset < 0 ? -1 : offsets[offset];
        }
    }

    private class IdentityHashSetIterator implements Iterator {
        private int next = 0;
        private int expectedCount = modCount;
        private int current = -1;
        private boolean hasNext;
        Object table[] = StrongInternPool.this.table;

        public boolean hasNext() {
            if (hasNext == true)
                return true;

            Object table[] = this.table;
            for (int i = next; i < table.length; i++) {
                if (table[i] != null) {
                    next = i;
                    return hasNext = true;
                }
            }

            next = table.length;
            return false;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (modCount != expectedCount)
                throw new ConcurrentModificationException();

            if (!hasNext && !hasNext())
                throw new NoSuchElementException();

            current = next++;
            hasNext = false;

            return (E) unmaskNull(table[current]);
        }

        public void remove() {
            if (modCount != expectedCount)
                throw new ConcurrentModificationException();

            int current = this.current;
            int delete = current;

            if (current == -1)
                throw new IllegalStateException();

            // Invalidate current (prevents multiple remove)
            this.current = -1;

            // Start were we relocate
            next = delete;

            Object[] table = this.table;
            if (table != StrongInternPool.this.table) {
                StrongInternPool.this.remove(table[delete]);
                table[delete] = null;
                expectedCount = modCount;
                return;
            }

            int length = table.length;
            int i = delete;

            table[delete] = null;
            size--;

            for (;;) {
                i = nextIndex(i, length);
                Object e = table[i];
                if (e == null)
                    break;

                int prefer = index(hash(e), length);
                if ((i < prefer && (prefer <= delete || delete <= i)) || (prefer <= delete && delete <= i)) {
                    // Snapshot the unseen portion of the table if we have
                    // to relocate an entry that was already seen by this
                    // iterator
                    if (i < current && current <= delete && table == StrongInternPool.this.table) {
                        int remaining = length - current;
                        Object[] newTable = new Object[remaining];
                        System.arraycopy(table, current, newTable, 0, remaining);

                        // Replace iterator's table.
                        // Leave table local var pointing to the real table
                        this.table = newTable;
                        next = 0;
                    }

                    // Do the swap on the real table
                    table[delete] = e;
                    table[i] = null;
                    delete = i;
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy