From 24f70c6048ddcd145edc201b0c13e3d06fd3707a Mon Sep 17 00:00:00 2001
From: brinn <brinn>
Date: Sun, 7 Nov 2010 17:35:49 +0000
Subject: [PATCH] add: support for multiple keys to TableMapNonUniqueKey
 (merged from cifex, 10.06.x)

SVN: 18569
---
 .../collections/IMultiKeyExtractor.java       | 32 ++++++++
 .../collections/TableMapNonUniqueKey.java     | 82 ++++++++++++++-----
 .../collections/TableMapNonUniqueKeyTest.java | 33 +++++++-
 3 files changed, 124 insertions(+), 23 deletions(-)
 create mode 100644 common/source/java/ch/systemsx/cisd/common/collections/IMultiKeyExtractor.java

diff --git a/common/source/java/ch/systemsx/cisd/common/collections/IMultiKeyExtractor.java b/common/source/java/ch/systemsx/cisd/common/collections/IMultiKeyExtractor.java
new file mode 100644
index 00000000000..3ab68b5cfc7
--- /dev/null
+++ b/common/source/java/ch/systemsx/cisd/common/collections/IMultiKeyExtractor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2007 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.common.collections;
+
+import java.util.Collection;
+
+/**
+ * Interface defining the role of a key extractor when an entity can have multiple keys.
+ * 
+ * @author Franz-Josef Elmer
+ */
+public interface IMultiKeyExtractor<K, E>
+{
+    /**
+     * Returns the keys of type <code>K</code> from an entity <code>E</code>.
+     */
+    public Collection<K> getKey(E e);
+}
diff --git a/common/source/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKey.java b/common/source/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKey.java
index bf657c1b6aa..68e3895233e 100644
--- a/common/source/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKey.java
+++ b/common/source/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKey.java
@@ -16,6 +16,8 @@
 
 package ch.systemsx.cisd.common.collections;
 
+import java.util.Collection;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -58,7 +60,7 @@ public class TableMapNonUniqueKey<K, E> implements Iterable<E>
 
     private final Map<K, Set<E>> map = new LinkedHashMap<K, Set<E>>();
 
-    private final IKeyExtractor<K, E> extractor;
+    private final IMultiKeyExtractor<K, E> extractor;
 
     private final UniqueValueViolationStrategy uniqueValueViolationStrategy;
 
@@ -74,6 +76,18 @@ public class TableMapNonUniqueKey<K, E> implements Iterable<E>
         this(rows, extractor, UniqueValueViolationStrategy.ERROR);
     }
 
+    /**
+     * Creates a new instance for the specified rows and key extractor.
+     * 
+     * @param rows Collection of rows of type <code>E</code>.
+     * @param extractor Strategy to extract a key of type <code>E</code> for an object of type
+     *            <code>E</code>.
+     */
+    public TableMapNonUniqueKey(final Iterable<E> rows, final IMultiKeyExtractor<K, E> extractor)
+    {
+        this(rows, extractor, UniqueValueViolationStrategy.ERROR);
+    }
+
     /**
      * Creates a new instance for the specified rows and key extractor.
      * 
@@ -83,6 +97,33 @@ public class TableMapNonUniqueKey<K, E> implements Iterable<E>
      */
     public TableMapNonUniqueKey(final Iterable<E> rows, final IKeyExtractor<K, E> extractor,
             UniqueValueViolationStrategy uniqueValueViolationStrategy)
+    {
+        assert rows != null : "Unspecified collection of rows.";
+        assert extractor != null : "Unspecified key extractor.";
+        assert uniqueValueViolationStrategy != null : "Unspecified unique value violation strategy.";
+        this.extractor = new IMultiKeyExtractor<K, E>()
+            {
+                public Collection<K> getKey(E e)
+                {
+                    return Collections.singleton(extractor.getKey(e));
+                }
+            };
+        this.uniqueValueViolationStrategy = uniqueValueViolationStrategy;
+        for (final E row : rows)
+        {
+            add(row);
+        }
+    }
+
+    /**
+     * Creates a new instance for the specified rows and key extractor.
+     * 
+     * @param rows Collection of rows of type <code>E</code>.
+     * @param extractor Strategy to extract a key of type <code>E</code> for an object of type
+     *            <code>E</code>.
+     */
+    public TableMapNonUniqueKey(final Iterable<E> rows, final IMultiKeyExtractor<K, E> extractor,
+            UniqueValueViolationStrategy uniqueValueViolationStrategy)
     {
         assert rows != null : "Unspecified collection of rows.";
         assert extractor != null : "Unspecified key extractor.";
@@ -115,25 +156,28 @@ public class TableMapNonUniqueKey<K, E> implements Iterable<E>
      */
     public final void add(final E row) throws UniqueValueViolationException
     {
-        final K key = extractor.getKey(row);
-        Set<E> set = map.get(key);
-        if (set == null)
-        {
-            set = new LinkedHashSet<E>();
-            map.put(key, set);
-            set.add(row);
-        } else if (uniqueValueViolationStrategy == UniqueValueViolationStrategy.KEEP_FIRST
-                || set.contains(row) == false)
-        {
-            set.add(row);
-        } else if (uniqueValueViolationStrategy == UniqueValueViolationStrategy.KEEP_LAST)
+        final Collection<K> keys = extractor.getKey(row);
+        for (K key : keys)
         {
-            set.remove(row);
-            set.add(row);
-        } else if (uniqueValueViolationStrategy == UniqueValueViolationStrategy.ERROR)
-        {
-            throw new UniqueValueViolationException("Row '" + row.toString()
-                    + "' already stored in the map.");
+            Set<E> set = map.get(key);
+            if (set == null)
+            {
+                set = new LinkedHashSet<E>();
+                map.put(key, set);
+                set.add(row);
+            } else if (uniqueValueViolationStrategy == UniqueValueViolationStrategy.KEEP_FIRST
+                    || set.contains(row) == false)
+            {
+                set.add(row);
+            } else if (uniqueValueViolationStrategy == UniqueValueViolationStrategy.KEEP_LAST)
+            {
+                set.remove(row);
+                set.add(row);
+            } else if (uniqueValueViolationStrategy == UniqueValueViolationStrategy.ERROR)
+            {
+                throw new UniqueValueViolationException("Row '" + row.toString()
+                        + "' already stored in the map.");
+            }
         }
     }
 
diff --git a/common/sourceTest/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKeyTest.java b/common/sourceTest/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKeyTest.java
index 0a6f190d19b..5ba87233c9f 100644
--- a/common/sourceTest/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKeyTest.java
+++ b/common/sourceTest/java/ch/systemsx/cisd/common/collections/TableMapNonUniqueKeyTest.java
@@ -16,13 +16,16 @@
 
 package ch.systemsx.cisd.common.collections;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 
 import static org.testng.AssertJUnit.*;
 
+import org.apache.commons.lang.StringUtils;
 import org.testng.annotations.Test;
 
 /**
@@ -33,17 +36,24 @@ import org.testng.annotations.Test;
 public class TableMapNonUniqueKeyTest
 {
 
-    final IKeyExtractor<Integer, String> integerExtractor = new IKeyExtractor<Integer, String>()
+    final IMultiKeyExtractor<Integer, String> integerExtractor = new IMultiKeyExtractor<Integer, String>()
         {
-            public Integer getKey(String e)
+            public Collection<Integer> getKey(String e)
             {
                 final int i = e.indexOf(':');
                 if (i >= 0)
                 {
-                    return Integer.parseInt(e.substring(i + 1));
+                    String intStr = e.substring(i + 1);
+                    String[] intStrs = StringUtils.split(intStr, ',');
+                    Collection<Integer> ints = new ArrayList<Integer>(intStrs.length);
+                    for (String inStr : intStrs) 
+                    {
+                        ints.add(Integer.parseInt(inStr));
+                    }
+                    return ints;
                 } else
                 {
-                    return Integer.parseInt(e);
+                    return Collections.singleton(Integer.parseInt(e));
                 }
             }
         };
@@ -146,4 +156,19 @@ public class TableMapNonUniqueKeyTest
                 .tryGet(42));
     }
 
+    @Test
+    public void testTryGetNonUniqueMultiKey()
+    {
+        final TableMapNonUniqueKey<Integer, String> tableMap =
+                new TableMapNonUniqueKey<Integer, String>(Arrays.asList("a:42,8", "7", "b:42", "0",
+                        "b:7", "c:42"), integerExtractor);
+        assertNull(tableMap.tryGet(10));
+        assertEquals(Collections.singleton("0"), tableMap.tryGet(0));
+        assertEquals(new HashSet<String>(Arrays.asList("7", "b:7")), tableMap.tryGet(7));
+        assertEquals(new HashSet<String>(Arrays.asList("a:42,8", "b:42", "c:42")), tableMap
+                .tryGet(42));
+        assertEquals(new HashSet<String>(Arrays.asList("a:42,8")), tableMap
+                .tryGet(8));
+    }
+
 }
-- 
GitLab