/*
* Copyright (c) 2003, Christian Edward Gruber
* Copyright (c) 2003, Israfil Consulting Services Corporation
*
* This software is licensed under the Berkeley Standard Distribution license,
* (BSD license), as defined below:
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of Israfil Consulting Services nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
* $Id: ReciprocalHashMap.java 129 2006-12-31 23:20:02Z cgruber $
*/
package net.israfil.foundation.collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A two-way map, backed by java.util.HashMaps, where each key matches one
* value and is unique, and where each value maps to a key, and is unique
* among all values.
*
* @author Christian Edward Gruber
*
*/
public class ReciprocalHashMap extends java.util.AbstractMap
implements ReciprocalMap {
Set> entrySetByKey = new HashSet>();
Set> entrySetByValue = new HashSet>();
@Override
public Set> entrySet() {
return entrySetByKey;
}
public Set> entrySetByValue() {
return entrySetByValue;
}
public boolean contains(K key, V value) {
V value1 = get(key);
if (value == value1) return true;
else if (value == null) return false; // assume null == null handled.
else return value.equals(value1);
}
public Entry getEntryByValue(V value) {
Iterator> i = entrySetByValue().iterator();
if (value==null) {
while (i.hasNext()) {
Entry e = i.next();
if (e.getKey()==null) return e;
}
} else {
while (i.hasNext()) {
Entry e = i.next();
if (value.equals(e.getKey())) return e;
}
}
return null;
}
public Entry getEntryByKey(K value) {
Iterator> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry e = i.next();
if (e.getKey()==null) return e;
}
} else {
while (i.hasNext()) {
Entry e = i.next();
if (value.equals(e.getKey())) return e;
}
}
return null;
}
public K getKey(V value) {
Entry e = getEntryByValue(value);
return (e == null) ? null : e.getValue();
}
public V getValue(K key) {
Entry e = getEntryByKey(key);
return (e == null) ? null : e.getValue();
}
public K removeByValue(V value) {
Entry valEntry = getEntryByValue(value);
Entry keyEntry = getEntryByKey(valEntry.getValue());
if (keyEntry != null) entrySetByKey.remove(keyEntry);
if (valEntry != null) entrySetByValue.remove(valEntry);
return keyEntry.getKey();
}
public V removeByKey(K key) {
Entry keyEntry = getEntryByKey(key);
Entry valEntry = getEntryByValue(keyEntry.getValue());
if (keyEntry != null) entrySetByKey.remove(keyEntry);
if (valEntry != null) entrySetByValue.remove(valEntry);
return valEntry.getKey();
}
@SuppressWarnings("unchecked")
@Override public V remove(Object key) {
return this.removeByKey((K)key);
}
@Override public V put(K key, V value) {
Entry keyEntry = getEntryByKey(key);
Entry valueEntry = getEntryByValue(value);
V oldValue = null;
if (keyEntry != null) {
Entry inverseKeyEntry = getEntryByValue(keyEntry.getValue());
keyEntry.getKey();
entrySetByKey.remove(keyEntry);
entrySetByValue.remove(inverseKeyEntry);
}
if (valueEntry != null) {
Entry inverseValueEntry = getEntryByKey(valueEntry.getValue());
oldValue = valueEntry.getKey();
entrySetByValue.remove(valueEntry);
entrySetByKey.remove(inverseValueEntry);
}
entrySetByKey.add(new ReciprocalEntry(key,value));
entrySetByValue.add(new ReciprocalEntry(value,key));
return oldValue;
}
static class ReciprocalEntry implements Entry {
private K key;
private V value;
public ReciprocalEntry(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode());
}
@Override public String toString() {
return "ReciprocalEntry[" + key + "=" + value + "]";
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) return false;
try {
Map.Entry e = (Map.Entry)o;
return safeEquals(key, e.getKey()) && safeEquals(value, e.getValue());
} catch (ClassCastException cce) {
return false;
}
}
private static boolean safeEquals(Object o1, Object o2) {
return (o1 == null ? o2 == null : o1.equals(o2));
}
}
}