blob: 44d09bf538fa17562d8ce8b5ac4161dfd4a80555 [file] [log] [blame]
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -07003 *
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
17package org.onosproject.store.primitives.resources.impl;
18
Jordan Halterman5e884352018-05-21 22:11:07 -070019import java.util.ArrayList;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070020import java.util.Arrays;
21import java.util.Collection;
22import java.util.Comparator;
23import java.util.List;
24import java.util.Map;
Jordan Halterman5e884352018-05-21 22:11:07 -070025import java.util.UUID;
Jordan Haltermanc8b591e2018-07-11 09:49:15 -070026import java.util.concurrent.ArrayBlockingQueue;
27import java.util.concurrent.BlockingQueue;
Jordan Halterman5e884352018-05-21 22:11:07 -070028import java.util.concurrent.TimeUnit;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070029
Jordan Halterman2bf177c2017-06-29 01:49:08 -070030import com.google.common.collect.Lists;
31import com.google.common.collect.Multiset;
32import com.google.common.collect.TreeMultiset;
33import io.atomix.protocols.raft.proxy.RaftProxy;
34import io.atomix.protocols.raft.service.RaftService;
35import org.apache.commons.collections.keyvalue.DefaultMapEntry;
36import org.junit.Test;
37import org.onlab.util.Tools;
Jordan Halterman5e884352018-05-21 22:11:07 -070038import org.onosproject.store.service.AsyncIterator;
Jordan Haltermanc8b591e2018-07-11 09:49:15 -070039import org.onosproject.store.service.MultimapEvent;
40import org.onosproject.store.service.MultimapEventListener;
Jordan Halterman2bf177c2017-06-29 01:49:08 -070041
Jordan Haltermanc8b591e2018-07-11 09:49:15 -070042import static org.junit.Assert.assertArrayEquals;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070043import static org.junit.Assert.assertEquals;
44import static org.junit.Assert.assertFalse;
Jordan Haltermanc8b591e2018-07-11 09:49:15 -070045import static org.junit.Assert.assertNotNull;
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070046import static org.junit.Assert.assertTrue;
47
48/**
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070049 * Tests the {@link AtomixConsistentSetMultimap}.
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070050 */
Jordan Halterman2bf177c2017-06-29 01:49:08 -070051public class AtomixConsistentSetMultimapTest extends AtomixTestBase<AtomixConsistentSetMultimap> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070052 private final String keyOne = "hello";
53 private final String keyTwo = "goodbye";
54 private final String keyThree = "foo";
55 private final String keyFour = "bar";
56 private final byte[] valueOne = Tools.getBytesUtf8(keyOne);
57 private final byte[] valueTwo = Tools.getBytesUtf8(keyTwo);
58 private final byte[] valueThree = Tools.getBytesUtf8(keyThree);
59 private final byte[] valueFour = Tools.getBytesUtf8(keyFour);
60 private final List<String> allKeys = Lists.newArrayList(keyOne, keyTwo,
61 keyThree, keyFour);
62 private final List<byte[]> allValues = Lists.newArrayList(valueOne,
63 valueTwo,
64 valueThree,
65 valueFour);
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070066
Jordan Halterman2bf177c2017-06-29 01:49:08 -070067 @Override
68 protected RaftService createService() {
69 return new AtomixConsistentSetMultimapService();
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -070070 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070071
72 @Override
Jordan Halterman2bf177c2017-06-29 01:49:08 -070073 protected AtomixConsistentSetMultimap createPrimitive(RaftProxy proxy) {
74 return new AtomixConsistentSetMultimap(proxy);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070075 }
76
77 /**
78 * Test that size behaves correctly (This includes testing of the empty
79 * check).
80 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070081 @Test
82 public void testSize() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -070083 AtomixConsistentSetMultimap map = createResource("testOneMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -070084 //Simplest operation case
85 map.isEmpty().thenAccept(result -> assertTrue(result));
86 map.put(keyOne, valueOne).
87 thenAccept(result -> assertTrue(result)).join();
88 map.isEmpty().thenAccept(result -> assertFalse(result));
89 map.size().thenAccept(result -> assertEquals(1, (int) result))
90 .join();
91 //Make sure sizing is dependent on values not keys
92 map.put(keyOne, valueTwo).
93 thenAccept(result -> assertTrue(result)).join();
94 map.size().thenAccept(result -> assertEquals(2, (int) result))
95 .join();
96 //Ensure that double adding has no effect
97 map.put(keyOne, valueOne).
98 thenAccept(result -> assertFalse(result)).join();
99 map.size().thenAccept(result -> assertEquals(2, (int) result))
100 .join();
101 //Check handling for multiple keys
102 map.put(keyTwo, valueOne)
103 .thenAccept(result -> assertTrue(result)).join();
104 map.put(keyTwo, valueTwo)
105 .thenAccept(result -> assertTrue(result)).join();
106 map.size().thenAccept(result -> assertEquals(4, (int) result))
107 .join();
108 //Check size with removal
109 map.remove(keyOne, valueOne).
110 thenAccept(result -> assertTrue(result)).join();
111 map.size().thenAccept(result -> assertEquals(3, (int) result))
112 .join();
Jon Hall7d77fe12018-04-24 18:03:10 -0700113 //Check behavior under remove of non-existent key
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700114 map.remove(keyOne, valueOne).
115 thenAccept(result -> assertFalse(result)).join();
116 map.size().thenAccept(result -> assertEquals(3, (int) result))
117 .join();
118 //Check clearing the entirety of the map
119 map.clear().join();
120 map.size().thenAccept(result -> assertEquals(0, (int) result))
121 .join();
122 map.isEmpty().thenAccept(result -> assertTrue(result));
123
124 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700125 }
126
127 /**
128 * Contains tests for value, key and entry.
129 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700130 @Test
131 public void containsTest() throws Throwable {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700132 AtomixConsistentSetMultimap map = createResource("testTwoMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700133
134 //Populate the maps
135 allKeys.forEach(key -> {
136 map.putAll(key, allValues)
137 .thenAccept(result -> assertTrue(result)).join();
138 });
139 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
140
141 //Test key contains positive results
142 allKeys.forEach(key -> {
143 map.containsKey(key)
144 .thenAccept(result -> assertTrue(result)).join();
145 });
146
147 //Test value contains positive results
148 allValues.forEach(value -> {
149 map.containsValue(value)
150 .thenAccept(result -> assertTrue(result)).join();
151 });
152
153 //Test contains entry for all possible entries
154 allKeys.forEach(key -> {
155 allValues.forEach(value -> {
156 map.containsEntry(key, value)
157 .thenAccept(result -> assertTrue(result)).join();
158 });
159 });
160
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700161 final String[] removedKey = new String[1];
162
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700163 //Test behavior after removals
164 allValues.forEach(value -> {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700165 allKeys.forEach(key -> {
166 map.remove(key, value)
167 .thenAccept(result -> assertTrue(result)).join();
168 map.containsEntry(key, value)
169 .thenAccept(result -> assertFalse(result)).join();
170 removedKey[0] = key;
171 });
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700172 });
173
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700174 //Check that contains key works properly for removed keys
175 map.containsKey(removedKey[0])
Jordan Halterman3b137372018-04-30 14:42:41 -0700176 .thenAccept(result -> assertFalse(result)).join();
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700177
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700178 //Check that contains value works correctly for removed values
179 allValues.forEach(value -> {
180 map.containsValue(value)
181 .thenAccept(result -> assertFalse(result)).join();
182 });
183
184 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700185 }
186
187 /**
188 * Contains tests for put, putAll, remove, removeAll and replace.
189 * @throws Exception
190 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700191 @Test
192 public void addAndRemoveTest() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700193 AtomixConsistentSetMultimap map = createResource("testThreeMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700194
195 //Test single put
196 allKeys.forEach(key -> {
197 //Value should actually be added here
198 allValues.forEach(value -> {
199 map.put(key, value)
200 .thenAccept(result -> assertTrue(result)).join();
201 //Duplicate values should be ignored here
202 map.put(key, value)
203 .thenAccept(result -> assertFalse(result)).join();
204 });
205 });
206
207 //Test single remove
208 allKeys.forEach(key -> {
209 //Value should actually be added here
210 allValues.forEach(value -> {
211 map.remove(key, value)
212 .thenAccept(result -> assertTrue(result)).join();
213 //Duplicate values should be ignored here
214 map.remove(key, value)
215 .thenAccept(result -> assertFalse(result)).join();
216 });
217 });
218
219 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
220
221 //Test multi put
222 allKeys.forEach(key -> {
223 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
224 .thenAccept(result -> assertTrue(result)).join();
225 map.putAll(key, Lists.newArrayList(allValues.subList(0, 2)))
226 .thenAccept(result -> assertFalse(result)).join();
227 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
228 .thenAccept(result -> assertTrue(result)).join();
229 map.putAll(key, Lists.newArrayList(allValues.subList(2, 4)))
230 .thenAccept(result -> assertFalse(result)).join();
231
232 });
233
234 //Test multi remove
235 allKeys.forEach(key -> {
236 //Split the lists to test how multiRemove can work piecewise
237 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
238 .thenAccept(result -> assertTrue(result)).join();
239 map.removeAll(key, Lists.newArrayList(allValues.subList(0, 2)))
240 .thenAccept(result -> assertFalse(result)).join();
241 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
242 .thenAccept(result -> assertTrue(result)).join();
243 map.removeAll(key, Lists.newArrayList(allValues.subList(2, 4)))
244 .thenAccept(result -> assertFalse(result)).join();
245 });
246
Jordan Halterman8c57a092018-06-04 14:53:06 -0700247 allKeys.forEach(key -> {
248 map.putAndGet(key, valueOne)
249 .thenAccept(result -> assertEquals(1, result.value().size()));
250 map.putAndGet(key, valueTwo)
251 .thenAccept(result -> assertEquals(2, result.value().size()));
252 map.putAndGet(key, valueThree)
253 .thenAccept(result -> assertEquals(3, result.value().size()));
254 map.putAndGet(key, valueFour)
255 .thenAccept(result -> assertEquals(4, result.value().size()));
256 });
257
258 allKeys.forEach(key -> {
259 map.removeAndGet(key, valueOne)
260 .thenAccept(result -> assertEquals(3, result.value().size()));
261 map.removeAndGet(key, valueTwo)
262 .thenAccept(result -> assertEquals(2, result.value().size()));
263 map.removeAndGet(key, valueThree)
264 .thenAccept(result -> assertEquals(1, result.value().size()));
265 map.removeAndGet(key, valueFour)
266 .thenAccept(result -> assertEquals(0, result.value().size()));
267 });
268
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700269 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
270
271 //Repopulate for next test
272 allKeys.forEach(key -> {
273 map.putAll(key, allValues)
274 .thenAccept(result -> assertTrue(result)).join();
275 });
276
277 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
278
279 //Test removeAll of entire entry
280 allKeys.forEach(key -> {
281 map.removeAll(key).thenAccept(result -> {
282 assertTrue(
283 byteArrayCollectionIsEqual(allValues, result.value()));
284 }).join();
285 map.removeAll(key).thenAccept(result -> {
286 assertFalse(
287 byteArrayCollectionIsEqual(allValues, result.value()));
288 }).join();
289 });
290
291 map.isEmpty().thenAccept(result -> assertTrue(result)).join();
292
293 //Repopulate for next test
294 allKeys.forEach(key -> {
295 map.putAll(key, allValues)
296 .thenAccept(result -> assertTrue(result)).join();
297 });
298
299 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
300
301 allKeys.forEach(key -> {
302 map.replaceValues(key, allValues)
303 .thenAccept(result ->
304 assertTrue(byteArrayCollectionIsEqual(allValues,
305 result.value())))
306 .join();
307 map.replaceValues(key, Lists.newArrayList())
308 .thenAccept(result ->
309 assertTrue(byteArrayCollectionIsEqual(allValues,
310 result.value())))
311 .join();
312 map.replaceValues(key, allValues)
313 .thenAccept(result ->
314 assertTrue(result.value().isEmpty()))
315 .join();
316 });
317
318
319 //Test replacements of partial sets
320 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
321
322 allKeys.forEach(key -> {
323 map.remove(key, valueOne)
324 .thenAccept(result ->
325 assertTrue(result)).join();
326 map.replaceValues(key, Lists.newArrayList())
327 .thenAccept(result ->
328 assertTrue(byteArrayCollectionIsEqual(
329 Lists.newArrayList(valueTwo, valueThree,
330 valueFour),
331 result.value())))
332 .join();
333 map.replaceValues(key, allValues)
334 .thenAccept(result ->
335 assertTrue(result.value().isEmpty()))
336 .join();
337 });
338
339 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700340 }
341
Jordan Halterman5e884352018-05-21 22:11:07 -0700342 @Test
343 public void testStreams() throws Exception {
344 AtomixConsistentSetMultimap map = createResource("testStreams");
Jordan Halterman15f33712018-06-21 00:00:15 -0700345 for (int i = 0; i < 100; i++) {
346 for (int j = 0; j < 100; j++) {
347 map.put(String.valueOf(i), String.valueOf(j).getBytes()).join();
348 }
Jordan Halterman5e884352018-05-21 22:11:07 -0700349 }
350
351 List<Map.Entry<String, byte[]>> entries = new ArrayList<>();
352 AsyncIterator<Map.Entry<String, byte[]>> iterator = map.iterator().get(5, TimeUnit.SECONDS);
353 while (iterator.hasNext().get(5, TimeUnit.SECONDS)) {
Jordan Halterman15f33712018-06-21 00:00:15 -0700354 map.put(keyOne, UUID.randomUUID().toString().getBytes()).join();
Jordan Halterman5e884352018-05-21 22:11:07 -0700355 entries.add(iterator.next().get(5, TimeUnit.SECONDS));
356 }
Jordan Halterman15f33712018-06-21 00:00:15 -0700357 assertEquals(10000, entries.size());
Jordan Halterman5e884352018-05-21 22:11:07 -0700358 }
359
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700360 /**
361 * Tests the get, keySet, keys, values, and entries implementations as well
alshabibef10b732016-05-26 16:10:08 -0700362 * as a trivial test of the asMap functionality (throws error).
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700363 * @throws Exception
364 */
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700365 @Test
366 public void testAccessors() throws Exception {
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700367 AtomixConsistentSetMultimap map = createResource("testFourMap");
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700368
369 //Populate for full map behavior tests
370 allKeys.forEach(key -> {
371 map.putAll(key, allValues)
372 .thenAccept(result -> assertTrue(result)).join();
373 });
374
375 map.size().thenAccept(result -> assertEquals(16, (int) result)).join();
376
377 allKeys.forEach(key -> {
378 map.get(key).thenAccept(result -> {
379 assertTrue(byteArrayCollectionIsEqual(allValues,
380 result.value()));
381 }).join();
382 });
383
384 //Test that the key set is correct
385 map.keySet()
386 .thenAccept(result ->
387 assertTrue(stringArrayCollectionIsEqual(allKeys,
388 result)))
389 .join();
390 //Test that the correct set and occurrence of values are found in the
391 //values result
392 map.values().thenAccept(result -> {
393 final Multiset<byte[]> set = TreeMultiset.create(
394 new ByteArrayComparator());
395 for (int i = 0; i < 4; i++) {
396 set.addAll(allValues);
397 }
398 assertEquals(16, result.size());
399 result.forEach(value -> assertTrue(set.remove(value)));
400 assertTrue(set.isEmpty());
401
402 }).join();
403
404 //Test that keys returns the right result including the correct number
405 //of each item
406 map.keys().thenAccept(result -> {
407 final Multiset<String> set = TreeMultiset.create();
408 for (int i = 0; i < 4; i++) {
409 set.addAll(allKeys);
410 }
411 assertEquals(16, result.size());
412 result.forEach(value -> assertTrue(set.remove(value)));
413 assertTrue(set.isEmpty());
414
415 }).join();
416
417 //Test that the right combination of key, value pairs are present
418 map.entries().thenAccept(result -> {
419 final Multiset<Map.Entry<String, byte[]>> set =
420 TreeMultiset.create(new EntryComparator());
421 allKeys.forEach(key -> {
422 allValues.forEach(value -> {
423 set.add(new DefaultMapEntry(key, value));
424 });
425 });
426 assertEquals(16, result.size());
427 result.forEach(entry -> assertTrue(set.remove(entry)));
428 assertTrue(set.isEmpty());
429 }).join();
430
431
432 //Testing for empty map behavior
433 map.clear().join();
434
435 allKeys.forEach(key -> {
436 map.get(key).thenAccept(result -> {
437 assertTrue(result.value().isEmpty());
438 }).join();
439 });
440
441 map.keySet().thenAccept(result -> assertTrue(result.isEmpty())).join();
442 map.values().thenAccept(result -> assertTrue(result.isEmpty())).join();
443 map.keys().thenAccept(result -> assertTrue(result.isEmpty())).join();
444 map.entries()
445 .thenAccept(result -> assertTrue(result.isEmpty())).join();
446
Aaron Kruglikovb5a41e52016-06-23 15:37:41 -0700447 map.destroy().join();
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700448 }
449
Jordan Haltermanc8b591e2018-07-11 09:49:15 -0700450 @Test
451 public void testMultimapEvents() throws Throwable {
452 final byte[] value1 = Tools.getBytesUtf8("value1");
453 final byte[] value2 = Tools.getBytesUtf8("value2");
454 final byte[] value3 = Tools.getBytesUtf8("value3");
455
456 AtomixConsistentSetMultimap map = createResource("testFourMap");
457 TestMultimapEventListener listener = new TestMultimapEventListener();
458
459 // add listener; insert new value into map and verify an INSERT event is received.
460 map.addListener(listener).thenCompose(v -> map.put("foo", value1)).join();
461 MultimapEvent<String, byte[]> event = listener.event();
462 assertNotNull(event);
463 assertEquals(MultimapEvent.Type.INSERT, event.type());
464 assertTrue(Arrays.equals(value1, event.newValue()));
465
466 // remove listener and verify listener is not notified.
467 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
468 assertFalse(listener.eventReceived());
469
470 // add listener; insert new value into map and verify an INSERT event is received.
471 map.addListener(listener)
472 .thenCompose(v -> map.replaceValues("foo", Arrays.asList(value2, value3))).join();
473 event = listener.event();
474 assertNotNull(event);
475 assertEquals(MultimapEvent.Type.REMOVE, event.type());
476 assertArrayEquals(value1, event.oldValue());
477 event = listener.event();
478 assertNotNull(event);
479 assertEquals(MultimapEvent.Type.INSERT, event.type());
480 assertArrayEquals(value3, event.newValue());
481
482 // remove listener and verify listener is not notified.
483 map.removeListener(listener).thenCompose(v -> map.put("foo", value2)).join();
484 assertFalse(listener.eventReceived());
485
486 map.removeListener(listener).join();
487 }
488
Aaron Kruglikova1801aa2016-07-12 15:17:30 -0700489 private AtomixConsistentSetMultimap createResource(String mapName) {
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700490 try {
Jordan Halterman2bf177c2017-06-29 01:49:08 -0700491 AtomixConsistentSetMultimap map = newPrimitive(mapName);
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700492 return map;
493 } catch (Throwable e) {
Ray Milkeydbd38212018-07-02 09:18:09 -0700494 throw new IllegalStateException(e.toString());
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700495 }
496 }
497
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700498 /**
499 * Returns two arrays contain the same set of elements,
500 * regardless of order.
501 * @param o1 first collection
502 * @param o2 second collection
503 * @return true if they contain the same elements
504 */
505 private boolean byteArrayCollectionIsEqual(
506 Collection<? extends byte[]> o1, Collection<? extends byte[]> o2) {
507 if (o1 == null || o2 == null || o1.size() != o2.size()) {
508 return false;
509 }
510 for (byte[] array1 : o1) {
511 boolean matched = false;
512 for (byte[] array2 : o2) {
513 if (Arrays.equals(array1, array2)) {
514 matched = true;
515 break;
516 }
517 }
518 if (!matched) {
519 return false;
520 }
521 }
522 return true;
523 }
524
525 /**
526 * Compares two collections of strings returns true if they contain the
527 * same strings, false otherwise.
528 * @param s1 string collection one
529 * @param s2 string collection two
530 * @return true if the two sets contain the same strings
531 */
532 private boolean stringArrayCollectionIsEqual(
533 Collection<? extends String> s1, Collection<? extends String> s2) {
534 if (s1 == null || s2 == null || s1.size() != s2.size()) {
535 return false;
536 }
537 for (String string1 : s1) {
538 boolean matched = false;
539 for (String string2 : s2) {
540 if (string1.equals(string2)) {
541 matched = true;
542 break;
543 }
544 }
545 if (!matched) {
546 return false;
547 }
548 }
549 return true;
550 }
551
552 /**
553 * Byte array comparator implementation.
554 */
555 private class ByteArrayComparator implements Comparator<byte[]> {
556
557 @Override
558 public int compare(byte[] o1, byte[] o2) {
559 if (Arrays.equals(o1, o2)) {
560 return 0;
561 } else {
562 for (int i = 0; i < o1.length && i < o2.length; i++) {
563 if (o1[i] < o2[i]) {
564 return -1;
565 } else if (o1[i] > o2[i]) {
566 return 1;
567 }
568 }
569 return o1.length > o2.length ? 1 : -1;
570 }
571 }
572 }
573
574 /**
575 * Entry comparator, uses both key and value to determine equality,
576 * for comparison falls back to the default string comparator.
577 */
578 private class EntryComparator
579 implements Comparator<Map.Entry<String, byte[]>> {
580
581 @Override
582 public int compare(Map.Entry<String, byte[]> o1,
583 Map.Entry<String, byte[]> o2) {
584 if (o1 == null || o1.getKey() == null || o2 == null ||
585 o2.getKey() == null) {
586 throw new IllegalArgumentException();
587 }
588 if (o1.getKey().equals(o2.getKey()) &&
589 Arrays.equals(o1.getValue(), o2.getValue())) {
590 return 0;
591 } else {
592 return o1.getKey().compareTo(o2.getKey());
593 }
594 }
595 }
Jordan Haltermanc8b591e2018-07-11 09:49:15 -0700596
597 private static class TestMultimapEventListener implements MultimapEventListener<String, byte[]> {
598
599 private final BlockingQueue<MultimapEvent<String, byte[]>> queue = new ArrayBlockingQueue<>(1);
600
601 @Override
602 public void event(MultimapEvent<String, byte[]> event) {
603 try {
604 queue.put(event);
605 } catch (InterruptedException e) {
606 throw new IllegalStateException(e);
607 }
608 }
609
610 public boolean eventReceived() {
611 return !queue.isEmpty();
612 }
613
614 public MultimapEvent<String, byte[]> event() throws InterruptedException {
615 return queue.take();
616 }
617 }
Aaron Kruglikov44a1fef2016-04-27 11:29:23 -0700618}