summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Stepper2008-01-18 14:01:05 (EST)
committerEike Stepper2008-01-18 14:01:05 (EST)
commite96b6045e0276d25b92e55a26708ee339a8f1ecf (patch)
tree521f9545d93318cda31a80a04b85bceaeb07485b
parent08be45f1841f8b4cd798e90518d4dc8d6a67b12c (diff)
downloadcdo-e96b6045e0276d25b92e55a26708ee339a8f1ecf.zip
cdo-e96b6045e0276d25b92e55a26708ee339a8f1ecf.tar.gz
cdo-e96b6045e0276d25b92e55a26708ee339a8f1ecf.tar.bz2
[215688] Create save points
https://bugs.eclipse.org/bugs/show_bug.cgi?id=215688
-rw-r--r--plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/MultiMapTest.java291
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/container/delegate/ContainerMap.java9
-rw-r--r--plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/MultiMap.java744
3 files changed, 1039 insertions, 5 deletions
diff --git a/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/MultiMapTest.java b/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/MultiMapTest.java
new file mode 100644
index 0000000..5c650ab
--- /dev/null
+++ b/plugins/org.eclipse.net4j.tests/src/org/eclipse/net4j/util/tests/MultiMapTest.java
@@ -0,0 +1,291 @@
+/***************************************************************************
+ * Copyright (c) 2004 - 2008 Eike Stepper, Germany.
+ * 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:
+ * Eike Stepper - initial API and implementation
+ **************************************************************************/
+package org.eclipse.net4j.util.tests;
+
+import org.eclipse.net4j.util.collection.MapEntry;
+import org.eclipse.net4j.util.collection.MultiMap.ListBased;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Eike Stepper
+ */
+public class MultiMapTest extends AbstractOMTest
+{
+ public void testListBased() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertEquals(true, multiMap.isEmpty());
+ assertEquals(0, multiMap.size());
+ assertEquals(false, multiMap.containsKey(1));
+ assertEquals(false, multiMap.containsValue("value" + 1));
+ assertEquals(null, multiMap.get(1));
+
+ addDelegate(multiMap, 0, 0);
+ assertEquals(true, multiMap.isEmpty());
+ assertEquals(0, multiMap.size());
+ assertEquals(false, multiMap.containsKey(1));
+ assertEquals(false, multiMap.containsValue("value" + 1));
+ assertEquals(null, multiMap.get(1));
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertEquals(false, multiMap.isEmpty());
+ assertEquals(30, multiMap.size());
+ assertEquals(true, multiMap.containsKey(1));
+ assertEquals(true, multiMap.containsValue("value" + 1));
+ assertEquals("value" + 1, multiMap.get(1));
+ assertEquals("value" + 6, multiMap.get(6));
+
+ addDelegate(multiMap, 6, 10);
+ assertEquals(false, multiMap.isEmpty());
+ assertEquals(30, multiMap.size());
+ assertEquals(true, multiMap.containsKey(1));
+ assertEquals(true, multiMap.containsValue("value" + 1));
+ assertEquals("value" + 1, multiMap.get(1));
+ assertEquals("value" + 6, multiMap.get(6));
+ assertEquals(null, multiMap.get(35));
+
+ addDelegate(multiMap, 26, 10);
+ assertEquals(false, multiMap.isEmpty());
+ assertEquals(35, multiMap.size());
+ assertEquals(true, multiMap.containsKey(1));
+ assertEquals(true, multiMap.containsValue("value" + 1));
+ assertEquals("value" + 1, multiMap.get(1));
+ assertEquals("value" + 6, multiMap.get(6));
+ assertEquals("value" + 35, multiMap.get(35));
+ }
+
+ public void testEntrySet() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertEquals(true, multiMap.entrySet().isEmpty());
+ assertEquals(0, multiMap.entrySet().size());
+ assertEquals(false, multiMap.entrySet().contains(new MapEntry<Integer, String>(1, "value1")));
+
+ addDelegate(multiMap, 0, 0);
+ assertEquals(true, multiMap.entrySet().isEmpty());
+ assertEquals(0, multiMap.entrySet().size());
+ assertEquals(false, multiMap.entrySet().contains(new MapEntry<Integer, String>(1, "value1")));
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertEquals(false, multiMap.entrySet().isEmpty());
+ assertEquals(30, multiMap.entrySet().size());
+ assertEquals(true, multiMap.entrySet().contains(new MapEntry<Integer, String>(1, "value1")));
+
+ addDelegate(multiMap, 6, 10);
+ assertEquals(false, multiMap.entrySet().isEmpty());
+ assertEquals(30, multiMap.entrySet().size());
+ assertEquals(true, multiMap.entrySet().contains(new MapEntry<Integer, String>(1, "value1")));
+ assertEquals(false, multiMap.entrySet().contains(new MapEntry<Integer, String>(35, "value35")));
+
+ addDelegate(multiMap, 26, 10);
+ assertEquals(false, multiMap.entrySet().isEmpty());
+ assertEquals(35, multiMap.entrySet().size());
+ assertEquals(true, multiMap.entrySet().contains(new MapEntry<Integer, String>(1, "value1")));
+ assertEquals(true, multiMap.entrySet().contains(new MapEntry<Integer, String>(35, "value35")));
+ }
+
+ public void testKeySet() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertEquals(true, multiMap.keySet().isEmpty());
+ assertEquals(0, multiMap.keySet().size());
+ assertEquals(false, multiMap.keySet().contains(1));
+
+ addDelegate(multiMap, 0, 0);
+ assertEquals(true, multiMap.keySet().isEmpty());
+ assertEquals(0, multiMap.keySet().size());
+ assertEquals(false, multiMap.keySet().contains(1));
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertEquals(false, multiMap.keySet().isEmpty());
+ assertEquals(30, multiMap.keySet().size());
+ assertEquals(true, multiMap.keySet().contains(1));
+
+ addDelegate(multiMap, 6, 10);
+ assertEquals(false, multiMap.keySet().isEmpty());
+ assertEquals(30, multiMap.keySet().size());
+ assertEquals(true, multiMap.keySet().contains(1));
+ assertEquals(false, multiMap.keySet().contains(35));
+
+ addDelegate(multiMap, 26, 10);
+ assertEquals(false, multiMap.keySet().isEmpty());
+ assertEquals(35, multiMap.keySet().size());
+ assertEquals(true, multiMap.keySet().contains(1));
+ assertEquals(true, multiMap.keySet().contains(35));
+ }
+
+ public void testValues() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertEquals(true, multiMap.values().isEmpty());
+ assertEquals(0, multiMap.values().size());
+ assertEquals(false, multiMap.values().contains("value1"));
+
+ addDelegate(multiMap, 0, 0);
+ assertEquals(true, multiMap.values().isEmpty());
+ assertEquals(0, multiMap.values().size());
+ assertEquals(false, multiMap.values().contains("value1"));
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertEquals(false, multiMap.values().isEmpty());
+ assertEquals(30, multiMap.values().size());
+ assertEquals(true, multiMap.values().contains("value1"));
+
+ addDelegate(multiMap, 6, 10);
+ assertEquals(false, multiMap.values().isEmpty());
+ assertEquals(30, multiMap.values().size());
+ assertEquals(true, multiMap.values().contains("value1"));
+ assertEquals(false, multiMap.values().contains("value35"));
+
+ addDelegate(multiMap, 26, 10);
+ assertEquals(false, multiMap.values().isEmpty());
+ assertEquals(35, multiMap.values().size());
+ assertEquals(true, multiMap.values().contains("value1"));
+ assertEquals(true, multiMap.values().contains("value35"));
+ }
+
+ public void testEntrySetIterator() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertIterator(new HashSet<Object>(), multiMap.entrySet());
+
+ addDelegate(multiMap, 0, 0);
+ assertIterator(new HashSet<Object>(), multiMap.entrySet());
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertIterator(createMapEntries(1, 30), multiMap.entrySet());
+
+ addDelegate(multiMap, 6, 10);
+ assertIterator(createMapEntries(1, 30), multiMap.entrySet());
+
+ addDelegate(multiMap, 26, 10);
+ assertIterator(createMapEntries(1, 35), multiMap.entrySet());
+ }
+
+ public void testKeySetIterator() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertIterator(new HashSet<Object>(), multiMap.keySet());
+
+ addDelegate(multiMap, 0, 0);
+ assertIterator(new HashSet<Object>(), multiMap.keySet());
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertIterator(createKeys(1, 30), multiMap.keySet());
+
+ addDelegate(multiMap, 6, 10);
+ assertIterator(createKeys(1, 30), multiMap.keySet());
+
+ addDelegate(multiMap, 26, 10);
+ assertIterator(createKeys(1, 35), multiMap.keySet());
+ }
+
+ public void testValuesIterator() throws Exception
+ {
+ ListBased<Integer, String> multiMap = new ListBased<Integer, String>();
+ assertIterator(new HashSet<Object>(), multiMap.values());
+
+ addDelegate(multiMap, 0, 0);
+ assertIterator(new HashSet<Object>(), multiMap.values());
+
+ addDelegate(multiMap, 1, 10);
+ addDelegate(multiMap, 11, 10);
+ addDelegate(multiMap, 21, 10);
+ assertIterator(createValues(1, 30), multiMap.values());
+
+ addDelegate(multiMap, 6, 10);
+ assertIterator(createValues(1, 30), multiMap.values());
+
+ addDelegate(multiMap, 26, 10);
+ assertIterator(createValues(1, 35), multiMap.values());
+ }
+
+ private void addDelegate(ListBased<Integer, String> multiMap, int start, int count)
+ {
+ Map<Integer, String> map = new HashMap<Integer, String>();
+ for (int i = 0; i < count; i++)
+ {
+ int key = start + i;
+ map.put(key, "value" + key);
+ }
+
+ multiMap.getDelegates().add(map);
+ }
+
+ private void assertIterator(Set<?> expectedObjects, Collection<?> actualObjects)
+ {
+ for (Object actualObject : actualObjects)
+ {
+ if (!expectedObjects.remove(actualObject))
+ {
+ fail("Unexpected object: " + actualObject);
+ }
+ }
+
+ if (!expectedObjects.isEmpty())
+ {
+ fail("Missing objects: " + expectedObjects);
+ }
+ }
+
+ private Set<Object> createMapEntries(int start, int count)
+ {
+ Set<Object> result = new HashSet<Object>();
+ for (int i = 0; i < count; i++)
+ {
+ int key = start + i;
+ result.add(new MapEntry<Integer, String>(key, "value" + key));
+ }
+
+ return result;
+ }
+
+ private Set<Object> createKeys(int start, int count)
+ {
+ Set<Object> result = new HashSet<Object>();
+ for (int i = 0; i < count; i++)
+ {
+ int key = start + i;
+ result.add(key);
+ }
+
+ return result;
+ }
+
+ private Set<Object> createValues(int start, int count)
+ {
+ Set<Object> result = new HashSet<Object>();
+ for (int i = 0; i < count; i++)
+ {
+ int key = start + i;
+ result.add("value" + key);
+ }
+
+ return result;
+ }
+}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/container/delegate/ContainerMap.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/container/delegate/ContainerMap.java
index c6f5155..ea033de 100644
--- a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/container/delegate/ContainerMap.java
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/internal/util/container/delegate/ContainerMap.java
@@ -19,7 +19,6 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
-import java.util.Map.Entry;
/**
* @author Eike Stepper
@@ -62,7 +61,7 @@ public class ContainerMap<K, V> extends AbstractDelegator<Map.Entry<K, V>> imple
{
event.addDelta(new ContainerMapEntry<K, V>(key, removed), IContainerDelta.Kind.REMOVED);
}
-
+
event.addDelta(new ContainerMapEntry<K, V>(key, value), IContainerDelta.Kind.ADDED);
fireEvent(event);
return removed;
@@ -85,10 +84,10 @@ public class ContainerMap<K, V> extends AbstractDelegator<Map.Entry<K, V>> imple
{
event.addDelta(new ContainerMapEntry<K, V>(key, removed), IContainerDelta.Kind.REMOVED);
}
-
+
event.addDelta(new ContainerMapEntry<K, V>(key, value), IContainerDelta.Kind.ADDED);
}
-
+
dispatchEvent(event);
}
@@ -102,7 +101,7 @@ public class ContainerMap<K, V> extends AbstractDelegator<Map.Entry<K, V>> imple
{
fireRemovedEvent(new ContainerMapEntry<Object, V>(key, removed));
}
-
+
return removed;
}
diff --git a/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/MultiMap.java b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/MultiMap.java
new file mode 100644
index 0000000..401ae52
--- /dev/null
+++ b/plugins/org.eclipse.net4j.util/src/org/eclipse/net4j/util/collection/MultiMap.java
@@ -0,0 +1,744 @@
+/***************************************************************************
+ * Copyright (c) 2004 - 2008 Eike Stepper, Germany.
+ * 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:
+ * Eike Stepper - initial API and implementation
+ **************************************************************************/
+package org.eclipse.net4j.util.collection;
+
+import org.eclipse.net4j.util.ObjectUtil;
+
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * @author Eike Stepper
+ */
+public abstract class MultiMap<K, V> implements Map<K, V>
+{
+ private transient Entries entries;
+
+ private transient Set<K> keys;
+
+ private transient Collection<V> values;
+
+ public MultiMap()
+ {
+ }
+
+ public abstract int getDelegateCount();
+
+ public Map<K, V> getDelegate(int index)
+ {
+ if (0 <= index && index < getDelegateCount())
+ {
+ return doGetDelegate(index);
+ }
+
+ return null;
+ }
+
+ /**
+ * @category WRITE
+ */
+ public void clear()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public V put(K key, V value)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public void putAll(Map<? extends K, ? extends V> t)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public V remove(Object key)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean containsKey(Object key)
+ {
+ return containsKey(key, getDelegateCount());
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean containsValue(Object value)
+ {
+ for (int i = 0; i < getDelegateCount(); i++)
+ {
+ Map<K, V> delegate = getDelegate(i);
+ if (delegate != null)
+ {
+ if (delegate.containsValue(value))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @category READ
+ */
+ public V get(Object key)
+ {
+ for (int i = 0; i < getDelegateCount(); i++)
+ {
+ Map<K, V> delegate = getDelegate(i);
+ if (delegate != null)
+ {
+ if (delegate.containsKey(key))
+ {
+ return delegate.get(key);
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @category READ
+ */
+ public int size()
+ {
+ int size = 0;
+ Map<K, V> delegate = getDelegate(0);
+ if (delegate != null)
+ {
+ size += delegate.size();
+ for (int i = 1; i < getDelegateCount(); i++)
+ {
+ delegate = getDelegate(i);
+ if (delegate != null)
+ {
+ Set<K> keySet = delegate.keySet();
+ for (K key : keySet)
+ {
+ if (!containsKey(key, i))
+ {
+ ++size;
+ }
+ }
+ }
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean isEmpty()
+ {
+ for (int i = 0; i < getDelegateCount(); i++)
+ {
+ Map<K, V> delegate = getDelegate(i);
+ if (delegate != null)
+ {
+ if (!delegate.isEmpty())
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public synchronized Set<Map.Entry<K, V>> entrySet()
+ {
+ if (entries == null)
+ {
+ entries = new Entries();
+ }
+
+ return entries;
+ }
+
+ public synchronized Set<K> keySet()
+ {
+ if (keys == null)
+ {
+ keys = new Keys();
+ }
+
+ return keys;
+ }
+
+ public synchronized Collection<V> values()
+ {
+ if (values == null)
+ {
+ values = new Values();
+ }
+
+ return values;
+ }
+
+ protected boolean containsKey(Object key, int delegateCount)
+ {
+ for (int i = 0; i < delegateCount; i++)
+ {
+ Map<K, V> delegate = getDelegate(i);
+ if (delegate != null)
+ {
+ if (delegate.containsKey(key))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ protected abstract Map<K, V> doGetDelegate(int index);
+
+ /**
+ * @author Eike Stepper
+ */
+ public static class ListBased<K, V> extends MultiMap<K, V>
+ {
+ private List<Map<K, V>> delegates;
+
+ public ListBased()
+ {
+ }
+
+ public ListBased(List<Map<K, V>> delegates)
+ {
+ this.delegates = delegates;
+ }
+
+ public List<Map<K, V>> getDelegates()
+ {
+ if (delegates == null)
+ {
+ delegates = createDelegates();
+ }
+
+ return delegates;
+ }
+
+ public void setDelegates(List<Map<K, V>> delegates)
+ {
+ this.delegates = delegates;
+ }
+
+ @Override
+ public int getDelegateCount()
+ {
+ return getDelegates().size();
+ }
+
+ @Override
+ protected Map<K, V> doGetDelegate(int index)
+ {
+ return getDelegates().get(index);
+ }
+
+ protected List<Map<K, V>> createDelegates()
+ {
+ return new ArrayList<Map<K, V>>();
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Entries extends AbstractSet<Entry<K, V>>
+ {
+ public Entries()
+ {
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean add(Entry<K, V> o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean addAll(Collection<? extends Entry<K, V>> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public void clear()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean remove(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean removeAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean retainAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ @SuppressWarnings("unchecked")
+ public boolean contains(Object o)
+ {
+ if (o instanceof Map.Entry)
+ {
+ for (int i = 0; i < getDelegateCount(); i++)
+ {
+ Map<K, V> delegate = getDelegate(i);
+ if (delegate != null)
+ {
+ K key = ((Map.Entry<K, V>)o).getKey();
+ if (delegate.containsKey(key))
+ {
+ V value = ((Map.Entry<K, V>)o).getValue();
+ if (ObjectUtil.equals(delegate.get(key), value))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean isEmpty()
+ {
+ return MultiMap.this.isEmpty();
+ }
+
+ /**
+ * @category READ
+ */
+ public int size()
+ {
+ return MultiMap.this.size();
+ }
+
+ public Iterator<Entry<K, V>> iterator()
+ {
+ return new Iterator<Entry<K, V>>()
+ {
+ private Entry<K, V> next;
+
+ private int delegateIndex = -1;
+
+ private Iterator<Entry<K, V>> delegateIt;
+
+ /**
+ * @category WRITE
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean hasNext()
+ {
+ next = null;
+ while (next == null)
+ {
+ if (delegateIt == null)
+ {
+ Map<K, V> delegate = getDelegate(++delegateIndex);
+ if (delegate == null)
+ {
+ // All delegates have been iterated
+ break;
+ }
+
+ delegateIt = delegate.entrySet().iterator();
+ }
+
+ if (delegateIt.hasNext())
+ {
+ next = delegateIt.next();
+
+ // Check if this key has been returned previously
+ if (containsKey(next.getKey(), delegateIndex))
+ {
+ next = null;
+ }
+ }
+ else
+ {
+ // Determine next delegate iterator in next loop
+ delegateIt = null;
+ }
+ }
+
+ return next != null;
+ }
+
+ /**
+ * @category READ
+ */
+ public Map.Entry<K, V> next()
+ {
+ if (next == null)
+ {
+ throw new NoSuchElementException();
+ }
+
+ try
+ {
+ return next;
+ }
+ finally
+ {
+ next = null;
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Keys extends AbstractSet<K>
+ {
+ public Keys()
+ {
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean add(K o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean addAll(Collection<? extends K> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public void clear()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean remove(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean removeAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean retainAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean contains(Object o)
+ {
+ return MultiMap.this.containsKey(o);
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean isEmpty()
+ {
+ return MultiMap.this.isEmpty();
+ }
+
+ /**
+ * @category READ
+ */
+ public int size()
+ {
+ return MultiMap.this.size();
+ }
+
+ /**
+ * @category READ
+ */
+ public Iterator<K> iterator()
+ {
+ return new Iterator<K>()
+ {
+ private K next;
+
+ private int delegateIndex = -1;
+
+ private Iterator<K> delegateIt;
+
+ /**
+ * @category WRITE
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean hasNext()
+ {
+ next = null;
+ while (next == null)
+ {
+ if (delegateIt == null)
+ {
+ Map<K, V> delegate = getDelegate(++delegateIndex);
+ if (delegate == null)
+ {
+ // All delegates have been iterated
+ break;
+ }
+
+ delegateIt = delegate.keySet().iterator();
+ }
+
+ if (delegateIt.hasNext())
+ {
+ next = delegateIt.next();
+
+ // Check if this key has been returned previously
+ if (containsKey(next, delegateIndex))
+ {
+ next = null;
+ }
+ }
+ else
+ {
+ // Determine next delegate iterator in next loop
+ delegateIt = null;
+ }
+ }
+
+ return next != null;
+ }
+
+ /**
+ * @category READ
+ */
+ public K next()
+ {
+ if (next == null)
+ {
+ throw new NoSuchElementException();
+ }
+
+ try
+ {
+ return next;
+ }
+ finally
+ {
+ next = null;
+ }
+ }
+ };
+ }
+ }
+
+ /**
+ * @author Eike Stepper
+ */
+ private final class Values extends AbstractCollection<V>
+ {
+ public Values()
+ {
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean add(V o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean addAll(Collection<? extends V> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public void clear()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean remove(Object o)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean removeAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category WRITE
+ */
+ public boolean retainAll(Collection<?> c)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean contains(Object o)
+ {
+ return MultiMap.this.containsValue(o);
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean isEmpty()
+ {
+ return MultiMap.this.isEmpty();
+ }
+
+ /**
+ * @category READ
+ */
+ public int size()
+ {
+ return MultiMap.this.size();
+ }
+
+ /**
+ * @category READ
+ */
+ public Iterator<V> iterator()
+ {
+ return new Iterator<V>()
+ {
+ private Iterator<Entry<K, V>> delegateIt = entrySet().iterator();
+
+ /**
+ * @category WRITE
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @category READ
+ */
+ public boolean hasNext()
+ {
+ return delegateIt.hasNext();
+ }
+
+ /**
+ * @category READ
+ */
+ public V next()
+ {
+ return delegateIt.next().getValue();
+ }
+ };
+ }
+ }
+}