/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.util.collections; import java.util.AbstractSet; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.NoSuchElementException; import java.util.Set; /** * Provides utility methods for working with arrays and collections. * * @author Emina Torlak */ public final class Containers { private static Comparator<Object> identityComparator; private static Comparator<Object> hashComparator; private Containers() {} /** * Returns a new iterator over the given array of items. * The iterator is backed by the given array. The contents * of the array are not modified by the iterator. The effect * of this method is the same as calling Iterators.iterator(0, items.length, items). * @throws NullPointerException items = null */ @SafeVarargs public static final <T, E extends T> Iterator<T> iterate(final E... items) { return new AscendingArrayIterator<T>(0, items.length, items); } /** * Returns a new iterator over the given array of items. * The iterator is backed by the given array. The contents * of the array are not modified by the iterator. * The returned iterator enumerates the items located between * indeces start, inclusive, and end, exclusive. If start < end, * the elements are returned in the ascending order; otherwise, * they are returned in the descending order. * @throws NullPointerException items = null * @throws IllegalArgumentException start < end && (start < 0 || end > items.length) || * start > end && (start >= items.length || end < -1) */ @SafeVarargs public static final <T, E extends T> Iterator<T> iterate(int start, int end, final E... items) { if (start < end) return new AscendingArrayIterator<T>(start,end,items); else if (start > end) return new DescendingArrayIterator<T>(start,end,items); else return emptyIterator(); } /** * Returns an iterator that has no elements. That is, * calls to hasNext will return false, and all other * calls will result in a runtime exception. * @return an empty iterator */ @SuppressWarnings("unchecked") public static final <T> Iterator<T> emptyIterator() { return (Iterator<T>) Collections.emptySet().iterator(); } /** * Calls System.arraycopy(src, srcPos, dest, destPos, length) and returns the destination array. * @ensures System.arraycopy(src, srcPos, dest, destPos, length) * @return dest */ public static final <T> T[] copy(T[] src, int srcPos, T[] dest, int destPos, int length) { System.arraycopy(src, srcPos, dest, destPos, length); return dest; } /** * Calls System.arraycopy(src, 0, dest, 0, src.length) and returns the destination array. * @requires dest.length >= src.length * @ensures System.arraycopy(src, 0, dest, 0, src.length) * @return dest */ public static final <T> T[] copy(T[] src, T[] dest) { System.arraycopy(src, 0, dest, 0, src.length); return dest; } /** * Returns a comparator that compares objects according to their * {@link System#identityHashCode(Object) identity hashcodes}. * @return a comparator that compares objects according to their * {@link System#identityHashCode(Object) identity hashcodes}. */ public static final Comparator<Object> identityComparator() { if (identityComparator==null) { identityComparator = new Comparator<Object>() { public int compare(Object o1, Object o2) { final int c1 = System.identityHashCode(o1); final int c2 = System.identityHashCode(o2); return c1 == c2 ? 0 : (c1 < c2 ? -1 : 1); } }; } return identityComparator; } /** * Returns 0 if o is null, otherwise returns o.hashCode(). * @return o=null => 0, o.hashCode() */ static final int hash(Object o) { return o==null ? 0 : o.hashCode(); } /** * Returns true if both o1 and o2 are null or if o1.equals(o2) * @return true if both o1 and o2 are null or if o1.equals(o2) */ static final boolean eq(Object o1, Object o2) { return o1==null ? o2==null : o1.equals(o2); } /** * Returns a comparator that compares objects according to their * {@link Object#hashCode() hashcodes}. The null reference is * considered to have a hashcode of 0. * @return a comparator that compares objects according to their * {@link Object#hashCode() hashcodes}. */ public static final Comparator<Object> hashComparator() { if (hashComparator==null) { hashComparator = new Comparator<Object>() { public int compare(Object o1, Object o2) { final int c1 = hash(o1); final int c2 = hash(o2); return c1 == c2 ? 0 : (c1 < c2 ? -1 : 1); } }; } return hashComparator; } /** * Calls {@link java.util.Arrays#sort(Object[], Comparator)} on the * given array and returns it. The elements are sorted in the ascending * order of their identity hashcodes. * @ensures java.util.Arrays.sort(array, {@link #identityComparator()}) * @return the given array, with its elements sorted in the increasing order of identity hashcodes */ public static final <T> T[] identitySort(T[] array) { java.util.Arrays.sort(array, identityComparator()); return array; } /** * Calls {@link java.util.Arrays#sort(Object[], Comparator)} on the * given array and returns it. The elements are sorted in the ascending * order of their hashcodes. * @ensures java.util.Arrays.sort(array, {@link #hashComparator()}) * @return the given array, with its elements sorted in the increasing order of hashcodes */ public static final <T> T[] hashSort(T[] array) { java.util.Arrays.sort(array, hashComparator()); return array; } /** * Searches the specified array for the specified object using the binary search algorithm * and reference equality. * The array must be sorted into ascending order according to the identity hashcodes of its elements * (as by {@link #identitySort(Object[])}) prior to making this call. If it is not sorted, * the results are undefined. If the array contains multiple occurences of the specified object, * there is no guarantee which one will be found. * @requires all i, j: [0..array.length) | i < j => array[i].hashCode() <= array[j].hashCode()) * @return index of the search key, if it is contained in the array; otherwise, (-(insertion point) - 1). * The insertion point is defined as the point at which the key would be inserted into the array: the * index of the first element greater than the key, or array.length, if all elements in the array are less * than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the * key is found. */ public static final int identityBinarySearch(Object[] array, Object key) { int low = 0; int high = array.length-1; int index = System.identityHashCode(key); while (low <= high) { int mid = (low + high) >>> 1; int midIndex = System.identityHashCode(array[mid]); if (midIndex < index) low = mid + 1; else if (midIndex > index) high = mid - 1; else { // index found, now check that variables are the same if (array[mid]==key) return mid; // check all variables with the same index (if any) for(int i = mid+1; i < array.length && System.identityHashCode(array[i])==index; i++) { if (array[i]==key) return i; } for(int i = mid-1; i >= 0 && System.identityHashCode(array[i])==index; i--) { if (array[i]==key) return i; } return -(mid+1); // var not found } } return -(low + 1); // key not found. } /** * Searches the specified array for the specified object using the binary search algorithm * and object equality. * The array must be sorted into ascending order according to the hashcodes of its elements * (as by {@link #hashSort(Object[])}) prior to making this call. If it is not sorted, * the results are undefined. If the array contains multiple occurences of the specified object, * there is no guarantee which one will be found. * @requires all i, j: [0..array.length) | i < j => System.identityHashCode(array[i]) <= System.identityHashCode(array[j]) * @return index of the search key, if it is contained in the array; otherwise, (-(insertion point) - 1). * The insertion point is defined as the point at which the key would be inserted into the array: the * index of the first element greater than the key, or array.length, if all elements in the array are less * than the specified key. Note that this guarantees that the return value will be >= 0 if and only if the * key is found. */ public static final int hashBinarySearch(Object[] array, Object key) { int low = 0; int high = array.length-1; int index = hash(key); while (low <= high) { int mid = (low + high) >>> 1; int midIndex = hash(array[mid]); if (midIndex < index) low = mid + 1; else if (midIndex > index) high = mid - 1; else { // index found, now check that variables are the same if (eq(array[mid], key)) return mid; // check all variables with the same index (if any) for(int i = mid+1; i < array.length && hash(array[i])==index; i++) { if (eq(array[i], key)) return i; } for(int i = mid-1; i >= 0 && hash(array[i])==index; i--) { if (eq(array[i], key)) return i; } return -(mid+1); // var not found } } return -(low + 1); // key not found. } /** * Returns an identity set backed by the given array (i.e. a set that uses * reference equality for comparisons). * The array must contain no duplicates, its elements must be * sorted in the increasing order of identity hashcodes (as by {@link #identitySort(Object[])}), and its contents * must not be changed while it is in use by the returned set. * @requires all i, j: [0..array.length) | i < j => System.identityHashCode(array[i]) <= System.identityHashCode(array[j]) * @return an unmodifiable identity Set view of the given array */ public static final <T> Set<T> asIdentitySet(final T[] array) { return new AbstractSet<T>() { public boolean contains(Object o) { return identityBinarySearch(array, o) >= 0; } public Iterator<T> iterator() { return iterate(array); } public int size() { return array.length; } public int hashCode() { int result = 0; for (Object o : array) { result += System.identityHashCode(o); } return result; } }; } /** * Returns a set backed by the given array (i.e. a set that uses * object equality for comparisons). * The array must contain no duplicates, its elements must be * sorted in the increasing order of hashcodes (as by {@link #hashSort(Object[])}), and its contents * must not be changed while it is in use by the returned set. * @requires all i, j: [0..array.length) | i < j => array[i].hashCode() <= array[j].hashCode * @return an unmodifiable Set view of the given array */ public static final <T> Set<T> asHashSet(final T[] array) { return new AbstractSet<T>() { public boolean contains(Object o) { return hashBinarySearch(array, o) >= 0; } public Iterator<T> iterator() { return iterate(array);} public int size() { return array.length;} }; } /** * Returns a new set that contains the asymmetric difference between the left and the right sets. * @return some s: Set<T> | s.elements = left.elements - right.elements */ public static final <T> Set<T> setDifference(Set<T> left, Set<T> right) { final Set<T> ret = new LinkedHashSet<T>(left); ret.removeAll(right); return ret; } /** * An unmodifying iterator over an array. */ private static abstract class ArrayIterator<T> implements Iterator<T> { final T[] items; final int end; int cursor; /** * Constructs a new iterator over the given array of items. * The iterator is backed by the given array. The contents * of the array are not modified by the iterator. The * constructed iterator returns the items located between the * indeces start, inclusive, and end, exclusive. * @requires items != null && * start < end => end in [0..items.length] && start in [0..end], * start in [0..items.length) && end in [-1..start] */ @SafeVarargs <E extends T> ArrayIterator(int start, int end, final E... items) { this.items = items; this.cursor = start; this.end = end; } public final void remove() { throw new UnsupportedOperationException(); } } /** * An ascending iterator over an array. */ private static final class AscendingArrayIterator<T> extends ArrayIterator<T> { /** * Constructs a new iterator over the given array of items. * @requires items != null && start < end * @throws IllegalArgumentException start < 0 || end > items.length */ <E extends T> AscendingArrayIterator(int start, int end, E[] items) { super(start, end, items); if (start < 0 || end > items.length) { throw new IllegalArgumentException("start < end && (start < 0 || end > items.length)"); } } public boolean hasNext() { return cursor >= 0 && cursor < end; } public T next() { if (!hasNext()) throw new NoSuchElementException(); return items[cursor++]; } } /** * A descending iterator over an array. */ private static final class DescendingArrayIterator<T> extends ArrayIterator<T> { /** * Constructs a new iterator over the given array of items. * @requires items != null && start > end * @throws IllegalArgumentException start >= items.length || end < -1 */ <E extends T> DescendingArrayIterator(int start, int end, E[] items) { super(start, end, items); if (start >= items.length || end < -1) { throw new IllegalArgumentException("start > end && (start >= items.length || end < -1)"); } } public boolean hasNext() { return cursor > end; } public T next() { if (!hasNext()) throw new NoSuchElementException(); return items[cursor--]; } } }