| /******************************************************************************* |
| * Copyright (c) 2006, 2007 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| * (through BidirectionalMap.java) |
| * Matthew Hall - bug 233306 |
| *******************************************************************************/ |
| package org.eclipse.core.databinding.observable.map; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.databinding.observable.Realm; |
| import org.eclipse.core.internal.databinding.Util; |
| |
| /** |
| * An |
| * <p> |
| * This class is thread safe. All state accessing methods must be invoked from |
| * the {@link Realm#isCurrent() current realm}. Methods for adding and removing |
| * listeners may be invoked from any thread. |
| * </p> |
| * |
| * @since 1.2 |
| */ |
| public class BidiObservableMap extends DecoratingObservableMap { |
| /** |
| * Inverse of wrapped map. When multiple keys map to the same value, they |
| * are combined into a Set. This field is null when no listeners are |
| * registered on this observable. |
| */ |
| private Map valuesToKeys; |
| |
| /** |
| * Constructs a BidirectionalMap tracking the given observable map. |
| * |
| * @param wrappedMap |
| * the observable map to track |
| */ |
| public BidiObservableMap(IObservableMap wrappedMap) { |
| super(wrappedMap, false); |
| } |
| |
| protected void firstListenerAdded() { |
| valuesToKeys = new HashMap(); |
| for (Iterator it = entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Entry) it.next(); |
| addMapping(entry.getKey(), entry.getValue()); |
| } |
| super.firstListenerAdded(); |
| } |
| |
| protected void lastListenerRemoved() { |
| super.lastListenerRemoved(); |
| valuesToKeys.clear(); |
| valuesToKeys = null; |
| } |
| |
| protected void handleMapChange(MapChangeEvent event) { |
| MapDiff diff = event.diff; |
| for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) { |
| Object addedKey = it.next(); |
| addMapping(addedKey, diff.getNewValue(addedKey)); |
| } |
| for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) { |
| Object changedKey = it.next(); |
| removeMapping(changedKey, diff.getOldValue(changedKey)); |
| addMapping(changedKey, diff.getNewValue(changedKey)); |
| } |
| for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) { |
| Object removedKey = it.next(); |
| removeMapping(removedKey, diff.getOldValue(removedKey)); |
| } |
| super.handleMapChange(event); |
| } |
| |
| public boolean containsValue(Object value) { |
| getterCalled(); |
| // Faster lookup |
| if (valuesToKeys != null) |
| return valuesToKeys.containsKey(value); |
| return super.containsValue(value); |
| } |
| |
| /** |
| * Adds a mapping from value to key in the valuesToKeys map. |
| * |
| * @param key |
| * the key being mapped |
| * @param value |
| * the value being mapped |
| */ |
| private void addMapping(Object key, Object value) { |
| if (!valuesToKeys.containsKey(value)) { |
| if (key instanceof Set) |
| key = new HashSet(Collections.singleton(key)); |
| valuesToKeys.put(value, key); |
| } else { |
| Object elementOrSet = valuesToKeys.get(value); |
| Set set; |
| if (elementOrSet instanceof Set) { |
| set = (Set) elementOrSet; |
| } else { |
| set = new HashSet(Collections.singleton(elementOrSet)); |
| valuesToKeys.put(value, set); |
| } |
| set.add(key); |
| } |
| } |
| |
| /** |
| * Removes a mapping from value to key in the valuesToKeys map. |
| * |
| * @param key |
| * the key being unmapped |
| * @param value |
| * the value being unmapped |
| */ |
| private void removeMapping(Object key, Object value) { |
| if (valuesToKeys.containsKey(value)) { |
| Object elementOrSet = valuesToKeys.get(value); |
| if (elementOrSet instanceof Set) { |
| Set set = (Set) elementOrSet; |
| set.remove(key); |
| if (set.isEmpty()) { |
| valuesToKeys.remove(value); |
| } |
| } else if (elementOrSet == key |
| || (elementOrSet != null && elementOrSet.equals(key))) { |
| valuesToKeys.remove(value); |
| } |
| } |
| } |
| |
| /** |
| * Returns the Set of keys that currently map to the given value. |
| * |
| * @param value |
| * the value associated with the keys in the returned Set. |
| * @return the Set of keys that map to the given value. If no keys map to |
| * the given value, an empty set is returned. |
| */ |
| public Set getKeys(Object value) { |
| // valuesToKeys is null when no listeners are registered |
| if (valuesToKeys == null) |
| return findKeys(value); |
| |
| if (!valuesToKeys.containsKey(value)) |
| return Collections.EMPTY_SET; |
| Object elementOrSet = valuesToKeys.get(value); |
| if (elementOrSet instanceof Set) |
| return Collections.unmodifiableSet((Set) elementOrSet); |
| return Collections.singleton(elementOrSet); |
| } |
| |
| /** |
| * Iterates the map and returns the set of keys which currently map to the |
| * given value. |
| * |
| * @param value |
| * the value to search for |
| * @return the set of keys which currently map to the specified value. |
| */ |
| private Set findKeys(Object value) { |
| Set keys = new HashSet(); |
| for (Iterator it = entrySet().iterator(); it.hasNext();) { |
| Map.Entry entry = (Map.Entry) it.next(); |
| if (Util.equals(entry.getValue(), value)) |
| keys.add(entry.getKey()); |
| } |
| return keys; |
| } |
| |
| public synchronized void dispose() { |
| if (valuesToKeys != null) { |
| valuesToKeys.clear(); |
| valuesToKeys = null; |
| } |
| super.dispose(); |
| } |
| } |