blob: e567269596eb1f6d1b7f092d6357ae52bd0ca9ad [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.netbeans.lib.editor.util;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Map implementation that allows to define the class that implements
* the <code>Map.Entry</code>.
* <br/>
* The present implementation does not allow <code>null</code> to be used
* as a key in the map. The client may use NULL_KEY masking on its own side
* if it wants to use null keys (see <code>java.util.HashMap</code> impl).
* <br/>
* The load factor is fixed to <code>1.0</code> but if the approximate map size
* is known there is a constructor that allows to pass the initial capacity.
* <br/>
* There are no additional attempts to generally improve hashing for poor hashing functions
* like those in <code>HashMap.hash()</code>.
* <br/>
* The iterators produced by this map are not fail-fast - they will continue iteration
* and their behavior is generally undefined after the modification.
* The caller should ensure that there will be no pending iterators during modification
* of this map.
* <br/>
* When iterating through entries in a bucket the <code>Object.equals()</code>
* is used for comparison.
* <br/>
*
* @author Miloslav Metelka
* @version 1.00
*/
public class CompactMap<K,V> implements Map<K,V> {
// Empty array would fail with present impl
private MapEntry<K,V>[] table;
private int size;
public CompactMap() {
table = allocateTableArray(1);
}
public CompactMap(int initialCapacity) {
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity) {
capacity <<= 1;
}
table = allocateTableArray(capacity);
}
public V get(Object key) {
MapEntry<K,V> e = findEntry(key);
return (e != null) ? e.getValue() : null;
}
/**
* Get an entry from a bucket that corresponds to the given hash code.
* <br/>
* This may be useful if the hash code can be computed without creating
* an actual object being stored in the map. The target object should
* provide some method for comparing itself to the passed arguments that represent
* the contained data.
*
* @param hashCode computed hash code.
* @return first entry in the particular bucket or null if there are no entries
* for the given hash code. The caller may need to iterate through the linked
* list of the entries to find the right entry.
*/
public MapEntry<K,V> getFirstEntry(int hashCode) {
return table[hashCode & (table.length - 1)];
}
public boolean containsKey(Object key) {
MapEntry<K,V> e = findEntry(key);
return (e != null);
}
public boolean containsValue(Object value) {
for (int i = table.length - 1; i >= 0 ; i--) {
for (MapEntry<K,V> e = table[i]; e != null; e = e.nextMapEntry()) {
if ((value == null && e.getValue() == null)
|| (value != null && value.equals(e.getValue()))
) {
return true;
}
}
}
return false;
}
/**
* Put the given entry into the map.
* <br/>
* The given entry should only be added to one compact map instance.
* <br/>
* Adding a single entry into multiple compact maps will break
* internal consistency of all the intended maps!
* <br/>
* If there will be an existing entry with a key that equals to the key
* in the entry parameter then the original entry will be replaced
* by the given entry.
*/
public MapEntry<K,V> putEntry(MapEntry<K,V> entry) {
Object key = entry.getKey();
int hash = key.hashCode();
int tableIndex = hash & (table.length - 1);
entry.setKeyHashCode(hash);
MapEntry<K,V> e = table[tableIndex];
MapEntry<K,V> prevEntry = null;
while (e != null) {
if (e == entry) { // Entry already added => do nothing
return entry;
}
if (hash == e.keyHashCode() && (key == e.getKey() || key.equals(e.getKey()))) {
// Found the entry -> replace it
if (prevEntry == null) {
table[tableIndex] = entry;
} else {
prevEntry.setNextMapEntry(entry);
}
entry.setNextMapEntry(e.nextMapEntry());
e.setNextMapEntry(null);
return e;
}
prevEntry = e;
e = e.nextMapEntry();
}
// Not found in present table => add the entry
addEntry(entry, tableIndex);
return null; // nothing replaced
}
public V put(K key, V value) {
int hash = key.hashCode();
int tableIndex = hash & (table.length - 1);
MapEntry<K,V> e = table[tableIndex];
while (e != null) {
if (hash == e.keyHashCode() && (key == e.getKey() || key.equals(e.getKey()))) {
// Found the entry
V oldValue = e.getValue();
e.setValue(value);
return oldValue;
}
e = e.nextMapEntry();
}
// Not found in present table => add the entry
e = new DefaultMapEntry<K,V>(key);
e.setValue(value);
e.setKeyHashCode(hash);
addEntry(e, tableIndex);
return null;
}
public void putAll(Map<? extends K,? extends V> map) {
for (Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
put(e.getKey(), e.getValue());
}
}
public V remove(Object key) {
MapEntry<K,V> e = removeEntryForKey(key);
return (e != null) ? e.getValue() : null;
}
/**
* Remove the given entry from the map.
* <br/>
* This method will search for the entry instance (not its key)
* so the given entry must be physically used in the map
* otherwise this method will not do anything.
*/
public MapEntry<K,V> removeEntry(MapEntry<K,V> entry) {
int hash = entry.keyHashCode();
int tableIndex = hash & (table.length - 1);
MapEntry<K,V> e = table[tableIndex];
MapEntry<K,V> prev = null;
while (e != null) {
if (e == entry) {
if (prev == null) {
table[tableIndex] = e.nextMapEntry();
} else {
prev.setNextMapEntry(e.nextMapEntry());
}
entry.setNextMapEntry(null);
size--;
return entry;
}
prev = entry;
entry = entry.nextMapEntry();
}
return null;
}
public void clear() {
// Retain present table array
for (int i = table.length - 1; i >= 0; i--) {
MapEntry<K,V> e = table[i];
table[i] = null;
// Unbind entries
while (e != null) {
MapEntry<K,V> next = e.nextMapEntry();
e.setNextMapEntry(null);
e = next;
}
}
size = 0;
}
public final int size() {
return size;
}
public boolean isEmpty() {
return (size() == 0);
}
public Set<Entry<K,V>> entrySet() {
return new EntrySet();
}
public Collection<V> values() {
throw new IllegalStateException("Not yet implemented");
}
public Set<K> keySet() {
throw new IllegalStateException("Not yet implemented");
}
private MapEntry<K,V> findEntry(Object key) {
int hash = key.hashCode();
int tableIndex = hash & (table.length - 1);
MapEntry<K,V> e = table[tableIndex];
while (e != null) {
if (hash == e.keyHashCode() && (key == e.getKey() || key.equals(e.getKey()))) {
return e;
}
e = e.nextMapEntry();
}
return null;
}
private void addEntry(MapEntry<K,V> entry, int tableIndex) {
entry.setNextMapEntry(table[tableIndex]);
table[tableIndex] = entry;
size++;
if (size > table.length) { // Fill factor is 1.0
MapEntry<K,V>[] newTable = allocateTableArray(Math.max(table.length << 1, 4));
for (int i = table.length - 1; i >= 0; i--) {
entry = table[i];
while (entry != null) {
MapEntry<K,V> next = entry.nextMapEntry();
int newIndex = entry.keyHashCode() & (newTable.length - 1);
entry.setNextMapEntry(newTable[newIndex]);
newTable[newIndex] = entry;
entry = next;
}
}
table = newTable;
}
}
private MapEntry<K,V> removeEntryForKey(Object key) {
int hash = key.hashCode();
int tableIndex = hash & (table.length - 1);
MapEntry<K,V> e = table[tableIndex];
MapEntry<K,V> prev = null;
while (e != null) {
if (hash == e.keyHashCode() && (key == e.getKey() || key.equals(e.getKey()))) {
if (prev == null) {
table[tableIndex] = e.nextMapEntry();
} else {
prev.setNextMapEntry(e.nextMapEntry());
}
e.setNextMapEntry(null);
size--;
return e;
}
prev = e;
e = e.nextMapEntry();
}
return null;
}
@SuppressWarnings("unchecked")
private MapEntry<K,V>[] allocateTableArray(int capacity) {
return new MapEntry[capacity];
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("{");
Iterator i = entrySet().iterator();
boolean hasNext = i.hasNext();
while (hasNext) {
Map.Entry e = (Map.Entry)i.next();
Object key = e.getKey();
Object value = e.getValue();
if (key == this)
buf.append("(this Map)");
else
buf.append(key);
buf.append("=");
if (value == this)
buf.append("(this Map)");
else
buf.append(value);
hasNext = i.hasNext();
if (hasNext)
buf.append(", ");
}
buf.append("}");
return buf.toString();
}
/**
* Abstract implementation of the map entry.
* <br/>
* This is suitable for the cases when e.g. the value is a primitive
* data type.
*/
public abstract static class MapEntry<K,V> implements Map.Entry<K,V> {
private MapEntry<K,V> nextMapEntry; // 12 bytes (8-Object + 4)
private int keyHashCode; // 16 bytes
public abstract K getKey();
public abstract V getValue();
public abstract V setValue(V value);
/**
* Used by {@link #hashCode()} to return the real hashCode for the value
* held by this map entry.
* <br/>
* <code>getValue().hashCode</code> cannot be used in general because
* <code>getValue()</code> may return <code>this</code> e.g. in case
* the value is represented by primitive data type. The method should
* use that real value for the hash code computation.
*/
protected abstract int valueHashCode();
/**
* Used by {@link #equals(Object)} to check whether the value
* held by this map entry equals to the the given value.
* <br/>
* <code>getValue().equals()</code> cannot be used in general because
* <code>getValue()</code> may return <code>this</code> e.g. in case
* the value is represented by primitive data type. The method should
* use that real value for the operation of this method.
*
* @param value2 value to be compared with value stored in this entry.
* The argument may be null.
*/
protected abstract boolean valueEquals(Object value2);
/**
* Get next map entry linked to this one.
* <br/>
* This method may be useful after using {@link #getFirstEntry(int)}.
*/
public final MapEntry<K,V> nextMapEntry() {
return nextMapEntry;
}
final void setNextMapEntry(MapEntry<K,V> next) {
this.nextMapEntry = next;
}
/**
* Get stored hash code of the key contained in this entry.
* <br/>
* This method may be useful after using {@link #getFirstEntry(int)}
* to quickly exclude entries which hash code differs from the requested one.
*/
public final int keyHashCode() {
return keyHashCode;
}
final void setKeyHashCode(int keyHashCode) {
this.keyHashCode = keyHashCode;
}
/**
* Implementation that adheres to {@link java.util.Map.Entry#hashCode()} contract.
*/
@Override
public final int hashCode() {
// keyHashCode() cannot be used always as the entry is possibly not yet contained in a map
int keyHash = (keyHashCode != 0) ? keyHashCode : getKey().hashCode();
return keyHash ^ valueHashCode();
}
/**
* Implementation that adheres to {@link java.util.Map.Entry#equals(Object)} contract.
*/
@Override
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry e = (Map.Entry)o;
K key = getKey();
Object key2 = e.getKey();
// Note: update needed if this map would allow for null keys
if (key == key2 || key.equals(key2)) {
return valueEquals(e.getValue());
}
}
return false;
}
}
/**
* Default implementation of the map entry similar to e.g. the <code>HashMap</code>.
* <br/>
* It may be extended as well if the should be an additional information stored
* in the map entry.
*/
public static class DefaultMapEntry<K,V> extends MapEntry<K,V> {
private K key; // 20 bytes
private V value; // 24 bytes
public DefaultMapEntry(K key) {
this.key = key;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
protected final int valueHashCode() {
return (value != null) ? value.hashCode() : 0;
}
protected final boolean valueEquals(Object value2) {
return (value == value2 || (value != null && value.equals(value2)));
}
@Override
public String toString() {
return "key=" + getKey() + ", value=" + getValue(); // NOI18N
}
}
private final class EntrySet extends AbstractSet<Entry<K,V>> {
public Iterator<Entry<K,V>> iterator() {
return new EntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
@SuppressWarnings("unchecked")
Map.Entry<K,V> e = (Map.Entry<K,V>)o;
MapEntry<K,V> candidate = findEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
@Override
public boolean remove(Object o) {
@SuppressWarnings("unchecked")
MapEntry<K,V> e = (MapEntry<K,V>)o;
return removeEntry(e) != null;
}
public int size() {
return CompactMap.this.size();
}
public void clear() {
CompactMap.this.clear();
}
}
private abstract class HashIterator {
MapEntry<K,V> next; // next entry to return
int index; // current slot
MapEntry<K,V> current; // current entry
HashIterator() {
MapEntry<K,V>[] t = table;
int i = t.length;
MapEntry<K,V> n = null;
if (size != 0) { // advance to first entry
while (i > 0 && (n = t[--i]) == null)
;
}
next = n;
index = i;
}
public boolean hasNext() {
return next != null;
}
MapEntry<K,V> nextEntry() {
MapEntry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
MapEntry<K,V> n = e.nextMapEntry();
MapEntry<K,V>[] t = table;
int i = index;
while (n == null && i > 0)
n = t[--i];
index = i;
next = n;
return current = e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
Object k = current.getKey();
current = null;
removeEntryForKey(k);
}
}
private final class ValueIterator extends HashIterator implements Iterator<V> {
public V next() {
return nextEntry().getValue();
}
}
private final class KeyIterator extends HashIterator implements Iterator<K> {
public K next() {
return nextEntry().getKey();
}
}
private final class EntryIterator extends HashIterator implements Iterator<Entry<K,V>> {
public Entry<K,V> next() {
return nextEntry();
}
}
}