Added an extended set that provides methods to get and (conditionally) replace existing entries

Change-Id: Iec788ac010c59f31ff6d08a84ae3642b2d662fb2
diff --git a/utils/misc/src/main/java/org/onlab/util/ExtendedSet.java b/utils/misc/src/main/java/org/onlab/util/ExtendedSet.java
new file mode 100644
index 0000000..0ee1278
--- /dev/null
+++ b/utils/misc/src/main/java/org/onlab/util/ExtendedSet.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onlab.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
+
+import com.google.common.collect.Iterators;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A Set providing additional get, insertOrReplace and conditionalRemove methods.
+ */
+public class ExtendedSet<E> implements Set<E> {
+
+    private final Map<E, E> map;
+
+    /**
+     * Constructs a new instance by backing it with the supplied Map.
+     * <p>
+     * Constructed ExtendedSet will have the same concurrency properties as that of the supplied Map.
+     *
+     * @param map input map.
+     */
+    public ExtendedSet(Map<E, E> map) {
+        this.map = map;
+    }
+
+    /**
+     * Returns set element that is equal to the specified object.
+     * @param o
+     * @return
+     */
+    public E get(Object o) {
+        return map.get(o);
+    }
+
+    /**
+     * Inserts the entry if it is not already in the set otherwise replaces the existing entry
+     * if the supplied predicate evaluates to true.
+     * @param entry entry to add
+     * @param entryTest predicate that is used to evaluate if the existing entry should be replaced
+     * @return true if the set is updated; false otherwise
+     */
+    public boolean insertOrReplace(E entry, Predicate<E> entryTest) {
+        AtomicBoolean updated = new AtomicBoolean(false);
+        map.compute(checkNotNull(entry), (k, v) -> {
+            if (v == null || entryTest.test(v)) {
+                updated.set(true);
+                return entry;
+            }
+            return v;
+        });
+        return updated.get();
+    }
+
+    /**
+     * Removes the entry if the supplied predicate evaluates to true.
+     * @param entry entry to remove
+     * @param entryTest predicate that is used to evaluated aginst the existing entry. Return value of
+     * true implies value should be removed.
+     * @return true if the set is updated; false otherwise
+     */
+    public boolean conditionalRemove(E entry, Predicate<E> entryTest) {
+        AtomicBoolean updated = new AtomicBoolean(false);
+        map.compute(entry, (k, v) -> {
+            if (entryTest.test(v)) {
+                updated.set(true);
+                return null;
+            }
+            return v;
+        });
+        return updated.get();
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return map.containsKey(o);
+    }
+
+    @Override
+    public Iterator<E> iterator() {
+        return Iterators.transform(map.entrySet().iterator(), Map.Entry::getValue);
+    }
+
+    @Override
+    public Object[] toArray() {
+        return map.values().toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return map.values().toArray(a);
+    }
+
+    @Override
+    public boolean add(E e) {
+        return map.putIfAbsent(e, e) == null;
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return map.remove(o) != null;
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return c.stream()
+                .map(map::containsKey)
+                .reduce(Boolean::logicalAnd)
+                .orElse(true);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends E> c) {
+        return c.stream()
+                .map(e -> map.putIfAbsent(e, e) == null)
+                .reduce(Boolean::logicalOr)
+                .orElse(false);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return c.stream()
+                .filter(e -> !map.containsKey(e))
+                .map(e -> map.remove(e) != null)
+                .reduce(Boolean::logicalOr)
+                .orElse(false);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return c.stream()
+                .map(e -> map.remove(e) != null)
+                .reduce(Boolean::logicalOr)
+                .orElse(false);
+    }
+
+    @Override
+    public void clear() {
+        map.clear();
+    }
+}
\ No newline at end of file
diff --git a/utils/misc/src/test/java/org/onlab/util/ExtendedSetTest.java b/utils/misc/src/test/java/org/onlab/util/ExtendedSetTest.java
new file mode 100644
index 0000000..d6eeb66
--- /dev/null
+++ b/utils/misc/src/test/java/org/onlab/util/ExtendedSetTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onlab.util;
+
+import java.util.Objects;
+
+import org.junit.Test;
+
+import com.google.common.collect.Maps;
+
+import static org.junit.Assert.*;
+
+/**
+ * Unit tests for ExtendedSet.
+ */
+public class ExtendedSetTest {
+
+    @Test
+    public void testGet() {
+        ExtendedSet<TestValue> set = new ExtendedSet<>(Maps.newConcurrentMap());
+        TestValue e1 = new TestValue("foo", 1);
+        set.add(e1);
+        TestValue lookupValue = new TestValue("foo", 2);
+        TestValue setEntry = set.get(lookupValue);
+        assertEquals(e1, setEntry);
+    }
+
+    @Test
+    public void testInsertOrReplace() {
+        ExtendedSet<TestValue> set = new ExtendedSet<>(Maps.newConcurrentMap());
+        TestValue small = new TestValue("foo", 1);
+        TestValue medium = new TestValue("foo", 2);
+        TestValue large = new TestValue("foo", 3);
+        // input TestValue will replace existing TestValue if its value2() is greater
+        // than existing entry's value2()
+        assertTrue(set.insertOrReplace(small, existing -> existing.value2() < small.value2()));
+        assertTrue(set.insertOrReplace(large, existing -> existing.value2() < large.value2()));
+        assertFalse(set.insertOrReplace(medium, existing -> existing.value2() < medium.value2()));
+
+        assertTrue(set.contains(small));
+        assertTrue(set.contains(medium));
+        assertTrue(set.contains(large));
+    }
+
+    @Test
+    public void testConditionalRemove() {
+        ExtendedSet<TestValue> set = new ExtendedSet<>(Maps.newConcurrentMap());
+        TestValue small = new TestValue("foo", 1);
+        TestValue medium = new TestValue("foo", 2);
+
+        assertTrue(set.add(small));
+        set.conditionalRemove(medium, existing -> existing.value2() < medium.value2);
+        assertFalse(set.contains(small));
+    }
+
+    private class TestValue {
+        private String value1;
+        private int value2;
+
+        public TestValue(String v1, int v2) {
+            this.value1 = v1;
+            this.value2 = v2;
+        }
+
+        public String value1() {
+            return value1;
+        }
+
+        public int value2() {
+            return value2;
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other instanceof TestValue) {
+                TestValue that = (TestValue) other;
+                return Objects.equals(value1, that.value1);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(value1);
+        }
+    }
+}