/*******************************************************************************
* 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.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
import org.eclipse.gef.common.beans.property.MultisetProperty;
import org.eclipse.gef.common.beans.property.ReadOnlyMultisetProperty;
import org.eclipse.gef.common.beans.property.ReadOnlyMultisetWrapper;
import org.eclipse.gef.common.beans.property.SimpleMultisetProperty;
import org.eclipse.gef.common.collections.CollectionUtils;
import org.eclipse.gef.common.collections.ObservableMultiset;
import org.eclipse.gef.common.tests.ObservableMultisetTests.InvalidationExpector;
import org.eclipse.gef.common.tests.ObservableMultisetTests.MultisetChangeExpector;
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.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.inject.Provider;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@RunWith(Parameterized.class)
public class MultisetPropertyTests {
protected static class ChangeExpector<E>
implements ChangeListener<ObservableMultiset<E>> {
private ObservableValue<ObservableMultiset<E>> source;
private LinkedList<ObservableMultiset<E>> oldValueQueue = new LinkedList<>();
private LinkedList<ObservableMultiset<E>> newValueQueue = new LinkedList<>();
public ChangeExpector(ObservableValue<ObservableMultiset<E>> source) {
this.source = source;
}
public void addExpectation(ObservableMultiset<E> oldValue,
ObservableMultiset<E> newValue) {
// We check that the reference to the observable value is correct,
// thus do not copy the passed in values.
oldValueQueue.addFirst(oldValue);
newValueQueue.addFirst(newValue);
}
@Override
public void changed(
ObservableValue<? extends ObservableMultiset<E>> observable,
ObservableMultiset<E> oldValue,
ObservableMultiset<E> newValue) {
if (oldValueQueue.size() <= 0) {
fail("Received unexpected change.");
}
assertEquals(source, observable);
assertEquals(oldValueQueue.pollLast(), oldValue);
assertEquals(newValueQueue.pollLast(), newValue);
}
public void check() {
if (oldValueQueue.size() > 0) {
fail("Did not receive " + oldValueQueue.size()
+ " expected changes.");
}
}
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[][] { { new Provider<MultisetProperty<Integer>>() {
@Override
public MultisetProperty<Integer> get() {
// test SimpleMultisetProperty, which is the
// 'default' implementation of the related
// ObservableValue.
return new SimpleMultisetProperty<>(CollectionUtils
.<Integer> observableHashMultiset());
}
} }, { new Provider<MultisetProperty<Integer>>() {
@Override
public MultisetProperty<Integer> get() {
// test ReadOnlyMultisetWrapper, which is the
// 'default' implementation of the related
// read-only property support.
return new ReadOnlyMultisetWrapper<>(CollectionUtils
.<Integer> observableHashMultiset());
}
} } });
}
private Provider<MultisetProperty<Integer>> propertyProvider;
public MultisetPropertyTests(
Provider<MultisetProperty<Integer>> propertyProvider) {
this.propertyProvider = propertyProvider;
}
@Test
public void bidirectionalBinding() {
MultisetProperty<Integer> property1 = propertyProvider.get();
MultisetProperty<Integer> property2 = propertyProvider.get();
// XXX: According to JavaFX contract, a bidirectional binding does not
// lead to the properties being regarded as bound.
property2.bindBidirectional(property1);
assertFalse(property1.isBound());
assertFalse(property2.isBound());
// change value of first property
ObservableMultiset<Integer> newValue = CollectionUtils
.observableHashMultiset();
newValue.add(1, 1);
property1.set(newValue);
assertEquals(newValue, property1.get());
assertEquals(newValue, property2.get());
assertEquals(property1, property2);
// change value of second property
newValue = CollectionUtils.observableHashMultiset();
newValue.add(2, 2);
property2.set(newValue);
assertEquals(property1, property2);
assertEquals(newValue, property1.get());
assertEquals(newValue, property2.get());
// unbind (ensure values are no longer synchronized)
property2.unbindBidirectional(property1);
assertFalse(property1.isBound());
assertFalse(property2.isBound());
newValue = CollectionUtils.observableHashMultiset();
newValue.add(3, 3);
property1.set(newValue);
assertNotEquals(property1, property2);
assertEquals(newValue, property1.get());
assertNotEquals(newValue, property2.get());
// bind on null (yields NPE)
try {
property2.bindBidirectional(null);
fail("Expected NullPointerException because binding to null is not valid.");
} catch (NullPointerException e) {
// expected
}
// unbind from null (yields NPE)
try {
property2.unbindBidirectional(null);
fail("Expected NullPointerException because binding to null is not valid.");
} catch (NullPointerException e) {
// expected
}
// bind on itself (yields IAE)
try {
property2.bindBidirectional(property2);
fail("Expected IllegalArgumentException because binding to itself is not valid.");
} catch (IllegalArgumentException e) {
// exptected
}
// unbind from itself (yields IAE)
try {
property2.unbindBidirectional(property2);
fail("Expected IllegalArgumentException because binding to itself is not valid.");
} catch (IllegalArgumentException e) {
// expected
}
}
/**
* Test the bidirectional content bindings as offered by
* {@link ReadOnlyMultisetProperty}.
*/
@Test
public void bidirectionalContentBinding() {
MultisetProperty<Integer> property1 = propertyProvider.get();
MultisetProperty<Integer> property2 = propertyProvider.get();
ObservableMultiset<Integer> backupMap = CollectionUtils
.observableHashMultiset();
property1.bindContentBidirectional(property2);
// XXX: According to JavaFX contract, a content binding does not lead to
// the properties being regarded as being bound
assertFalse(property1.isBound());
assertFalse(property2.isBound());
// add
property1.add(1);
backupMap.add(1);
check(property1, property2, backupMap);
// add_count
property1.add(1, 2);
backupMap.add(1, 2);
check(property1, property2, backupMap);
// addAll
Multiset<Integer> toAdd = HashMultiset.create();
toAdd.add(2, 2);
toAdd.add(3, 3);
property1.addAll(toAdd);
backupMap.addAll(toAdd);
check(property1, property2, backupMap);
// remove
property1.remove(1);
backupMap.remove(1);
check(property1, property2, backupMap);
// remove with count
property1.remove(2, 2);
backupMap.remove(2, 2);
check(property1, property2, backupMap);
// set count
property1.setCount(3, 6);
backupMap.setCount(3, 6);
check(property1, property2, backupMap);
// set count with old
property1.setCount(3, 6, 3);
backupMap.setCount(3, 6, 3);
check(property1, property2, backupMap);
// clear
property1.clear();
backupMap.clear();
check(property1, property2, backupMap);
// unbind property2, ensure values are no longer synchronized
property2.unbindContentBidirectional(property1);
property1.add(1, 4);
assertNotEquals(property2.get(), property1.get());
assertNotEquals(property2.sizeProperty().get(),
property1.sizeProperty().get());
assertNotEquals(property2.emptyProperty().get(),
property1.emptyProperty().get());
// unbind property2 from null (yields NPE)
try {
property2.unbindContentBidirectional(null);
fail("Expected NullPointerException.");
} catch (NullPointerException e) {
assertEquals("Cannot bind to null value.", e.getMessage());
}
// unbind property2 from itself (yields IAE)
try {
property2.unbindContentBidirectional(property2);
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertEquals("Cannot bind source to itself.", e.getMessage());
}
// bind property2 to null (yields NPE)
try {
property2.bindContentBidirectional(null);
fail("Expected NullPointerException.");
} catch (NullPointerException e) {
assertEquals("Cannot bind to null value.", e.getMessage());
}
// bind property2 to itself (yields IAE)
try {
property2.bindContentBidirectional(property2);
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertEquals("Cannot bind source to itself.", e.getMessage());
}
}
/**
* Check change notifications for observed value changes are properly fired.
*/
@Test
public void changeNotifications() {
MultisetProperty<Integer> property = propertyProvider.get();
// initialize property
property.add(1, 1);
property.add(2, 2);
// register listener
InvalidationExpector invalidationListener = new InvalidationExpector();
MultisetChangeExpector<Integer> multisetChangeListener = new MultisetChangeExpector<>(
property);
ChangeExpector<Integer> changeListener = new ChangeExpector<>(property);
property.addListener(invalidationListener);
property.addListener(multisetChangeListener);
property.addListener(changeListener);
// change property value (disjoint values)
ObservableMultiset<Integer> newValue = CollectionUtils
.observableHashMultiset();
newValue.add(3, 3);
newValue.add(4, 4);
newValue.add(5, 5);
newValue.add(6, 6);
invalidationListener.expect(1);
changeListener.addExpectation(property.get(), newValue);
multisetChangeListener.addAtomicExpectation();
multisetChangeListener.addElementaryExpection(1, 1, 0);
multisetChangeListener.addElementaryExpection(2, 2, 0);
multisetChangeListener.addElementaryExpection(3, 0, 3);
multisetChangeListener.addElementaryExpection(4, 0, 4);
multisetChangeListener.addElementaryExpection(5, 0, 5);
multisetChangeListener.addElementaryExpection(6, 0, 6);
property.set(newValue);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// change property value (non-disjoint values)
newValue = CollectionUtils.observableHashMultiset();
newValue.add(4, 2);
newValue.add(5, 5);
newValue.add(6, 8);
newValue.add(7, 7);
invalidationListener.expect(1);
changeListener.addExpectation(property.get(), newValue);
multisetChangeListener.addAtomicExpectation();
multisetChangeListener.addElementaryExpection(3, 3, 0);
multisetChangeListener.addElementaryExpection(4, 2, 0);
multisetChangeListener.addElementaryExpection(6, 0, 2);
multisetChangeListener.addElementaryExpection(7, 0, 7);
property.set(newValue);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// change property value (change to null)
invalidationListener.expect(1);
changeListener.addExpectation(property.get(), null);
multisetChangeListener.addAtomicExpectation();
multisetChangeListener.addElementaryExpection(4, 2, 0);
multisetChangeListener.addElementaryExpection(5, 5, 0);
multisetChangeListener.addElementaryExpection(6, 8, 0);
multisetChangeListener.addElementaryExpection(7, 7, 0);
property.set(null);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// set to null again (no expectation)
property.set(null);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// change property value (change from null)
newValue = CollectionUtils.observableHashMultiset();
newValue.add(1, 1);
invalidationListener.expect(1);
changeListener.addExpectation(property.get(), newValue);
multisetChangeListener.addAtomicExpectation();
multisetChangeListener.addElementaryExpection(1, 0, 1);
property.set(newValue);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// set to identical value (no notifications expected)
property.set(newValue);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// set to equal value (no list change notification expected)
newValue = CollectionUtils.observableHashMultiset();
newValue.add(1, 1);
invalidationListener.expect(1);
changeListener.addExpectation(property.get(), newValue);
property.set(newValue);
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// modify value (change equality but not identit)y; no change
// notification expected
invalidationListener.expect(1);
multisetChangeListener.addAtomicExpectation();
multisetChangeListener.addElementaryExpection(1, 1, 0);
property.get().removeAll(Arrays.asList(1));
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
// only touch value (don't change identity nor equality); no
// notifications expected
property.get().removeAll(Arrays.asList(1));
invalidationListener.check();
multisetChangeListener.check();
changeListener.check();
}
protected void check(MultisetProperty<Integer> property1,
MultisetProperty<Integer> property2,
ObservableMultiset<Integer> backupMap) {
assertEquals(property1, property2);
assertEquals(property1.get(), property2.get());
assertEquals(backupMap, property1.get());
assertEquals(backupMap, property2.get());
assertEquals(backupMap.size(), property1.sizeProperty().get());
assertEquals(backupMap.size(), property2.sizeProperty().get());
assertEquals(backupMap.isEmpty(), property1.emptyProperty().get());
assertEquals(backupMap.isEmpty(), property2.emptyProperty().get());
}
@Test
public void unidirectionalBinding() {
MultisetProperty<Integer> property1 = propertyProvider.get();
MultisetProperty<Integer> property2 = propertyProvider.get();
// bind properly
property2.bind(property1);
assertFalse(property1.isBound());
assertTrue(property2.isBound());
// change value of first property
ObservableMultiset<Integer> newValue = CollectionUtils
.observableHashMultiset();
newValue.add(1);
property1.set(newValue);
assertEquals(newValue, property1.get());
assertEquals(newValue, property2.get());
assertEquals(property1, property2);
// set value on second (bound) property (yields IAE)
try {
property2.set(CollectionUtils.<Integer> observableHashMultiset());
fail("Expected IllegalArgumentException because property is bound.");
} catch (IllegalArgumentException e) {
assertEquals("A bound value cannot be set.", e.getMessage());
}
// unbind
property2.unbind();
assertFalse(property1.isBound());
assertFalse(property2.isBound());
// change value after binding has been removed
newValue = CollectionUtils.observableHashMultiset();
newValue.add(3, 3);
property1.set(newValue);
assertNotEquals(property1, property2);
assertEquals(newValue, property1.get());
assertNotEquals(newValue, property2.get());
// bind on null (yields NPE)
try {
property2.bind(null);
fail("Expected NullPointerException because binding to null is not valid.");
} catch (NullPointerException e) {
assertEquals("Cannot bind to null.", e.getMessage());
}
// according to JavaFX, binding on itself does not yield an IAE here
}
/**
* Test the unidirectional content bindings as offered by
* {@link ReadOnlyMultisetProperty}.
*/
@Test
public void unidirectionalContentBinding() {
MultisetProperty<Integer> property1 = propertyProvider.get();
MultisetProperty<Integer> property2 = propertyProvider.get();
ObservableMultiset<Integer> backupMap = CollectionUtils
.observableHashMultiset();
property2.bindContent(property1);
// XXX: According to JavaFX contract, content binding does not lead to
// the properties being regarded as being bound.
assertFalse(property1.isBound());
assertFalse(property2.isBound());
// add
property1.add(1);
backupMap.add(1);
check(property1, property2, backupMap);
// add_count
property1.add(1, 2);
backupMap.add(1, 2);
check(property1, property2, backupMap);
// addAll
Multiset<Integer> toAdd = HashMultiset.create();
toAdd.add(2, 2);
toAdd.add(3, 3);
property1.addAll(toAdd);
backupMap.addAll(toAdd);
check(property1, property2, backupMap);
// remove
property1.remove(1);
backupMap.remove(1);
check(property1, property2, backupMap);
// remove with count
property1.remove(2, 2);
backupMap.remove(2, 2);
check(property1, property2, backupMap);
// set count
property1.setCount(3, 6);
backupMap.setCount(3, 6);
check(property1, property2, backupMap);
// set count with old
property1.setCount(3, 6, 3);
backupMap.setCount(3, 6, 3);
check(property1, property2, backupMap);
// clear
property1.clear();
backupMap.clear();
check(property1, property2, backupMap);
// unbind property2, ensure values are no longer synchronized
property2.unbindContent(property1);
property1.add(1);
assertNotEquals(property2.get(), property1.get());
assertNotEquals(property2.sizeProperty().get(),
property1.sizeProperty().get());
assertNotEquals(property2.emptyProperty().get(),
property1.emptyProperty().get());
// unbind property2 from null (yields NPE)
try {
property2.unbindContent(null);
fail("Expected NullPointerException.");
} catch (NullPointerException e) {
assertEquals("Cannot unbind from null value.", e.getMessage());
}
// unbind property2 from itself (yields IAE)
try {
property2.unbindContent(property2);
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertEquals("Cannot unbind source to itself.", e.getMessage());
}
// bind property2 to null (yields NPE)
try {
property2.bindContent(null);
fail("Expected NullPointerException.");
} catch (NullPointerException e) {
assertEquals("Cannot bind to null value.", e.getMessage());
}
// bind property2 to itself (yields IAE)
try {
property2.bindContent(property2);
fail("Expected IllegalArgumentException.");
} catch (IllegalArgumentException e) {
assertEquals("Cannot bind source to itself.", e.getMessage());
}
}
}