diff --git a/src/main/java/io/usethesource/vallang/impl/persistent/PersistentHashIndexedBinaryRelation.java b/src/main/java/io/usethesource/vallang/impl/persistent/PersistentHashIndexedBinaryRelation.java index 62d7c6472..399656704 100644 --- a/src/main/java/io/usethesource/vallang/impl/persistent/PersistentHashIndexedBinaryRelation.java +++ b/src/main/java/io/usethesource/vallang/impl/persistent/PersistentHashIndexedBinaryRelation.java @@ -42,6 +42,7 @@ import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; import io.usethesource.vallang.IWriter; +import io.usethesource.vallang.impl.util.collections.ShareableValuesHashSet; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.util.AbstractTypeBag; import io.usethesource.vallang.util.RotatingQueue; @@ -593,13 +594,13 @@ private java.util.Set computeClosureDelta() { iLefts.put(leftValues); interestingLeftSides.put(key, leftValues); - rightValues = new HashSet<>(); + rightValues = new ShareableValuesHashSet(); potentialRightSides.put(key, rightValues); } leftValues.put(value); if (rightValues == null) { - rightValues = new HashSet<>(); + rightValues = new ShareableValuesHashSet(); } rightValues.add(value); @@ -609,7 +610,7 @@ private java.util.Set computeClosureDelta() { int nextSize = 0; // Compute - final java.util.Set newTuples = new HashSet<>(); + final java.util.Set newTuples = new ShareableValuesHashSet(); do { Map> rightSides = potentialRightSides; potentialRightSides = new HashMap<>(); diff --git a/src/main/java/io/usethesource/vallang/impl/util/collections/ShareableValuesHashSet.java b/src/main/java/io/usethesource/vallang/impl/util/collections/ShareableValuesHashSet.java new file mode 100644 index 000000000..77bff52d9 --- /dev/null +++ b/src/main/java/io/usethesource/vallang/impl/util/collections/ShareableValuesHashSet.java @@ -0,0 +1,509 @@ +/******************************************************************************* +* 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 io.usethesource.vallang.impl.util.collections; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import io.usethesource.vallang.IValue; +import io.usethesource.vallang.impl.persistent.ValueFactory; + +/** + * A specialized version of the ShareableSet, specifically meant for storing values. + * + * @author Arnold Lankamp + */ +public final class ShareableValuesHashSet implements Set, Iterable{ + private final static int INITIAL_LOG_SIZE = 4; + + private int modSize; + private int hashMask; + + private Entry[] data; + + private int threshold; + + private int load; + + private int currentHashCode; + + @SuppressWarnings("unchecked") + public ShareableValuesHashSet(){ + super(); + + modSize = INITIAL_LOG_SIZE; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + data = (Entry[]) new Entry[tableSize]; + + threshold = tableSize; + + load = 0; + + currentHashCode = 0; + } + + public ShareableValuesHashSet(ShareableValuesHashSet shareableValuesHashSet){ + super(); + + modSize = shareableValuesHashSet.modSize; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + data = shareableValuesHashSet.data.clone(); + + threshold = tableSize; + + load = shareableValuesHashSet.load; + + currentHashCode = shareableValuesHashSet.currentHashCode; + } + + @SuppressWarnings("unchecked") + public void clear(){ + modSize = INITIAL_LOG_SIZE; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + data = (Entry[]) new Entry[tableSize]; + + threshold = tableSize; + + load = 0; + + currentHashCode = 0; + } + + @SuppressWarnings("unchecked") + private void rehash(){ + modSize++; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + Entry[] newData = (Entry[]) new Entry[tableSize]; + + threshold = tableSize; + + Entry[] oldData = data; + for(int i = oldData.length - 1; i >= 0; i--){ + Entry entry = oldData[i]; + + if(entry != null){ + // Determine the last unchanged entry chain. + Entry lastUnchangedEntryChain = entry; + int newLastUnchangedEntryChainIndex = entry.hash & hashMask; + + Entry 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; + } + + private void ensureCapacity(){ + if(load > threshold){ + rehash(); + } + } + + public boolean addTuple(IValue... fields) { + return add(ValueFactory.getInstance().tuple(fields)); + } + + public boolean add(IValue value){ + ensureCapacity(); + + int hash = value.hashCode(); + int position = hash & hashMask; + + Entry currentStartEntry = data[position]; + // Check if the value is already in here. + if(currentStartEntry != null){ + Entry 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; + } + + public boolean contains(Object object){ + IValue value = (IValue) object; + + int hash = value.hashCode(); + int position = hash & hashMask; + + Entry entry = data[position]; + while(entry != null){ + if(hash == entry.hash && value.equals(entry.value)) return true; + + entry = entry.next; + } + + return false; + } + + public boolean containsMatch(Object object){ + IValue value = (IValue) object; + + int hash = value.hashCode(); + int position = hash & hashMask; + + Entry entry = data[position]; + while(entry != null){ + if(hash == entry.hash && value.match(entry.value)) { + return true; + } + + entry = entry.next; + } + + for (Entry e : data) { + if (e != null && value.match(e.value)) { + return true; + } + } + + return false; + } + + public boolean remove(Object object){ + IValue value = (IValue) object; + + int hash = value.hashCode(); + int position = hash & hashMask; + + Entry currentStartEntry = data[position]; + if(currentStartEntry != null){ + Entry entry = currentStartEntry; + do{ + if(hash == entry.hash && entry.value.equals(value)){ + Entry 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; + } + + public int size(){ + return load; + } + + public boolean isEmpty(){ + return (load == 0); + } + + public Iterator iterator(){ + return new SetIterator(data); + } + + public boolean addAll(Collection collection){ + boolean changed = false; + + Iterator collectionIterator = collection.iterator(); + while(collectionIterator.hasNext()){ + changed |= add(collectionIterator.next()); + } + + return changed; + } + + public boolean containsAll(Collection collection){ + Iterator collectionIterator = collection.iterator(); + while(collectionIterator.hasNext()){ + if(!contains(collectionIterator.next())) return false; + } + + return true; + } + + public boolean retainAll(Collection collection){ + boolean changed = false; + + Iterator valuesIterator = iterator(); + while(valuesIterator.hasNext()){ + IValue value = valuesIterator.next(); + if(!collection.contains(value)){ + remove(value); + + changed = true; + } + } + + return changed; + } + + public boolean removeAll(Collection collection){ + boolean changed = false; + + Iterator collectionIterator = collection.iterator(); + while(collectionIterator.hasNext()){ + Object value = collectionIterator.next(); + changed |= remove(value); + } + + return changed; + } + + public Object[] toArray(){ + Object[] values = new Object[load]; + + Iterator valuesIterator = iterator(); + int i = 0; + while(valuesIterator.hasNext()){ + values[i++] = valuesIterator.next(); + } + + return values; + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] array){ + if(array.length < load) return (T[]) toArray(); + + Iterator valuesIterator = iterator(); + int i = 0; + while(valuesIterator.hasNext()){ + array[i++] = (T) valuesIterator.next(); + } + + for(; i < load; i++){ + array[i] = null; + } + + return array; + } + + public String toString(){ + StringBuilder buffer = new StringBuilder(); + + buffer.append('{'); + for(int i = 0; i < data.length; i++){ + buffer.append('['); + Entry 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(); + } + + public int hashCode(){ + return currentHashCode; + } + + public boolean isEqual(ShareableValuesHashSet other){ + if(other == null) return false; + + 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 otherIterator = other.iterator(); + while(otherIterator.hasNext()){ + if(!contains(otherIterator.next())) return false; + } + return true; + } + + public boolean match(ShareableValuesHashSet other){ + if(other == null) return false; + + if(other.size() != size()) return false; + + if(isEmpty()) return true; // No need to check if the sets are empty. + + Iterator otherIterator = other.iterator(); + while(otherIterator.hasNext()){ + if(!containsMatch(otherIterator.next())) return false; + } + return true; + } + + private boolean containsTruelyEqual(IValue value){ + int hash = value.hashCode(); + int position = hash & hashMask; + + Entry entry = data[position]; + while(entry != null){ + if(hash == entry.hash && value.equals(entry.value)) return true; + + entry = entry.next; + } + + return false; + } + + public boolean equals(@Nullable Object o){ + if(o == null) { + return false; + } + + if(o.getClass() == getClass()){ + ShareableValuesHashSet other = (ShareableValuesHashSet) 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 otherIterator = other.iterator(); + while(otherIterator.hasNext()){ + if(!containsTruelyEqual(otherIterator.next())) return false; + } + return true; + } + + return false; + } + + private static class Entry{ + public final int hash; + public final V value; + + public final Entry next; + + public Entry(int hash, V value, Entry next){ + super(); + + this.hash = hash; + this.value = value; + + this.next = next; + } + + public String toString(){ + StringBuilder buffer = new StringBuilder(); + + buffer.append('<'); + buffer.append(value); + buffer.append('>'); + + return buffer.toString(); + } + } + + private static class SetIterator implements Iterator{ + private final Entry[] data; + + private Entry current; + private int index; + + public SetIterator(Entry[] entries){ + super(); + + data = entries; + + index = data.length - 1; + current = new Entry<>(0, null, data[index]); + locateNext(); + } + + private void locateNext(){ + Entry next = current.next; + if(next != null){ + current = next; + return; + } + + for(int i = index - 1; i >= 0 ; i--){ + Entry entry = data[i]; + if(entry != null){ + current = entry; + index = i; + return; + } + } + + current = null; + index = 0; + } + + public boolean hasNext(){ + return (current != null); + } + + public IValue next(){ + if(!hasNext()) throw new NoSuchElementException("There are no more elements in this iteration"); + + IValue value = current.value; + locateNext(); + + return value; + } + + public void remove(){ + throw new UnsupportedOperationException("This iterator doesn't support removal."); + } + } +} diff --git a/src/main/java/io/usethesource/vallang/util/ShareableHashSet.java b/src/main/java/io/usethesource/vallang/util/ShareableHashSet.java new file mode 100644 index 000000000..9d9e23db9 --- /dev/null +++ b/src/main/java/io/usethesource/vallang/util/ShareableHashSet.java @@ -0,0 +1,624 @@ +/******************************************************************************* +* 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 io.usethesource.vallang.util; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * This set implementation is shareable and can be easily cloned + * (simple arraycopy of the entries array). + * + * @author Arnold Lankamp + * + * @param + * The value type. + */ +public final class ShareableHashSet implements Set, Iterable{ + private final static int INITIAL_LOG_SIZE = 4; + + private int modSize; + private int hashMask; + + private Entry[] data; + + private int threshold; + + private int load; + + private int currentHashCode; + + /** + * Default constructor. + */ + @SuppressWarnings("unchecked") + public ShareableHashSet(){ + super(); + + modSize = INITIAL_LOG_SIZE; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + data = (Entry[]) new Entry[tableSize]; + + threshold = tableSize; + + load = 0; + + currentHashCode = 0; + } + + /** + * Copy constructor. + * + * @param sharedHashSet + * The set to copy. + */ + public ShareableHashSet(ShareableHashSet 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. + */ + @SuppressWarnings("unchecked") + public void clear(){ + modSize = INITIAL_LOG_SIZE; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + data = (Entry[]) new Entry[tableSize]; + + threshold = tableSize; + + load = 0; + + currentHashCode = 0; + } + + /** + * Rehashes this set. + */ + private void rehash(){ + modSize++; + int tableSize = 1 << modSize; + hashMask = tableSize - 1; + @SuppressWarnings("unchecked") + Entry[] newData = (Entry[]) new Entry[tableSize]; + + threshold = tableSize; + + Entry[] oldData = data; + for(int i = oldData.length - 1; i >= 0; i--){ + Entry entry = oldData[i]; + + if(entry != null){ + // Determine the last unchanged entry chain. + Entry lastUnchangedEntryChain = entry; + int newLastUnchangedEntryChainIndex = entry.hash & hashMask; + + Entry 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 currentStartEntry = data[position]; + // Check if the value is already in here. + if(currentStartEntry != null){ + Entry 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 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 currentStartEntry = data[position]; + if(currentStartEntry != null){ + Entry entry = currentStartEntry; + do{ + if(hash == entry.hash && entry.value.equals(value)){ + Entry 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 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 collection){ + boolean changed = false; + + Iterator 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 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 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. + */ + @SuppressWarnings("unchecked") + public T[] toArray(T[] array){ + if(array.length < load) return (T[]) toArray(); + + Iterator 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 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) + */ + @Override + public boolean equals(@Nullable Object o){ + if (o == null) { + return false; + } + + if (o.getClass() == getClass()){ + ShareableHashSet other = (ShareableHashSet) 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 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 + * The value type. + */ + private static class Entry{ + public final int hash; + public final V value; + + public final Entry 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 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 + * The value type. + */ + private static class SetIterator implements Iterator{ + private final Entry[] data; + + private Entry current; + private int index; + + /** + * Constructor. + * + * @param entries + * The entries to iterator over. + */ + public SetIterator(Entry[] 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 next = current.next; + if(next != null){ + current = next; + return; + } + + for(int i = index - 1; i >= 0 ; i--){ + Entry 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."); + } + } +}