blob: 8b208af53d0753663fec4d47ad183f6c4e4fb9d2 [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.geode.internal.admin;
/**
* A <code>ListenerIdMap</code> maps ints to an <code>Object</code>. This is an optimization because
* using a {@link java.util.HashMap} for this purposed proved to be too slow because of all of the
* {@link Integer}s that had to be created.
*/
public class ListenerIdMap {
/** The contents of the map */
protected Entry[] table;
/** The total number of mappings in the map */
private int count;
/**
* Once the number of mappings in the map exceeds the threshold, the map is rehased. The threshold
* is the capacity * loadFactor.
*/
private int threshold;
/** The load factor of the map */
private float loadFactor;
//////////////////// Constructors ////////////////////
/**
* Creates a new, empty map with the given initial capacity (number of buckets) and load factor.
*/
public ListenerIdMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException(String.format("Illegal Initial Capacity: %s",
Integer.valueOf(initialCapacity)));
}
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException(String.format("Illegal Load factor: %s",
new Float(loadFactor)));
}
if (initialCapacity == 0) {
initialCapacity = 1;
}
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int) (initialCapacity * loadFactor);
}
/**
* Creates a new, empty map with the default initial capacity (11 buckets) and load factor (0.75).
*/
public ListenerIdMap() {
this(11, 0.75f);
}
//////////////////// Instance Methods ////////////////////
/**
* Returns the number of mappings in this map
*/
public int size() {
return this.count;
}
/**
* Returns <code>true</code> if this map contains a mapping for the given key.
*
* @throws IllegalArgumentException <code>key</code> is less than zero
*/
public boolean containsKey(int key) {
Entry[] table = this.table;
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket]; e != null; e = e.next) {
if (e.key == key) {
return true;
}
}
return false;
}
/**
* Returns the object to which the given key is mapped. If no object is mapped to the given key,
* <code>null</code> is returned.
*
* @throws IllegalArgumentException <code>key</code> is less than zero
*/
public Object get(int key) {
Entry[] table = this.table;
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket]; e != null; e = e.next) {
if (e.key == key) {
return e.value;
}
}
return null;
}
/**
* Rehashes this map into a new map with a large number of buckets. It is called when the number
* of entries in the map exceeds the capacity and load factor.
*/
private void rehash() {
int oldCapacity = table.length;
Entry oldMap[] = table;
int newCapacity = oldCapacity * 2 + 1;
Entry newMap[] = new Entry[newCapacity];
threshold = (int) (newCapacity * loadFactor);
table = newMap;
for (int i = oldCapacity; i-- > 0;) {
for (Entry old = oldMap[i]; old != null;) {
Entry e = old;
old = old.next;
int index = Math.abs(e.key) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
/**
* Creates a mapping between the given key (object id) and an object. Returns the previous value,
* or <code>null</code> if there was none.
*
* @throws IllegalArgumentException <code>key</code> is less than zero
*/
public Object put(int key, Object value) {
Entry[] table = this.table;
// Is the key already in the table?
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket]; e != null; e = e.next) {
if (e.key == key) {
Object old = e.value;
e.value = value;
return old;
}
}
// Adjust the table, if necessary
if (this.count >= this.threshold) {
rehash();
table = this.table;
bucket = Math.abs(key) % table.length;
}
Entry e = new Entry();
e.key = key;
e.value = value;
e.next = table[bucket];
table[bucket] = e;
count++;
return null;
}
/**
* Removes the mapping for the given key. Returns the object to which the key was mapped, or
* <code>null</code> otherwise.
*/
public Object remove(int key) {
Entry[] table = this.table;
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket], prev = null; e != null; prev = e, e = e.next) {
if (key == e.key) {
if (prev != null)
prev.next = e.next;
else
table[bucket] = e.next;
count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
/**
* Returns all of the objects in the map
*/
public Object[] values() {
Object[] values = new Object[this.size()];
Entry[] table = this.table;
int i = 0;
for (int bucket = 0; bucket < table.length; bucket++) {
for (Entry e = table[bucket]; e != null; e = e.next) {
values[i++] = e.value;
}
}
return values;
}
/**
* Returns all of the entries in the map
*/
public ListenerIdMap.Entry[] entries() {
Entry[] entries = new Entry[this.size()];
Entry[] table = this.table;
int i = 0;
for (int bucket = 0; bucket < table.length; bucket++) {
for (Entry e = table[bucket]; e != null; e = e.next) {
entries[i++] = e;
}
}
return entries;
}
/**
* Returns an iterator over the {@link Entry}s of this map. Note that this iterator is <b>not</b>
* fail-fast. That is, it is the user's responsibility to ensure that the map does not change
* while it is being iterated over.
*/
public EntryIterator iterator() {
return new EntryIterator();
}
/////////////////////// Inner Classes ///////////////////////
/**
* Inner class that represents an entry in the map
*/
public static class Entry {
/** The key of the entry */
int key;
/** The value of the entry */
Object value;
/** The next entry in the collision chain */
Entry next;
public int getKey() {
return this.key;
}
public Object getValue() {
return this.value;
}
}
/**
* A class for iterating over the contents of an <code>ObjIdMap</code>
*/
public class EntryIterator {
/** The current collision chain we're traversing */
private int index;
/** The next Entry we'll iterate over */
private Entry next;
//////////////////// Instance Methods ////////////////////
/**
* Returns the next Entry to visit. Will return <code>null</code> after we have iterated through
* all of the entries.
*/
public Entry next() {
while (this.next == null && this.index < table.length) {
if (table[index] != null) {
this.next = table[index];
}
this.index++;
}
Entry oldNext = this.next;
if (oldNext != null) {
this.next = oldNext.next;
}
return oldNext;
}
}
}