| /******************************************************************************* |
| * Copyright (c) 2003, 2008 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 |
| *******************************************************************************/ |
| |
| package org.eclipse.osgi.framework.eventmgr; |
| |
| import java.util.*; |
| |
| /** |
| * A copy-on-write identity map. Write operations result in copying the underlying data so that |
| * simultaneous read operations are not affected. |
| * This allows for safe, unsynchronized traversal. |
| * |
| * <p> |
| * Note: This class uses identity for key and value comparison, not equals. |
| * |
| * @since 3.5 |
| */ |
| public class CopyOnWriteIdentityMap implements Map { |
| /** |
| * The empty array singleton instance. |
| */ |
| private static final Entry[] emptyArray = new Entry[0]; |
| |
| /** |
| * The array of entries. This field is volatile so it can be |
| * accessed from unsynchronized reader methods. |
| */ |
| private volatile Entry[] entries; |
| |
| /** |
| * Creates an empty map. |
| * |
| */ |
| public CopyOnWriteIdentityMap() { |
| entries = emptyArray; |
| } |
| |
| /** |
| * Copy constructor. |
| * |
| * @param source The CopyOnWriteMap to copy. |
| */ |
| public CopyOnWriteIdentityMap(CopyOnWriteIdentityMap source) { |
| this.entries = source.entries(); |
| } |
| |
| /* These methods modify the map and are synchronized. */ |
| |
| /** |
| * Add a key, value pair to the map. |
| * If the key object is already in the map, then its value is replaced with the new value. |
| * Keys are compared using identity. |
| * |
| * @param key The key object to be added to the list. |
| * @param value The value object to be associated with the key. |
| * This may be null. |
| * @return <code>null</code> if the specified key was newly added to the map. |
| * Otherwise the previous value of the key. |
| * @throws IllegalArgumentException If key is null. |
| */ |
| public synchronized Object put(Object key, Object value) { |
| if (key == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| int size = entries.length; |
| for (int i = 0; i < size; i++) { |
| if (entries[i].key == key) { |
| Object v = entries[i].value; |
| if (v == value) { |
| return v; |
| } |
| Entry[] newEntries = new Entry[size]; |
| System.arraycopy(entries, 0, newEntries, 0, size); |
| newEntries[i] = new Entry(key, value); |
| entries = newEntries; |
| return v; |
| } |
| } |
| |
| Entry[] newEntries = new Entry[size + 1]; |
| if (size > 0) { |
| System.arraycopy(entries, 0, newEntries, 0, size); |
| } |
| newEntries[size] = new Entry(key, value); |
| entries = newEntries; |
| return null; |
| } |
| |
| /** |
| * Add all the entries from the specified map to this map. |
| * |
| * @param source The map whose entries are to be added to this map. |
| */ |
| public void putAll(Map source) { |
| int sourceSize = source.size(); |
| if (sourceSize == 0) { |
| return; |
| } |
| if (source instanceof CopyOnWriteIdentityMap) { |
| putAll(((CopyOnWriteIdentityMap) source).entries()); |
| return; |
| } |
| |
| Entry[] toCopy = new Entry[sourceSize]; |
| Iterator iter = source.entrySet().iterator(); |
| for (int i = 0; i < sourceSize; i++) { |
| Map.Entry mapEntry = (Map.Entry) iter.next(); |
| toCopy[i] = new Entry(mapEntry.getKey(), mapEntry.getValue()); |
| } |
| putAll(toCopy); |
| } |
| |
| /** |
| * Add all the keys from the specified array to this map with the value |
| * <code>null</code>. |
| * |
| * @param keys The array of keys to be added to this map. |
| */ |
| public void putAll(Object[] keys) { |
| int sourceSize = keys.length; |
| if (sourceSize == 0) { |
| return; |
| } |
| Entry[] toCopy = new Entry[sourceSize]; |
| for (int i = 0; i < sourceSize; i++) { |
| toCopy[i] = new Entry(keys[i], null); |
| } |
| putAll(toCopy); |
| } |
| |
| /** |
| * Add all the entries to this map. |
| * |
| * @param toCopy Array of entries to add to this map. |
| */ |
| private synchronized void putAll(Entry[] toCopy) { |
| int sourceSize = toCopy.length; |
| int size = entries.length; |
| Entry[] newEntries = new Entry[size + sourceSize]; |
| System.arraycopy(entries, 0, newEntries, 0, size); |
| copy: for (int n = 0; n < sourceSize; n++) { |
| Entry copy = toCopy[n]; |
| for (int i = 0; i < size; i++) { |
| if (newEntries[i].key == copy.key) { |
| newEntries[i] = copy; |
| continue copy; |
| } |
| } |
| newEntries[size] = copy; |
| size++; |
| } |
| if (size == newEntries.length) { |
| entries = newEntries; |
| return; |
| } |
| Entry[] e = new Entry[size]; |
| System.arraycopy(newEntries, 0, e, 0, size); |
| entries = e; |
| } |
| |
| /** |
| * Remove a key from the map and returns the value associated with the key. |
| * Key objects are compared using identity. |
| * |
| * @param key The key object to be removed from the map. |
| * @return <code>null</code> if the key was not in the list. |
| * Otherwise, the value associated with the key. |
| * @throws IllegalArgumentException If key is null. |
| */ |
| public synchronized Object remove(Object key) { |
| if (key == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| int size = entries.length; |
| for (int i = 0; i < size; i++) { |
| if (entries[i].key == key) { |
| Object v = entries[i].value; |
| if (size == 1) { |
| entries = emptyArray; |
| return v; |
| } |
| Entry[] newEntries = new Entry[size - 1]; |
| if (i > 0) { |
| System.arraycopy(entries, 0, newEntries, 0, i); |
| } |
| int next = size - 1 - i; |
| if (next > 0) { |
| System.arraycopy(entries, i + 1, newEntries, i, next); |
| } |
| entries = newEntries; |
| return v; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Remove all entries from the map. |
| * |
| */ |
| public synchronized void clear() { |
| entries = emptyArray; |
| } |
| |
| /* These methods only read the map and are not synchronized. */ |
| |
| /** |
| * Accessor for methods which only read the entries. |
| * @return The array of entries. Callers to this method MUST NOT |
| * modify the returned array. |
| */ |
| private Entry[] entries() { |
| return entries; |
| } |
| |
| /** |
| * Is the map empty? |
| * |
| * @return <code>true</code> if the list is empty. |
| */ |
| public boolean isEmpty() { |
| return size() == 0; |
| } |
| |
| /** |
| * Return the number of entries in the map. |
| * |
| * @return The number of entries in the map. |
| */ |
| public int size() { |
| return entries().length; |
| } |
| |
| /** |
| * Return the value object for the specified key. |
| * Keys are compared using identity. |
| * |
| * @param key The key object. |
| * @return The value object for the specified key. |
| * @throws IllegalArgumentException If key is null. |
| */ |
| public Object get(Object key) { |
| if (key == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| Entry[] e = entries(); |
| for (int i = 0; i < e.length; i++) { |
| if (e[i].key == key) { |
| return e[i].value; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Check if the map contains the specified key. |
| * Keys are compared using identity. |
| * |
| * @param key The key object. |
| * @return <code>true</code> if the specified key is in the map. |
| * @throws IllegalArgumentException If key is null. |
| */ |
| public boolean containsKey(Object key) { |
| if (key == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| Entry[] e = entries(); |
| for (int i = 0; i < e.length; i++) { |
| if (e[i].key == key) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Check if the map contains the specified value. |
| * Values are compared using identity. |
| * |
| * @param value The value object. |
| * @return <code>true</code> if the specified value is in the map. |
| */ |
| public boolean containsValue(Object value) { |
| Entry[] e = entries(); |
| for (int i = 0; i < e.length; i++) { |
| if (e[i].value == value) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a snapshot of the entries in this map. |
| * The returned set will NOT be changed by future changes to this map. |
| * |
| * @return A Set of Map.Entry for each entry in this map. |
| * The set and the entries returned by the set cannot be modified. |
| */ |
| public Set entrySet() { |
| return new EntrySet(entries(), EntrySet.ENTRY); |
| } |
| |
| /** |
| * Returns a snapshot of the keys in this map. |
| * The returned set will NOT be changed by future changes to this map. |
| * |
| * @return A Set of the key objects in this map |
| * The set cannot be modified. |
| */ |
| public Set keySet() { |
| return new EntrySet(entries(), EntrySet.KEY); |
| } |
| |
| /** |
| * Returns a snapshot of the values in this map. |
| * The returned collection will NOT be changed by future changes to this map. |
| * |
| * @return A Collection of the value objects in this map. |
| * The collection cannot be modified. |
| */ |
| public Collection values() { |
| return new EntrySet(entries(), EntrySet.VALUE); |
| } |
| |
| /** |
| * This class represents the entry in this Map. |
| * Entry is immutable. |
| */ |
| private static class Entry implements Map.Entry { |
| /** |
| * Key object. |
| */ |
| final Object key; |
| |
| /** |
| * Value object. |
| */ |
| final Object value; |
| |
| /** |
| * Constructor for map entry. |
| * @param key Key object in entry. Used for uniqueness. |
| * @param value Value object stored with key object. |
| */ |
| Entry(final Object key, final Object value) { |
| this.key = key; |
| this.value = value; |
| } |
| |
| public Object getKey() { |
| return key; |
| } |
| |
| public Object getValue() { |
| return value; |
| } |
| |
| public Object setValue(Object value) { |
| throw new UnsupportedOperationException(); // entries cannot be modified. |
| } |
| } |
| |
| /** |
| * Set class used for entry and key sets and values collections. |
| * |
| * This class is immutable. |
| */ |
| private static class EntrySet extends AbstractSet { |
| private final Entry[] entries; |
| private final int returnType; |
| final static int ENTRY = 1; |
| final static int KEY = 2; |
| final static int VALUE = 3; |
| |
| EntrySet(Entry[] entries, int returnType) { |
| this.entries = entries; |
| this.returnType = returnType; |
| } |
| |
| public Iterator iterator() { |
| return new EntryIterator(entries, returnType); |
| } |
| |
| public int size() { |
| return entries.length; |
| } |
| } |
| |
| /** |
| * Iterator class used for entry and key sets and values collections. |
| * |
| */ |
| private static class EntryIterator implements Iterator { |
| private final Entry[] entries; |
| private final int returnType; |
| private int cursor = 0; |
| |
| EntryIterator(Entry[] entries, int returnType) { |
| this.entries = entries; |
| this.returnType = returnType; |
| } |
| |
| public boolean hasNext() { |
| return cursor < entries.length; |
| } |
| |
| public Object next() { |
| if (cursor == entries.length) { |
| throw new NoSuchElementException(); |
| } |
| switch (returnType) { |
| case EntrySet.ENTRY : |
| return entries[cursor++]; |
| case EntrySet.KEY : |
| return entries[cursor++].key; |
| case EntrySet.VALUE : |
| return entries[cursor++].value; |
| } |
| throw new InternalError(); |
| } |
| |
| public void remove() { |
| throw new UnsupportedOperationException(); // the collection cannot be modified. |
| } |
| } |
| } |