Madan Jampani
Committed by Gerrit Code Review

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

Change-Id: Iec788ac010c59f31ff6d08a84ae3642b2d662fb2
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.util;
17 +
18 +import java.util.Collection;
19 +import java.util.Iterator;
20 +import java.util.Map;
21 +import java.util.Set;
22 +import java.util.concurrent.atomic.AtomicBoolean;
23 +import java.util.function.Predicate;
24 +
25 +import com.google.common.collect.Iterators;
26 +import static com.google.common.base.Preconditions.checkNotNull;
27 +
28 +/**
29 + * A Set providing additional get, insertOrReplace and conditionalRemove methods.
30 + */
31 +public class ExtendedSet<E> implements Set<E> {
32 +
33 + private final Map<E, E> map;
34 +
35 + /**
36 + * Constructs a new instance by backing it with the supplied Map.
37 + * <p>
38 + * Constructed ExtendedSet will have the same concurrency properties as that of the supplied Map.
39 + *
40 + * @param map input map.
41 + */
42 + public ExtendedSet(Map<E, E> map) {
43 + this.map = map;
44 + }
45 +
46 + /**
47 + * Returns set element that is equal to the specified object.
48 + * @param o
49 + * @return
50 + */
51 + public E get(Object o) {
52 + return map.get(o);
53 + }
54 +
55 + /**
56 + * Inserts the entry if it is not already in the set otherwise replaces the existing entry
57 + * if the supplied predicate evaluates to true.
58 + * @param entry entry to add
59 + * @param entryTest predicate that is used to evaluate if the existing entry should be replaced
60 + * @return true if the set is updated; false otherwise
61 + */
62 + public boolean insertOrReplace(E entry, Predicate<E> entryTest) {
63 + AtomicBoolean updated = new AtomicBoolean(false);
64 + map.compute(checkNotNull(entry), (k, v) -> {
65 + if (v == null || entryTest.test(v)) {
66 + updated.set(true);
67 + return entry;
68 + }
69 + return v;
70 + });
71 + return updated.get();
72 + }
73 +
74 + /**
75 + * Removes the entry if the supplied predicate evaluates to true.
76 + * @param entry entry to remove
77 + * @param entryTest predicate that is used to evaluated aginst the existing entry. Return value of
78 + * true implies value should be removed.
79 + * @return true if the set is updated; false otherwise
80 + */
81 + public boolean conditionalRemove(E entry, Predicate<E> entryTest) {
82 + AtomicBoolean updated = new AtomicBoolean(false);
83 + map.compute(entry, (k, v) -> {
84 + if (entryTest.test(v)) {
85 + updated.set(true);
86 + return null;
87 + }
88 + return v;
89 + });
90 + return updated.get();
91 + }
92 +
93 + @Override
94 + public int size() {
95 + return map.size();
96 + }
97 +
98 + @Override
99 + public boolean isEmpty() {
100 + return map.isEmpty();
101 + }
102 +
103 + @Override
104 + public boolean contains(Object o) {
105 + return map.containsKey(o);
106 + }
107 +
108 + @Override
109 + public Iterator<E> iterator() {
110 + return Iterators.transform(map.entrySet().iterator(), Map.Entry::getValue);
111 + }
112 +
113 + @Override
114 + public Object[] toArray() {
115 + return map.values().toArray();
116 + }
117 +
118 + @Override
119 + public <T> T[] toArray(T[] a) {
120 + return map.values().toArray(a);
121 + }
122 +
123 + @Override
124 + public boolean add(E e) {
125 + return map.putIfAbsent(e, e) == null;
126 + }
127 +
128 + @Override
129 + public boolean remove(Object o) {
130 + return map.remove(o) != null;
131 + }
132 +
133 + @Override
134 + public boolean containsAll(Collection<?> c) {
135 + return c.stream()
136 + .map(map::containsKey)
137 + .reduce(Boolean::logicalAnd)
138 + .orElse(true);
139 + }
140 +
141 + @Override
142 + public boolean addAll(Collection<? extends E> c) {
143 + return c.stream()
144 + .map(e -> map.putIfAbsent(e, e) == null)
145 + .reduce(Boolean::logicalOr)
146 + .orElse(false);
147 + }
148 +
149 + @Override
150 + public boolean retainAll(Collection<?> c) {
151 + return c.stream()
152 + .filter(e -> !map.containsKey(e))
153 + .map(e -> map.remove(e) != null)
154 + .reduce(Boolean::logicalOr)
155 + .orElse(false);
156 + }
157 +
158 + @Override
159 + public boolean removeAll(Collection<?> c) {
160 + return c.stream()
161 + .map(e -> map.remove(e) != null)
162 + .reduce(Boolean::logicalOr)
163 + .orElse(false);
164 + }
165 +
166 + @Override
167 + public void clear() {
168 + map.clear();
169 + }
170 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.util;
17 +
18 +import java.util.Objects;
19 +
20 +import org.junit.Test;
21 +
22 +import com.google.common.collect.Maps;
23 +
24 +import static org.junit.Assert.*;
25 +
26 +/**
27 + * Unit tests for ExtendedSet.
28 + */
29 +public class ExtendedSetTest {
30 +
31 + @Test
32 + public void testGet() {
33 + ExtendedSet<TestValue> set = new ExtendedSet<>(Maps.newConcurrentMap());
34 + TestValue e1 = new TestValue("foo", 1);
35 + set.add(e1);
36 + TestValue lookupValue = new TestValue("foo", 2);
37 + TestValue setEntry = set.get(lookupValue);
38 + assertEquals(e1, setEntry);
39 + }
40 +
41 + @Test
42 + public void testInsertOrReplace() {
43 + ExtendedSet<TestValue> set = new ExtendedSet<>(Maps.newConcurrentMap());
44 + TestValue small = new TestValue("foo", 1);
45 + TestValue medium = new TestValue("foo", 2);
46 + TestValue large = new TestValue("foo", 3);
47 + // input TestValue will replace existing TestValue if its value2() is greater
48 + // than existing entry's value2()
49 + assertTrue(set.insertOrReplace(small, existing -> existing.value2() < small.value2()));
50 + assertTrue(set.insertOrReplace(large, existing -> existing.value2() < large.value2()));
51 + assertFalse(set.insertOrReplace(medium, existing -> existing.value2() < medium.value2()));
52 +
53 + assertTrue(set.contains(small));
54 + assertTrue(set.contains(medium));
55 + assertTrue(set.contains(large));
56 + }
57 +
58 + @Test
59 + public void testConditionalRemove() {
60 + ExtendedSet<TestValue> set = new ExtendedSet<>(Maps.newConcurrentMap());
61 + TestValue small = new TestValue("foo", 1);
62 + TestValue medium = new TestValue("foo", 2);
63 +
64 + assertTrue(set.add(small));
65 + set.conditionalRemove(medium, existing -> existing.value2() < medium.value2);
66 + assertFalse(set.contains(small));
67 + }
68 +
69 + private class TestValue {
70 + private String value1;
71 + private int value2;
72 +
73 + public TestValue(String v1, int v2) {
74 + this.value1 = v1;
75 + this.value2 = v2;
76 + }
77 +
78 + public String value1() {
79 + return value1;
80 + }
81 +
82 + public int value2() {
83 + return value2;
84 + }
85 +
86 + @Override
87 + public boolean equals(Object other) {
88 + if (other instanceof TestValue) {
89 + TestValue that = (TestValue) other;
90 + return Objects.equals(value1, that.value1);
91 + }
92 + return false;
93 + }
94 +
95 + @Override
96 + public int hashCode() {
97 + return Objects.hash(value1);
98 + }
99 + }
100 +}