blob: a1d12cdc2077a54200b61e19e437d0e3b34fc559 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2012 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.common.utils;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* This implementation is similar to the {@link java.util.LinkedHashSet}, except that it does not rely on an
* underlying map but on the {@link CompactHashSet} instead.
* <p>
* <b>Note: great care must be exercised if mutable objects are inserted in this set.</b> The behavior of the
* {@link CompactLinkedHashSet} is not specified if the value of one of its content is changed in a manner
* that affects {@link Object#equals(Object)} comparisons.
* </p>
* <p>
* The {@link CompactLinkedHashSet} has been implemented to be a memory-efficient replacement for the
* {@link java.util.LinkedHashSet}. However, its speed performance, though on par with that of the
* {@link java.util.LinkedHashSet} 's in most cases, is not guaranteed to be equivalent in all use cases.
* </p>
* <p>
* The {@link java.util.LinkedHashSet} uses open hashing to resolve hash collisions, the
* {@link CompactLinkedHashSet} relies on linear probing closed hashing instead. See the documentation of
* {@link CompactHashSet} for more details on this.
* </p>
* <p>
* This class offers constant time performance for the basic {@link #add(Object)}, {@link #contains(Object)}
* and {@link #remove(Object)} operations if the {@link Object#hashCode()} function of the contained Objects
* evenly disperses the elements. Iteration over this set's elements requires time proportional to its
* internal array's length (which is equal to the next power of two greater than the highest {@link #size()}
* this set attained).
* </p>
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
* @param <E>
* Type of the elements contained by this Set.
* @since 3.1
*/
public final class CompactLinkedHashSet<E> extends CompactHashSet<E> {
/** The head of our linked list. */
transient LinkedListHeader header;
/**
* Constructs an empty set with default capacity and load factor.
*
* @see CompactHashSet#CompactHashSet()
*/
public CompactLinkedHashSet() {
super();
}
/**
* Constructs a new set containing the elements in the given collection. The set is created with the
* default load factor and an initial capacity sufficient for holding all of the values contained by the
* given collection.
*
* @param collection
* The collection which values are to be placed in the new set.
* @see CompactHashSet#CompactHashSet(Collection)
*/
public CompactLinkedHashSet(Collection<? extends E> collection) {
super(collection);
}
/**
* Constructs an empty set with an initial capacity sufficient for holding the given number of elements
* and the default load factor.
*
* @param elementCount
* The number of elements we expect to hold in the new set.
* @see CompactHashSet#CompactHashSet(int)
*/
public CompactLinkedHashSet(int elementCount) {
super(elementCount);
}
/**
* Constructs an empty set with an initial capacity sufficient for holding the given number of elements
* and the given load factor.
*
* @param elementCount
* The number of elements we expect to hold in the new set.
* @param loadFactor
* The load factor of the new set.
* @see CompactHashSet#CompactHashSet(int, float)
*/
public CompactLinkedHashSet(int elementCount, float loadFactor) {
super(elementCount, loadFactor);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.common.utils.CompactHashSet#clear()
*/
@Override
public void clear() {
super.clear();
header.next = header;
header.last = header;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.common.utils.CompactHashSet#iterator()
*/
@Override
public Iterator<E> iterator() {
return new LinkedSetIterator();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.common.utils.CompactHashSet#deleteIndex(int)
*/
@Override
protected void deleteIndex(int index) {
super.deleteIndex(index);
Entry previous = header;
Entry entry = header.next;
while (entry.index != index && entry != header) {
previous = entry;
entry = entry.next;
}
/*
* TODO entry == header would mean we haven't found the index to delete in the linked list. How could
* we be in such a state? Try and reproduce [341596].
*/
if (entry != header) {
previous.next = entry.next;
if (entry == header.last) {
header.last = previous;
}
}
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.common.utils.CompactHashSet#init()
*/
@Override
protected void init() {
header = new LinkedListHeader();
header.next = header;
header.last = header;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.common.utils.CompactHashSet#rehash(int)
*/
@SuppressWarnings("unchecked")
@Override
protected void rehash(int newCapacity) {
final int mask = newCapacity - 1;
final E[] temp = (E[])new Object[newCapacity];
for (Entry entry = header.next; entry != header; entry = entry.next) {
E value = data[entry.index];
final int hash = supplementalHash(value.hashCode());
int insertionIndex = hash & mask;
while (temp[insertionIndex] != null) {
insertionIndex = (insertionIndex + 1) & mask;
}
entry.index = insertionIndex;
temp[insertionIndex] = value;
}
data = temp;
threshold = (int)(newCapacity * loadFactor);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.common.utils.CompactHashSet#setIndex(int, java.lang.Object)
*/
@Override
protected void setIndex(int index, E element) {
super.setIndex(index, element);
Entry newEntry = new Entry(index);
header.last.next = newEntry;
header.last = newEntry;
newEntry.next = header;
}
/**
* This will serve as the header of our singly linked list.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private static final class LinkedListHeader extends Entry {
/** References the last entry of this list. */
Entry last;
/**
* Increases visibility of the default constructor.
*/
LinkedListHeader() {
super(-1);
}
}
/**
* This implementation of a linked list entry will allow us to track the insertion order of this set's
* values.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private static class Entry {
/** Index in the set's underlying array where this entry is located. */
int index;
/** Keeps a reference to the next entry of this list. */
Entry next;
/**
* Constructs a list entry given its value.
*
* @param index
* Index in the set's underlying array where this entry is located.
*/
Entry(int index) {
this.index = index;
}
}
/**
* Implementation of an {@link Iterator} for the {@link CompactLinkedHashSet}.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private final class LinkedSetIterator implements Iterator<E> {
/**
* Keeps track of the value of {@link #modCount} this iterator expects. This will allow for the
* {@link #next()} and {@link #remove()} to fail in {@link ConcurrentModificationException} fast
* whenever the set has been modified without using this iterator's {@link #remove()} method.
*/
private int expectedModCount = modCount;
/** Last element returned by a call to {@link #next()}. */
private Entry lastReturned;
/** Next element to be returned by this iterator. */
private Entry nextElem;
/**
* Default constructor.
*/
LinkedSetIterator() {
nextElem = header.next;
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return nextElem != header;
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#next()
*/
public E next() {
checkComodification();
if (nextElem == header) {
throw new NoSuchElementException();
}
E result = data[nextElem.index];
lastReturned = nextElem;
nextElem = nextElem.next;
return unmaskNull(result);
}
/**
* {@inheritDoc}
*
* @see java.util.Iterator#remove()
*/
public void remove() {
if (lastReturned == null) {
throw new IllegalStateException();
}
checkComodification();
deleteIndex(lastReturned.index);
expectedModCount++;
lastReturned = null;
}
/**
* Checks for concurrent modifications.
*
* @throws ConcurrentModificationException
* Thrown if the set this iterator traverses has been concurrently modified.
*/
private void checkComodification() throws ConcurrentModificationException {
if (expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
}