/*
* Copyright 2004-2012 the Seasar Foundation and the Others.
*
* Licensed 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.seasar.util.collection;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.seasar.util.exception.SNoSuchElementException;
import static org.seasar.util.misc.AssertionUtil.*;
/**
* 配列の性質を併せ持つ {@link Map}です。
*
* @author higa
* @param <K>
* キーの型
* @param <V>
* 値の型
*
*/
public class ArrayMap<K, V> extends AbstractMap<K, V> implements Map<K, V>,
Cloneable, Externalizable {
private static final long serialVersionUID = 1L;
/** 初期容量のデフォルト値 */
public static final int INITIAL_CAPACITY = 17;
/** 負荷係数のデフォルト値 */
public static final float LOAD_FACTOR = 0.75f;
/** 負荷係数 */
protected transient int threshold;
/** マップとしてのエントリ */
protected transient Entry<K, V>[] mapTable;
/** 配列としてのエントリ */
protected transient Entry<K, V>[] listTable;
/** 要素数 */
protected transient int size = 0;
/** {@link Set}としてのビュー */
protected transient Set<? extends Map.Entry<K, V>> entrySet = null;
/**
* デフォルトの初期容量を持つインスタンスを構築します。
*/
public ArrayMap() {
this(INITIAL_CAPACITY);
}
/**
* 指定された初期容量を持つインスタンスを構築します。
*
* @param initialCapacity
* 初期容量
*/
@SuppressWarnings("unchecked")
public ArrayMap(int initialCapacity) {
if (initialCapacity <= 0) {
initialCapacity = INITIAL_CAPACITY;
}
mapTable = new Entry[initialCapacity];
listTable = new Entry[initialCapacity];
threshold = (int) (initialCapacity * LOAD_FACTOR);
}
/**
* 指定された{@link Map}と同じマッピングでインスタンスを構築します。
*
* @param map
* マッピングがこのマップに配置されるマップ
*/
public ArrayMap(final Map<? extends K, ? extends V> map) {
this((int) (map.size() / LOAD_FACTOR) + 1);
putAll(map);
}
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public boolean containsValue(final Object value) {
return indexOf(value) >= 0;
}
/**
* 値に対するインデックスを返します。
*
* @param value
* 値
* @return 値に対するインデックス。値が含まれていない場合は{@literal -1}
*/
public int indexOf(final Object value) {
if (value != null) {
for (int i = 0; i < size; i++) {
if (value.equals(listTable[i].value)) {
return i;
}
}
} else {
for (int i = 0; i < size; i++) {
if (listTable[i].value == null) {
return i;
}
}
}
return -1;
}
@Override
public boolean containsKey(final Object key) {
final Entry<K, V>[] tbl = mapTable;
if (key != null) {
final int hashCode = key.hashCode();
final int index = (hashCode & 0x7FFFFFFF) % tbl.length;
for (Entry<K, V> e = tbl[index]; e != null; e = e.next) {
if (e.hashCode == hashCode && key.equals(e.key)) {
return true;
}
}
} else {
for (Entry<K, V> e = tbl[0]; e != null; e = e.next) {
if (e.key == null) {
return true;
}
}
}
return false;
}
@Override
public V get(final Object key) {
final Entry<K, V>[] tbl = mapTable;
if (key != null) {
final int hashCode = key.hashCode();
final int index = (hashCode & 0x7FFFFFFF) % tbl.length;
for (Entry<K, V> e = tbl[index]; e != null; e = e.next) {
if (e.hashCode == hashCode && key.equals(e.key)) {
return e.value;
}
}
} else {
for (Entry<K, V> e = tbl[0]; e != null; e = e.next) {
if (e.key == null) {
return e.value;
}
}
}
return null;
}
/**
* インデックスで指定された位置の値を返します。
*
* @param index
* インデックス
* @return インデックスで指定された位置の値
*/
public V getAt(final int index) {
return getEntryAt(index).getValue();
}
/**
* インデックスで指定された位置のキーを返します。
*
* @param index
* インデックス
* @return インデックスで指定された位置のキー
*/
public K getKeyAt(final int index) {
return getEntryAt(index).getKey();
}
/**
* インデックスで指定された位置の{@link java.util.Map.Entry}を返します。
*
* @param index
* インデックス
* @return インデックスで指定された位置の{@link java.util.Map.Entry}
*/
public Map.Entry<K, V> getEntryAt(final int index) {
assertIndex(index < size, "Index:" + index + ", Size:" + size);
return listTable[index];
}
@Override
public V put(final K key, final V value) {
int hashCode = 0;
int index = 0;
if (key != null) {
hashCode = key.hashCode();
index = (hashCode & 0x7FFFFFFF) % mapTable.length;
for (Entry<K, V> e = mapTable[index]; e != null; e = e.next) {
if ((e.hashCode == hashCode) && key.equals(e.key)) {
return swapValue(e, value);
}
}
} else {
for (Entry<K, V> e = mapTable[0]; e != null; e = e.next) {
if (e.key == null) {
return swapValue(e, value);
}
}
}
ensureCapacity();
index = (hashCode & 0x7FFFFFFF) % mapTable.length;
final Entry<K, V> e =
new Entry<K, V>(hashCode, key, value, mapTable[index]);
mapTable[index] = e;
listTable[size++] = e;
return null;
}
/**
* インデックスで指定された位置に値を設定します。
*
* @param index
* インデックス
* @param value
* 値
*/
public void setAt(final int index, final V value) {
getEntryAt(index).setValue(value);
}
@Override
public V remove(final Object key) {
final Entry<K, V> e = removeMap(key);
if (e != null) {
final V value = e.value;
removeList(entryIndexOf(e));
e.clear();
return value;
}
return null;
}
/**
* インデックスで指定された位置のエントリを削除します。
*
* @param index
* インデックス
* @return インデックスで指定された位置にあったエントリの値
*/
public V removeAt(final int index) {
final Entry<K, V> e = removeList(index);
final V value = e.value;
removeMap(e.key);
e.clear();
return value;
}
@Override
public void putAll(final Map<? extends K, ? extends V> map) {
for (final Map.Entry<? extends K, ? extends V> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public void clear() {
for (int i = 0; i < mapTable.length; i++) {
mapTable[i] = null;
}
for (int i = 0; i < listTable.length; i++) {
listTable[i] = null;
}
size = 0;
}
/**
* 配列に変換します。
*
* @return 配列
*/
public Object[] toArray() {
final Object[] array = new Object[size];
for (int i = 0; i < array.length; i++) {
array[i] = getAt(i);
}
return array;
}
/**
* 配列に変換します。
*
* @param proto
* 要素の格納先の配列。配列のサイズが十分でない場合は、同じ実行時の型で新しい配列が格納用として割り当てられる
* @return 配列
*/
public V[] toArray(final V[] proto) {
@SuppressWarnings("unchecked")
final V[] array =
proto.length >= size ? proto : (V[]) Array.newInstance(proto
.getClass()
.getComponentType(), size);
for (int i = 0; i < array.length; i++) {
array[i] = getAt(i);
}
if (array.length > size) {
array[size] = null;
}
return array;
}
@Override
public boolean equals(final Object o) {
if (o == null) {
return false;
}
if (!(o instanceof ArrayMap)) {
return false;
}
@SuppressWarnings("unchecked")
final ArrayMap<K, V> e = (ArrayMap<K, V>) o;
if (size != e.size) {
return false;
}
for (int i = 0; i < size; i++) {
if (!listTable[i].equals(e.listTable[i])) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int h = 0;
for (int i = 0; i < size; i++) {
h += listTable[i].hashCode();
}
return h;
}
@SuppressWarnings("unchecked")
@Override
public Set<Map.Entry<K, V>> entrySet() {
if (entrySet == null) {
entrySet = new AbstractSet<Entry<K, V>>() {
@Override
public Iterator<Entry<K, V>> iterator() {
return new ArrayMapIterator();
}
@Override
public boolean contains(final Object o) {
final Entry<K, V> entry = (Entry<K, V>) o;
final int index =
(entry.hashCode & 0x7FFFFFFF) % mapTable.length;
for (Entry<K, V> e = mapTable[index]; e != null; e = e.next) {
if (e.equals(entry)) {
return true;
}
}
return false;
}
@Override
public boolean remove(final Object o) {
if (!(o instanceof Entry)) {
return false;
}
final Entry<K, V> entry = (Entry<K, V>) o;
return ArrayMap.this.remove(entry.key) != null;
}
@Override
public int size() {
return size;
}
@Override
public void clear() {
ArrayMap.this.clear();
}
};
}
return (Set<Map.Entry<K, V>>) entrySet;
}
@Override
public void writeExternal(final ObjectOutput out) throws IOException {
out.writeInt(listTable.length);
out.writeInt(size);
for (int i = 0; i < size; i++) {
out.writeObject(listTable[i].key);
out.writeObject(listTable[i].value);
}
}
@SuppressWarnings("unchecked")
@Override
public void readExternal(final ObjectInput in) throws IOException,
ClassNotFoundException {
final int num = in.readInt();
mapTable = new Entry[num];
listTable = new Entry[num];
threshold = (int) (num * LOAD_FACTOR);
final int size = in.readInt();
for (int i = 0; i < size; i++) {
final K key = (K) in.readObject();
final V value = (V) in.readObject();
put(key, value);
}
}
@Override
public Object clone() {
final ArrayMap<K, V> copy = new ArrayMap<K, V>();
copy.threshold = threshold;
copy.mapTable = Arrays.copyOf(mapTable, size);
copy.listTable = Arrays.copyOf(listTable, size);
copy.size = size;
return copy;
}
/**
* エントリのインデックスを返します。
*
* @param entry
* エントリ
* @return エントリのインデックス
*/
protected int entryIndexOf(final Entry<K, V> entry) {
for (int i = 0; i < size; i++) {
if (listTable[i] == entry) {
return i;
}
}
return -1;
}
/**
* キーで指定されたエントリをマップのエントリから削除します。
*
* @param key
* キー
* @return 削除されたエントリ。キーに対応するエントリがなかった場合は{@literal null}
*/
protected Entry<K, V> removeMap(final Object key) {
int hashCode = 0;
int index = 0;
if (key != null) {
hashCode = key.hashCode();
index = (hashCode & 0x7FFFFFFF) % mapTable.length;
for (Entry<K, V> e = mapTable[index], prev = null; e != null; prev =
e, e = e.next) {
if ((e.hashCode == hashCode) && key.equals(e.key)) {
if (prev != null) {
prev.next = e.next;
} else {
mapTable[index] = e.next;
}
return e;
}
}
} else {
for (Entry<K, V> e = mapTable[index], prev = null; e != null; prev =
e, e = e.next) {
if ((e.hashCode == hashCode) && e.key == null) {
if (prev != null) {
prev.next = e.next;
} else {
mapTable[index] = e.next;
}
return e;
}
}
}
return null;
}
/**
* インデックスで指定されたエントリをリストのエントリから削除します。
*
* @param index
* 削除するエントリのインデックス
* @return 削除されたエントリ
*/
protected Entry<K, V> removeList(final int index) {
final Entry<K, V> e = listTable[index];
final int numMoved = size - index - 1;
if (numMoved > 0) {
System.arraycopy(listTable, index + 1, listTable, index, numMoved);
}
listTable[--size] = null;
return e;
}
/**
* サイズが閾値を超えた場合に容量を確保します。
*/
protected void ensureCapacity() {
if (size >= threshold) {
final Entry<K, V>[] oldTable = listTable;
final int newCapacity = oldTable.length * 2 + 1;
@SuppressWarnings("unchecked")
final Entry<K, V>[] newMapTable = new Entry[newCapacity];
@SuppressWarnings("unchecked")
final Entry<K, V>[] newListTable = new Entry[newCapacity];
threshold = (int) (newCapacity * LOAD_FACTOR);
System.arraycopy(oldTable, 0, newListTable, 0, size);
for (int i = 0; i < size; i++) {
Entry<K, V> old = oldTable[i];
final int index = (old.hashCode & 0x7FFFFFFF) % newCapacity;
final Entry<K, V> e = old;
old = old.next;
e.next = newMapTable[index];
newMapTable[index] = e;
}
mapTable = newMapTable;
listTable = newListTable;
}
}
/**
* エントリの値を新しい値で置き換えます。
*
* @param entry
* エントリ
* @param value
* エントリの新しい値
* @return エントリの置き換えられる前の値
*/
protected V swapValue(final Entry<K, V> entry, final V value) {
final V old = entry.value;
entry.value = value;
return old;
}
/**
* {@link ArrayMap}用の{@link Iterator}です。
*/
protected class ArrayMapIterator implements Iterator<Entry<K, V>> {
/** 現在のインデックス */
protected int current = 0;
/** 最後にアクセスした要素のインデックス */
protected int last = -1;
@Override
public boolean hasNext() {
return current != size;
}
@Override
public Entry<K, V> next() {
try {
final Entry<K, V> n = listTable[current];
last = current++;
return n;
} catch (final IndexOutOfBoundsException e) {
throw new SNoSuchElementException("current=" + current);
}
}
@Override
public void remove() {
assertState(last != -1, "last == -1");
ArrayMap.this.remove(last);
if (last < current) {
current--;
}
last = -1;
}
}
/**
* {@link ArrayMap}の{@link Map.Entry}です。
*
* @param <K>
* キーの型
* @param <V>
* キーの値
*/
protected static class Entry<K, V> implements Map.Entry<K, V>,
Externalizable {
private static final long serialVersionUID = -6625980241350717177L;
/** ハッシュ値 */
protected transient int hashCode;
/** キー */
protected transient K key;
/** 値 */
protected transient V value;
/** 次のエントリ */
protected transient Entry<K, V> next;
/**
* インスタンスを構築します。
*/
public Entry() {
}
/**
* インスタンスを構築します。
*
* @param hashCode
* ハッシュ値
* @param key
* キー
* @param value
* 値
* @param next
* 次のエントリ
*/
public Entry(final int hashCode, final K key, final V value,
final Entry<K, V> next) {
this.hashCode = hashCode;
this.key = key;
this.value = value;
this.next = next;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(final V value) {
final V oldValue = value;
this.value = value;
return oldValue;
}
/**
* 状態をクリアします。
*/
public void clear() {
key = null;
value = null;
next = null;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Entry)) {
return false;
}
@SuppressWarnings("unchecked")
final Entry<K, V> e = (Entry<K, V>) o;
return (key != null ? key.equals(e.key) : e.key == null)
&& (value != null ? value.equals(e.value) : e.value == null);
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public String toString() {
return key + "=" + value;
}
@Override
public void writeExternal(final ObjectOutput s) throws IOException {
s.writeInt(hashCode);
s.writeObject(key);
s.writeObject(value);
s.writeObject(next);
}
@SuppressWarnings("unchecked")
@Override
public void readExternal(final ObjectInput s) throws IOException,
ClassNotFoundException {
hashCode = s.readInt();
key = (K) s.readObject();
value = (V) s.readObject();
next = (Entry<K, V>) s.readObject();
}
}
}