### Eclipse Workspace Patch 1.0 #P org.eclipse.jface.tests.databinding Index: src/org/eclipse/jface/tests/databinding/BindingTestSuite.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java,v retrieving revision 1.87 diff -u -r1.87 BindingTestSuite.java --- src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 3 Jul 2008 16:30:03 -0000 1.87 +++ src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 19 Jul 2008 18:18:27 -0000 @@ -14,7 +14,7 @@ * Matthew Hall - bugs 210115, 212468, 212223, 206839, 208858, 208322, * 212518, 215531, 221351, 184830, 213145, 218269, 239015, * 237703 - * Ovidio Mallo - bug 237163 + * Ovidio Mallo - bugs 237163, 175737 *******************************************************************************/ package org.eclipse.jface.tests.databinding; @@ -114,6 +114,7 @@ import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableListTest; import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableSetTest; import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableValueTest; +import org.eclipse.core.tests.internal.databinding.observable.masterdetail.ListDetailObservableListTest; import org.eclipse.core.tests.internal.databinding.validation.AbstractStringToNumberValidatorTest; import org.eclipse.core.tests.internal.databinding.validation.NumberToByteValidatorTest; import org.eclipse.core.tests.internal.databinding.validation.NumberToDoubleValidatorTest; @@ -308,6 +309,7 @@ addTest(DetailObservableListTest.suite()); addTest(DetailObservableSetTest.suite()); addTest(DetailObservableValueTest.suite()); + addTest(ListDetailObservableListTest.suite()); // org.eclipse.core.tests.internal.databinding.validation addTestSuite(AbstractStringToNumberValidatorTest.class); Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailObservableListTest.java =================================================================== RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailObservableListTest.java diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailObservableListTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailObservableListTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,308 @@ +/******************************************************************************* + * Copyright (c) 2008 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 175737) + ******************************************************************************/ + +package org.eclipse.core.tests.internal.databinding.observable.masterdetail; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.eclipse.core.databinding.beans.BeansObservables; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.IObservableCollection; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.WritableList; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.WritableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailObservableList; +import org.eclipse.jface.databinding.conformance.ObservableListContractTest; +import org.eclipse.jface.databinding.conformance.delegate.AbstractObservableCollectionContractDelegate; +import org.eclipse.jface.databinding.conformance.util.ValueChangeEventTracker; +import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; + +/** + * @since 1.2 + */ +public class ListDetailObservableListTest extends AbstractDefaultRealmTestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(ListDetailObservableListTest.class + .getName()); + suite.addTestSuite(ListDetailObservableListTest.class); + suite.addTest(ObservableListContractTest.suite(new Delegate())); + return suite; + } + + public void testUnmodifiability() { + WritableList masterObservableList = new WritableList(); + masterObservableList.add(new WritableValue()); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + null); + + try { + ldol.add(new WritableValue()); + fail("ListDetailObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + + try { + ldol.remove(new WritableValue()); + fail("ListDetailObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + + try { + ldol.set(0, new WritableValue()); + fail("ListDetailObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + } + + public void testDetailValueType() { + WritableList masterObservableList = new WritableList(); + masterObservableList.add(new WritableValue(new Person(), Person.class)); + + ListDetailObservableList ldol1 = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + assertSame(String.class, ((IObservableValue) ldol1.get(0)) + .getValueType()); + + ListDetailObservableList ldol2 = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + null); + assertNull(((IObservableValue) ldol2.get(0)).getValueType()); + } + + public void testDetailListElementType() { + ListDetailObservableList ldol = new ListDetailObservableList( + new WritableList(), BeansObservables.valueFactory("name"), + String.class); + + assertSame(IObservableValue.class, ldol.getElementType()); + } + + public void testMasterListInitiallyNotEmpty() { + WritableList masterObservableList = new WritableList(); + masterObservableList.add(new WritableValue(new Person(), Person.class)); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + assertEquals(masterObservableList.size(), ldol.size()); + } + + public void testChangeMaster() { + WritableList masterObservableList = new WritableList(); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + assertTrue(ldol.isEmpty()); + + Person p1 = new Person(); + p1.setName("name1"); + masterObservableList.add(new WritableValue(p1, Person.class)); + assertEquals(masterObservableList.size(), ldol.size()); + assertEquals(p1.getName(), ((IObservableValue) ldol.get(0)).getValue()); + + Person p2 = new Person(); + p2.setName("name2"); + masterObservableList.add(new WritableValue(p2, Person.class)); + assertEquals(masterObservableList.size(), ldol.size()); + assertEquals(p2.getName(), ((IObservableValue) ldol.get(1)).getValue()); + + masterObservableList.remove(0); + assertEquals(masterObservableList.size(), ldol.size()); + assertEquals(p2.getName(), ((IObservableValue) ldol.get(0)).getValue()); + + masterObservableList.remove(0); + assertTrue(ldol.isEmpty()); + } + + public void testChangeDetail() { + WritableList masterObservableList = new WritableList(); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + WritableValue masterObservable = new WritableValue(null, Person.class); + masterObservableList.add(masterObservable); + IObservableValue detailObservable = (IObservableValue) ldol.get(0); + + // Change the detail attribute explicitly. + Person p1 = new Person(); + masterObservable.setValue(p1); + p1.setName("name1"); + assertEquals(p1.getName(), detailObservable.getValue()); + p1.setName("name2"); + assertEquals(p1.getName(), detailObservable.getValue()); + + // Change the detail attribute by changing the master. + Person p2 = new Person(); + masterObservable.setValue(p2); + detailObservable.setValue("name3"); + assertEquals(p2.getName(), detailObservable.getValue()); + } + + public void testDetailObservableChangeEvent() { + WritableList masterObservableList = new WritableList(); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + Person person = new Person(); + masterObservableList.add(new WritableValue(person, Person.class)); + IObservableValue detailObservable = (IObservableValue) ldol.get(0); + + ValueChangeEventTracker changeTracker = ValueChangeEventTracker + .observe(detailObservable); + detailObservable.addValueChangeListener(changeTracker); + + assertEquals(0, changeTracker.count); + + person.setName("new name"); + assertEquals(1, changeTracker.count); + } + + public void testMasterNull() { + WritableList masterObservableList = new WritableList(); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + masterObservableList.add(new WritableValue(null, null)); + assertNull(((IObservableValue) ldol.get(0)).getValue()); + } + + public void testDetailObservableValuesAreDisposed() { + final List detailObservables = new ArrayList(); + IObservableFactory detailValueFactory = new IObservableFactory() { + public IObservable createObservable(Object target) { + TestObservableValue detailObservable = new TestObservableValue(); + detailObservables.add(detailObservable); + return detailObservable; + } + }; + WritableList masterObservableList = new WritableList(); + ListDetailObservableList ldol = new ListDetailObservableList( + masterObservableList, detailValueFactory, null); + + masterObservableList.add(new WritableValue(new Object(), null)); + masterObservableList.add(new WritableValue(new Object(), null)); + + assertEquals(ldol.size(), detailObservables.size()); + + assertFalse(((TestObservableValue) detailObservables.get(0)).disposed); + assertFalse(((TestObservableValue) detailObservables.get(1)).disposed); + + masterObservableList.remove(1); + assertFalse(((TestObservableValue) detailObservables.get(0)).disposed); + assertTrue(((TestObservableValue) detailObservables.get(1)).disposed); + + ldol.dispose(); + assertTrue(((TestObservableValue) detailObservables.get(0)).disposed); + assertTrue(((TestObservableValue) detailObservables.get(1)).disposed); + } + + public void testMasterObservableValuesAreNotDisposed() { + WritableList masterObservableList = new WritableList(); + new ListDetailObservableList(masterObservableList, BeansObservables + .valueFactory("name"), String.class); + + TestObservableValue masterObservable = new TestObservableValue(); + masterObservableList.add(masterObservable); + + assertFalse(masterObservable.disposed); + + masterObservableList.remove(0); + assertFalse(masterObservable.disposed); + } + + private static class Delegate extends + AbstractObservableCollectionContractDelegate { + public IObservableCollection createObservableCollection(Realm realm, + int elementCount) { + WritableList masterObservableList = new WritableList(realm); + for (int i = 0; i < elementCount; i++) { + masterObservableList.add(new WritableValue(realm, new Person(), + Person.class)); + } + + return new TestListDetailObservableList(masterObservableList, + BeansObservables.valueFactory(realm, "name"), String.class); + } + + public void change(IObservable observable) { + TestListDetailObservableList ldol = (TestListDetailObservableList) observable; + ldol.masterObservableList.add(new WritableValue(observable + .getRealm(), new Person(), Person.class)); + } + + public Object getElementType(IObservableCollection collection) { + return IObservableValue.class; + } + } + + private static class TestListDetailObservableList extends + ListDetailObservableList { + final IObservableList masterObservableList; + + public TestListDetailObservableList( + IObservableList masterObservableList, + IObservableFactory detailValueFactory, Object detailType) { + super(masterObservableList, detailValueFactory, detailType); + this.masterObservableList = masterObservableList; + } + } + + private static class TestObservableValue extends WritableValue { + boolean disposed = false; + + public synchronized void dispose() { + disposed = true; + super.dispose(); + } + } + + private static class Person { + private final PropertyChangeSupport pcs = new PropertyChangeSupport( + this); + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + pcs.firePropertyChange("name", this.name, this.name = name); + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + pcs.addPropertyChangeListener(listener); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + pcs.removePropertyChangeListener(listener); + } + } +} #P org.eclipse.core.databinding Index: src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java,v retrieving revision 1.9 diff -u -r1.9 MasterDetailObservables.java --- src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java 24 Mar 2008 19:13:39 -0000 1.9 +++ src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java 19 Jul 2008 18:18:27 -0000 @@ -9,6 +9,7 @@ * IBM Corporation - initial API and implementation * Brad Reynolds - bug 147515 * Matthew Hall - bug 221704 + * Ovidio Mallo - bug 175737 *******************************************************************************/ package org.eclipse.core.databinding.observable.masterdetail; @@ -21,6 +22,7 @@ import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableMap; import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableSet; import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailObservableList; /** * Allows for the observation of an attribute, the detail, of an observable @@ -116,4 +118,32 @@ IObservableFactory detailFactory) { return new DetailObservableMap(detailFactory, master); } + + /** + * Creates an unmodifiable observable list of detail observable values from + * an observable list of master observable values. For every master + * observable value in the given list, the provided factory is used to + * create the corresponding detail observable value. This can e.g. be used + * to create a list of observable values that represent the same property of + * a set of selected objects in a table. + * + * @param masterList + * the list of master observable values to track + * @param detailFactory + * a factory for creating {@link IObservableValue} instances + * given a current value of one of the masters + * @param detailType + * the value type of the detail observable values, typically of + * type java.lang.Class and can be null + * @return a list of observable values of the given value type that, for any + * current value of any of the master values, behaves like the + * observable value created by the factory for that current value. + * + * @since 1.2 + */ + public static IObservableList detailValueList(IObservableList masterList, + IObservableFactory detailFactory, Object detailType) { + return new ListDetailObservableList(masterList, detailFactory, + detailType); + } } Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailObservableList.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailObservableList.java diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailObservableList.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailObservableList.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2008 Ovidio Mallo 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: + * Ovidio Mallo - initial API and implementation (bug 175737) + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; +import org.eclipse.core.databinding.observable.list.ListDiff; +import org.eclipse.core.databinding.observable.list.ListDiffEntry; +import org.eclipse.core.databinding.observable.list.ObservableList; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables; +import org.eclipse.core.databinding.observable.value.IObservableValue; + +/** + * Unmodifiable observable list whose elements are the detail observable values + * of the master observable values stored in a given list. For every master + * observable value in that list, a user-provided factory is used to create the + * corresponding detail observable value. + * + * @since 1.2 + */ +public class ListDetailObservableList extends ObservableList { + + private IObservableList masterObservableList; + + private IObservableFactory detailFactory; + + private Object detailType; + + private IListChangeListener masterListListener = new IListChangeListener() { + public void handleListChange(ListChangeEvent event) { + applyMasterListDiff(event.diff); + } + }; + + /** + * Constructs a new ListDetailObservableList. + * + * @param masterObservableList + * the list of master observable values to track + * @param detailFactory + * a factory for creating {@link IObservableValue} instances + * given a current value of one of the masters + * @param detailType + * the value type of the detail observable values or + * null + */ + public ListDetailObservableList(IObservableList masterObservableList, + IObservableFactory detailFactory, Object detailType) { + super(masterObservableList.getRealm(), new ArrayList(), + IObservableValue.class); + this.masterObservableList = masterObservableList; + this.detailFactory = detailFactory; + this.detailType = detailType; + + masterObservableList.addListChangeListener(masterListListener); + + ListDiff initMasterDiff = Diffs.computeListDiff(Collections.EMPTY_LIST, + masterObservableList); + applyMasterListDiff(initMasterDiff); + } + + private void applyMasterListDiff(ListDiff masterListDiff) { + ListDiffEntry[] masterEntries = masterListDiff.getDifferences(); + ListDiffEntry[] detailEntries = new ListDiffEntry[masterEntries.length]; + for (int i = 0; i < masterEntries.length; i++) { + ListDiffEntry masterEntry = masterEntries[i]; + int index = masterEntry.getPosition(); + + IObservableValue detailObservable; + if (masterEntry.isAddition()) { + // When adding a new master, we must create a new detail. + IObservableValue masterObservable = (IObservableValue) masterEntry + .getElement(); + detailObservable = MasterDetailObservables.detailValue( + masterObservable, detailFactory, detailType); + + // Perform the actual addition. + wrappedList.add(index, detailObservable); + } else { + // When removing an existing master, we dispose the + // corresponding detail. + detailObservable = (IObservableValue) wrappedList.get(index); + detailObservable.dispose(); + + // Perform the actual removal. + wrappedList.remove(index); + } + + // Create the corresponding diff for the detail list. + detailEntries[i] = Diffs.createListDiffEntry(index, masterEntry + .isAddition(), detailObservable); + } + + // Fire a list change event with the adapted diffs on the detail list. + fireListChange(Diffs.createListDiff(detailEntries)); + } + + public void dispose() { + if (masterObservableList != null) { + masterObservableList.removeListChangeListener(masterListListener); + } + + if (wrappedList != null) { + for (Iterator iter = wrappedList.iterator(); iter.hasNext();) { + ((IObservableValue) iter.next()).dispose(); + } + } + + masterObservableList = null; + masterListListener = null; + detailFactory = null; + detailType = null; + + super.dispose(); + } +}