/******************************************************************************* * Copyright (c) 2009 Centrum Wiskunde en Informatica (CWI) * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Arnold Lankamp - interfaces and implementation *******************************************************************************/ package org.rascalmpl.value.util; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; /** * This set implementation is shareable and can be easily cloned * (simple arraycopy of the entries array). * * @author Arnold Lankamp * * @param <V> * The value type. */ public final class ShareableHashSet<V> implements Set<V>, Iterable<V>{ private final static int INITIAL_LOG_SIZE = 4; private int modSize; private int hashMask; private Entry<V>[] data; private int threshold; private int load; private int currentHashCode; /** * Default constructor. */ public ShareableHashSet(){ super(); modSize = INITIAL_LOG_SIZE; int tableSize = 1 << modSize; hashMask = tableSize - 1; data = (Entry<V>[]) new Entry[tableSize]; threshold = tableSize; load = 0; currentHashCode = 0; } /** * Copy constructor. * * @param sharedHashSet * The set to copy. */ public ShareableHashSet(ShareableHashSet<V> sharedHashSet){ super(); modSize = sharedHashSet.modSize; int tableSize = 1 << modSize; hashMask = tableSize - 1; data = sharedHashSet.data.clone(); threshold = tableSize; load = sharedHashSet.load; currentHashCode = sharedHashSet.currentHashCode; } /** * Removes all the entries from this set. */ public void clear(){ modSize = INITIAL_LOG_SIZE; int tableSize = 1 << modSize; hashMask = tableSize - 1; data = (Entry<V>[]) new Entry[tableSize]; threshold = tableSize; load = 0; currentHashCode = 0; } /** * Rehashes this set. */ private void rehash(){ modSize++; int tableSize = 1 << modSize; hashMask = tableSize - 1; Entry<V>[] newData = (Entry<V>[]) new Entry[tableSize]; threshold = tableSize; Entry<V>[] oldData = data; for(int i = oldData.length - 1; i >= 0; i--){ Entry<V> entry = oldData[i]; if(entry != null){ // Determine the last unchanged entry chain. Entry<V> lastUnchangedEntryChain = entry; int newLastUnchangedEntryChainIndex = entry.hash & hashMask; Entry<V> e = entry.next; while(e != null){ int newIndex = e.hash & hashMask; if(newIndex != newLastUnchangedEntryChainIndex){ lastUnchangedEntryChain = e; newLastUnchangedEntryChainIndex = newIndex; } e = e.next; } newData[newLastUnchangedEntryChainIndex] = lastUnchangedEntryChain; // Reconstruct the other entries (if necessary). while(entry != lastUnchangedEntryChain){ int hash = entry.hash; int position = hash & hashMask; newData[position] = new Entry<>(hash, entry.value, newData[position]); entry = entry.next; } } } data = newData; } /** * Makes sure the size of the entry array and the load of the set stay in proper relation to * eachother. */ private void ensureCapacity(){ if(load > threshold){ rehash(); } } /** * Inserts the given value into this set. * * @param value * The value to insert. * @return Returns true if this set didn't contain the given value yet; false if it did. */ public boolean add(V value){ ensureCapacity(); int hash = value.hashCode(); int position = hash & hashMask; Entry<V> currentStartEntry = data[position]; // Check if the value is already in here. if(currentStartEntry != null){ Entry<V> entry = currentStartEntry; do{ if(hash == entry.hash && entry.value.equals(value)){ return false; // Return false if it's already present. } entry = entry.next; }while(entry != null); } data[position] = new Entry<>(hash, value, currentStartEntry); // Insert the new entry. load++; currentHashCode ^= hash; // Update the current hashcode of this map. return true; } /** * Checks if this set contains the given value. * * @param value * The value to check for. * @return True if this set contains the given value; false otherwise. */ public boolean contains(Object value){ int hash = value.hashCode(); int position = hash & hashMask; Entry<V> entry = data[position]; while(entry != null){ if(hash == entry.hash && value.equals(entry.value)) return true; entry = entry.next; } return false; } /** * Removes the given object from this set (if present.) * * @param value * The value to remove. * @return True if this set contained the given object; false otherwise. */ public boolean remove(Object value){ int hash = value.hashCode(); int position = hash & hashMask; Entry<V> currentStartEntry = data[position]; if(currentStartEntry != null){ Entry<V> entry = currentStartEntry; do{ if(hash == entry.hash && entry.value.equals(value)){ Entry<V> e = data[position]; data[position] = entry.next; // Reconstruct the other entries (if necessary). while(e != entry){ data[position] = new Entry<>(e.hash, e.value, data[position]); e = e.next; } load--; currentHashCode ^= hash; // Update the current hashcode of this set. return true; } entry = entry.next; }while(entry != null); } return false; } /** * Returns the number of values this set currently contains. * * @return The number of values this set currently contains. */ public int size(){ return load; } /** * Checks whether or not this set is empty. * * @return True is this set is empty; false otherwise. */ public boolean isEmpty(){ return (load == 0); } /** * Constructs an iterator for this set. * * @return An iterator for this set. * * @see java.lang.Iterable#iterator() */ public Iterator<V> iterator(){ return new SetIterator<>(data); } /** * Adds all the elements from the given collection to this set. * * @param collection * The collection that contains the elements to add. * @return True if this set changed; false if it didn't. */ public boolean addAll(Collection<? extends V> collection){ boolean changed = false; Iterator<? extends V> collectionIterator = collection.iterator(); while(collectionIterator.hasNext()){ changed |= add(collectionIterator.next()); } return changed; } /** * Checks if the collection contains all the elements in the given collection. * * @param collection * The collection that contains the elements to check for. * @return True if this set contains all the elements in the given collections; false if it * didn't. */ public boolean containsAll(Collection<?> collection){ Iterator<?> collectionIterator = collection.iterator(); while(collectionIterator.hasNext()){ if(!contains(collectionIterator.next())) return false; } return true; } /** * Removes all the elements from this set which are not present in the given collection. * * @param collection * The collection that contains the elements which need to be retained. * @return True if this set changed; false if it didn't. */ public boolean retainAll(Collection<?> collection){ boolean changed = false; Iterator<V> valuesIterator = iterator(); while(valuesIterator.hasNext()){ V value = valuesIterator.next(); if(!collection.contains(value)){ remove(value); changed = true; } } return changed; } /** * Removes all the elements in the given collection from this set. * * @param collection * The collection that contains the elements to remove from this set. * @return True if this set change; false if it didn't. */ public boolean removeAll(Collection<?> collection){ boolean changed = false; Iterator<?> collectionIterator = collection.iterator(); while(collectionIterator.hasNext()){ Object value = collectionIterator.next(); changed |= remove(value); } return changed; } /** * Returns all the elements from this set in an array. * * @return All the elements from this set in an array. */ public Object[] toArray(){ Object[] values = new Object[load]; Iterator<V> valuesIterator = iterator(); int i = 0; while(valuesIterator.hasNext()){ values[i++] = valuesIterator.next(); } return values; } /** * Returns all the elements from this set in an array. * * @param array * The array to use; in case it isn't large enough a new one will be allocated. * @return All the elements from this set in an array. */ public <T> T[] toArray(T[] array){ if(array.length < load) return (T[]) toArray(); Iterator<V> valuesIterator = iterator(); int i = 0; while(valuesIterator.hasNext()){ array[i++] = (T) valuesIterator.next(); } for(; i < load; i++){ array[i] = null; } return array; } /** * Prints the internal representation of this set to a string. * * @see java.lang.Object#toString() */ public String toString(){ StringBuilder buffer = new StringBuilder(); buffer.append('{'); for(int i = 0; i < data.length; i++){ buffer.append('['); Entry<V> e = data[i]; if(e != null){ buffer.append(e); e = e.next; while(e != null){ buffer.append(','); buffer.append(e); e = e.next; } } buffer.append(']'); } buffer.append('}'); return buffer.toString(); } /** * Returns the current hash code of this set. * * @return The current hash code of this set. * * @see java.lang.Object#hashCode() */ public int hashCode(){ return currentHashCode; } /** * Check whether or not the current content of this set is equal to that of the given object / set. * * @return True if the content of this set is equal to the given object / set. * * @see java.lang.Object#equals(Object) */ public boolean equals(Object o){ if(o == null) return false; if(o.getClass() == getClass()){ ShareableHashSet<V> other = (ShareableHashSet<V>) o; if(other.currentHashCode != currentHashCode) return false; if(other.size() != size()) return false; if(isEmpty()) return true; // No need to check if the sets are empty. Iterator<V> otherIterator = other.iterator(); while(otherIterator.hasNext()){ if(!contains(otherIterator.next())) return false; } return true; } return false; } /** * Entry, used for containing values and constructing buckets. * * @author Arnold Lankamp * * @param <V> * The value type. */ private static class Entry<V>{ public final int hash; public final V value; public final Entry<V> next; /** * Constructor. * * @param hash * The hash code of the value * @param value * The value * @param next * A reference to the next entry in the bucket (if any). */ public Entry(int hash, V value, Entry<V> next){ super(); this.hash = hash; this.value = value; this.next = next; } /** * Prints the internal representation of this entry to a string. * * @see java.lang.Object#toString() */ public String toString(){ StringBuilder buffer = new StringBuilder(); buffer.append('<'); buffer.append(value); buffer.append('>'); return buffer.toString(); } } /** * Iterator for this set. * * @author Arnold Lankamp * * @param <V> * The value type. */ private static class SetIterator<V> implements Iterator<V>{ private final Entry<V>[] data; private Entry<V> current; private int index; /** * Constructor. * * @param entries * The entries to iterator over. */ public SetIterator(Entry<V>[] entries){ super(); data = entries; index = data.length - 1; current = new Entry<>(0, null, data[index]); locateNext(); } /** * Locates the next value in the set. */ private void locateNext(){ Entry<V> next = current.next; if(next != null){ current = next; return; } for(int i = index - 1; i >= 0 ; i--){ Entry<V> entry = data[i]; if(entry != null){ current = entry; index = i; return; } } current = null; index = 0; } /** * Checks if there are more elements in this iteration. * * @see java.util.Iterator#hasNext() */ public boolean hasNext(){ return (current != null); } /** * Returns the next element in this iteration. * * @return The next element in this iteration. * @throws NoSuchElementException * Thrown if there are no more elements in this iteration when calling this * method. * * @see java.util.Iterator#next() */ public V next(){ if(!hasNext()) throw new NoSuchElementException("There are no more elements in this iteration"); V value = current.value; locateNext(); return value; } /** * Removal is not supported by this iterator. * * @throws java.lang.UnsupportedOperationException * * @see java.util.Iterator#remove() */ public void remove(){ throw new UnsupportedOperationException("This iterator doesn't support removal."); } } }