package net.kennux.cubicworld.util; import java.lang.reflect.Array; /** * <pre> * The object map holds multiple objects and maps them to a key. * The main difference between HashMap and ObjectMap is ObjectMap uses the * Equals() function to check if a key is similar to the given one. * * Only use this map if you cannot use a hashmap! * * This class is fully thread-safe! * </pre> * * @author KennuX * */ public class ObjectMap<K, V> { /** * Gets locked if anything accesses keys or values array. */ private Object lockObject = new Object(); /** * Contains all keys stored in this map. * The index of a key maps to an object in the values array. */ private K[] keys; /** * Contains all valus stored in this map. * The index of a value maps to an object in the keys array. */ private V[] values; /** * The base size defines how big the array will get initialized and * extended. * So if the baseSize is 128, after adding 128 elements the arrays will get * resized to 256. */ private final int baseSize = 128; /** * The class of the key's object type. * Used to call Array.newInstance() * TODO Maybe don't use Array.newInstance? */ private Class<?> keyClass; /** * The class of the value's object type. * Used to call Array.newInstance() * TODO Maybe don't use Array.newInstance? */ private Class<?> valueClass; @SuppressWarnings("unchecked") public ObjectMap(Class<?> keyClass, Class<?> valueClass) { // Init array this.keys = (K[]) Array.newInstance(keyClass, this.baseSize); this.values = (V[]) Array.newInstance(valueClass, this.baseSize); // Set classes this.keyClass = keyClass; this.valueClass = valueClass; } /** * Checks if this map contains the given key. * * @param key * @return */ public boolean containsKey(K key) { synchronized (this.lockObject) { return this.findIndex(key) != -1; } } /** * Extends the arrays keys and values. * It will recreate them with their current size + baseSize and copy the old * content into the new ones. */ @SuppressWarnings("unchecked") private void extendArrays() { int newSize = this.keys.length + this.baseSize; K[] newKeysArray = (K[]) Array.newInstance(this.keyClass, newSize); V[] newValuesArray = (V[]) Array.newInstance(this.valueClass, newSize); System.arraycopy(this.keys, 0, newKeysArray, 0, this.keys.length); System.arraycopy(this.values, 0, newValuesArray, 0, this.values.length); this.keys = newKeysArray; this.values = newValuesArray; } /** * Returns a free index to store a new object. * If the keys and values arrays are full it will extend them by baseSize. * * @return */ private int findFreeIndex() { int freeIndex = -1; // Search free index for (int i = 0; i < this.keys.length; i++) { if (this.keys[i] == null) { freeIndex = i; break; } } // We got one? if (freeIndex == -1) { // Nope... extend the arrays! freeIndex = this.keys.length; this.extendArrays(); } return freeIndex; } /** * Finds the index for the given key. * Will return -1 if the object was not found. * * @param key * @return */ private int findIndex(K key) { for (int i = 0; i < this.keys.length; i++) { // Check for equality if (this.keys[i] != null && this.keys[i].equals(key)) { return i; } } return -1; } /** * Returns the value saved to this map for key. * Returns null if there is no value for the given key. * * @param key */ public V get(K key) { synchronized (this.lockObject) { // Find the index int index = this.findIndex(key); // Wasn't it found? if (index == -1) return null; return this.values[index]; } } /** * Clones the keys array and returns it. * * @return */ public K[] getKeys() { synchronized (this.lockObject) { return this.keys.clone(); } } /** * Clones the values array and returns it. * * @return */ public V[] getValues() { synchronized (this.lockObject) { return this.values.clone(); } } /** * Puts the given value with the given key to this map. * * @param key * @param value */ public void put(K key, V value) { synchronized (this.lockObject) { int freeIndex = this.findFreeIndex(); // Set data this.keys[freeIndex] = key; this.values[freeIndex] = value; } } }