rocks.inspectit.shared.all.instrumentation.classcache.util.SortedArraySet.java Source code

Java tutorial

Introduction

Here is the source code for rocks.inspectit.shared.all.instrumentation.classcache.util.SortedArraySet.java

Source

package rocks.inspectit.shared.all.instrumentation.classcache.util;

import java.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.Comparator;
import java.util.Iterator;

import org.apache.commons.collections.iterators.ObjectArrayIterator;

/**
 * Non-thread safe set that is backed with the sorted array.
 * <p>
 * The set must be initialized with the {@link Comparator} that will define sorting order of the
 * elements in the array. In addition, this comparator must only return <code>0</code> as a result
 * from a compare operation if elements compared are equal in set terms. Meaning that if comparator
 * returns zero <code>0</code> as a result from a compare for two different elements, only one can
 * be placed in the set.
 * <p>
 * This set is performing better in both terms of memory and speed over the similar implementations
 * (especially for small amount of elements). The adding/removing operations do require an array
 * copy operation. Note that comparator should be as fast as possible, since it's directly
 * influencing the speed of adding/removing the elements.
 * <p>
 * Regarding a memory, this class has a fixed cost of 16 bytes + the array holding the references to
 * the elements (that's 12 bytes + 4 bytes for each reference). Thus, there is a very small cost for
 * the maintaining the set.
 *
 * @author Ivan Senic
 *
 * @param <E>
 *            Type of the elements in set.
 */
public class SortedArraySet<E> extends AbstractCollection<E> implements UpdateableSet<E> {

    /**
     * Mask for element already exists.
     */
    private static final int ELEMENT_EXISTS_MASK = 1;

    /**
     * Empty array to denote we have nothing in the set.
     */
    private static final Object[] EMPTY_ARRAY = new Object[0];

    /**
     * The array holding the elements in the set.
     */
    @SuppressWarnings("unchecked")
    private E[] array = (E[]) EMPTY_ARRAY;

    /**
     * Comparator used in sorting the elements in the backing array. This comparator must only
     * return <code>0</code> as a result from a compare operation if elements compared are equal in
     * set terms. Meaning that if comparator returns zero <code>0</code> as a result from a compare
     * for two different elements, only one can be placed in the set.
     */
    private final Comparator<E> comparator;

    /**
     * Default constructor.
     *
     * @param comparator
     *            Comparator used in sorting the elements in the backing array. This comparator must
     *            only return <code>0</code> as a result from a compare operation if elements
     *            compared are equal in set terms. Meaning that if comparator returns zero
     *            <code>0</code> as a result from a compare for two different elements, only one can
     *            be placed in the set.
     */
    public SortedArraySet(Comparator<E> comparator) {
        if (null == comparator) {
            throw new IllegalArgumentException("Comparator for the SortedArraySet must not be null.");
        }

        this.comparator = comparator;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean add(E e) {
        if (null == e) {
            throw new NullPointerException("Null element"); // NOPMD
        }

        E[] update;

        long l = findInsertDataFor(array, e);

        if (ELEMENT_EXISTS_MASK == getUpperInt(l)) {
            return false;
        }

        int i = getLowerInt(l);

        int length = array.length;
        update = (E[]) new Object[length + 1];

        System.arraycopy(array, 0, update, 0, i);
        System.arraycopy(array, i, update, i + 1, length - i);
        update[i] = e;

        array = update;

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public void addOrUpdate(E e) {
        if (null == e) {
            throw new NullPointerException("Null element"); // NOPMD
        }

        E[] update;

        long l = findInsertDataFor(array, e);
        int i = getLowerInt(l);

        int length = array.length;

        if (ELEMENT_EXISTS_MASK == getUpperInt(l)) {
            if (e == array[i]) {
                // this is the same object no need to update
                return;
            }

            // replacing the element just
            array[i] = e;
        } else {
            // we add new element
            update = (E[]) new Object[length + 1];

            System.arraycopy(array, 0, update, 0, i);
            System.arraycopy(array, i, update, i + 1, length - i);

            update[i] = e;
            array = update;
        }
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean remove(Object o) {
        if (null == o) {
            throw new NullPointerException("Null element"); // NOPMD
        }

        E[] update;

        int i = find(array, (E) o);

        if (i < 0) {
            return false;
        }

        int length = array.length;
        update = (E[]) new Object[length - 1];

        System.arraycopy(array, 0, update, 0, i);
        System.arraycopy(array, i + 1, update, i, length - i - 1);

        array = update;

        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        return array.length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty() {
        return array.length == 0;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean contains(Object o) {
        return find(array, (E) o) > -1;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public Iterator<E> iterator() {
        return new ArraySetIterator(array);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object[] toArray() {
        return arrayCopyOf(array, array.length, array.getClass());
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    @Override
    public void clear() {
        array = (E[]) EMPTY_ARRAY;
    }

    /**
     * Finds index for an element in the array. If the element does not exists by comparator it will
     * return negative value.
     *
     * @param array
     *            Array to find element in.
     * @param element
     *            Element to find.
     * @return Index where element is located.
     */
    private int find(E[] array, E element) {
        int min = 0;
        int max = array.length - 1;

        while (max >= min) {

            int mid = midpoint(min, max);
            int compare = comparator.compare(array[mid], element);

            // if no difference then we have it
            if (0 == compare) {
                return mid;
            }

            // otherwise adapt min and max
            min = (compare < 0) ? mid + 1 : min;
            max = (compare > 0) ? mid - 1 : max;
        }

        return -1;
    }

    /**
     * Finds index for a element to insert and informs if element already exists. If the element
     * already exists by comparator the function the upper int in the returned long will have the
     * value of {@value #ELEMENT_EXISTS_MASK}. Lower int always holds the index value.
     *
     * @param array
     *            Array to find element in.
     * @param element
     *            Element to find.
     * @return Data where element should be inserted. The upper int in the returned long will have
     *         the value of {@value #ELEMENT_EXISTS_MASK} if same element already exists. Lower int
     *         always holds the index value.
     */
    private long findInsertDataFor(E[] array, E element) {
        int min = 0;
        int max = array.length;

        while (max > min) {

            int mid = midpoint(min, max);
            int compare = comparator.compare(array[mid], element);

            // if no difference then we return mask and index
            if (0 == compare) {
                return pack(ELEMENT_EXISTS_MASK, mid);
            }

            // otherwise adapt min and max
            min = (compare < 0) ? mid + 1 : min;
            max = (compare > 0) ? mid : max;

            if (max == min) {
                return pack(0, max);
            }
        }

        return 0;
    }

    /**
     * Finds midpoint.
     *
     * @param min
     *            Minimum.
     * @param max
     *            Maximum.
     * @return Midpoint between two.
     */
    protected int midpoint(int min, int max) {
        return min + ((max - min) >> 1);
    }

    /**
     * Packs two integers into a long.
     *
     * @param upper
     *            Upper int.
     * @param lower
     *            Lower int.
     * @return long as a result.
     */
    protected long pack(int upper, int lower) {
        return (((long) upper) << 32) | lower;
    }

    /**
     * Returns int value packed in the lower 4 bytes of the long.
     *
     * @param l
     *            long
     * @return Value that is packed to the lower int value.
     */
    protected int getLowerInt(long l) {
        // masking all upper bits first and then returning the int
        return (int) (l & 0x00000000FFFFFFFF);
    }

    /**
     * Returns int value packed in the lower 4 bytes of the long.
     *
     * @param l
     *            long
     * @return Value that is packed to the upper int value.
     */
    protected int getUpperInt(long l) {
        // shifting 32 bits (4 bytes) and returning as int
        return (int) (l >> 32);
    }

    /**
     * Returns element on specific index in array. Sub-classes can use this to retrieve elements
     * from specific indexes.
     *
     * @param index
     *            Index to retrieve.
     * @return Element or <code>null</code> if index is out of bound.
     */
    protected E getAt(int index) {
        if ((index >= 0) && (index < array.length)) {
            return array[index];
        }
        return null;
    }

    /**
     * JDK7 version of the Arrays.copyOf.
     * <p>
     * <b>IMPORTANT:</b> The class code is copied/taken from
     * <a href="http://hg.openjdk.java.net/jdk7">OpenJDK JDK7</a>. Original authors are Josh Bloch,
     * Neal Gafter and John Rose. License info can be found
     * <a href="http://openjdk.java.net/faq/">here</a>.
     *
     * @param <T>
     *            type of array
     * @param <U>
     *            original
     * @param original
     *            the array to be copied
     * @param newLength
     *            the length of the copy to be returned
     * @param newType
     *            the class of the copy to be returned
     * @return a copy of the original array, truncated or padded with nulls to obtain the specified
     *         length
     */
    @SuppressWarnings("unchecked")
    private static <T, U> T[] arrayCopyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object) newType == (Object) Object[].class) ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
        return copy;
    }

    /**
     * Iterator to use in the set.
     *
     * @author Ivan Senic
     *
     */
    private class ArraySetIterator extends ObjectArrayIterator {

        /**
         * @param array
         *            the array to iterate over
         */
        public ArraySetIterator(Object[] array) {
            super(array);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void remove() {
            SortedArraySet.this.remove(this.array[index]);
        }
    }

}