blob: e84299aa7f102dd7312cd856b406b120b20602ee [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.framework.internal.core;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class KeyedHashSet {
protected static final int MINIMUM_SIZE = 7;
protected int elementCount = 0;
protected KeyedElement[] elements;
protected boolean replace;
private int capacity;
public KeyedHashSet() {
this(MINIMUM_SIZE, true);
}
public KeyedHashSet(boolean replace) {
this(MINIMUM_SIZE, replace);
}
public KeyedHashSet(int capacity) {
this(capacity, true);
}
public KeyedHashSet(int capacity, boolean replace) {
elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)];
this.replace = replace;
this.capacity = capacity;
}
public KeyedHashSet(KeyedHashSet original) {
elements = new KeyedElement[original.elements.length];
System.arraycopy(original.elements, 0, elements, 0, original.elements.length);
elementCount = original.elementCount;
replace = original.replace;
capacity = original.capacity;
}
/**
* Adds an element to this set. If an element with the same key already exists,
* replaces it depending on the replace flag.
* @return true if the element was added/stored, false otherwise
*/
public boolean add(KeyedElement element) {
int hash = hash(element);
// search for an empty slot at the end of the array
for (int i = hash; i < elements.length; i++) {
if (elements[i] == null) {
elements[i] = element;
elementCount++;
// grow if necessary
if (shouldGrow())
expand();
return true;
}
if (elements[i].compare(element)) {
if (replace)
elements[i] = element;
return replace;
}
}
// search for an empty slot at the beginning of the array
for (int i = 0; i < hash - 1; i++) {
if (elements[i] == null) {
elements[i] = element;
elementCount++;
// grow if necessary
if (shouldGrow())
expand();
return true;
}
if (elements[i].compare(element)) {
if (replace)
elements[i] = element;
return replace;
}
}
// if we didn't find a free slot, then try again with the expanded set
expand();
return add(element);
}
public void addAll(KeyedElement[] toAdd) {
for (int i = 0; i < toAdd.length; i++)
add(toAdd[i]);
}
public boolean contains(KeyedElement element) {
return get(element) != null;
}
public boolean containsKey(Object key) {
return getByKey(key) != null;
}
public KeyedElement[] elements() {
return (KeyedElement[]) elements(new KeyedElement[elementCount]);
}
public Object[] elements(Object[] result) {
int j = 0;
for (int i = 0; i < elements.length; i++) {
KeyedElement element = elements[i];
if (element != null)
result[j++] = element;
}
return result;
}
/**
* The array isn't large enough so double its size and rehash
* all its current values.
*/
protected void expand() {
KeyedElement[] oldElements = elements;
elements = new KeyedElement[elements.length * 2];
int maxArrayIndex = elements.length - 1;
for (int i = 0; i < oldElements.length; i++) {
KeyedElement element = oldElements[i];
if (element != null) {
int hash = hash(element);
while (elements[hash] != null) {
hash++;
if (hash > maxArrayIndex)
hash = 0;
}
elements[hash] = element;
}
}
}
/**
* Returns the set element with the given id, or null
* if not found.
*/
public KeyedElement getByKey(Object key) {
if (elementCount == 0)
return null;
int hash = keyHash(key);
// search the last half of the array
for (int i = hash; i < elements.length; i++) {
KeyedElement element = elements[i];
if (element == null)
return null;
if (element.getKey().equals(key))
return element;
}
// search the beginning of the array
for (int i = 0; i < hash - 1; i++) {
KeyedElement element = elements[i];
if (element == null)
return null;
if (element.getKey().equals(key))
return element;
}
// nothing found so return null
return null;
}
/**
* Returns the set element with the given id, or null
* if not found.
*/
public KeyedElement get(KeyedElement key) {
if (elementCount == 0)
return null;
int hash = hash(key);
// search the last half of the array
for (int i = hash; i < elements.length; i++) {
KeyedElement element = elements[i];
if (element == null)
return null;
if (element.compare(key))
return element;
}
// search the beginning of the array
for (int i = 0; i < hash - 1; i++) {
KeyedElement element = elements[i];
if (element == null)
return null;
if (element.compare(key))
return element;
}
// nothing found so return null
return null;
}
public boolean isEmpty() {
return elementCount == 0;
}
/**
* The element at the given index has been removed so move
* elements to keep the set properly hashed.
*/
protected void rehashTo(int anIndex) {
int target = anIndex;
int index = anIndex + 1;
if (index >= elements.length)
index = 0;
KeyedElement element = elements[index];
while (element != null) {
int hashIndex = hash(element);
boolean match;
if (index < target)
match = !(hashIndex > target || hashIndex <= index);
else
match = !(hashIndex > target && hashIndex <= index);
if (match) {
elements[target] = element;
target = index;
}
index++;
if (index >= elements.length)
index = 0;
element = elements[index];
}
elements[target] = null;
}
public boolean removeByKey(Object key) {
if (elementCount == 0)
return false;
int hash = keyHash(key);
for (int i = hash; i < elements.length; i++) {
KeyedElement element = elements[i];
if (element == null)
return false;
if (element.getKey().equals(key)) {
rehashTo(i);
elementCount--;
return true;
}
}
for (int i = 0; i < hash - 1; i++) {
KeyedElement element = elements[i];
if (element == null)
return false;
if (element.getKey().equals(key)) {
rehashTo(i);
elementCount--;
return true;
}
}
return true;
}
public boolean remove(KeyedElement toRemove) {
if (elementCount == 0)
return false;
int hash = hash(toRemove);
for (int i = hash; i < elements.length; i++) {
KeyedElement element = elements[i];
if (element == null)
return false;
if (element.compare(toRemove)) {
rehashTo(i);
elementCount--;
return true;
}
}
for (int i = 0; i < hash - 1; i++) {
KeyedElement element = elements[i];
if (element == null)
return false;
if (element.compare(toRemove)) {
rehashTo(i);
elementCount--;
return true;
}
}
return false;
}
private int hash(KeyedElement element) {
return Math.abs(element.getKeyHashCode()) % elements.length;
}
private int keyHash(Object key) {
return Math.abs(key.hashCode()) % elements.length;
}
public void removeAll(KeyedElement[] toRemove) {
for (int i = 0; i < toRemove.length; i++)
remove(toRemove[i]);
}
private boolean shouldGrow() {
return elementCount > elements.length * 0.75;
}
public int size() {
return elementCount;
}
public String toString() {
StringBuffer result = new StringBuffer(100);
result.append("{"); //$NON-NLS-1$
boolean first = true;
for (int i = 0; i < elements.length; i++) {
if (elements[i] != null) {
if (first)
first = false;
else
result.append(", "); //$NON-NLS-1$
result.append(elements[i]);
}
}
result.append("}"); //$NON-NLS-1$
return result.toString();
}
public int countCollisions() {
int result = 0;
int lastHash = 0;
boolean found = false;
for (int i = 0; i < elements.length; i++) {
KeyedElement element = elements[i];
if (element == null)
found = false;
else {
int hash = hash(element);
if (found)
if (lastHash == hash)
result++;
else
found = false;
else {
lastHash = hash;
found = true;
}
}
}
return result;
}
public Iterator iterator() {
return new KeyedHashSetIterator();
}
class KeyedHashSetIterator implements Iterator {
private int currentIndex = -1;
private int found;
public boolean hasNext() {
return found < elementCount;
}
public Object next() {
if (!hasNext())
throw new NoSuchElementException();
while (++currentIndex < elements.length)
if (elements[currentIndex] != null) {
found++;
return elements[currentIndex];
}
// this would mean we have less elements than we thought
throw new NoSuchElementException();
}
public void remove() {
// as allowed by the API
throw new UnsupportedOperationException();
}
}
public void clear() {
elements = new KeyedElement[Math.max(MINIMUM_SIZE, capacity * 2)];
elementCount = 0;
}
}