All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jdt.internal.compiler.apt.util.ManyToMany Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2006, 2007 BEA Systems, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    [email protected] - initial API and implementation 
 *                      (originally in org.eclipse.jdt.apt.core)
 *******************************************************************************/
package org.eclipse.jdt.internal.compiler.apt.util;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Manage a Map>, with reverse links so that it is possible to
 * efficiently find all T1s that have a particular T2 associated with them.
 * Access to the map is synchronized, so that it is possible to read and
 * write simultaneously from multiple threads.
 * 

* The map permits the null value for keys nor for value elements. *

* Design invariants preserved by all operations on this map are as follows: *

    *
  • If a key exists, it has at least one value associated with it; that is, * for all k such that null != containsKey(k), getValues(k) returns a non-empty * set.
  • *
  • If a value exists, it has at least one key associated with it; that is, * for all v such that null != containsValue(v), getKeys(v) returns a non-empty * set.
  • */ public class ManyToMany { private final Map> _forward = new HashMap>(); private final Map> _reverse = new HashMap>(); private boolean _dirty = false; /** * Empty all maps. If the maps previously contained entries, * this will set the dirty bit. * @return true if the maps contained any entries prior to being cleared */ public synchronized boolean clear() { boolean hadContent = !_forward.isEmpty() || !_reverse.isEmpty(); _reverse.clear(); _forward.clear(); _dirty |= hadContent; return hadContent; } /** * Sets the dirty bit to false. Internal operations do not use the dirty * bit; clearing it will not affect behavior of the map. It's just there * for the convenience of callers who don't want to keep track of every * put() and remove(). */ public synchronized void clearDirtyBit() { _dirty = false; } /** * Equivalent to keySet().contains(key). * @return true if the map contains the specified key. */ public synchronized boolean containsKey(T1 key) { return _forward.containsKey(key); } /** * Is there a key that is mapped to the specified value? * Search within the forward map. * @return true if such a key exists */ public synchronized boolean containsKeyValuePair(T1 key, T2 value) { Set values = _forward.get(key); if (null == values) { return false; } return values.contains(value); } /** * Equivalent to values().contains(value). * @return true if the map contains the specified value (regardless * of what key it might be associated with). */ public synchronized boolean containsValue(T2 value) { return _reverse.containsKey(value); } /** * Search the reverse map for all keys that have been associated with * a particular value. * @return the set of keys that are associated with the specified value, * or an empty set if the value does not exist in the map. */ public synchronized Set getKeys(T2 value) { Set keys = _reverse.get(value); if (null == keys) { return Collections.emptySet(); } return new HashSet(keys); } /** * Search the forward map for all values associated with a particular key. * Returns a copy of the set of values. * @return a copy of the set of values that are associated with the * specified key, or an empty set if the key does not exist in the map. */ public synchronized Set getValues(T1 key) { Set values = _forward.get(key); if (null == values) { return Collections.emptySet(); } return new HashSet(values); } /** * @return a copy of the set of all keys (that is, all items of type T1). * If the maps are empty, the returned set will be empty, not null. The * returned set can be modified by the caller without affecting the map. * @see #getValueSet() */ public synchronized Set getKeySet() { Set keys = new HashSet(_forward.keySet()); return keys; } /** * @return a copy of the set of all values (that is, all items of type T2). * If the maps are empty, the returned set will be empty, not null. The * returned set can be modified by the caller without affecting the map. * @see #getKeySet() */ public synchronized Set getValueSet() { Set values = new HashSet(_reverse.keySet()); return values; } /** * Return the state of the dirty bit. All operations that change the state * of the maps, including @see #clear(), set the dirty bit if any content actually * changed. The only way to clear the dirty bit is to call @see #clearDirtyBit(). * @return true if the map content has changed since it was created or since * the last call to clearDirtyBit(). * @see #clearDirtyBit() */ public synchronized boolean isDirty() { return _dirty; } /** * Check whether key has an association to any values other * than value - that is, whether the same key has been added * with multiple values. Equivalent to asking whether the intersection of * getValues(key) and the set containing value is * non-empty. * @return true iff key is in the map and is associated * with values other than value. * @see #valueHasOtherKeys(Object, Object) */ public synchronized boolean keyHasOtherValues(T1 key, T2 value) { Set values = _forward.get(key); if (values == null) return false; int size = values.size(); if (size == 0) return false; else if (size > 1) return true; else // size == 1 return !values.contains(value); } /** * Associate the specified value with the key. Adds the entry * to both the forward and reverse maps. Adding the same value * twice to a particular key has no effect. Because this is a * many-to-many map, adding a new value for an existing key does * not change the existing association, it adds a new one. * @param key can be null * @param value can be null * @return true if the key/value pair did not exist prior to being added */ public synchronized boolean put(T1 key, T2 value) { // Add to forward map Set values = _forward.get(key); if (null == values) { values = new HashSet(); _forward.put(key, values); } boolean added = values.add(value); _dirty |= added; // Add to reverse map Set keys = _reverse.get(value); if (null == keys) { keys = new HashSet(); _reverse.put(value, keys); } keys.add(key); assert checkIntegrity(); return added; } /** * Remove a particular key-value association. This is the inverse * of put(key, value). If the key does not exist, or the value * does not exist, or the association does not exist, this call * has no effect. * @return true if the key/value pair existed in the map prior to removal */ public synchronized boolean remove(T1 key, T2 value) { Set values = _forward.get(key); if (values == null) { assert checkIntegrity(); return false; } boolean removed = values.remove(value); if (values.isEmpty()) { _forward.remove(key); } if (removed) { _dirty = true; // it existed, so we need to remove from reverse map as well Set keys = _reverse.get(value); keys.remove(key); if (keys.isEmpty()) { _reverse.remove(value); } } assert checkIntegrity(); return removed; } /** * Remove the key and its associated key/value entries. * Calling removeKey(k) is equivalent to calling remove(k,v) * for every v in getValues(k). * @return true if the key existed in the map prior to removal */ public synchronized boolean removeKey(T1 key) { // Remove all back-references to key. Set values = _forward.get(key); if (null == values) { // key does not exist in map. assert checkIntegrity(); return false; } for (T2 value : values) { Set keys = _reverse.get(value); if (null != keys) { keys.remove(key); if (keys.isEmpty()) { _reverse.remove(value); } } } // Now remove the forward references from key. _forward.remove(key); _dirty = true; assert checkIntegrity(); return true; } /** * Remove the value and its associated key/value entries. * Calling removeValue(v) is equivalent to calling remove(k,v) * for every k in getKeys(v). * @return true if the value existed in the map prior to removal. */ public synchronized boolean removeValue(T2 value) { // Remove any forward references to value Set keys = _reverse.get(value); if (null == keys) { // value does not exist in map. assert checkIntegrity(); return false; } for (T1 key : keys) { Set values = _forward.get(key); if (null != values) { values.remove(value); if (values.isEmpty()) { _forward.remove(key); } } } // Now remove the reverse references from value. _reverse.remove(value); _dirty = true; assert checkIntegrity(); return true; } /** * Check whether value has an association from any keys other * than key - that is, whether the same value has been added * with multiple keys. Equivalent to asking whether the intersection of * getKeys(value) and the set containing key is * non-empty. * @return true iff value is in the map and is associated * with keys other than key. * @see #keyHasOtherValues(Object, Object) */ public synchronized boolean valueHasOtherKeys(T2 value, T1 key) { Set keys = _reverse.get(key); if (keys == null) return false; int size = keys.size(); if (size == 0) return false; else if (size > 1) return true; else // size == 1 return !keys.contains(key); } /** * Check the integrity of the internal data structures. This is intended to * be called within an assert, so that if asserts are disabled the integrity * checks will not cause a performance impact. * @return true if everything is okay. * @throws IllegalStateException if there is a problem. */ private boolean checkIntegrity() { // For every T1->T2 mapping in the forward map, there should be a corresponding // T2->T1 mapping in the reverse map. for (Map.Entry> entry : _forward.entrySet()) { Set values = entry.getValue(); if (values.isEmpty()) { throw new IllegalStateException("Integrity compromised: forward map contains an empty set"); //$NON-NLS-1$ } for (T2 value : values) { Set keys = _reverse.get(value); if (null == keys || !keys.contains(entry.getKey())) { throw new IllegalStateException("Integrity compromised: forward map contains an entry missing from reverse map: " + value); //$NON-NLS-1$ } } } // And likewise in the other direction. for (Map.Entry> entry : _reverse.entrySet()) { Set keys = entry.getValue(); if (keys.isEmpty()) { throw new IllegalStateException("Integrity compromised: reverse map contains an empty set"); //$NON-NLS-1$ } for (T1 key : keys) { Set values = _forward.get(key); if (null == values || !values.contains(entry.getKey())) { throw new IllegalStateException("Integrity compromised: reverse map contains an entry missing from forward map: " + key); //$NON-NLS-1$ } } } return true; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy