ArraySet.java Source code

Java tutorial

Introduction

Here is the source code for ArraySet.java

Source

/*
 * Copyright 2004-2010 the Seasar Foundation and the Others.
 *
 * 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.slim3.util;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * A set acts like array.
 * 
 * @author higa
 * @param <E>
 *            the element type
 * @since 1.0.0
 */
public class ArraySet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable {

    static final long serialVersionUID = -5024744406713321676L;

    private transient ArrayMap<E, Object> map;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    /**
     * Constructor.
     */
    public ArraySet() {
        map = new ArrayMap<E, Object>();
    }

    /**
     * Constructor.
     * 
     * @param c
     *            the collection whose elements are to be placed into this set.
     * @throws NullPointerException
     *             if the specified collection is null.
     */
    public ArraySet(Collection<? extends E> c) throws NullPointerException {
        if (c == null) {
            throw new NullPointerException("The c parameter must not be null.");
        }
        map = new ArrayMap<E, Object>(Math.max((int) (c.size() / .75f) + 1, 16));
        addAll(c);
    }

    /**
     * Constructor.
     * 
     * @param initialCapacity
     *            the initial capacity of the hash table.
     * @throws IllegalArgumentException
     *             if the initial capacity is less than zero.
     */
    public ArraySet(int initialCapacity) {
        map = new ArrayMap<E, Object>(initialCapacity);
    }

    /**
     * Returns an element specified by the index.
     * 
     * @param index
     *            the index
     * @return an element
     */
    public E get(int index) {
        return map.getKey(index);
    }

    /**
     * Returns an iterator over the elements in this set. The elements are
     * returned in no particular order.
     * 
     * @return an Iterator over the elements in this set.
     * @see ConcurrentModificationException
     */
    @Override
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    /**
     * Returns the number of elements in this set (its cardinality).
     * 
     * @return the number of elements in this set (its cardinality).
     */
    @Override
    public int size() {
        return map.size();
    }

    /**
     * Returns <tt>true</tt> if this set contains no elements.
     * 
     * @return <tt>true</tt> if this set contains no elements.
     */
    @Override
    public boolean isEmpty() {
        return map.isEmpty();
    }

    /**
     * Returns <tt>true</tt> if this set contains the specified element.
     * 
     * @param o
     *            element whose presence in this set is to be tested.
     * @return <tt>true</tt> if this set contains the specified element.
     */
    @Override
    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    /**
     * Adds the specified element to this set if it is not already present.
     * 
     * @param o
     *            element to be added to this set.
     * @return <tt>true</tt> if the set did not already contain the specified
     *         element.
     */
    @Override
    public boolean add(E o) {
        return map.put(o, PRESENT) == null;
    }

    /**
     * Removes the specified element from this set if it is present.
     * 
     * @param o
     *            object to be removed from this set, if present.
     * @return <tt>true</tt> if the set contained the specified element.
     */
    @Override
    public boolean remove(Object o) {
        return map.remove(o) == PRESENT;
    }

    /**
     * Removes all of the elements from this set.
     */
    @Override
    public void clear() {
        map.clear();
    }

    /**
     * Returns a shallow copy of this <tt>HashSet</tt> instance: the elements
     * themselves are not cloned.
     * 
     * @return a shallow copy of this set.
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object clone() {
        try {
            ArraySet<E> newSet = (ArraySet<E>) super.clone();
            newSet.map = (ArrayMap<E, Object>) map.clone();
            return newSet;
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    /**
     * Save the state of this <tt>HashSet</tt> instance to a stream (that is,
     * serialize this set).
     * 
     * @serialData The capacity of the backing <tt>HashMap</tt> instance (int),
     *             and its load factor (float) are emitted, followed by the size
     *             of the set (the number of elements it contains) (int),
     *             followed by all of its elements (each an Object) in no
     *             particular order.
     */
    private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
        s.defaultWriteObject();
        s.writeInt(map.size());
        for (Iterator<E> i = map.keySet().iterator(); i.hasNext();) {
            s.writeObject(i.next());
        }
    }

    /**
     * Reconstitute the <tt>HashSet</tt> instance from a stream (that is,
     * deserialize it).
     */
    @SuppressWarnings("unchecked")
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();
        int size = s.readInt();
        map = new ArrayMap<E, Object>(size);
        for (int i = 0; i < size; i++) {
            E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }
}

class ArrayMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Externalizable {

    private static final long serialVersionUID = 1L;

    /**
     * The default initial capacity.
     */
    protected static final int INITIAL_CAPACITY = 17;

    /**
     * The default load factor.
     */
    protected static final float LOAD_FACTOR = 0.75f;

    /**
     * The threshold to extend the capacity.
     */
    protected transient int threshold;

    /**
     * The map table.
     */
    protected transient Entry<K, V>[] mapTable;

    /**
     * The list table.
     */
    protected transient Entry<K, V>[] listTable;

    /**
     * The number of entries.
     */
    protected transient int size = 0;

    /**
     * The set of entries.
     */
    protected transient Set<Map.Entry<K, V>> entrySet = null;

    /**
     * Constructor.
     */
    public ArrayMap() {
        this(INITIAL_CAPACITY);
    }

    /**
     * Constructor.
     * 
     * @param initialCapacity
     *            the initial capacity
     */
    public ArrayMap(int initialCapacity) {
        if (initialCapacity <= 0) {
            initialCapacity = INITIAL_CAPACITY;
        }
        mapTable = createArray(initialCapacity);
        listTable = createArray(initialCapacity);
        threshold = (int) (initialCapacity * LOAD_FACTOR);
    }

    /**
     * Constructor.
     * 
     * @param map
     *            other map
     */
    public ArrayMap(Map<? extends K, ? extends V> map) {
        this((int) (map.size() / LOAD_FACTOR) + 1);
        putAll(map);
    }

    @Override
    public final int size() {
        return size;
    }

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

    @Override
    public final boolean containsValue(Object value) {
        return indexOf(value) >= 0;
    }

    /**
     * Returns the index in the list table.
     * 
     * @param value
     *            the value
     * @return the index
     */
    public final int indexOf(Object value) {
        if (value != null) {
            for (int i = 0; i < size; i++) {
                if (value.equals(listTable[i].value)) {
                    return i;
                }
            }
        } else {
            for (int i = 0; i < size; i++) {
                if (listTable[i].value == null) {
                    return i;
                }
            }
        }
        return -1;
    }

    @Override
    public boolean containsKey(Object key) {
        Entry<K, V>[] tbl = mapTable;
        if (key != null) {
            int hashCode = key.hashCode();
            int index = (hashCode & 0x7FFFFFFF) % tbl.length;
            for (Entry<K, V> e = tbl[index]; e != null; e = e.next) {
                if (e.hashCode == hashCode && key.equals(e.key)) {
                    return true;
                }
            }
        } else {
            for (Entry<K, V> e = tbl[0]; e != null; e = e.next) {
                if (e.key == null) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public V get(Object key) {
        Entry<K, V>[] tbl = mapTable;
        if (key != null) {
            int hashCode = key.hashCode();
            int index = (hashCode & 0x7FFFFFFF) % tbl.length;
            for (Entry<K, V> e = tbl[index]; e != null; e = e.next) {
                if (e.hashCode == hashCode && key.equals(e.key)) {
                    return e.value;
                }
            }
        } else {
            for (Entry<K, V> e = tbl[0]; e != null; e = e.next) {
                if (e.key == null) {
                    return e.value;
                }
            }
        }
        return null;
    }

    /**
     * Returns the value for the index.
     * 
     * @param index
     *            the index
     * @return the value
     */
    public final V get(int index) {
        return getEntry(index).value;
    }

    /**
     * Returns the key for the index.
     * 
     * @param index
     *            the index
     * @return the key
     */
    public final K getKey(int index) {
        return getEntry(index).key;
    }

    /**
     * Returns the entry for the index.
     * 
     * @param index
     *            the index
     * @return the entry
     */
    public final Entry<K, V> getEntry(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size);
        }
        return listTable[index];
    }

    @Override
    public V put(K key, V value) {
        int hashCode = 0;
        int index = 0;

        if (key != null) {
            hashCode = key.hashCode();
            index = (hashCode & 0x7FFFFFFF) % mapTable.length;
            for (Entry<K, V> e = mapTable[index]; e != null; e = e.next) {
                if ((e.hashCode == hashCode) && key.equals(e.key)) {
                    return swapValue(e, value);
                }
            }
        } else {
            for (Entry<K, V> e = mapTable[0]; e != null; e = e.next) {
                if (e.key == null) {
                    return swapValue(e, value);
                }
            }
        }
        ensureCapacity();
        index = (hashCode & 0x7FFFFFFF) % mapTable.length;
        Entry<K, V> e = new Entry<K, V>(hashCode, key, value, mapTable[index]);
        mapTable[index] = e;
        listTable[size++] = e;
        return null;
    }

    /**
     * Sets the value for the index.
     * 
     * @param index
     *            the index
     * @param value
     *            the value
     */
    public final void set(int index, V value) {
        getEntry(index).setValue(value);
    }

    @Override
    public V remove(Object key) {
        Entry<K, V> e = removeMap(key);
        if (e != null) {
            V value = e.value;
            removeList(indexOf(e));
            e.clear();
            return value;
        }
        return null;
    }

    /**
     * Removes the entry for the index.
     * 
     * @param index
     *            the index
     * @return the removed value
     */
    public final V remove(int index) {
        Entry<K, V> e = removeList(index);
        V value = e.value;
        removeMap(e.key);
        e.value = null;
        return value;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map) {
        for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = map.entrySet().iterator(); i.hasNext();) {
            Map.Entry<? extends K, ? extends V> e = i.next();
            put(e.getKey(), e.getValue());
        }
    }

    @Override
    public final void clear() {
        for (int i = 0; i < mapTable.length; i++) {
            mapTable[i] = null;
        }
        for (int i = 0; i < listTable.length; i++) {
            listTable[i] = null;
        }
        size = 0;
    }

    /**
     * Converts this object to an array.
     * 
     * @return an array
     */
    public final Object[] toArray() {
        Object[] array = new Object[size];
        for (int i = 0; i < array.length; i++) {
            array[i] = get(i);
        }
        return array;
    }

    /**
     * Converts this object to an array.
     * 
     * @param proto
     *            the prototype
     * @return an array
     */
    public final Object[] toArray(final Object proto[]) {
        Object[] array = proto;
        if (proto.length < size) {
            array = (Object[]) Array.newInstance(proto.getClass().getComponentType(), size);
        }
        for (int i = 0; i < array.length; i++) {
            array[i] = get(i);
        }
        if (array.length > size) {
            array[size] = null;
        }
        return array;
    }

    @Override
    @SuppressWarnings("unchecked")
    public final boolean equals(Object o) {
        if (!getClass().isInstance(o)) {
            return false;
        }
        ArrayMap<K, V> e = (ArrayMap) o;
        if (size != e.size) {
            return false;
        }
        for (int i = 0; i < size; i++) {
            if (!listTable[i].equals(e.listTable[i])) {
                return false;
            }
        }
        return true;
    }

    @Override
    public final Set<Map.Entry<K, V>> entrySet() {
        if (entrySet == null) {
            entrySet = new AbstractSet<Map.Entry<K, V>>() {
                @Override
                public Iterator<Map.Entry<K, V>> iterator() {
                    return new ArrayMapIterator();
                }

                @Override
                @SuppressWarnings("unchecked")
                public boolean contains(Object o) {
                    if (!(o instanceof Entry)) {
                        return false;
                    }
                    Entry<K, V> entry = (Entry<K, V>) o;
                    int index = (entry.hashCode & 0x7FFFFFFF) % mapTable.length;
                    for (Entry<K, V> e = mapTable[index]; e != null; e = e.next) {
                        if (e.equals(entry)) {
                            return true;
                        }
                    }
                    return false;
                }

                @Override
                @SuppressWarnings("unchecked")
                public boolean remove(Object o) {
                    if (!(o instanceof Entry)) {
                        return false;
                    }
                    Entry<K, V> entry = (Entry) o;
                    return ArrayMap.this.remove(entry.key) != null;
                }

                @Override
                public int size() {
                    return size;
                }

                @Override
                public void clear() {
                    ArrayMap.this.clear();
                }
            };
        }
        return entrySet;
    }

    public final void writeExternal(final ObjectOutput out) throws IOException {
        out.writeInt(listTable.length);
        out.writeInt(size);
        for (int i = 0; i < size; i++) {
            out.writeObject(listTable[i].key);
            out.writeObject(listTable[i].value);
        }
    }

    @SuppressWarnings("unchecked")
    public final void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {

        int num = in.readInt();
        mapTable = createArray(num);
        listTable = createArray(num);
        threshold = (int) (num * LOAD_FACTOR);
        int size = in.readInt();
        for (int i = 0; i < size; i++) {
            K key = (K) in.readObject();
            V value = (V) in.readObject();
            put(key, value);
        }
    }

    @Override
    public Object clone() {
        ArrayMap<K, V> copy = new ArrayMap<K, V>();
        copy.threshold = threshold;
        copy.mapTable = mapTable;
        copy.listTable = listTable;
        copy.size = size;
        return copy;
    }

    private final int indexOf(final Entry<K, V> entry) {
        for (int i = 0; i < size; i++) {
            if (listTable[i] == entry) {
                return i;
            }
        }
        return -1;
    }

    private final Entry<K, V> removeMap(Object key) {
        int hashCode = 0;
        int index = 0;

        if (key != null) {
            hashCode = key.hashCode();
            index = (hashCode & 0x7FFFFFFF) % mapTable.length;
            for (Entry<K, V> e = mapTable[index], prev = null; e != null; prev = e, e = e.next) {
                if ((e.hashCode == hashCode) && key.equals(e.key)) {
                    if (prev != null) {
                        prev.next = e.next;
                    } else {
                        mapTable[index] = e.next;
                    }
                    return e;
                }
            }
        } else {
            for (Entry<K, V> e = mapTable[index], prev = null; e != null; prev = e, e = e.next) {
                if ((e.hashCode == hashCode) && e.key == null) {
                    if (prev != null) {
                        prev.next = e.next;
                    } else {
                        mapTable[index] = e.next;
                    }
                    return e;
                }
            }
        }
        return null;
    }

    private final Entry<K, V> removeList(int index) {
        Entry<K, V> e = listTable[index];
        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(listTable, index + 1, listTable, index, numMoved);
        }
        listTable[--size] = null;
        return e;
    }

    private final void ensureCapacity() {
        if (size >= threshold) {
            Entry<K, V>[] oldTable = listTable;
            int newCapacity = oldTable.length * 2 + 1;
            Entry<K, V>[] newMapTable = createArray(newCapacity);
            Entry<K, V>[] newListTable = createArray(newCapacity);
            threshold = (int) (newCapacity * LOAD_FACTOR);
            System.arraycopy(oldTable, 0, newListTable, 0, size);
            for (int i = 0; i < size; i++) {
                Entry<K, V> old = oldTable[i];
                int index = (old.hashCode & 0x7FFFFFFF) % newCapacity;
                Entry<K, V> e = old;
                old = old.next;
                e.next = newMapTable[index];
                newMapTable[index] = e;
            }
            mapTable = newMapTable;
            listTable = newListTable;
        }
    }

    private final V swapValue(final Entry<K, V> entry, final V value) {
        V old = entry.value;
        entry.value = value;
        return old;
    }

    @SuppressWarnings("unchecked")
    private Entry<K, V>[] createArray(final int length) {
        return new Entry[length];
    }

    private class ArrayMapIterator implements Iterator<Map.Entry<K, V>> {

        private int current = 0;

        private int last = -1;

        public boolean hasNext() {
            return current != size;
        }

        public Map.Entry<K, V> next() {
            try {
                Entry<K, V> n = listTable[current];
                last = current++;
                return n;
            } catch (IndexOutOfBoundsException e) {
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (last == -1) {
                throw new IllegalStateException();
            }
            ArrayMap.this.remove(last);
            if (last < current) {
                current--;
            }
            last = -1;
        }
    }

    private static class Entry<K, V> implements Map.Entry<K, V>, Externalizable {

        private static final long serialVersionUID = 1L;

        transient int hashCode;

        transient K key;

        transient V value;

        transient Entry<K, V> next;

        /**
         * Constructor.
         * 
         * @param hashCode
         *            the hashCode
         * @param key
         *            the key
         * @param value
         *            the value
         * @param next
         *            the next
         */
        public Entry(final int hashCode, final K key, final V value, final Entry<K, V> next) {

            this.hashCode = hashCode;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

        public V setValue(final V value) {
            V oldValue = value;
            this.value = value;
            return oldValue;
        }

        /**
         * Clears contents.
         */
        public void clear() {
            key = null;
            value = null;
            next = null;
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            Entry<?, ?> e = (Entry<?, ?>) o;
            return (key != null ? key.equals(e.key) : e.key == null)
                    && (value != null ? value.equals(e.value) : e.value == null);
        }

        @Override
        public int hashCode() {
            return hashCode;
        }

        @Override
        public String toString() {
            return key + "=" + value;
        }

        public void writeExternal(final ObjectOutput s) throws IOException {
            s.writeInt(hashCode);
            s.writeObject(key);
            s.writeObject(value);
            s.writeObject(next);
        }

        @SuppressWarnings("unchecked")
        public void readExternal(final ObjectInput s) throws IOException, ClassNotFoundException {

            hashCode = s.readInt();
            key = (K) s.readObject();
            value = (V) s.readObject();
            next = (Entry) s.readObject();
        }
    }
}