/* * 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; import org.apache.geode.internal.i18n.LocalizedStrings; import java.lang.Math; /** * 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(LocalizedStrings.ListenerIdMap_ILLEGAL_INITIAL_CAPACITY_0 .toLocalizedString(Integer.valueOf(initialCapacity))); } if (loadFactor <= 0 || Float.isNaN(loadFactor)) { throw new IllegalArgumentException(LocalizedStrings.ListenerIdMap_ILLEGAL_LOAD_FACTOR_0 .toLocalizedString(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 he is iterating over it. */ 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; } } }