blob: a97f3a0c841afeb7423d7493ae87303b83f2b1e6 [file] [log] [blame]
/*
* 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.uima.internal.util;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.NoSuchElementException;
/**
* A Map from non-null Objects of type T to ints
* int value 0 reserved to mean object is not in the table.
* This impl is for use in a single thread case only, for table updates
* Multiple reader threads are OK if there's no writing.
*
* Supports shrinking (reallocating the big table)
*
* Removed objects replaced with special marker object in the table
* so find operations continue to work (they can't stop upon finding this object).
*
*/
public class Obj2IntIdentityHashMap<T> extends Common_hash_support {
private final T removedMarker;
private final Class<T> componentType; // for rieifying the T type
// private final float loadFactor = DEFAULT_LOAD_FACTOR;
// private final int initialCapacity;
// private int sizeWhichTriggersExpansion;
// private int size; // number of elements in the table
// private int nbrRemoved; // number of removed elements (coded as removed)
// the actual Object table
private T [] keys;
private int [] values;
// private boolean secondTimeShrinkable = false;
public Obj2IntIdentityHashMap(Class<T> clazz, T removedMarker) {
this(12, clazz, removedMarker); // default initial size
}
/**
* @param initialCapacity - you can add this many before expansion
* @param clazz - a superclass of the stored items
* @param removedMarker - a unique value never stored in the table, used to mark removed items
*/
public Obj2IntIdentityHashMap(int initialCapacity, Class<T> clazz, T removedMarker) {
super(initialCapacity);
this.componentType = clazz;
this.removedMarker = removedMarker;
newTable(this.initialCapacity);
}
// private void newTableKeepSize(int capacity) {
// keys = (T[]) Array.newInstance(componentType, capacity);
// values = new int[capacity];
// sizeWhichTriggersExpansion = (int)(capacity * loadFactor);
// nbrRemoved = 0;
// }
//
// private void incrementSize() {
// if (size + nbrRemoved >= sizeWhichTriggersExpansion) {
// increaseTableCapacity();
// }
// size++;
// }
//
// public boolean wontExpand() {
// return wontExpand(1);
// }
//
// public boolean wontExpand(int n) {
// return (size + nbrRemoved + n) < sizeWhichTriggersExpansion;
// }
//
// public int getCapacity() {
// return keys.length;
// }
//
// /**
// * This may not increase the table capacity, but may just
// * clean out the REMOVED items
// */
// private void increaseTableCapacity() {
// final int oldCapacity = getCapacity();
// // keep same capacity if just removing the "removed" markers would
// // shrink the used slots to the same they would have been had there been no removed, and
// // the capacity was doubled.
// final int newCapacity = (nbrRemoved > size) ? oldCapacity : oldCapacity << 1;
//
// final T [] oldKeys = keys;
// final int [] oldValues = values;
// if (TUNE) {System.out.println("Capacity increasing from " + oldCapacity + " to " + newCapacity);}
// newTableKeepSize(newCapacity);
// for (int i = 0; i < oldKeys.length; i++) {
// T key = oldKeys[i];
// if (key != null && key != removedMarker) {
// putInner(key, oldValues[i]);
// }
// }
// }
//
// // called by clear
// private void newTable(int capacity) {
// newTableKeepSize(capacity);
// resetTable();
// }
//
// private void resetHistogram() {
// if (TUNE) {
// histogram = new int[200];
// Arrays.fill(histogram, 0);
// maxProbe = 0;
// }
// }
//
// private void resetArray() {
// Arrays.fill(keys, null);
// Arrays.fill(values, 0);
// resetTable();
// }
//
// private void resetTable() {
// resetHistogram();
// size = 0;
// }
//
// public void clear() {
// secondTimeShrinkable = Misc.maybeShrink(
// secondTimeShrinkable, size, getCapacity(), 2, initialCapacity,
// newCapacity -> newTable(newCapacity),
// () -> resetArray());
// }
/**
* returns a position in the key/value table
* if the key is not found, then the position will be to the
* place where the key would be put upon adding (without reusing REMOVED, and the
* current internal value of keys[position] would be 0.
*
* if the key is found, then keys[position] == key
* @param obj the object to find
* @return the probeAddr in keys array - might reference a slot holding null, or the key value if found
*/
private int findPosition(final T obj) {
if (obj == null) {
throw new IllegalArgumentException("null is an invalid key");
}
return findPosition(
// key hash
Misc.hashInt(System.identityHashCode(obj)),
// is_eq_or_is_not_present
i -> keys[i] == null || keys[i] == obj,
// is_removed_key
i -> keys[i] == removedMarker);
}
// private int findPosition(final T obj, final boolean includeRemoved) {
// if (obj == null) {
// throw new IllegalArgumentException("null is an invalid key");
// }
//
// final int hash = Misc.hashInt(System.identityHashCode(obj)); // identity hash map
// int nbrProbes = 1;
// int probeDelta = 1;
// int probeAddr;
//
// final T[] localKeys = keys;
// final int bitMask = localKeys.length - 1;
// probeAddr = hash & bitMask;
// while (true) {
// final T testKey = localKeys[probeAddr];
// if (testKey == null || testKey == obj || (includeRemoved && testKey == removedMarker)) {
// break;
// }
//
// nbrProbes++;
// probeAddr = bitMask & (probeAddr + (probeDelta++));
// }
//
// if (TUNE) {
// final int pv = histogram[nbrProbes];
//
// histogram[nbrProbes] = 1 + pv;
// if (maxProbe < nbrProbes) {
// maxProbe = nbrProbes;
// }
// }
//
// return probeAddr;
// }
public boolean contains(Object obj) { // arg must be Object to fit Collection API
return (componentType.isAssignableFrom(obj.getClass())) ? (find((T) obj) != -1) : false;
}
/**
* @param obj the object to find in the table (if it is there)
* @return the position of obj in the table, or -1 if not in the table
*/
public int find(T obj) {
if (obj == null || size() == 0) {
return -1;
}
final int pos = findPosition(obj);
return (obj == keys[pos]) ? pos : -1; // == because identity hash map
}
public int get(T obj) {
if (obj == null || size() == 0) {
return 0;
}
int pos = findPosition(obj);
return values[pos];
}
/**
*
* @param obj the key
* @param value the value
* @return the previous value, or 0 if this map did not already contain the specified key
*/
public int put(T obj, int value) {
int i = findPosition(obj);
if (keys[i] == obj) { // identityHashMap
int prevValue = values[i];
values[i] = value;
return prevValue;
}
if (found_removed != -1) {
i = found_removed;
}
keys[i] = obj;
values[i] = value;
commonPutOrAddNotFound();
return 0;
}
/**
* used for increasing table size
* @param rawKey
*/
private void putInner(T obj, int value) {
final int i = findPosition(obj);
assert(keys[i] == null);
keys[i] = obj;
values[i] = value;
}
/**
* Can't replace the item with a null because other keys that were
* stored in the table which previously collided with the removed item
* won't be found. UIMA-4204
* @param rawKey the value to remove
* @return the value previously associated with the key, or 0 if none
*/
public int remove(Object rawKey) {
if (rawKey == null) {
return 0;
}
final int pos = findPosition((T) rawKey); // null or equal obj
int r = values[pos];
if (rawKey == keys[pos]) {
values[pos] = 0;
keys[pos] = removedMarker;
commonRemove();
}
return r;
}
// private int removeAtPosition(int pos) {
// // found, remove it
// keys[pos] = (T) removedMarker; // at runtime, this cast is a no-op
// int r = values[pos];
// values[pos] = 0;
// size--;
// nbrRemoved ++;
// return r;
// }
//
// public int size() {
// return size;
// }
//
// public void showHistogram() {
// if (TUNE) {
// int sumI = 0;
// for (int i : histogram) {
// sumI += i;
// }
//
// System.out.format(
// "Histogram of number of probes, loadfactor = %.1f, maxProbe=%,d nbr of find operations at last table size=%,d%n",
// loadFactor, maxProbe, sumI);
// for (int i = 0; i <= maxProbe; i++) {
// if (i == maxProbe && histogram[i] == 0) {
// System.out.println("huh?");
// }
// System.out.println(i + ": " + histogram[i]);
// }
//
// System.out.println("bytes / entry = " + (float) (keys.length) * 4 / size());
// System.out.format("size = %,d, prevExpansionTriggerSize = %,d, next = %,d%n",
// size(),
// (int) ((keys.length >>> 1) * loadFactor),
// (int) (keys.length * loadFactor));
// }
// }
// /**
// */
// /**
// * For iterator use
// * @param index - a magic number returned by the internal find
// * @return the T at that spot, or null if nothing there
// */
// public int get(int index) {
// T obj = keys[index];
// if (obj == null || obj == removedMarker) {
// return 0; // not present
// }
// return values[index];
// }
//
// /**
// * advance pos until it points to a non 0 or is 1 past end
// * @param pos -
// * @return updated pos
// */
// public int moveToNextFilled(int pos) {
// if (pos < 0) {
// pos = 0;
// }
//
// final int max = getCapacity();
// while (true) {
// if (pos >= max) {
// return pos;
// }
// Object v = keys[pos];
// if (v != null && v != removedMarker) {
// return pos;
// }
// pos ++;
// }
// }
//
// /**
// * decrement pos until it points to a non 0 or is -1
// * @param pos -
// * @return updated pos
// */
// public int moveToPreviousFilled(int pos) {
// final int max = getCapacity();
// if (pos > max) {
// pos = max - 1;
// }
//
// while (true) {
// if (pos < 0) {
// return pos;
// }
// T v = keys[pos];
// if (v != null && v != removedMarker) {
// return pos;
// }
// pos --;
// }
// }
//
private class Obj2IntIdentityHashMapIterator implements IntListIterator {
/** always keep at valid position */
private int curPosition;
private final int firstPosition;
private Obj2IntIdentityHashMapIterator() {
this.curPosition = moveToNextFilled(0);
this.firstPosition = this.curPosition;
}
@Override
public final boolean hasNext() {
return curPosition < keys_length() && curPosition >= 0;
}
@Override
public final int next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
int r = values[curPosition];
curPosition = moveToNextFilled(curPosition + 1);
return r;
}
// if uncomment this, need to add it to the IntListIterator Interface
// public void remove() {
// int pos = moveToPrevious(curPosition - 1);
// if (pos >= 0) {
// removeAtPosition(pos);
// }
// }
@Override
public final int nextNvc() {
int r = values[curPosition];
curPosition = moveToNextFilled(curPosition + 1);
return r;
}
@Override
public boolean hasPrevious() {
throw new UnsupportedOperationException();
}
@Override
public int previous() {
throw new UnsupportedOperationException();
}
@Override
public int previousNvc() {
throw new UnsupportedOperationException();
}
@Override
public void moveToStart() {
this.curPosition = firstPosition;
}
@Override
public void moveToEnd() {
this.curPosition = moveToPreviousFilled(getCapacity() - 1);
}
}
public T[] getKeys() {
T[] r = (T[]) Array.newInstance(componentType, size());
int i = 0;
for (T key : keys) {
if (key != null && key != removedMarker) {
r[i++] = key;
}
}
return r;
}
public IntListIterator iterator() {
return new Obj2IntIdentityHashMapIterator();
}
// public int moveToFirst() {
// return (size() == 0) ? -1 : moveToNextFilled(0);
// }
//
// public int moveToLast() {
// return (size() == 0) ? -1 : moveToPreviousFilled(getCapacity() -1);
// }
//
// public int moveToNext(int position) {
// if (position < 0) {
// return position;
// }
// final int n = moveToNextFilled(position + 1);
// return (n >= getCapacity()) ? -1 : n;
// }
//
// public int moveToPrevious(int position) {
// if (position >= getCapacity()) {
// return -1;
// }
// return moveToPreviousFilled(position - 1);
// }
//
// public boolean isValid(int position) {
// return (position >= 0) && (position < getCapacity());
// }
//
// /**
// * if the fs is in the set, the iterator should return it.
// * if not, move to the first - just to return something.
// * @param fs position to this fs
// * @return the index if present, otherwise moveToNextFileed(0);
// */
// public int moveTo(FeatureStructure fs) {
// if (componentType.isAssignableFrom(fs.getClass())) {
// int pos = find((T)fs);
// if (pos >= 0) {
// return pos;
// }
// }
// return moveToFirst();
// }
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return String
.format(
"%s [loadFactor=%s, initialCapacity=%s, sizeWhichTriggersExpansion=%s, size=%s, secondTimeShrinkable=%s%n keys=%s]",
this.getClass().getName(), loadFactor, initialCapacity, sizeWhichTriggersExpansion, size(), secondTimeShrinkable,
Arrays.toString(keys));
}
@Override
protected boolean is_valid_key(int pos) {
return keys[pos] != null && keys[pos] != removedMarker;
}
@Override
protected int keys_length() {
return keys.length;
}
@Override
protected void newKeysAndValues(int capacity) {
keys = (T[]) Array.newInstance(componentType, capacity);
values = new int[capacity];
}
@Override
protected void clearKeysAndValues() {
Arrays.fill(keys, null);
Arrays.fill(values, 0);
}
@Override
protected void copy_to_new_table(
/* ignored */int newCapacity,
/* ignored */int oldCapacity,
CommonCopyOld2New commonCopy) {
final T[] oldKeys = keys;
final int[] oldValues = values;
commonCopy.apply(
// copyToNew
i -> putInner(oldKeys[i], oldValues[i]),
// is_valid_old_key
i -> oldKeys[i] != null && oldKeys[i] != removedMarker
);
}
// public boolean isEmpty() {
// return size() == 0;
// }
}