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

org.plumelib.util.IdentityArraySet Maven / Gradle / Ivy

package org.plumelib.util;

import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import org.checkerframework.checker.index.qual.GTENegativeOne;
import org.checkerframework.checker.index.qual.IndexOrHigh;
import org.checkerframework.checker.index.qual.LTEqLengthOf;
import org.checkerframework.checker.index.qual.LessThan;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.lock.qual.GuardSatisfied;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signedness.qual.UnknownSignedness;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;

/**
 * A set backed by an array. It uses object identity (==) for comparison. It permits null values and
 * its iterator has deterministic ordering.
 *
 * 

Compared to a set built on IdentityHashMap: For very small sets, this uses much less space, * has comparable performance, and (like a LinkedHashSet) is deterministic, with elements returned * in the order they were inserted. For large sets, this is significantly less performant than other * set implementations. * * @param the type of the set elements */ @SuppressWarnings({ "index", // TODO "keyfor", // https://tinyurl.com/cfissue/4558 "lock", // not yet annotated for the Lock Checker "nullness" // temporary; nullness is tricky because of null-padded arrays }) public class IdentityArraySet extends AbstractSet { /** The values. Null if capacity=0. */ private @Nullable E[] values; /** The number of used slots in the representation of this. */ private @NonNegative @LessThan("values.length + 1") @IndexOrHigh({"values"}) int size = 0; // An alternate representation would also store the hash code of each key, for quicker querying. /** * The number of times this set's size has been modified by adding or removing an element. This * field is used to make view iterators fail-fast. */ transient int sizeModificationCount = 0; // Constructors /** * Constructs an empty {@code IdentityArraySet} with the specified initial capacity. * * @param initialCapacity the initial capacity * @throws IllegalArgumentException if the initial capacity is negative */ @SuppressWarnings({ "unchecked", // generic array cast "samelen:assignment", // initialization "allcheckers:purity.not.sideeffectfree.assign.field", // initializes `this` "allcheckers:purity.not.sideeffectfree.call" // calls `super` }) @SideEffectFree public IdentityArraySet(int initialCapacity) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity == 0) { this.values = null; } else { this.values = (E[]) new Object[initialCapacity]; } } /** Constructs an empty {@code IdentityArraySet} with the default initial capacity. */ @SideEffectFree public IdentityArraySet() { this(4); } /** * Private constructor. Installs the given objects in this as its representation, without making * defensive copies. * * @param values the values * @param size the number of used items in the array; may be less than its length */ @SuppressWarnings({ "samelen:assignment", // initialization "allcheckers:purity.not.sideeffectfree.assign.field", // initializes `this` "allcheckers:purity.not.sideeffectfree.call" // calls `super` }) @SideEffectFree private IdentityArraySet(E[] values, @LTEqLengthOf({"values"}) int size) { this.values = values; this.size = size; } /** * Constructs a new {@code IdentityArraySet} with the same elements as the given collection. * * @param m the collection whose elements are to be placed in the new set * @throws NullPointerException if the given set is null */ @SuppressWarnings({ "allcheckers:purity", // initializes `this` "lock:method.guarantee.violated", // initializes `this` "nullness:method.invocation", // inference failure; // https://github.com/typetools/checker-framework/issues/979 ? }) @SideEffectFree public IdentityArraySet(Collection m) { this(m.size()); addAll(m); } // Private helper functions /** * Adds an element to this set. * * @param index the index of {@code value} in {@code values}. If -1, add a new element. Otherwise, * do nothing. * @param value the value * @return true if the method modified this set */ @SuppressWarnings({"InvalidParam"}) // Error Prone stupidly warns about field `values` private boolean add(@GTENegativeOne int index, E value) { if (index != -1) { return false; } // Add a new element. if ((size == 0 && values == null) || (size == values.length)) { grow(); } values[size] = value; size++; sizeModificationCount++; return true; } /** Increases the capacity of the array. */ @SuppressWarnings({"unchecked"}) // generic array cast private void grow() { if (values == null) { this.values = (E[]) new Object[4]; } else { int newCapacity = 2 * values.length; values = Arrays.copyOf(values, newCapacity); } } /** * Remove the element at the given index. Does nothing if index is -1. * * @param index the index of the element to remove * @return true if this set was modified */ private boolean removeIndex(@GTENegativeOne int index) { if (index == -1) { return false; } System.arraycopy(values, index + 1, values, index, size - index - 1); size--; sizeModificationCount++; return true; } // Query Operations @Pure @Override public @NonNegative int size() { return size; } @Pure @Override public boolean isEmpty() { return size == 0; } /** * Returns the index of the given value, or -1 if it does not appear. Uses {@code ==} for * comparison. * * @param value a value to find * @return the index of the given value, or -1 if it does not appear */ @SuppressWarnings("interning:not.interned") // object identity comparison @Pure private int indexOf(@GuardSatisfied @Nullable @UnknownSignedness Object value) { if (values == null) { return -1; } for (int i = 0; i < size; i++) { if (value == values[i]) { return i; } } return -1; } @Pure @Override public boolean contains(@GuardSatisfied @Nullable @UnknownSignedness Object value) { return indexOf(value) != -1; } // Modification Operations @Override public boolean add(E value) { int index = indexOf(value); return add(index, value); } @Override public boolean remove(@GuardSatisfied @Nullable @UnknownSignedness Object value) { int index = indexOf(value); return removeIndex(index); } // Bulk Operations @Override public boolean addAll(Collection c) { if (c.isEmpty()) { return false; } return super.addAll(c); } @Override public boolean removeAll(Collection c) { if (c.isEmpty()) { return false; } return super.removeAll(c); } // Inherit retainAll() from AbstractCollection. @Override public void clear() { if (size != 0) { size = 0; sizeModificationCount++; } } /////////////////////////////////////////////////////////////////////////// // iterators @Override public Iterator iterator() { return new ArraySetIterator(); } /** An iterator over the IdentityArraySet. */ private class ArraySetIterator implements Iterator { /** The first unread index; the index of the next value to return. */ @NonNegative int index; /** True if remove() has been called since the last call to next(). */ boolean removed; /** The modification count when the iterator is created, for fail-fast. */ int initialSizeModificationCount; /** Creates a new ArraySetIterator. */ @SideEffectFree ArraySetIterator() { index = 0; removed = true; // can't remove until next() has been called initialSizeModificationCount = sizeModificationCount; } /** * Returns true if this has another element. * * @return true if this has another element */ @Pure @Override public final boolean hasNext() { return index < size(); } @Override public final E next() { if (!hasNext()) { throw new NoSuchElementException(); } removed = false; return values[index++]; } /** Removes the previously-returned element. */ @Override public final void remove() { if (removed) { throw new IllegalStateException( "Called remove() on ArraySetIterator without calling next() first."); } if (initialSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } // Remove the previously returned element, so use index-1. @SuppressWarnings("lowerbound:assignment") // removed==false, so index>0. @NonNegative int newIndex = index - 1; index = newIndex; IdentityArraySet.this.removeIndex(index); initialSizeModificationCount = sizeModificationCount; removed = true; } } /////////////////////////////////////////////////////////////////////////// // Comparison and hashing: equals and hashCode are inherited from AbstractSet. // Defaultable methods @Override public void forEach(Consumer action) { Objects.requireNonNull(action); if (values == null) { return; } int oldSizeModificationCount = sizeModificationCount; for (int index = 0; index < size; index++) { E e; try { e = values[index]; } catch (IndexOutOfBoundsException exc) { throw new ConcurrentModificationException(exc); } action.accept(e); } if (oldSizeModificationCount != sizeModificationCount) { throw new ConcurrentModificationException(); } } /** * Returns a copy of this. * * @return a copy of this */ @SuppressWarnings("unchecked") @SideEffectFree @Override public IdentityArraySet clone() { return new IdentityArraySet(Arrays.copyOf(values, size), size); } /** * Returns the internal representation, printed. * * @return the internal representation, printed */ @SideEffectFree /* package-private */ String repr() { return String.format( "size=%d capacity=%s %s", size, (values == null ? 0 : values.length), Arrays.toString(values)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy