Madan Jampani
Committed by Gerrit Code Review

Updated ECMap remove call to return the value that was removed

Change-Id: Id7eacc04f4bb9322e4f98da5664c2fa46e0ea6fc
...@@ -99,16 +99,14 @@ public interface EventuallyConsistentMap<K, V> { ...@@ -99,16 +99,14 @@ public interface EventuallyConsistentMap<K, V> {
99 /** 99 /**
100 * Removes the mapping associated with the specified key from the map. 100 * Removes the mapping associated with the specified key from the map.
101 * <p> 101 * <p>
102 - * Note: this differs from the specification of {@link java.util.Map} 102 + * Clients are expected to register an {@link EventuallyConsistentMapListener} if
103 - * because it does not return the previous value associated with the key.
104 - * Clients are expected to register an
105 - * {@link EventuallyConsistentMapListener} if
106 * they are interested in receiving notification of updates to the map. 103 * they are interested in receiving notification of updates to the map.
107 * </p> 104 * </p>
108 * 105 *
109 * @param key the key to remove the mapping for 106 * @param key the key to remove the mapping for
107 + * @return previous value associated with key, or null if there was no mapping for key.
110 */ 108 */
111 - void remove(K key); 109 + V remove(K key);
112 110
113 /** 111 /**
114 * Removes the given key-value mapping from the map, if it exists. 112 * Removes the given key-value mapping from the map, if it exists.
......
...@@ -49,6 +49,7 @@ import java.util.HashMap; ...@@ -49,6 +49,7 @@ import java.util.HashMap;
49 import java.util.LinkedList; 49 import java.util.LinkedList;
50 import java.util.List; 50 import java.util.List;
51 import java.util.Map; 51 import java.util.Map;
52 +import java.util.Optional;
52 import java.util.Set; 53 import java.util.Set;
53 import java.util.Timer; 54 import java.util.Timer;
54 import java.util.concurrent.ConcurrentHashMap; 55 import java.util.concurrent.ConcurrentHashMap;
...@@ -58,11 +59,14 @@ import java.util.concurrent.ExecutorService; ...@@ -58,11 +59,14 @@ import java.util.concurrent.ExecutorService;
58 import java.util.concurrent.Executors; 59 import java.util.concurrent.Executors;
59 import java.util.concurrent.ScheduledExecutorService; 60 import java.util.concurrent.ScheduledExecutorService;
60 import java.util.concurrent.TimeUnit; 61 import java.util.concurrent.TimeUnit;
62 +import java.util.concurrent.atomic.AtomicReference;
61 import java.util.function.BiFunction; 63 import java.util.function.BiFunction;
62 import java.util.stream.Collectors; 64 import java.util.stream.Collectors;
63 65
64 import static com.google.common.base.Preconditions.checkNotNull; 66 import static com.google.common.base.Preconditions.checkNotNull;
65 import static com.google.common.base.Preconditions.checkState; 67 import static com.google.common.base.Preconditions.checkState;
68 +import static java.util.Objects.isNull;
69 +import static java.util.Objects.nonNull;
66 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; 70 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
67 import static org.onlab.util.BoundedThreadPool.newFixedThreadPool; 71 import static org.onlab.util.BoundedThreadPool.newFixedThreadPool;
68 import static org.onlab.util.Tools.groupedThreads; 72 import static org.onlab.util.Tools.groupedThreads;
...@@ -347,42 +351,54 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -347,42 +351,54 @@ public class EventuallyConsistentMapImpl<K, V>
347 } 351 }
348 352
349 @Override 353 @Override
350 - public void remove(K key) { 354 + public V remove(K key) {
351 checkState(!destroyed, destroyedMessage); 355 checkState(!destroyed, destroyedMessage);
352 checkNotNull(key, ERROR_NULL_KEY); 356 checkNotNull(key, ERROR_NULL_KEY);
353 357
354 // TODO prevent calls here if value is important for timestamp 358 // TODO prevent calls here if value is important for timestamp
355 Timestamp timestamp = timestampProvider.apply(key, null); 359 Timestamp timestamp = timestampProvider.apply(key, null);
356 360
357 - if (removeInternal(key, timestamp)) { 361 + Optional<V> removedValue = removeInternal(key, timestamp);
358 - notifyPeers(new RemoveEntry<>(key, timestamp), 362 + if (removedValue == null) {
359 - peerUpdateFunction.apply(key, null)); 363 + return null;
360 - notifyListeners(new EventuallyConsistentMapEvent<>(
361 - EventuallyConsistentMapEvent.Type.REMOVE, key, null));
362 } 364 }
365 + notifyPeers(new RemoveEntry<>(key, timestamp),
366 + peerUpdateFunction.apply(key, null));
367 + notifyListeners(new EventuallyConsistentMapEvent<>(
368 + EventuallyConsistentMapEvent.Type.REMOVE, key, removedValue.orElse(null)));
369 +
370 + return removedValue.orElse(null);
363 } 371 }
364 372
365 - private boolean removeInternal(K key, Timestamp timestamp) { 373 + /**
374 + * Returns null if the timestamp is for a outdated request i.e.
375 + * the value is the map is more recent or a tombstone exists with a
376 + * more recent timestamp.
377 + * Returns non-empty optional if a value was indeed removed from the map.
378 + * Returns empty optional if map did not contain a value for the key but the existing
379 + * tombstone is older than this timestamp.
380 + * @param key key
381 + * @param timestamp timestamp for remove request
382 + * @return Optional value.
383 + */
384 + private Optional<V> removeInternal(K key, Timestamp timestamp) {
366 if (timestamp == null) { 385 if (timestamp == null) {
367 - return false; 386 + return null;
368 } 387 }
369 388
370 counter.incrementCount(); 389 counter.incrementCount();
371 - final MutableBoolean updated = new MutableBoolean(false); 390 + final AtomicReference<Optional<V>> removedValue = new AtomicReference<>(null);
372 -
373 items.compute(key, (k, existing) -> { 391 items.compute(key, (k, existing) -> {
374 if (existing != null && existing.isNewerThan(timestamp)) { 392 if (existing != null && existing.isNewerThan(timestamp)) {
375 - updated.setFalse();
376 return existing; 393 return existing;
377 } else { 394 } else {
378 - updated.setTrue(); 395 + removedValue.set(existing == null ? Optional.empty() : Optional.of(existing.value()));
379 - // remove from items map
380 return null; 396 return null;
381 } 397 }
382 }); 398 });
383 399
384 - if (updated.isFalse()) { 400 + if (isNull(removedValue.get())) {
385 - return false; 401 + return null;
386 } 402 }
387 403
388 boolean updatedTombstone = false; 404 boolean updatedTombstone = false;
...@@ -397,11 +413,14 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -397,11 +413,14 @@ public class EventuallyConsistentMapImpl<K, V>
397 } 413 }
398 } 414 }
399 415
400 - if (updated.booleanValue() && persistent) { 416 + if (persistent) {
401 persistentStore.remove(key, timestamp); 417 persistentStore.remove(key, timestamp);
402 } 418 }
403 419
404 - return (!tombstonesDisabled && updatedTombstone) || updated.booleanValue(); 420 + if (tombstonesDisabled || updatedTombstone) {
421 + return removedValue.get();
422 + }
423 + return null;
405 } 424 }
406 425
407 @Override 426 @Override
...@@ -412,7 +431,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -412,7 +431,7 @@ public class EventuallyConsistentMapImpl<K, V>
412 431
413 Timestamp timestamp = timestampProvider.apply(key, value); 432 Timestamp timestamp = timestampProvider.apply(key, value);
414 433
415 - if (removeInternal(key, timestamp)) { 434 + if (nonNull(removeInternal(key, timestamp))) {
416 notifyPeers(new RemoveEntry<>(key, timestamp), 435 notifyPeers(new RemoveEntry<>(key, timestamp),
417 peerUpdateFunction.apply(key, value)); 436 peerUpdateFunction.apply(key, value));
418 notifyListeners(new EventuallyConsistentMapEvent<>( 437 notifyListeners(new EventuallyConsistentMapEvent<>(
...@@ -641,7 +660,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -641,7 +660,7 @@ public class EventuallyConsistentMapImpl<K, V>
641 if (remoteDeadTimestamp != null && 660 if (remoteDeadTimestamp != null &&
642 remoteDeadTimestamp.isNewerThan(localValue.timestamp())) { 661 remoteDeadTimestamp.isNewerThan(localValue.timestamp())) {
643 // sender has a more recent remove 662 // sender has a more recent remove
644 - if (removeInternal(key, remoteDeadTimestamp)) { 663 + if (nonNull(removeInternal(key, remoteDeadTimestamp))) {
645 externalEvents.add(new EventuallyConsistentMapEvent<>( 664 externalEvents.add(new EventuallyConsistentMapEvent<>(
646 EventuallyConsistentMapEvent.Type.REMOVE, key, null)); 665 EventuallyConsistentMapEvent.Type.REMOVE, key, null));
647 } 666 }
...@@ -697,7 +716,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -697,7 +716,7 @@ public class EventuallyConsistentMapImpl<K, V>
697 local.timestamp())) { 716 local.timestamp())) {
698 // If the remote has a more recent tombstone than either our local 717 // If the remote has a more recent tombstone than either our local
699 // value, then do a remove with their timestamp 718 // value, then do a remove with their timestamp
700 - if (removeInternal(key, remoteDeadTimestamp)) { 719 + if (nonNull(removeInternal(key, remoteDeadTimestamp))) {
701 externalEvents.add(new EventuallyConsistentMapEvent<>( 720 externalEvents.add(new EventuallyConsistentMapEvent<>(
702 EventuallyConsistentMapEvent.Type.REMOVE, key, null)); 721 EventuallyConsistentMapEvent.Type.REMOVE, key, null));
703 } 722 }
...@@ -744,7 +763,7 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -744,7 +763,7 @@ public class EventuallyConsistentMapImpl<K, V>
744 // TODO clean this for loop up 763 // TODO clean this for loop up
745 for (AbstractEntry<K, V> entry : events) { 764 for (AbstractEntry<K, V> entry : events) {
746 final K key = entry.key(); 765 final K key = entry.key();
747 - final V value; 766 + V value;
748 final Timestamp timestamp = entry.timestamp(); 767 final Timestamp timestamp = entry.timestamp();
749 final EventuallyConsistentMapEvent.Type type; 768 final EventuallyConsistentMapEvent.Type type;
750 if (entry instanceof PutEntry) { 769 if (entry instanceof PutEntry) {
...@@ -764,7 +783,11 @@ public class EventuallyConsistentMapImpl<K, V> ...@@ -764,7 +783,11 @@ public class EventuallyConsistentMapImpl<K, V>
764 success = putInternal(key, value, timestamp); 783 success = putInternal(key, value, timestamp);
765 break; 784 break;
766 case REMOVE: 785 case REMOVE:
767 - success = removeInternal(key, timestamp); 786 + Optional<V> removedValue = removeInternal(key, timestamp);
787 + success = removedValue != null;
788 + if (success) {
789 + value = removedValue.orElse(null);
790 + }
768 break; 791 break;
769 default: 792 default:
770 success = false; 793 success = false;
......
...@@ -326,8 +326,9 @@ public class EventuallyConsistentMapImplTest { ...@@ -326,8 +326,9 @@ public class EventuallyConsistentMapImplTest {
326 EventuallyConsistentMapListener<String, String> listener 326 EventuallyConsistentMapListener<String, String> listener
327 = getListener(); 327 = getListener();
328 listener.event(new EventuallyConsistentMapEvent<>( 328 listener.event(new EventuallyConsistentMapEvent<>(
329 + EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1));
330 + listener.event(new EventuallyConsistentMapEvent<>(
329 EventuallyConsistentMapEvent.Type.REMOVE, KEY1, null)); 331 EventuallyConsistentMapEvent.Type.REMOVE, KEY1, null));
330 - expectLastCall().times(2);
331 listener.event(new EventuallyConsistentMapEvent<>( 332 listener.event(new EventuallyConsistentMapEvent<>(
332 EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1)); 333 EventuallyConsistentMapEvent.Type.PUT, KEY1, VALUE1));
333 listener.event(new EventuallyConsistentMapEvent<>( 334 listener.event(new EventuallyConsistentMapEvent<>(
...@@ -425,9 +426,9 @@ public class EventuallyConsistentMapImplTest { ...@@ -425,9 +426,9 @@ public class EventuallyConsistentMapImplTest {
425 EventuallyConsistentMapListener<String, String> listener 426 EventuallyConsistentMapListener<String, String> listener
426 = getListener(); 427 = getListener();
427 listener.event(new EventuallyConsistentMapEvent<>( 428 listener.event(new EventuallyConsistentMapEvent<>(
428 - EventuallyConsistentMapEvent.Type.REMOVE, KEY1, null)); 429 + EventuallyConsistentMapEvent.Type.REMOVE, KEY1, VALUE1));
429 listener.event(new EventuallyConsistentMapEvent<>( 430 listener.event(new EventuallyConsistentMapEvent<>(
430 - EventuallyConsistentMapEvent.Type.REMOVE, KEY2, null)); 431 + EventuallyConsistentMapEvent.Type.REMOVE, KEY2, VALUE2));
431 replay(listener); 432 replay(listener);
432 433
433 // clear() on an empty map is a no-op - no messages will be sent 434 // clear() on an empty map is a no-op - no messages will be sent
......