/*
* Beanfabrics Framework Copyright (C) by Michael Karneim, beanfabrics.org
* Use is subject to license terms. See license.txt.
*/
// TODO javadoc - remove this comment only when the class and all non-public
// methods and fields are documented
package org.beanfabrics.util;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
/**
* The <code>OrderPreservingMap</code> is a {@link Map} that preserves the oder
* in which the elements where added. It gives access to it's values by a hash
* key but also by an index.
*
* @author Michael Karneim
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public class OrderPreservingMap<K, V> implements Map<K, V>, Cloneable {
private HashMap<K, V> map = new HashMap<K, V>();
private ArrayList<K> orderKeys = new ArrayList<K>();
private ArrayList<V> orderValues = new ArrayList<V>();
public OrderPreservingMap() {
}
@SuppressWarnings("unchecked")
public OrderPreservingMap(OrderPreservingMap<K, V> other) {
this.map = (HashMap<K, V>)other.map.clone();
this.orderKeys = (ArrayList<K>)other.orderKeys.clone();
this.orderValues = (ArrayList<V>)other.orderValues.clone();
}
public OrderPreservingMap(Map<K, V> map, Collection<K> orderedKeys, Collection<V> orderedEntries) {
this.map = new HashMap<K, V>(map);
this.orderKeys = new ArrayList<K>(orderedKeys);
this.orderValues = new ArrayList<V>(orderedEntries);
}
@SuppressWarnings("unchecked")
public Object clone() {
try {
final OrderPreservingMap<K, V> other = (OrderPreservingMap<K, V>)super.clone();
other.map = (HashMap<K, V>)this.map.clone();
other.orderKeys = (ArrayList<K>)this.orderKeys.clone();
other.orderValues = (ArrayList<V>)this.orderValues.clone();
return other;
} catch (CloneNotSupportedException ex) {
// this never should happen since we implemnet Cloneable
throw new UndeclaredThrowableException(ex);
}
}
@SuppressWarnings("unchecked")
public void putAll(Map<? extends K, ? extends V> other) {
if (other == null || other.size() == 0) {
return;
}
if (other instanceof OrderPreservingMap) {
// fill in the entries in the given order (if not contained here and therefore being replaced).
// Attention: not only copies the ordering but the index, too
final OrderPreservingMap<? extends K, ? extends V> otherHashMap = (OrderPreservingMap<? extends K, ? extends V>)other;
final Object[] orderedKeys = otherHashMap.keyArray();
for (int i = 0; i < orderedKeys.length; i++) {
this.put((K)orderedKeys[i], (V)other.get(orderedKeys[i]));
}
} else {
for (Entry<? extends K, ? extends V> entry : other.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
}
public void putAll(Collection<Entry<K,V>> entries) {
if ( entries == null ) {
return;
}
for (Entry<K,V> entry : entries) {
this.put(entry.getKey(), entry.getValue());
}
}
public V put(K key, V value) {
if (value == null) {
throw new IllegalArgumentException("value==null");
}
if (key == null) {
throw new IllegalArgumentException("key==null");
}
final V old = this.map.put(key, value);
if (old == value) {
return old;
}
if (old != null) {
final int index = this.orderKeys.indexOf(key);
this.orderValues.remove(index);
this.orderValues.add(index, value);
return old;
}
this.orderKeys.add(key);
this.orderValues.add(value);
return null;
}
public V put(K key, V value, int toIndex) {
if (value == null) {
throw new IllegalArgumentException("value==null");
}
if (key == null) {
throw new IllegalArgumentException("key==null");
}
final V old = this.map.put(key, value);
if (old == null) {
this.orderKeys.add(toIndex, key);
this.orderValues.add(toIndex, value);
return null;
} else {
int oldIndex = this.orderKeys.indexOf(key);
if (oldIndex == toIndex) {
return old;
} else {
this.orderKeys.add(toIndex, key);
this.orderKeys.remove(oldIndex);
this.orderValues.add(toIndex, value);
this.orderValues.remove(oldIndex);
return old;
}
}
}
public Set<K> keySet() {
return new HashSet<K>(this.map.keySet());
}
public Set<K> keySetReference() {
return this.map.keySet();
}
public Object[] keyArray() {
return this.orderKeys.toArray();
}
public Collection<K> orderedKeys() {
return new ArrayList<K>(this.orderKeys);
}
public Collection<K> orderedKeysReference() {
return this.orderKeys;
}
public Set<Map.Entry<K, V>> entrySet() {
return new HashSet<Map.Entry<K, V>>(this.map.entrySet());
}
public Set<Map.Entry<K, V>> entrySetReference() {
return this.map.entrySet();
}
public Collection<V> values() {
return new ArrayList<V>(this.orderValues);
}
public Collection<V> valuesReference() {
return this.orderValues;
}
public V get(Object key) {
return this.map.get(key);
}
public V get(int i) {
return this.orderValues.get(i);
}
public K getKey(int index) {
return this.orderKeys.get(index);
}
public Collection<V> getAll(Set<K> keys) {
final LinkedList<V> result = new LinkedList<V>();
for (K key : keys) {
final V value = this.map.get(key);
if (value == null) {
continue;
}
result.add(value);
}
return result;
}
public int indexOfKey(Object key) {
return this.orderKeys.indexOf(key);
}
// public int moveTo(K key, int newIndex) {
// final int oldIndex = this.orderKeys.indexOf(key);
// if (oldIndex == -1) {
// return -1;
// }
// this.orderKeys.remove(oldIndex);
// this.orderKeys.add(newIndex, key);
// final V entry = this.orderEntries.remove(oldIndex);
// this.orderEntries.add(newIndex, entry);
// return oldIndex;
// }
// public void replace(int index, K key, V value) {
// if (index < 0 || index >= this.size()) {
// throw new IllegalArgumentException(
// "Index out of bounds. Index must be between 0 and "
// + this.size() + ". But index was " + index);
// }
// final V oldAtIndex = this.get(index);
// final V oldAtKey = this.get(key);
// if (oldAtKey != null) {
// throw new IllegalArgumentException(
// "Can't replace value, because key is already used. The key was "
// + key + ".");
// }
// this.remove(index);
// this.put(key, value);
// this.moveTo(key, index);
// }
public int indexOf(Object object) {
return this.orderValues.indexOf(object);
}
public boolean containsKey(Object key) {
return this.map.containsKey(key);
}
public boolean containsValue(Object value) {
return this.map.containsValue(value);
}
public boolean containsAll(Collection<V> list) {
return this.orderValues.containsAll(list);
}
public void reverseOrder() {
final int len = this.orderKeys.size();
final ArrayList<K> newOrderKeys = new ArrayList<K>(len);
final ArrayList<V> newOrderEntries = new ArrayList<V>(len);
final ListIterator<K> itKeys = this.orderKeys.listIterator(len);
final ListIterator<V> itEntries = this.orderValues.listIterator(len);
for (int i = 0; i < len; i++) {
final K key = itKeys.previous();
final V entry = itEntries.previous();
newOrderKeys.add(key);
newOrderEntries.add(entry);
}
this.orderValues = newOrderEntries;
this.orderKeys = newOrderKeys;
}
public void reorder(Collection<K> keys) {
final int len = keys.size();
if (len != this.size()) {
throw new IllegalArgumentException("Can't reorder listCell with provided keys. The number of the provided keys must match this listCell's size.");
}
final ArrayList<K> newOrderKeys = new ArrayList<K>(len);
final ArrayList<V> newOrderEntries = new ArrayList<V>(len);
for (K key : keys) {
final V entry = this.get(key);
if (entry == null) {
throw new IllegalArgumentException("Can't reorder listCell with provided keys. Key '" + key + "' is not in this listCell.");
}
newOrderKeys.add(key);
newOrderEntries.add(entry);
}
this.orderValues = newOrderEntries;
this.orderKeys = newOrderKeys;
}
public void reorder(K[] keys) {
final int len = keys.length;
if (len != this.size()) {
throw new IllegalArgumentException("Can't reorder listCell with provided keys. The number of the provided keys must match this listCell's size.");
}
final ArrayList<K> newOrderKeys = new ArrayList<K>(len);
final ArrayList<V> newOrderEntries = new ArrayList<V>(len);
for (int i = 0; i < len; i++) {
final K key = keys[i];
final V entry = this.get(key);
if (entry == null) {
throw new IllegalArgumentException("Can't reorder listCell with provided keys. Key '" + key + "' is not in this listCell.");
}
newOrderKeys.add(key);
newOrderEntries.add(entry);
}
this.orderValues = newOrderEntries;
this.orderKeys = newOrderKeys;
}
public V remove(Object key) {
final V result = this.map.remove(key);
if (result == null) {
return null;
}
final int index = this.orderKeys.indexOf(key);
this.orderKeys.remove(index);
this.orderValues.remove(index);
return result;
}
public V remove(int index) {
final V result = this.orderValues.remove(index);
if (result == null) {
return null;
}
final K key = this.orderKeys.remove(index);
this.map.remove(key);
return result;
}
public boolean removeAllKeys(Set<K> keys) {
boolean result = false;
for (K key : keys) {
final V value = this.map.remove(key);
if (value == null) {
continue;
}
result = true;
int index = this.orderKeys.indexOf(key);
this.orderKeys.remove(index);
this.orderValues.remove(index);
}
return result;
}
public void retainAllKeys(Set<K> keys) {
final HashSet<K> keepKeys = new HashSet<K>(keys.size());
keepKeys.addAll(this.map.keySet());
keepKeys.retainAll(keys);
final HashMap<K, V> newMap = new HashMap<K, V>(keepKeys.size());
for (K key : keepKeys) {
final V value = this.map.remove(key);
newMap.put(key, value);
}
final HashMap<K, V> toRemoveMap = this.map;
this.map = newMap;
for (K key : toRemoveMap.keySet()) {
final int index = this.orderKeys.indexOf(key);
this.orderKeys.remove(index);
this.orderValues.remove(index);
}
}
public void clear() {
this.orderKeys.clear();
this.orderValues.clear();
this.map.clear();
}
public int size() {
return this.orderKeys.size();
}
public boolean isEmpty() {
return this.orderKeys.isEmpty();
}
public V[] toArray(V[] dest) {
return this.orderValues.toArray(dest);
}
public Object[] toArray() {
return this.orderValues.toArray();
}
@SuppressWarnings("unchecked")
public Collection<V> toCollection() {
return (ArrayList<V>)this.orderValues.clone();
}
public ListIterator<V> listIterator(int index) {
return this.orderValues.listIterator(index);
}
public ListIterator<K> keyListIterator(int index) {
return this.orderKeys.listIterator(index);
}
}