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);
+ }
+ }
+}