All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidad.util.ArrayMap Maven / Gradle / Ivy

The newest version!
/*
 * 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.myfaces.trinidad.util;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;



/**
 * A Map implementation that stores its contents in a single
 * array.  This approach is significantly faster for small sets of
 * data than the use of a HashMap or Hashtable, though potentially
 * much slower for very large sets.
 * 

* ArrayMap is optimized for many-reads-few-write. In particular, * it reallocates its array on any insertion or deletion. *

* ArrayMap also includes a series of static methods for managing the * Object array. These may be used in place of instantiating an * ArrayMap for clients that don't need a Map implementation. * Clients using these methods must be careful to store the returned * Object array on any mutator method. They also must provide their * own synchronization, if needed. When using these static methods, * clients can opt to search for objects by identity (via * getByIdentity()) instead of equality, while the static * get() method will try identity before equality. This * latter approach is extremely fast but still safe for retrieval of * Strings that have all been interned, especially if misses are * infrequent (since misses do require a scan using Object.equals()). * It's worth remembering that String constants are always interned, * as required by the language specification. *

* @version $Name: $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/util/ArrayMap.java#0 $) $Date: 10-nov-2005.19:08:36 $ */ // -= Simon Lessard =- // Using a single array for both the key and the value leads to many // problems, especially with type safety. Using parallel arrays or // a single array containing nodes would be a much cleaner/safer idea. // =-= AdamWiner =-= // True, but the whole point of this class is maximal efficiency for // small, transient arrays. The type safety problems are entirely internal, // not exposed to clients. Parallel arrays or arrays containing nodes // would be less efficient - if you're willing to allocate bonus objects, // and don't care about efficiency, just use HashMap. public class ArrayMap extends AbstractMap implements Cloneable { /** * Creates an empty ArrayMap, preallocating nothing. */ public ArrayMap() { this(0, 1); } /** * Creates an ArrayMap, preallocating for a certain size. * @param size the number of elements to pre-allocate for */ public ArrayMap(int size) { this(size, 1); } /** * Creates an ArrayMap, preallocating for a certain size. * @param size the number of elements to pre-allocate for * @param increment the number of additional elements to * allocate for when overruning */ public ArrayMap(int size, int increment) { if ((increment < 1) || (size < 0)) throw new IllegalArgumentException(); if (size > 0) _array = new Object[2 * size]; _increment = increment; } /** * Returns the key at a specific index in the map. */ @SuppressWarnings("unchecked") public K getKey(int index) { if ((index < 0) || (index >= size())) throw new IndexOutOfBoundsException(); return (K) _array[index * 2]; } /** * Returns the value at a specific index in the map. */ @SuppressWarnings("unchecked") public V getValue(int index) { if ((index < 0) || (index >= size())) throw new IndexOutOfBoundsException(); return (V) _array[index * 2 + 1]; } /** * Gets the object stored with the given key. Scans first * by object identity, then by object equality. */ static public Object get(Object[] array, Object key) { Object o = getByIdentity(array, key); if (o != null) return o; return getByEquality(array, key); } /** * Gets the object stored with the given key, using * only object identity. */ static public Object getByIdentity(Object[] array, Object key) { if (array != null) { int length = array.length; for (int i = 0; i < length; i += 2) { if (array[i] == key) return array[i + 1]; } } return null; } /** * Gets the object stored with the given key, using * only object equality. */ static public Object getByEquality(Object[] array, Object key) { if (array != null) { int length = array.length; for (int i = 0; i < length; i += 2) { Object targetKey = array[i]; if (targetKey == null) return null; else if (targetKey.equals(key)) return array[i + 1]; } } return null; } /** * Adds the key/value pair to the array, returning a * new array if necessary. */ static public Object[] put(Object[] array, Object key, Object value) { if (array != null) { int length = array.length; for (int i = 0; i < length; i+=2) { Object curKey = array[i]; if (((curKey != null) && (curKey.equals(key))) || (curKey == key)) { array[i + 1] = value; return array; } } } return _addToArray(array, key, value, 1); } /** * Removes the value for the key from the array, returning a * new array if necessary. */ static public Object[] remove(Object[] array, Object key) { return remove(array, key, true); } /** * Removes the value for the key from the array, returning a * new array if necessary. */ static public Object[] remove(Object[] array, Object key, boolean reallocate) { if (array != null) { int length = array.length; for (int i = 0; i < length; i+=2) { Object curKey = array[i]; if (((curKey != null) && curKey.equals(key)) || (curKey == key)) { Object[] newArray = array; if (reallocate) { newArray = new Object[length - 2]; System.arraycopy(array, 0, newArray, 0, i); } System.arraycopy(array, i + 2, newArray, i, length - i - 2); if (!reallocate) { array[length - 1] = null; array[length - 2] = null; } return newArray; } } } return array; } // // GENERIC MAP API // @Override public int size() { return _size; } @Override public boolean containsValue(Object value) { int entryCount = size() * 2; for (int i = 0; i < entryCount; i += 2) { if (_equals(value, _array[i + 1])) return true; } return false; } @Override public boolean containsKey(Object key) { int entryCount = size() * 2; for (int i = 0; i < entryCount; i += 2) { if (_equals(key, _array[i])) return true; } return false; } /** * Returns an enumeration of the keys in this map. * the Iterator methods on the returned object to fetch the elements * sequentially. */ @SuppressWarnings("unchecked") public Iterator keys() { int size = _size; if (size == 0) return null; ArrayList keyList = new ArrayList(); int i = (size-1)*2; while(i>=0) { keyList.add((K) _array[i]); i=i-2; } return keyList.iterator(); } /** * Returns an Iterator of keys in the array. */ public static Iterator getKeys(Object[] array) { if (array == null) return null; ArrayList keyList = new ArrayList(); int i = array.length-2; while(i>=0) { keyList.add(array[i]); i=i-2; } return keyList.iterator(); } /** * Returns an Iterator of values in the array. */ public static Iterator getValues(Object[] array) { if (array == null) return null; ArrayList valueList = new ArrayList(); int i = array.length-1; while(i>=0) { valueList.add(array[i]); i=i-2; } return valueList.iterator(); } /** * Clones the map. */ @SuppressWarnings("unchecked") @Override public Object clone() { try { ArrayMap am = (ArrayMap) super.clone(); am._array = _array.clone(); am._size = _size; am._increment = _increment; return am; } catch (CloneNotSupportedException cnse) { // Should never reach here return null; } } @Override public Set> entrySet() { if (_entrySet == null) { _entrySet = new AbstractSet>() { @Override public int size() { return ArrayMap.this.size(); } @Override public Iterator> iterator() { return new Iterator>() { public boolean hasNext() { return (_index < ArrayMap.this.size()); } public void remove() { // remove() removes the last entry returned by next(), // not the one about to be seen; so that's actually // the entry at (_index - 1). if ((_index == 0) || _removed) throw new IllegalStateException(); _removed = true; // Shrink the array by one int size = ArrayMap.this.size(); Object[] array = ArrayMap.this._array; if (size > _index) { System.arraycopy(array, _index * 2, array, (_index - 1) * 2, (size - _index) * 2); } // Null out the last elements (for GC) array[size * 2 - 2] = null; array[size * 2 - 1] = null; ArrayMap.this._size = size - 1; // And push the index back one _index = _index - 1; } public Map.Entry next() { if (!hasNext()) throw new NoSuchElementException(); final int index = _index; _removed = false; _index = index + 1; return new Map.Entry() { public K getKey() { return ArrayMap.this.getKey(index); } public V getValue() { return ArrayMap.this.getValue(index); } public V setValue(V value) { V oldValue = getValue(); ArrayMap.this._array[index * 2 + 1] = value; return oldValue; } @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; return _equals(getKey(), e.getKey()) && _equals(getValue(), e.getValue()); } @Override public int hashCode() { Object key = getKey(); Object value = getValue(); return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode()); } }; } private int _index; private boolean _removed; }; } }; } return _entrySet; } @SuppressWarnings("unchecked") @Override public V get(Object key) { return (V) getByEquality(_array, key); //return getByIdentity(_array, key); } @SuppressWarnings("unchecked") public V getByIdentity(Object key) { return (V) getByIdentity(_array, key); } @SuppressWarnings("unchecked") @Override public V put(K key, V value) { if (value == null) return remove(key); Object[] array = _array; // Use getByEquality(). In the vast majority // of cases, the object isn't there. So getByIdentity() // will fail, and we'll call getByEquality() anyway. Object o = getByEquality(array, key); if (o == null) { int size = _size * 2; if ((array != null) && (size < array.length)) { array[size] = key; array[size + 1] = value; } else { _array = _addToArray(array, key, value, _increment); } _size = _size + 1; } else { // (Actually, I know in this case that the returned array // isn't going to change, but that'd be a good way to introduce // a bug if we ever to change the implementation) _array = put(array, key, value); } return (V) o; } @SuppressWarnings("unchecked") @Override public V remove(Object key) { Object[] array = _array; Object o = get(array, key); if (o != null) { remove(array, key, false); _size = _size - 1; } return (V) o; } /** * Removes all elements from the ArrayMap. */ @Override public void clear() { int size = _size; if (size > 0) { size = size * 2; for (int i = 0; i < size; i++) _array[i] = null; _size = 0; } } /** * Adds the key/value pair to the array, returning a * new array if necessary. */ private static Object[] _addToArray(Object[] array, Object key, Object value, int increment) { Object[] newArray = null; if (array != null) { int length = array.length; newArray = new Object[length + (2 * increment)]; System.arraycopy(array, 0, newArray, 2, length); } else { newArray = new Object[2 * increment]; } newArray[0] = key; newArray[1] = value; return newArray; } private static boolean _equals(Object a, Object b) { if (a == null) return b == null; return a.equals(b); } private Object[] _array; private int _size; private int _increment; private Set> _entrySet; }