UnmodifiableSet.java

/*
 * Copyright (C) 2018 Stein Eldar Johnsen
 *
 * 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 net.morimekta.collect;

import net.morimekta.collect.collectors.UnmodifiableSetCollector;

import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collector;

import static java.lang.Integer.highestOneBit;
import static java.lang.Math.max;
import static java.util.Objects.requireNonNull;

/**
 * Simple immutable set. This set is order-preserving, so when iterating
 * the elements, they will come in the same order as added. If same value
 * was added multiple times, the first entry counts.
 *
 * @param <E> The set element type.
 */
public final class UnmodifiableSet<E> extends UnmodifiableCollection<E> implements Set<E> {
    // -------- Collectors --------

    /**
     * Collect stream to an unmodifiable set.
     *
     * @param <E> The set item type.
     * @return Collector instance.
     */
    public static <E> Collector<E, ?, UnmodifiableSet<E>> toSet() {
        return new UnmodifiableSetCollector<>();
    }

    // -------- Constructors --------

    /**
     * @param array Array of items to make set of.
     * @param <E>   The set item type.
     * @return The unmodifiable set.
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <E> UnmodifiableSet<E> asSet(E... array) {
        if (array.length == 0) {
            return setOf();
        }
        Object[] copy = Arrays.copyOf(array, array.length);
        return new UnmodifiableSet<>(copy.length, copy, makeHashTable(copy, copy.length));
    }

    /**
     * @param iterable Iterable of items to make set of.
     * @param <E>      The set item type.
     * @return The unmodifiable set.
     */
    @SuppressWarnings("unchecked")
    public static <E> UnmodifiableSet<E> asSet(Iterable<? extends E> iterable) {
        if (iterable instanceof Collection) {
            return asSet((Collection<E>) iterable);
        }
        return asSet(iterable.iterator());
    }

    /**
     * @param iterator Iterator of items to make set of.
     * @param <E>      The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> asSet(Iterator<? extends E> iterator) {
        if (!iterator.hasNext()) {
            return setOf();
        }
        return UnmodifiableSet.<E>newBuilder().addAll(iterator).build();
    }

    /**
     * @param enumeration Enumeration of items to make set of.
     * @param <E>         The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> asSet(Enumeration<? extends E> enumeration) {
        if (!enumeration.hasMoreElements()) {
            return setOf();
        }
        return UnmodifiableSet.<E>newBuilder().addAll(enumeration).build();
    }

    /**
     * @param collection Collection of items to make set of.
     * @param <E>        The set item type.
     * @return The unmodifiable set.
     */
    @SuppressWarnings("unchecked")
    public static <E> UnmodifiableSet<E> asSet(Collection<? extends E> collection) {
        if (collection.size() == 0) {
            return setOf();
        }
        if (collection instanceof UnmodifiableSet) {
            return (UnmodifiableSet<E>) collection;
        }
        if (collection instanceof UnmodifiableSortedSet) {
            UnmodifiableSortedSet<E> set = (UnmodifiableSortedSet<E>) collection;
            return new UnmodifiableSet<>(set.length, set.array, makeHashTable(set.array, set.length));
        }
        if (collection instanceof Set) {
            Object[] array = collection.toArray();
            return new UnmodifiableSet<>(array.length, array, makeHashTable(array, array.length));
        }
        return UnmodifiableSet.<E>newBuilder(collection.size())
                              .addAll(collection)
                              .build();
    }

    /**
     * @param <E> The set item type.
     * @return The unmodifiable empty set.
     */
    @SuppressWarnings("unchecked")
    public static <E> UnmodifiableSet<E> setOf() {
        return (UnmodifiableSet<E>) EMPTY;
    }

    /**
     * @param e   The item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable singleton set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e) {
        return construct(e);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2) {
        return construct(e1, e2);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3) {
        return construct(e1, e2, e3);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param e4  The fourth item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4) {
        return construct(e1, e2, e3, e4);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param e4  The fourth item to make a set of.
     * @param e5  The fifth item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4, E e5) {
        return construct(e1, e2, e3, e4, e5);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param e4  The fourth item to make a set of.
     * @param e5  The fifth item to make a set of.
     * @param e6  The sixth item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4, E e5, E e6) {
        return construct(e1, e2, e3, e4, e5, e6);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param e4  The fourth item to make a set of.
     * @param e5  The fifth item to make a set of.
     * @param e6  The sixth item to make a set of.
     * @param e7  The seventh item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7) {
        return construct(e1, e2, e3, e4, e5, e6, e7);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param e4  The fourth item to make a set of.
     * @param e5  The fifth item to make a set of.
     * @param e6  The sixth item to make a set of.
     * @param e7  The seventh item to make a set of.
     * @param e8  The eighth item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8);
    }

    /**
     * @param e1  The first item to make a set of.
     * @param e2  The second item to make a set of.
     * @param e3  The third item to make a set of.
     * @param e4  The fourth item to make a set of.
     * @param e5  The fifth item to make a set of.
     * @param e6  The sixth item to make a set of.
     * @param e7  The seventh item to make a set of.
     * @param e8  The eighth item to make a set of.
     * @param e9  The ninth item to make a set of.
     * @param <E> The set item type.
     * @return The unmodifiable set.
     */
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) {
        return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9);
    }

    /**
     * @param e1   The first item to make a set of.
     * @param e2   The second item to make a set of.
     * @param e3   The third item to make a set of.
     * @param e4   The fourth item to make a set of.
     * @param e5   The fifth item to make a set of.
     * @param e6   The sixth item to make a set of.
     * @param e7   The seventh item to make a set of.
     * @param e8   The eighth item to make a set of.
     * @param e9   The ninth item to make a set of.
     * @param e10  The tenth item to make a set of.
     * @param more More items to put in set.
     * @param <E>  The set item type.
     * @return The unmodifiable set.
     */
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <E> UnmodifiableSet<E> setOf(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E... more) {
        return UnmodifiableSet
                .<E>newBuilder(10 + more.length)
                .add(e1)
                .add(e2)
                .add(e3)
                .add(e4)
                .add(e5)
                .add(e6)
                .add(e7)
                .add(e8)
                .add(e9)
                .add(e10)
                .addAll(more)
                .build();
    }

    /**
     * @param <E> The set item type.
     * @return A builder for an unmodifiable set.
     */
    public static <E> UnmodifiableSet.Builder<E> newBuilder() {
        return new UnmodifiableSet.Builder<>(4);
    }

    /**
     * @param initialCapacity The initial expected capacity of the map.
     * @param <E>             The set item type.
     * @return A builder for an unmodifiable set.
     */
    public static <E> UnmodifiableSet.Builder<E> newBuilder(int initialCapacity) {
        return new UnmodifiableSet.Builder<>(max(1, initialCapacity));
    }

    // -------- UnmodifiableCollection --------

    @Override
    public UnmodifiableSet<E> reversed() {
        if (length < 2) {
            return this;
        }
        var out = reversedArray();
        var hashTable = makeHashTable(out, length);
        return new UnmodifiableSet<>(length, out, hashTable);
    }

    // -------- Object --------

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof Set)) {
            return false;
        }
        @SuppressWarnings("rawtypes")
        Set other = (Set) obj;
        if (other.size() != length) {
            return false;
        }
        for (Object o : (Set<?>) obj) {
            if (!contains(o)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("{");
        for (int i = 0; i < length; ++i) {
            if (i > 0) {
                builder.append(", ");
            }
            builder.append(array[i].toString());
        }
        return builder.append("}").toString();
    }

    @Override
    public int hashCode() {
        if (hashCode == null) {
            int hash = Objects.hash(getClass(), length);
            for (int i = 0; i < length; ++i) {
                hash ^= Objects.hash(array[i]);
            }
            hashCode = hash;
        }
        return hashCode;
    }

    // -------- Collection --------

    @Override
    public boolean contains(Object o) {
        if (o == null) {
            return false;
        }
        int hash = o.hashCode();
        for (int hp = hash & mask; ; hp = ++hp & mask) {
            Object p = hashTable[hp];
            if (p == null) {
                return false;
            }
            if (p.equals(o)) {
                return true;
            }
        }
    }

    // -------- UnmodifiableCollection --------

    /**
     * Get list with only the items matching the predicate.
     *
     * @param predicate The predicate to keep item in response.
     * @return The filtered list.
     */
    @Override
    public UnmodifiableSet<E> filtered(Predicate<? super E> predicate) {
        return (UnmodifiableSet<E>) super.filtered(predicate);
    }

    // -------- UnmodifiableSet --------

    /**
     * A builder for making unmodifiable set.
     *
     * @param <E> The set item type.
     */
    public final static class Builder<E>
            implements SetBuilder<E>, UnmodifiableCollectionBuilder<E, UnmodifiableSet<E>, Builder<E>> {
        private Object[] array;
        private int      size;
        private Object[] hashTable;
        private int      mask;
        private boolean  forceRehash;

        Builder(int initialSize) {
            array = new Object[initialSize];
            size = 0;
            hashTable = new Object[hashTableSizeFor(array.length, MIN_HASH_TABLE_SIZE)];
            mask = hashTable.length - 1;
            forceRehash = false;
        }

        @Override
        public Builder<E> add(E e) {
            requireNonNull(e, "null item");
            ensureCapacity(size + 1);
            hashTableAdd(e);
            return this;
        }

        @Override
        @SafeVarargs
        @SuppressWarnings("varargs")
        public final Builder<E> addAll(E... items) {
            checkNotNull(items);
            if (items.length == 0) {
                return this;
            }
            ensureCapacity(size + items.length);
            for (E e : items) {
                hashTableAdd(e);
            }
            return this;
        }

        @Override
        public Builder<E> addAll(Collection<? extends E> collection) {
            checkNotNull(collection);
            if (collection.isEmpty()) {
                return this;
            }
            ensureCapacity(size + collection.size());
            for (E e : collection) {
                hashTableAdd(e);
            }
            return this;
        }

        @Override
        public UnmodifiableSet<E> build() {
            forceRehash = true;  // In case the builder is continuously used.
            if (size == 0) {
                return setOf();
            }
            return new UnmodifiableSet<>(size, array, hashTable);
        }

        private void ensureCapacity(int capacity) {
            if (array.length < capacity) {
                Object[] old = array;
                array = new Object[max(capacity, array.length * 2)];
                System.arraycopy(old, 0, array, 0, size);
                forceRehash = true;
            }
            if (forceRehash) {
                hashTable = makeHashTable(array, size, hashTableSizeFor(array.length, hashTable.length));
                mask = hashTable.length - 1;
                forceRehash = false;
            }
        }

        private void hashTableAdd(Object e) {
            int hash = e.hashCode();
            for (int hp = hash & mask; ; hp = ++hp & mask) {
                Object p = hashTable[hp];
                if (p == null) {
                    hashTable[hp] = e;
                    array[size++] = e;
                    break;
                } else if (p.equals(e)) {
                    break;
                }
            }
        }
    }

    // -------- Private --------

    UnmodifiableSet(int size, Object[] array, Object[] hashTable) {
        super(0, size, array);
        this.hashTable = hashTable;
        this.mask = hashTable.length - 1;
    }

    @Override
    UnmodifiableCollection<E> newInstance(Object[] array, int length) {
        return new UnmodifiableSet<>(length, array, makeHashTable(array, length));
    }

    @SafeVarargs
    @SuppressWarnings("varargs")
    static <E> UnmodifiableSet<E> construct(E... array) {
        return UnmodifiableSet.<E>newBuilder(array.length).addAll(array).build();
    }

    private static int hashTableSizeFor(final int arraySize, int currentHashTableSize) {
        int tableSize = max(highestOneBit(arraySize) << 1, max(MIN_HASH_TABLE_SIZE, currentHashTableSize));
        if (tableSize < (int) (MIN_OVER_CAPACITY * (double) arraySize)) {
            tableSize <<= 1;
        }
        return tableSize;
    }

    static Object[] makeHashTable(Object[] array, int size) {
        return makeHashTable(array, size, hashTableSizeFor(size, MIN_HASH_TABLE_SIZE));
    }

    private static Object[] makeHashTable(Object[] array, int size, int hashTableSize) {
        int mask = hashTableSize - 1;
        Object[] hashTable = new Object[hashTableSize];
        for (int i = 0; i < size; ++i) {
            Object o = array[i];
            // if (o == null) throw new NullPointerException("Null item at index " + i + " of " + size);
            int hash = o.hashCode();
            int hp = hash & mask;
            for (; ; ) {
                Object p = hashTable[hp];
                if (p == null) {
                    hashTable[hp] = o;
                    break;
                }
                hp = ++hp & mask;
            }
        }

        return hashTable;
    }

    private static final double MIN_OVER_CAPACITY   = 1.38;
    private static final int    MIN_HASH_TABLE_SIZE = 2;

    private static final UnmodifiableSet<Object> EMPTY = new UnmodifiableSet<>(
            0, EMPTY_ARRAY, new Object[MIN_HASH_TABLE_SIZE]);

    private final transient Object[] hashTable;
    private final transient int      mask;

}