From 7c279747beb43c7e88633a6228a155a30e6834f7 Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Mon, 27 May 2024 11:38:33 -0400 Subject: Initial import --- .../foundation/collections/ReciprocalHashMap.java | 202 +++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java (limited to 'israfil-foundation-collections/src/main/java/net/israfil/foundation/collections/ReciprocalHashMap.java') 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 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)); + } + } + +} \ No newline at end of file -- cgit v1.2.3