/*******************************************************************************
* Copyright (c) 2016 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Nyßen (itemis AG) - initial API & implementation
*
*******************************************************************************/
package org.eclipse.gef.common.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import org.eclipse.gef.common.beans.binding.SetMultimapExpressionHelper;
import org.eclipse.gef.common.beans.property.ReadOnlySetMultimapWrapper;
import org.eclipse.gef.common.beans.property.SimpleSetMultimapProperty;
import org.eclipse.gef.common.collections.CollectionUtils;
import org.eclipse.gef.common.collections.ObservableSetMultimap;
import org.eclipse.gef.common.collections.SetMultimapChangeListener;
import org.eclipse.gef.common.collections.SetMultimapListenerHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.inject.Provider;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
/**
* Tests for correct behavior of {@link ObservableSetMultimap} implementations,
* including respective {@link ObservableValue observable values}, as well as
* related {@link SetMultimapListenerHelper} and
* {@link SetMultimapExpressionHelper} helper classes. Concrete implementations
* are tested by parameterizing the test with a respective Provider, which is
* done for {@link ObservableSetMultimapWrapper} as well as
* {@link SimpleSetMultimapProperty} and {@link ReadOnlySetMultimapWrapper}.
* <p>
* Ensures that correct behavior of the underlying {@link SetMultimap} is
* preserved and that {@link InvalidationListener} and
* {@link SetMultimapChangeListener}, as well as {@link ChangeListener} (in case
* of observable values) are notified properly.
* <p>
* Test strategy is to use a backup {@link SetMultimap} on which to apply the
* same operations as on the two be tested {@link ObservableSetMultimap}, so
* that same behavior is ensured.
*
* @author anyssen
*
*/
@RunWith(Parameterized.class)
public class ObservableSetMultimapTests {
protected static class InvalidationExpector
implements InvalidationListener {
int expect = 0;
public void check() {
if (expect > 0) {
fail("Did not receive " + expect
+ " expected invalidation event.");
}
}
public void expect(int expext) {
this.expect = expext;
}
@Override
public void invalidated(Observable observable) {
if (expect-- <= 0) {
fail("Did not expect an invalidation event.");
}
}
}
protected static class SetMultimapChangeExpector<K, V>
implements SetMultimapChangeListener<K, V> {
private ObservableSetMultimap<K, V> source;
private LinkedList<LinkedList<K>> keyQueue = new LinkedList<>();
private LinkedList<LinkedList<Set<V>>> addedValuesQueue = new LinkedList<>();
private LinkedList<LinkedList<Set<V>>> removedValuesQueue = new LinkedList<>();
public SetMultimapChangeExpector(ObservableSetMultimap<K, V> source) {
this.source = source;
}
public void addAtomicExpectation() {
keyQueue.addFirst(new LinkedList<K>());
addedValuesQueue.addFirst(new LinkedList<Set<V>>());
removedValuesQueue.addFirst(new LinkedList<Set<V>>());
}
public void addElementaryExpectation(K key, Set<V> removedValues,
Set<V> addedValues) {
if (keyQueue.size() <= 0) {
throw new IllegalArgumentException(
"Add atomic expectation first.");
}
keyQueue.getFirst().addFirst(key);
addedValuesQueue.getFirst().addFirst(new HashSet<>(addedValues));
removedValuesQueue.getFirst()
.addFirst(new HashSet<>(removedValues));
}
public void check() {
if (keyQueue.size() > 0) {
fail("Did not receive " + keyQueue.size()
+ " expected changes.");
}
}
@Override
public void onChanged(
org.eclipse.gef.common.collections.SetMultimapChangeListener.Change<? extends K, ? extends V> change) {
if (keyQueue.size() <= 0) {
fail("Received unexpected change " + change);
}
LinkedList<K> elementaryKeysQueue = keyQueue.pollLast();
LinkedList<Set<V>> elementaryAddedValuesQueue = addedValuesQueue
.pollLast();
LinkedList<Set<V>> elementaryRemovedValuesQueue = removedValuesQueue
.pollLast();
assertEquals(source, change.getSetMultimap());
StringBuffer expectedString = new StringBuffer();
while (change.next()) {
if (elementaryKeysQueue.size() <= 0) {
fail("Did not expect another elementary change");
}
// check key
K expectedKey = elementaryKeysQueue.pollLast();
assertEquals(expectedKey, change.getKey());
// check added values
Set<V> expectedAddedValues = elementaryAddedValuesQueue
.pollLast();
assertEquals(expectedAddedValues, change.getValuesAdded());
if (expectedAddedValues != null
&& !expectedAddedValues.isEmpty()) {
assertTrue(change.wasAdded());
} else {
assertFalse(change.wasAdded());
}
// check removed values
Set<V> expectedRemovedValues = elementaryRemovedValuesQueue
.pollLast();
assertEquals(expectedRemovedValues, change.getValuesRemoved());
if (expectedRemovedValues != null
&& !expectedRemovedValues.isEmpty()) {
assertTrue(change.wasRemoved());
} else {
assertFalse(change.wasRemoved());
}
// check string representation
if (!expectedString.toString().isEmpty()) {
expectedString.append(" ");
}
if (expectedAddedValues.isEmpty()
&& !expectedRemovedValues.isEmpty()) {
expectedString.append("Removed " + expectedRemovedValues
+ " for key " + expectedKey + ".");
} else if (!expectedAddedValues.isEmpty()
&& expectedRemovedValues.isEmpty()) {
expectedString.append("Added " + expectedAddedValues
+ " for key " + expectedKey + ".");
} else {
expectedString.append("Replaced " + expectedRemovedValues
+ " by " + expectedAddedValues + " for key "
+ expectedKey + ".");
}
}
if (elementaryKeysQueue.size() > 0) {
fail("Did not receive " + elementaryKeysQueue.size()
+ " expected elementary changes.");
}
assertEquals(expectedString.toString(), change.toString());
}
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ new Provider<ObservableSetMultimap<Integer, String>>() {
@Override
public ObservableSetMultimap<Integer, String> get() {
// test ObservableSetMultimapWrapper as the 'default'
// implementation of ObservableSetMultimap
return CollectionUtils.observableHashMultimap();
}
} }, { new Provider<ObservableSetMultimap<Integer, String>>() {
@Override
public ObservableSetMultimap<Integer, String> get() {
// test SimpleSetMultimapProperty, which is the
// 'default' implementation of the related
// ObservableValue.
return new SimpleSetMultimapProperty<>(CollectionUtils
.<Integer, String> observableHashMultimap());
}
} }, { new Provider<ObservableSetMultimap<Integer, String>>() {
@Override
public ObservableSetMultimap<Integer, String> get() {
// test ReadOnlySetMultimapWrapper, which is the
// 'default' implementation of the related
// read-only support.
return new ReadOnlySetMultimapWrapper<>(CollectionUtils
.<Integer, String> observableHashMultimap());
}
} } });
}
private ObservableSetMultimap<Integer, String> observable;
private Provider<ObservableSetMultimap<Integer, String>> observableProvider;
private InvalidationExpector invalidationListener;
private SetMultimapChangeExpector<Integer, String> setMultimapChangeListener;
public ObservableSetMultimapTests(
Provider<ObservableSetMultimap<Integer, String>> sourceProvider) {
this.observableProvider = sourceProvider;
}
@Before
public void before() {
observable = observableProvider.get();
}
protected void check(ObservableSetMultimap<Integer, String> observable,
SetMultimap<Integer, String> backupMap) {
assertEquals(backupMap, observable);
if (observable instanceof ReadOnlySetMultimapWrapper) {
assertEquals(backupMap,
((ReadOnlySetMultimapWrapper<Integer, String>) observable)
.getReadOnlyProperty().get());
}
}
protected void checkListeners() {
invalidationListener.check();
setMultimapChangeListener.check();
}
@Test
public void clear() {
// initialize maps with some values
observable.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
observable.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
observable.putAll(null, Sets.newHashSet(null, "null"));
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
backupMap.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
backupMap.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
backupMap.putAll(null, Sets.newHashSet(null, "null"));
check(observable, backupMap);
registerListeners();
// remove all values
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Sets.newHashSet(null, "null"), Collections.<String> emptySet());
setMultimapChangeListener.addElementaryExpectation(1,
Sets.newHashSet("1-1", "1-2", "1-3"),
Collections.<String> emptySet());
setMultimapChangeListener.addElementaryExpectation(2,
Sets.newHashSet("2-1", "2-2", "2-3"),
Collections.<String> emptySet());
observable.clear();
backupMap.clear();
check(observable, backupMap);
checkListeners();
// clear again (while already empty)
invalidationListener.expect(0);
observable.clear();
backupMap.clear();
check(observable, backupMap);
checkListeners();
}
@Test
public void listenersNotProperlyIterating() {
SetMultimapChangeListener<Integer, String> setMultimapChangeListener = new SetMultimapChangeListener<Integer, String>() {
@Override
public void onChanged(
SetMultimapChangeListener.Change<? extends Integer, ? extends String> change) {
// initially cursor is left of first change
try {
// call getKey() without next
change.getKey();
fail("Expect IllegalArgumentException, because next() has not been called.");
} catch (IllegalStateException e) {
assertEquals(
"Need to call next() before getKey() can be called.",
e.getMessage());
}
try {
// call wasAdded() without next
change.wasAdded();
fail("Expect IllegalArgumentException, because next() has not been called.");
} catch (IllegalStateException e) {
assertEquals(
"Need to call next() before wasAdded() can be called.",
e.getMessage());
}
try {
// call getValuesAdded() without next
change.getValuesAdded();
fail("Expect IllegalArgumentException, because next() has not been called.");
} catch (IllegalStateException e) {
assertEquals(
"Need to call next() before getValuesAdded() can be called.",
e.getMessage());
}
try {
// call wasRemoved() without next
change.wasRemoved();
fail("Expect IllegalArgumentException, because next() has not been called.");
} catch (IllegalStateException e) {
assertEquals(
"Need to call next() before wasRemoved() can be called.",
e.getMessage());
}
try {
// call getValuesRemoved() without next
change.getValuesRemoved();
fail("Expect IllegalArgumentException, because next() has not been called.");
} catch (IllegalStateException e) {
assertEquals(
"Need to call next() before getValuesRemoved() can be called.",
e.getMessage());
}
// put cursor right of last change
while (change.next()) {
}
change.next();
try {
// call getKey() without next
change.getKey();
fail("Expect IllegalArgumentException, because next() return value has not been respected.");
} catch (IllegalStateException e) {
assertEquals(
"May only call getKey() if next() returned true.",
e.getMessage());
}
try {
// call wasAdded() without next
change.wasAdded();
fail("Expect IllegalArgumentException, because next() return value has not been respected.");
} catch (IllegalStateException e) {
assertEquals(
"May only call wasAdded() if next() returned true.",
e.getMessage());
}
try {
// call getValuesAdded() without next
change.getValuesAdded();
fail("Expect IllegalArgumentException, because next() return value has not been respected.");
} catch (IllegalStateException e) {
assertEquals(
"May only call getValuesAdded() if next() returned true.",
e.getMessage());
}
try {
// call wasRemoved() without next
change.wasRemoved();
fail("Expect IllegalArgumentException, because next() return value has not been respected.");
} catch (IllegalStateException e) {
assertEquals(
"May only call wasRemoved() if next() returned true.",
e.getMessage());
}
try {
// call getValuesRemoved() without next
change.getValuesRemoved();
fail("Expect IllegalArgumentException, because next() return value has not been respected.");
} catch (IllegalStateException e) {
assertEquals(
"May only call getValuesRemoved() if next() returned true.",
e.getMessage());
}
}
};
observable.addListener(setMultimapChangeListener);
// ensure no concurrent modification exceptions result
observable.put(1, "1");
}
/**
* Checks that its safe (and does not lead to a
* {@link ConcurrentModificationException} if a listener registers or
* unregisters itself as the result of a notification.
*/
@Test
public void listenersProvokingConcurrentModifications() {
// add listeners
InvalidationListener invalidationListener = new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
// unregister ourselves
observable.removeListener(this);
// register ourselves (again)
observable.addListener(this);
}
};
observable.addListener(invalidationListener);
if (observable instanceof ObservableValue) {
// register change listener as well
@SuppressWarnings("unchecked")
ObservableValue<ObservableSetMultimap<Integer, String>> observableValue = (ObservableValue<ObservableSetMultimap<Integer, String>>) observable;
ChangeListener<ObservableSetMultimap<Integer, String>> changeListener = new ChangeListener<ObservableSetMultimap<Integer, String>>() {
@Override
public void changed(
ObservableValue<? extends ObservableSetMultimap<Integer, String>> observable,
ObservableSetMultimap<Integer, String> oldValue,
ObservableSetMultimap<Integer, String> newValue) {
// unregister ourselves
observable.removeListener(this);
// register ourselves (again)
observable.addListener(this);
}
};
observableValue.addListener(changeListener);
}
SetMultimapChangeListener<Integer, String> setMultimapChangeListener = new SetMultimapChangeListener<Integer, String>() {
@Override
public void onChanged(
org.eclipse.gef.common.collections.SetMultimapChangeListener.Change<? extends Integer, ? extends String> change) {
// unregister ourselves
change.getSetMultimap().removeListener(this);
// register ourselves (again)
change.getSetMultimap().addListener(this);
}
};
observable.addListener(setMultimapChangeListener);
// ensure no concurrent modification exceptions result
observable.put(1, "1-1");
}
@Test
public void listenersRegisteredMoreThanOnce() {
// register listeners (twice)
InvalidationExpector invalidationListener = new InvalidationExpector();
SetMultimapChangeExpector<Integer, String> setMultimapChangeListener = new SetMultimapChangeExpector<>(
observable);
observable.addListener(invalidationListener);
observable.addListener(invalidationListener);
// add and remove should have no effect
InvalidationListener invalidationListener2 = new InvalidationListener() {
@Override
public void invalidated(Observable observable) {
// ignore
}
};
observable.addListener(invalidationListener2);
observable.removeListener(invalidationListener2);
observable.addListener(setMultimapChangeListener);
observable.addListener(setMultimapChangeListener);
// add and remove should have no effect
SetMultimapChangeListener<Integer, String> setMultimapChangeListener2 = new SetMultimapChangeListener<Integer, String>() {
@Override
public void onChanged(
org.eclipse.gef.common.collections.SetMultimapChangeListener.Change<? extends Integer, ? extends String> change) {
// ignore
}
};
observable.addListener(setMultimapChangeListener2);
observable.removeListener(setMultimapChangeListener2);
// perform put
invalidationListener.expect(2);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Collections.singleton("1"));
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Collections.singleton("1"));
assertTrue(observable.put(1, "1"));
invalidationListener.check();
setMultimapChangeListener.check();
// remove single listener occurrence
observable.removeListener(invalidationListener);
observable.removeListener(setMultimapChangeListener);
// perform another put
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(2,
Collections.<String> emptySet(), Collections.singleton("2"));
assertTrue(observable.put(2, "2"));
invalidationListener.check();
setMultimapChangeListener.check();
// remove listeners and ensure no notifications are received
observable.removeListener(invalidationListener);
observable.removeListener(setMultimapChangeListener);
assertTrue(observable.put(3, "3"));
setMultimapChangeListener.check();
}
@Test
public void put() {
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
check(observable, backupMap);
// register listeners
registerListeners();
// put a single value
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Collections.singleton("1-1"));
assertEquals(backupMap.put(1, "1-1"), observable.put(1, "1-1"));
check(observable, backupMap);
checkListeners();
// put a second value
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Collections.singleton("1-2"));
assertEquals(backupMap.put(1, "1-2"), observable.put(1, "1-2"));
check(observable, backupMap);
checkListeners();
// put a different value
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(2,
Collections.<String> emptySet(), Collections.singleton("2"));
assertEquals(backupMap.put(2, "2"), observable.put(2, "2"));
check(observable, backupMap);
checkListeners();
// null key and values are allowed within SetMultimap
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Collections.<String> emptySet(),
Collections.<String> singleton(null));
assertEquals(backupMap.put(null, null), observable.put(null, null));
check(observable, backupMap);
checkListeners();
// add a real value to null key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Collections.<String> emptySet(),
Collections.<String> singleton("null"));
assertEquals(backupMap.put(null, "null"), observable.put(null, "null"));
check(observable, backupMap);
checkListeners();
// put same value again should not yield any notification.
assertEquals(backupMap.put(2, "2"), observable.put(2, "2"));
check(observable, backupMap);
checkListeners();
}
@Test
public void putAll_multipleKeys() {
// prepare backup SetMultimap
SetMultimap<Integer, String> backupMap = HashMultimap.create();
check(observable, backupMap);
// register listeners
registerListeners();
// add distinct values for different keys
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Sets.newHashSet("1-1", "1-2"));
setMultimapChangeListener.addElementaryExpectation(2,
Collections.<String> emptySet(), Sets.newHashSet("2-1", "2-2"));
SetMultimap<Integer, String> toAdd = HashMultimap.create();
toAdd.putAll(1, Sets.newHashSet("1-1", "1-2"));
toAdd.putAll(2, Sets.newHashSet("2-1", "2-2"));
assertEquals(backupMap.putAll(toAdd), observable.putAll(toAdd));
check(observable, backupMap);
checkListeners();
// add new and already registered values for different keys
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Sets.newHashSet("1-3"));
setMultimapChangeListener.addElementaryExpectation(2,
Collections.<String> emptySet(), Sets.newHashSet("2-3"));
toAdd = HashMultimap.create();
toAdd.putAll(1, Sets.newHashSet("1-2", "1-3"));
toAdd.putAll(2, Sets.newHashSet("2-2", "2-3"));
assertEquals(backupMap.putAll(toAdd), observable.putAll(toAdd));
check(observable, backupMap);
checkListeners();
// add already registered values
toAdd = HashMultimap.create();
toAdd.putAll(1, Sets.newHashSet("1-2", "1-3"));
toAdd.putAll(2, Sets.newHashSet("2-2", "2-3"));
assertEquals(backupMap.putAll(toAdd), observable.putAll(toAdd));
check(observable, backupMap);
checkListeners();
}
@Test
public void putAll_singleKey() {
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
check(observable, backupMap);
// register listeners
registerListeners();
// add two new distinct values
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Sets.newHashSet("1-1", "1-2"));
assertEquals(backupMap.putAll(1, Arrays.asList("1-1", "1-2")),
observable.putAll(1, Arrays.asList("1-1", "1-2")));
check(observable, backupMap);
checkListeners();
// add a new and an already added value
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.<String> emptySet(), Sets.newHashSet("1-3"));
assertEquals(backupMap.putAll(1, Arrays.asList("1-2", "1-3")),
observable.putAll(1, Arrays.asList("1-2", "1-3")));
check(observable, backupMap);
checkListeners();
// put already added values
assertEquals(backupMap.putAll(1, Arrays.asList("1-2", "1-3")),
observable.putAll(1, Arrays.asList("1-2", "1-3")));
check(observable, backupMap);
checkListeners();
}
protected void registerListeners() {
invalidationListener = new InvalidationExpector();
setMultimapChangeListener = new SetMultimapChangeExpector<>(observable);
observable.addListener(invalidationListener);
observable.addListener(setMultimapChangeListener);
}
@Test
public void remove_entry() {
// initialize maps with some values
observable.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
observable.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
observable.putAll(null, Sets.newHashSet(null, "null"));
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
backupMap.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
backupMap.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
backupMap.putAll(null, Sets.newHashSet(null, "null"));
check(observable, backupMap);
// register listeners
registerListeners();
// remove a Compound value
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Collections.singleton("1-1"), Collections.<String> emptySet());
assertEquals(backupMap.remove(1, "1-1"), observable.remove(1, "1-1"));
check(observable, backupMap);
checkListeners();
// remove null value from null key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Collections.<String> singleton(null),
Collections.<String> emptySet());
assertEquals(backupMap.remove(null, null),
observable.remove(null, null));
check(observable, backupMap);
checkListeners();
// remove real value from null key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Collections.<String> singleton("null"),
Collections.<String> emptySet());
assertEquals(backupMap.remove(null, "null"),
observable.remove(null, "null"));
check(observable, backupMap);
checkListeners();
// try to remove not contained value
assertEquals(backupMap.remove(1, "1-1"), observable.remove(1, "1-1"));
check(observable, backupMap);
checkListeners();
// try to remove entry with key of wrong type
assertEquals(backupMap.remove("1", "1-1"),
observable.remove("1", "1-1"));
check(observable, backupMap);
checkListeners();
// try to remove entry with value of wrong type
assertEquals(backupMap.remove(1, 1), observable.remove(1, 1));
check(observable, backupMap);
checkListeners();
}
@Test
public void removeAll_CompoundKey() {
// initialize maps with some values
observable.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
observable.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
observable.putAll(null, Sets.newHashSet(null, "null"));
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
backupMap.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
backupMap.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
backupMap.putAll(null, Sets.newHashSet(null, "null"));
check(observable, backupMap);
// register listeners
registerListeners();
// remove values for a single key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Sets.newHashSet("1-1", "1-2", "1-3"),
Collections.<String> emptySet());
assertEquals(backupMap.removeAll(1), observable.removeAll(1));
check(observable, backupMap);
checkListeners();
// remove values for null key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Sets.newHashSet(null, "null"), Collections.<String> emptySet());
assertEquals(backupMap.removeAll(null), observable.removeAll(null));
check(observable, backupMap);
checkListeners();
// try to remove values for not contained key
assertEquals(backupMap.removeAll(4711), observable.removeAll(4711));
check(observable, backupMap);
checkListeners();
// try to remove values of key with wrong type
assertEquals(backupMap.removeAll("4711"), observable.removeAll("4711"));
check(observable, backupMap);
checkListeners();
}
@Test
public void replaceAll() {
// initialize maps with some values
observable.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
observable.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
observable.putAll(3, Sets.newHashSet("3-1", "3-2", "3-3"));
observable.putAll(null, Sets.newHashSet(null, "null"));
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
backupMap.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
backupMap.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
backupMap.putAll(3, Sets.newHashSet("3-1", "3-2", "3-3"));
backupMap.putAll(null, Sets.newHashSet(null, "null"));
check(observable, backupMap);
// register listeners
registerListeners();
// remove all values
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(null,
Sets.newHashSet(null, "null"), Collections.<String> emptySet()); // removed
// null
// key
setMultimapChangeListener.addElementaryExpectation(2,
Sets.newHashSet("2-1", "2-3"), Collections.<String> emptySet()); // removed
// 2-1,
// 2-3
setMultimapChangeListener.addElementaryExpectation(3,
Sets.newHashSet("3-3"), Sets.newHashSet("3-4")); // removed 3-3,
// added 3-4
setMultimapChangeListener.addElementaryExpectation(4,
Collections.<String> emptySet(), Sets.newHashSet("4-1"));
SetMultimap<Integer, String> toReplace = HashMultimap.create();
toReplace.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3")); // leave
// unchanged
toReplace.putAll(2, Sets.newHashSet("2-2")); // remove values (2-1, 2-3)
toReplace.putAll(3, Sets.newHashSet("3-1", "3-2", "3-4")); // change
// values
// (removed
// 3-3,
// added
// 3-4)
toReplace.putAll(4, Sets.newHashSet("4-1")); // add entry
observable.replaceAll(toReplace);
backupMap.clear();
backupMap.putAll(toReplace);
check(observable, backupMap);
checkListeners();
// replace with same contents (should not have any effect)
invalidationListener.expect(0);
observable.replaceAll(toReplace);
check(observable, backupMap);
checkListeners();
}
@Test
public void replaceValues() {
// initialize maps with some values
observable.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
observable.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
observable.putAll(null, Sets.newHashSet(null, "null"));
// prepare backup map
SetMultimap<Integer, String> backupMap = HashMultimap.create();
backupMap.putAll(1, Sets.newHashSet("1-1", "1-2", "1-3"));
backupMap.putAll(2, Sets.newHashSet("2-1", "2-2", "2-3"));
backupMap.putAll(null, Sets.newHashSet(null, "null"));
check(observable, backupMap);
// register listeners
registerListeners();
// replace all values of a specific key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(1,
Sets.newHashSet("1-1", "1-2", "1-3"),
Sets.newHashSet("1-4", "1-5", "1-6"));
assertEquals(
backupMap.replaceValues(1,
Sets.newHashSet("1-4", "1-5", "1-6")),
observable.replaceValues(1,
Sets.newHashSet("1-4", "1-5", "1-6")));
check(observable, backupMap);
checkListeners();
// use replacement to clear values for a key
invalidationListener.expect(1);
setMultimapChangeListener.addAtomicExpectation();
setMultimapChangeListener.addElementaryExpectation(2,
Sets.newHashSet("2-1", "2-2", "2-3"),
Collections.<String> emptySet());
assertEquals(
backupMap.replaceValues(2, Collections.<String> emptySet()),
observable.replaceValues(2, Collections.<String> emptySet()));
check(observable, backupMap);
checkListeners();
// try to replace values for non existing key
assertEquals(
backupMap.replaceValues(4711, Sets.newHashSet("4", "7", "1")),
observable.replaceValues(4711, Sets.newHashSet("4", "7", "1")));
check(observable, backupMap);
checkListeners();
}
/**
* Confirm {@link ObservableSetMultimap} works as expected even if no
* listeners are registered.
*/
@Test
public void withoutListeners() {
// put
assertTrue(observable.put(4711, "4"));
assertTrue(observable.put(4711, "7"));
assertTrue(observable.put(4711, "1"));
assertFalse(observable.put(4711, "1"));
assertEquals(Sets.newHashSet("4", "7", "1", "1"), observable.get(4711));
// remove all
observable.removeAll(4711);
assertTrue(observable.isEmpty());
}
}