summaryrefslogtreecommitdiff
path: root/israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java
diff options
context:
space:
mode:
Diffstat (limited to 'israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java')
-rw-r--r--israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java202
1 files changed, 202 insertions, 0 deletions
diff --git a/israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java b/israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java
new file mode 100644
index 0000000..0ac0164
--- /dev/null
+++ b/israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java
@@ -0,0 +1,202 @@
+/*
+ * 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 <a href="mailto:cgruber@israfil.net">Christian Edward Gruber</a>
+ *
+ */
+public class ReciprocalHashMap<K,V> extends java.util.AbstractMap<K,V>
+ implements ReciprocalMap<K,V> {
+
+ Set<Map.Entry<K,V>> entrySetByKey = new HashSet<Map.Entry<K,V>>();
+ Set<Map.Entry<V,K>> entrySetByValue = new HashSet<Map.Entry<V,K>>();
+
+ @Override
+ public Set<Map.Entry<K,V>> entrySet() {
+ return entrySetByKey;
+ }
+
+ public Set<Map.Entry<V,K>> 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<V,K> getEntryByValue(V value) {
+ Iterator<Entry<V,K>> i = entrySetByValue().iterator();
+ if (value==null) {
+ while (i.hasNext()) {
+ Entry<V,K> e = i.next();
+ if (e.getKey()==null) return e;
+ }
+ } else {
+ while (i.hasNext()) {
+ Entry<V,K> e = i.next();
+ if (value.equals(e.getKey())) return e;
+ }
+ }
+ return null;
+ }
+ public Entry<K,V> getEntryByKey(K value) {
+ Iterator<Entry<K,V>> i = entrySet().iterator();
+ if (value==null) {
+ while (i.hasNext()) {
+ Entry<K,V> e = i.next();
+ if (e.getKey()==null) return e;
+ }
+ } else {
+ while (i.hasNext()) {
+ Entry<K,V> e = i.next();
+ if (value.equals(e.getKey())) return e;
+ }
+ }
+ return null;
+ }
+
+ public K getKey(V value) {
+ Entry<V,K> e = getEntryByValue(value);
+ return (e == null) ? null : e.getValue();
+ }
+
+ public V getValue(K key) {
+ Entry<K,V> e = getEntryByKey(key);
+ return (e == null) ? null : e.getValue();
+ }
+
+ public K removeByValue(V value) {
+ Entry<V,K> valEntry = getEntryByValue(value);
+ Entry<K,V> 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<K,V> keyEntry = getEntryByKey(key);
+ Entry<V,K> 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<K,V> keyEntry = getEntryByKey(key);
+ Entry<V,K> valueEntry = getEntryByValue(value);
+
+ V oldValue = null;
+
+ if (keyEntry != null) {
+ Entry<V,K> inverseKeyEntry = getEntryByValue(keyEntry.getValue());
+ keyEntry.getKey();
+ entrySetByKey.remove(keyEntry);
+ entrySetByValue.remove(inverseKeyEntry);
+ }
+ if (valueEntry != null) {
+ Entry<K,V> inverseValueEntry = getEntryByKey(valueEntry.getValue());
+ oldValue = valueEntry.getKey();
+ entrySetByValue.remove(valueEntry);
+ entrySetByKey.remove(inverseValueEntry);
+ }
+ entrySetByKey.add(new ReciprocalEntry<K,V>(key,value));
+ entrySetByValue.add(new ReciprocalEntry<V,K>(value,key));
+
+ return oldValue;
+ }
+
+ static class ReciprocalEntry<K, V> implements Entry<K, V> {
+ 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<K,V> e = (Map.Entry<K,V>)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));
+ }
+ }
+
+} \ No newline at end of file