/* * 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)); } } }