/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hashtable;
import java.io.PrintStream;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
/**
* LightWeightGSet variation with double hash function
*
* @author tomasz
*
*/
public class LightWeightGSetMulti implements LightWeightSet {
static final int MAX_ARRAY_LENGTH = 1 << 30; // prevent int overflow problem
static final int MIN_ARRAY_LENGTH = 1;
private final LongInfo[] entries;
private final int hash_mask;
private int size = 0;
private volatile int modification = 0;
public LightWeightGSetMulti(final int recommended_length) {
final int actual = actualArrayLength(recommended_length);
entries = new LongInfo[actual];
hash_mask = entries.length - 1;
}
private static int actualArrayLength(int recommended) {
if (recommended > MAX_ARRAY_LENGTH) {
return MAX_ARRAY_LENGTH;
} else if (recommended < MIN_ARRAY_LENGTH) {
return MIN_ARRAY_LENGTH;
} else {
final int a = Integer.highestOneBit(recommended);
return a == recommended ? a : a << 1;
}
}
public int size() {
return size;
}
private int getIndex1(final LongInfo key) {
return key.hashCode1() & hash_mask;
}
private int getIndex2(final LongInfo key) {
return key.hashCode2() & hash_mask;
}
private LongInfo convert(final LongInfo e) {
@SuppressWarnings("unchecked")
final LongInfo r = (LongInfo) e;
return r;
}
public LongInfo get(final LongInfo key) {
// validate key
if (key == null) {
throw new NullPointerException("key == null");
}
// find element
int index = getIndex1(key);
for (LongInfo e = entries[index]; e != null; e = e.getNext()) {
if (e.equals(key)) {
return convert(e);
}
}
index = getIndex2(key);
for (LongInfo e = entries[index]; e != null; e = e.getNext()) {
if (e.equals(key)) {
return convert(e);
}
}
// element not found
return null;
}
public boolean contains(final LongInfo key) {
return get(key) != null;
}
public LongInfo put(final LongInfo element) {
// validate element
if (element == null) {
throw new NullPointerException("Null element is not supported.");
}
if (!(element instanceof LongInfo)) {
throw new IllegalArgumentException(
"!(element instanceof LinkedElement), element.getClass()="
+ element.getClass());
}
final LongInfo e = (LongInfo) element;
// find index
int index = getIndex1(element);
LongInfo existing = null;
if (entries[index] != null) {
existing = remove(index, element);
if (existing == null) {
index = getIndex2(element);
existing = remove(index, element);
}
}
e.setNext(entries[index]);
entries[index] = e;
// insert the element to the head of the linked list
modification++;
size++;
return existing;
}
private LongInfo remove(final int index, final LongInfo key) {
if (entries[index] == null) {
return null;
} else if (entries[index].equals(key)) {
// remove the head of the linked list
modification++;
size--;
final LongInfo e = entries[index];
entries[index] = e.getNext();
e.setNext(null);
return convert(e);
} else {
// head != null and key is not equal to head
// search the element
LongInfo prev = entries[index];
for (LongInfo curr = prev.getNext(); curr != null;) {
if (curr.equals(key)) {
// found the element, remove it
modification++;
size--;
prev.setNext(curr.getNext());
curr.setNext(null);
return convert(curr);
} else {
prev = curr;
curr = curr.getNext();
}
}
// element not found
return null;
}
}
public LongInfo remove(final LongInfo key) {
// validate key
if (key == null) {
throw new NullPointerException("key == null");
}
LongInfo r = remove(getIndex1(key), key);
if (r == null) {
return remove(getIndex2(key), key);
}
return r;
}
public Iterator<LongInfo> iterator() {
return new SetIterator();
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder(getClass().getSimpleName());
b.append("(size=").append(size).append(String.format(", %08x", hash_mask))
.append(", modification=").append(modification)
.append(", entries.length=").append(entries.length).append(")");
return b.toString();
}
/** Print detailed information of this object. */
public void printDetails(final PrintStream out) {
out.print(this + ", entries = [");
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
LongInfo e = entries[i];
out.print("\n " + i + ": " + e);
for (e = e.getNext(); e != null; e = e.getNext()) {
out.print(" -> " + e);
}
}
}
out.println("\n]");
}
private class SetIterator implements Iterator<LongInfo> {
/** The starting modification for fail-fast. */
private final int startModification = modification;
/** The current index of the entry array. */
private int index = -1;
/** The next element to return. */
private LongInfo next = nextNonemptyEntry();
/** Find the next nonempty entry starting at (index + 1). */
private LongInfo nextNonemptyEntry() {
for (index++; index < entries.length && entries[index] == null; index++)
;
return index < entries.length ? entries[index] : null;
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public LongInfo next() {
if (modification != startModification) {
throw new ConcurrentModificationException("modification="
+ modification + " != startModification = " + startModification);
}
final LongInfo e = convert(next);
// find the next element
final LongInfo n = next.getNext();
next = n != null ? n : nextNonemptyEntry();
return e;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove is not supported.");
}
}
}