### Eclipse Workspace Patch 1.0 #P org.eclipse.core.databinding.property Index: src/org/eclipse/core/databinding/property/value/ValueProperty.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.property/src/org/eclipse/core/databinding/property/value/ValueProperty.java,v retrieving revision 1.3 diff -u -r1.3 ValueProperty.java --- src/org/eclipse/core/databinding/property/value/ValueProperty.java 25 May 2009 20:52:27 -0000 1.3 +++ src/org/eclipse/core/databinding/property/value/ValueProperty.java 8 Mar 2010 16:41:27 -0000 @@ -7,15 +7,18 @@ * * Contributors: * Matthew Hall - initial API and implementation (bug 194734) - * Matthew Hall - bug 195222 + * Matthew Hall - bugs 195222, 300043 ******************************************************************************/ package org.eclipse.core.databinding.property.value; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.map.IObservableMap; import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables; +import org.eclipse.core.databinding.observable.set.IObservableSet; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.property.list.IListProperty; import org.eclipse.core.databinding.property.map.IMapProperty; @@ -56,6 +59,30 @@ .getRealm()), getValueType()); } + /** + * @since 1.3 + */ + public IObservableList observeDetail(IObservableList master) { + return MasterDetailObservables.detailListValues(master, + valueFactory(master.getRealm()), getValueType()); + } + + /** + * @since 1.3 + */ + public IObservableMap observeDetail(IObservableSet master) { + return MasterDetailObservables.detailSetValues(master, + valueFactory(master.getRealm()), getValueType()); + } + + /** + * @since 1.3 + */ + public IObservableMap observeDetail(IObservableMap master) { + return MasterDetailObservables.detailMapValues(master, + valueFactory(master.getRealm()), getValueType()); + } + public final IValueProperty value(IValueProperty detailValue) { return new ValuePropertyDetailValue(this, detailValue); } Index: src/org/eclipse/core/databinding/property/list/IListProperty.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.property/src/org/eclipse/core/databinding/property/list/IListProperty.java,v retrieving revision 1.3 diff -u -r1.3 IListProperty.java --- src/org/eclipse/core/databinding/property/list/IListProperty.java 25 May 2009 20:52:27 -0000 1.3 +++ src/org/eclipse/core/databinding/property/list/IListProperty.java 8 Mar 2010 16:41:27 -0000 @@ -7,7 +7,7 @@ * * Contributors: * Matthew Hall - initial API and implementation (bug 194734) - * Matthew Hall - bug 195222 + * Matthew Hall - bug 195222, 300043 ******************************************************************************/ package org.eclipse.core.databinding.property.list; @@ -111,4 +111,12 @@ * properties */ public IListProperty values(IValueProperty detailValue); + + /** + * @param reducer + * the strategy that reduces the list down to a value. + * @return a read-only reduced value from this list property + * @since 1.3 + */ + public IValueProperty reduce(IListReducer reducer); } Index: src/org/eclipse/core/databinding/property/list/ListProperty.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.property/src/org/eclipse/core/databinding/property/list/ListProperty.java,v retrieving revision 1.3 diff -u -r1.3 ListProperty.java --- src/org/eclipse/core/databinding/property/list/ListProperty.java 25 May 2009 20:52:27 -0000 1.3 +++ src/org/eclipse/core/databinding/property/list/ListProperty.java 8 Mar 2010 16:41:27 -0000 @@ -7,7 +7,7 @@ * * Contributors: * Matthew Hall - initial API and implementation (bug 194734) - * Matthew Hall - bug 195222 + * Matthew Hall - bug 195222, 300043 ******************************************************************************/ package org.eclipse.core.databinding.property.list; @@ -20,6 +20,7 @@ import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.databinding.property.value.IValueProperty; import org.eclipse.core.internal.databinding.property.ListPropertyDetailValuesList; +import org.eclipse.core.internal.databinding.property.list.ReducedListValueProperty; /** * Abstract implementation of IListProperty. @@ -55,4 +56,11 @@ public final IListProperty values(IValueProperty detailValue) { return new ListPropertyDetailValuesList(this, detailValue); } + + /** + * @since 1.3 + */ + public final IValueProperty reduce(IListReducer reducer) { + return new ReducedListValueProperty(this, reducer); + } } Index: META-INF/MANIFEST.MF =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.property/META-INF/MANIFEST.MF,v retrieving revision 1.3 diff -u -r1.3 MANIFEST.MF --- META-INF/MANIFEST.MF 25 Aug 2009 04:57:24 -0000 1.3 +++ META-INF/MANIFEST.MF 8 Mar 2010 16:41:27 -0000 @@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.databinding.property -Bundle-Version: 1.2.100.qualifier +Bundle-Version: 1.3.0.qualifier Bundle-ClassPath: . Bundle-Vendor: %providerName Bundle-Localization: plugin Index: src/org/eclipse/core/databinding/property/list/IListReducer.java =================================================================== RCS file: src/org/eclipse/core/databinding/property/list/IListReducer.java diff -N src/org/eclipse/core/databinding/property/list/IListReducer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/property/list/IListReducer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2010 Matthew Hall 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: + * Matthew Hall - initial API and implementation (bug 300043) + ******************************************************************************/ + +package org.eclipse.core.databinding.property.list; + +import java.util.List; + +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.value.IObservableValue; + +/** + * A list-to-value reducer. List reducers may be used to condense a list + * property down to a value property. + * + * @since 1.3 + * @noimplement This interface is not intended to be implemented by clients. + * @see ListReducer + */ +public interface IListReducer { + + /** + * @param list + * the list to reduce + * @return the list reduction + */ + public Object reduce(List list); + + /** + * @return the value type of the reduction + */ + public Object getValueType(); + + /** + * @param master + * the master list to reduce + * @return an IObservableValue observing the reduction of the specified + * IObservableList + */ + public IObservableValue observeDetail(IObservableList master); + +} Index: src/org/eclipse/core/internal/databinding/property/value/ReducedListObservableValue.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/property/value/ReducedListObservableValue.java diff -N src/org/eclipse/core/internal/databinding/property/value/ReducedListObservableValue.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/property/value/ReducedListObservableValue.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010 Matthew Hall 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: + * Matthew Hall - initial API and implementation (bug 300043) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.property.value; + +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.value.ComputedValue; +import org.eclipse.core.databinding.property.list.IListReducer; + +/** + * + */ +public class ReducedListObservableValue extends ComputedValue { + + private IObservableList masterList; + private IListReducer reducer; + + /** + * @param masterList + * @param reducer + */ + public ReducedListObservableValue(IObservableList masterList, + IListReducer reducer) { + super(masterList.getRealm(), reducer.getValueType()); + this.masterList = masterList; + this.reducer = reducer; + } + + protected Object calculate() { + return reducer.reduce(masterList); + } + +} Index: src/org/eclipse/core/internal/databinding/property/list/ReducedListValueProperty.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/property/list/ReducedListValueProperty.java diff -N src/org/eclipse/core/internal/databinding/property/list/ReducedListValueProperty.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/property/list/ReducedListValueProperty.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2010 Matthew Hall 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: + * Matthew Hall - initial API and implementation (bug 300043) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.property.list; + +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.property.list.IListReducer; +import org.eclipse.core.databinding.property.list.ListProperty; +import org.eclipse.core.databinding.property.value.ValueProperty; +import org.eclipse.core.internal.databinding.property.PropertyObservableUtil; + +/** + * + */ +public class ReducedListValueProperty extends ValueProperty { + + private final ListProperty masterProperty; + private final IListReducer reducer; + + /** + * @param masterProperty + * @param reducer + */ + public ReducedListValueProperty(ListProperty masterProperty, + IListReducer reducer) { + this.masterProperty = masterProperty; + this.reducer = reducer; + } + + public Object getValueType() { + return reducer.getValueType(); + } + + public IObservableValue observe(Realm realm, Object source) { + IObservableList masterList; + + ObservableTracker.setIgnore(true); + try { + masterList = masterProperty.observe(realm, source); + } finally { + ObservableTracker.setIgnore(false); + } + + IObservableValue detailValue = reducer.observeDetail(masterList); + PropertyObservableUtil.cascadeDispose(detailValue, masterList); + return detailValue; + } +} Index: src/org/eclipse/core/databinding/property/list/ListReducer.java =================================================================== RCS file: src/org/eclipse/core/databinding/property/list/ListReducer.java diff -N src/org/eclipse/core/databinding/property/list/ListReducer.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/property/list/ListReducer.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2010 Matthew Hall 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: + * Matthew Hall - initial API and implementation (bug 300043) + ******************************************************************************/ + +package org.eclipse.core.databinding.property.list; + +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.internal.databinding.property.value.ReducedListObservableValue; + +/** + * Abstract base implementation if {@link IListReducer}. + * + * @since 1.3 + */ +public abstract class ListReducer implements IListReducer { + + private final Object valueType; + + /** + * Constructs a list reducer with the given value type + * + * @param valueType + * the reduction value type + */ + protected ListReducer(Object valueType) { + this.valueType = valueType; + } + + public Object getValueType() { + return valueType; + } + + public IObservableValue observeDetail(IObservableList master) { + return new ReducedListObservableValue(master, this); + } + +} Index: src/org/eclipse/core/databinding/property/list/ListReducers.java =================================================================== RCS file: src/org/eclipse/core/databinding/property/list/ListReducers.java diff -N src/org/eclipse/core/databinding/property/list/ListReducers.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/databinding/property/list/ListReducers.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2010 Matthew Hall 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: + * Matthew Hall - initial API and implementation (bug 300043) + ******************************************************************************/ + +package org.eclipse.core.databinding.property.list; + +import java.util.Iterator; +import java.util.List; + +/** + * Factory class for common list reducers + * + * @since 1.3 + */ +public class ListReducers { + /** + * @param delimiter + * the delimiter between each list element + * @return a String-valued list reducer which concatenates all elements in + * the list, with the specified delimiter between each element. + */ + public static IListReducer join(final String delimiter) { + return new ListReducer(String.class) { + public Object reduce(List list) { + StringBuffer buf = new StringBuffer(); + + for (Iterator it = list.iterator(); it.hasNext();) { + buf.append(it.next()); + if (it.hasNext()) + buf.append(delimiter); + } + + return buf.toString(); + } + }; + } +} #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.120 diff -u -r1.120 BindingTestSuite.java --- src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 25 Feb 2010 05:01:30 -0000 1.120 +++ src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 8 Mar 2010 16:41:29 -0000 @@ -17,7 +17,7 @@ * 246103, 249992, 256150, 256543, 262269, 175735, 262946, * 255734, 263693, 169876, 266038, 268336, 270461, 271720, * 283204, 281723, 283428 - * Ovidio Mallo - bugs 237163, 235195, 299619 + * Ovidio Mallo - bugs 237163, 235195, 299619, 300043 *******************************************************************************/ package org.eclipse.jface.tests.databinding; @@ -140,6 +140,7 @@ import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableMapTest; 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.ListDetailValueObservableListTest; import org.eclipse.core.tests.internal.databinding.property.value.ListSimpleValueObservableListTest; import org.eclipse.core.tests.internal.databinding.property.value.MapSimpleValueObservableMapTest; import org.eclipse.core.tests.internal.databinding.property.value.SetSimpleValueObservableMapTest; @@ -379,6 +380,7 @@ addTestSuite(DetailObservableMapTest.class); addTest(DetailObservableSetTest.suite()); addTest(DetailObservableValueTest.suite()); + addTest(ListDetailValueObservableListTest.suite()); // org.eclipse.core.tests.internal.databinding.property.value addTestSuite(MapSimpleValueObservableMapTest.class); Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java =================================================================== RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,354 @@ +/******************************************************************************* + * Copyright (c) 2010 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 300043) + ******************************************************************************/ + +package org.eclipse.core.tests.internal.databinding.observable.masterdetail; + +import java.util.ArrayList; +import java.util.Collections; +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.ListDiff; +import org.eclipse.core.databinding.observable.list.ListDiffEntry; +import org.eclipse.core.databinding.observable.list.WritableList; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.WritableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailValueObservableList; +import org.eclipse.jface.databinding.conformance.ObservableListContractTest; +import org.eclipse.jface.databinding.conformance.delegate.AbstractObservableCollectionContractDelegate; +import org.eclipse.jface.databinding.conformance.util.ListChangeEventTracker; +import org.eclipse.jface.examples.databinding.model.SimplePerson; +import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; + +/** + * @since 1.3 + */ +public class ListDetailValueObservableListTest extends + AbstractDefaultRealmTestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(ListDetailValueObservableListTest.class + .getName()); + suite.addTestSuite(ListDetailValueObservableListTest.class); + suite.addTest(ObservableListContractTest.suite(new Delegate())); + return suite; + } + + public void testUnmodifiability() { + WritableList masterObservableList = new WritableList(); + masterObservableList.add(new SimplePerson()); + masterObservableList.add(new SimplePerson()); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + null); + + try { + ldol.add("name"); + fail("ListDetailValueObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + + try { + ldol.remove(masterObservableList.get(0)); + fail("ListDetailValueObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + + try { + ldol.removeAll(Collections.singleton(masterObservableList.get(0))); + fail("ListDetailValueObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + + try { + ldol.retainAll(Collections.EMPTY_LIST); + fail("ListDetailValueObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + + try { + ldol.move(0, 1); + fail("ListDetailValueObservableList must not be modifiable."); + } catch (UnsupportedOperationException e) { + // expected exception + } + } + + public void testDetailListElementType() { + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + new WritableList(), BeansObservables.valueFactory("name"), + String.class); + + assertSame(String.class, ldol.getElementType()); + } + + public void testGetObserved() { + WritableList masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, BeansObservables.valueFactory("name"), String.class); + + // The observed object is the master list. + assertSame(masterList, ldol.getObserved()); + } + + public void testMasterListInitiallyNotEmpty() { + WritableList masterObservableList = new WritableList(); + masterObservableList.add(new SimplePerson()); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + // Make sure that a non-empty master list is initialized correctly. + assertEquals(masterObservableList.size(), ldol.size()); + } + + public void testChangeMaster() { + WritableList masterObservableList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + // Initially, the detail list is empty. + assertTrue(ldol.isEmpty()); + + // Add a first person and check that it's name is in the detail list. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterObservableList.add(p1); + assertEquals(masterObservableList.size(), ldol.size()); + assertEquals(p1.getName(), ldol.get(0)); + + // Add a second person and check that it's name is in the detail list. + SimplePerson p2 = new SimplePerson(); + p2.setName("name2"); + masterObservableList.add(p2); + assertEquals(masterObservableList.size(), ldol.size()); + assertEquals(p2.getName(), ldol.get(1)); + + // Remove the first person from the master list and check that we still + // have the name of the second person in the detail list. + masterObservableList.remove(0); + assertEquals(masterObservableList.size(), ldol.size()); + assertEquals(p2.getName(), ldol.get(0)); + + // Remove the second person as well. + masterObservableList.remove(0); + assertTrue(ldol.isEmpty()); + } + + public void testChangeDetail() { + WritableList masterObservableList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + // Change the detail attribute explicitly. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterObservableList.add(p1); + assertEquals(p1.getName(), ldol.get(0)); + p1.setName("name2"); + assertEquals(p1.getName(), ldol.get(0)); + + // Change the detail attribute by changing the master. + SimplePerson p2 = new SimplePerson(); + p2.setName("name3"); + masterObservableList.set(0, p2); + assertEquals(p2.getName(), ldol.get(0)); + } + + public void testSet() { + WritableList masterObservableList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + // Change the detail attribute explicitly. + SimplePerson person = new SimplePerson(); + person.setName("name1"); + masterObservableList.add(person); + assertEquals(person.getName(), ldol.get(0)); + + // Set a new name on the detail list. + ldol.set(0, "name2"); + // Check that the name has been propagated to the master. + assertEquals("name2", person.getName()); + assertEquals(person.getName(), ldol.get(0)); + } + + public void testDetailObservableChangeEvent() { + WritableList masterObservableList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + ListChangeEventTracker changeTracker = ListChangeEventTracker + .observe(ldol); + + SimplePerson person = new SimplePerson(); + person.setName("old name"); + + // Initially, we should not have received any event. + assertEquals(0, changeTracker.count); + + // Add the person and check that we receive an addition event on the + // correct index and with the correct value. + masterObservableList.add(person); + assertEquals(1, changeTracker.count); + assertEquals(1, changeTracker.event.diff.getDifferences().length); + assertTrue(changeTracker.event.diff.getDifferences()[0].isAddition()); + assertEquals(0, changeTracker.event.diff.getDifferences()[0] + .getPosition()); + assertEquals(person.getName(), changeTracker.event.diff + .getDifferences()[0].getElement()); + + // Change the detail property and check that we receive a replace event. + person.setName("new name"); + assertEquals(2, changeTracker.count); + assertEquals(2, changeTracker.event.diff.getDifferences().length); + assertIsSingleReplaceDiff(changeTracker.event.diff, 0, "old name", + "new name"); + } + + private void assertIsSingleReplaceDiff(ListDiff diff, int index, + Object oldElement, Object newElement) { + // We should have 2 diff entries. + assertEquals(2, diff.getDifferences().length); + + ListDiffEntry entry1 = diff.getDifferences()[0]; + ListDiffEntry entry2 = diff.getDifferences()[1]; + + // One diff entry must be an addition, the other a removal. + assertTrue(entry1.isAddition() != entry2.isAddition()); + + // Check for the index on the diff entries. + assertEquals(index, entry1.getPosition()); + assertEquals(index, entry2.getPosition()); + + // Check for the old/new element values on both diff entries. + if (entry1.isAddition()) { + assertEquals(oldElement, entry2.getElement()); + assertEquals(newElement, entry1.getElement()); + } else { + assertEquals(oldElement, entry1.getElement()); + assertEquals(newElement, entry2.getElement()); + } + } + + public void testMasterNull() { + WritableList masterObservableList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, BeansObservables.valueFactory("name"), + String.class); + + // Make sure null values are handled gracefully. + masterObservableList.add(null); + assertNull(ldol.get(0)); + } + + public void testDetailObservableValuesAreDisposed() { + final List detailObservables = new ArrayList(); + IObservableFactory detailValueFactory = new IObservableFactory() { + public IObservable createObservable(Object target) { + WritableValue detailObservable = new WritableValue(); + // Remember the created observables. + detailObservables.add(detailObservable); + return detailObservable; + } + }; + + WritableList masterObservableList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterObservableList, detailValueFactory, null); + + masterObservableList.add(new Object()); + masterObservableList.add(new Object()); + + assertEquals(ldol.size(), detailObservables.size()); + + // No detail observables should be disposed yet. + assertFalse(((WritableValue) detailObservables.get(0)).isDisposed()); + assertFalse(((WritableValue) detailObservables.get(1)).isDisposed()); + + // Only the detail observable for the removed master should be disposed. + masterObservableList.remove(1); + assertFalse(((WritableValue) detailObservables.get(0)).isDisposed()); + assertTrue(((WritableValue) detailObservables.get(1)).isDisposed()); + + // After disposing the detail list, all detail observables should be + // disposed. + ldol.dispose(); + assertTrue(((WritableValue) detailObservables.get(0)).isDisposed()); + assertTrue(((WritableValue) detailObservables.get(1)).isDisposed()); + } + + public void testDisposeOnMasterDisposed() { + WritableList masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, BeansObservables.valueFactory("name"), String.class); + + // Initially, nothing should be disposed. + assertFalse(masterList.isDisposed()); + assertFalse(ldol.isDisposed()); + + // Upon disposing the master list, the detail list should be disposed as + // well. + masterList.dispose(); + assertTrue(masterList.isDisposed()); + assertTrue(ldol.isDisposed()); + } + + 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 SimplePerson()); + } + + return new TestListDetailValueObservableList(masterObservableList, + BeansObservables.valueFactory(realm, "name"), String.class); + } + + public void change(IObservable observable) { + TestListDetailValueObservableList ldol = (TestListDetailValueObservableList) observable; + ldol.masterObservableList.add(new SimplePerson()); + } + + public Object getElementType(IObservableCollection collection) { + return String.class; + } + } + + private static class TestListDetailValueObservableList extends + ListDetailValueObservableList { + final IObservableList masterObservableList; + + public TestListDetailValueObservableList( + IObservableList masterObservableList, + IObservableFactory detailValueFactory, Object detailType) { + super(masterObservableList, detailValueFactory, detailType); + this.masterObservableList = masterObservableList; + } + } +} #P org.eclipse.jface.examples.databinding Index: src/org/eclipse/jface/examples/databinding/snippets/Snippet025TableViewerWithPropertyDerivedColumns.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.jface.examples.databinding/src/org/eclipse/jface/examples/databinding/snippets/Snippet025TableViewerWithPropertyDerivedColumns.java,v retrieving revision 1.5 diff -u -r1.5 Snippet025TableViewerWithPropertyDerivedColumns.java --- src/org/eclipse/jface/examples/databinding/snippets/Snippet025TableViewerWithPropertyDerivedColumns.java 25 May 2009 20:52:49 -0000 1.5 +++ src/org/eclipse/jface/examples/databinding/snippets/Snippet025TableViewerWithPropertyDerivedColumns.java 8 Mar 2010 16:41:29 -0000 @@ -14,6 +14,9 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import org.eclipse.core.databinding.DataBindingContext; import org.eclipse.core.databinding.beans.BeanProperties; @@ -21,10 +24,12 @@ import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.list.WritableList; import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.property.list.ListReducers; +import org.eclipse.core.databinding.property.value.IValueProperty; import org.eclipse.jface.databinding.swt.SWTObservables; import org.eclipse.jface.databinding.swt.WidgetProperties; -import org.eclipse.jface.databinding.viewers.ViewerSupport; import org.eclipse.jface.databinding.viewers.ViewerProperties; +import org.eclipse.jface.databinding.viewers.ViewerSupport; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.ComboViewer; @@ -106,11 +111,28 @@ String name = "Donald Duck"; Person mother; Person father; + List children = new ArrayList(); public Person(String name, Person mother, Person father) { this.name = name; this.mother = mother; this.father = father; + if (mother != null) { + mother.addChild(this); + } + if (father != null) { + father.addChild(this); + } + } + + private void addChild(Person person) { + children.add(person); + firePropertyChange("children", null, children); + } + + private void removeChild(Person person) { + children.remove(person); + firePropertyChange("children", null, children); } public String getName() { @@ -125,8 +147,18 @@ return mother; } + public Person[] getChildren() { + return (Person[]) children.toArray(new Person[children.size()]); + } + public void setMother(Person mother) { + if (this.mother != null) { + this.mother.removeChild(this); + } firePropertyChange("mother", this.mother, this.mother = mother); + if (this.mother != null) { + this.mother.addChild(this); + } } public Person getFather() { @@ -134,7 +166,13 @@ } public void setFather(Person father) { + if (this.father != null) { + this.father.removeChild(this); + } firePropertyChange("father", this.father, this.father = father); + if (this.father != null) { + this.father.addChild(this); + } } } @@ -145,6 +183,7 @@ // data access tier. Since this snippet doesn't have any persistent objects // ro retrieve, this ViewModel just instantiates a model object to edit. static class ViewModel { + // The model to bind private IObservableList people = new WritableList(); { @@ -197,6 +236,7 @@ createColumn("Maternal Grandfather"); createColumn("Paternal Grandmother"); createColumn("Paternal Grandfather"); + createColumn("Children").setWidth(200); duckFamily.setLinesVisible(true); @@ -216,17 +256,18 @@ GridLayoutFactory.swtDefaults().numColumns(2).applyTo(shell); // Open and return the Shell - shell.setSize(800, 300); + shell.setSize(1000, 300); shell.open(); return shell; } - private void createColumn(String string) { + private TableColumn createColumn(String string) { final TableColumn column = new TableColumn(duckFamily, SWT.NONE); column.setText(string); column.pack(); if (column.getWidth() < 100) column.setWidth(100); + return column; } protected void bindGUI(DataBindingContext dbc) { @@ -240,10 +281,7 @@ }); ViewerSupport.bind(peopleViewer, viewModel.getPeople(), - BeanProperties.values(Person.class, new String[] { "name", - "mother.name", "father.name", "mother.mother.name", - "mother.father.name", "father.mother.name", - "father.father.name" })); + getProperties()); IObservableValue masterSelection = ViewerProperties .singleSelection().observe(peopleViewer); @@ -269,4 +307,20 @@ "father").observeDetail(masterSelection)); } } + + private static IValueProperty[] getProperties() { + List properties = new ArrayList(); + + // Add the "simple" properties. + properties.addAll(Arrays.asList(BeanProperties.values(Person.class, + new String[] { "name", "mother.name", "father.name", + "mother.mother.name", "mother.father.name", + "father.mother.name", "father.father.name" }))); + + // Add the "complex" property. + properties.add(BeanProperties.list("children").values("name").reduce( + ListReducers.join(", "))); + + return properties.toArray(new IValueProperty[properties.size()]); + } } #P org.eclipse.core.databinding.observable Index: src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java,v retrieving revision 1.10 diff -u -r1.10 MasterDetailObservables.java --- src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java 17 Oct 2008 16:44:25 -0000 1.10 +++ src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java 8 Mar 2010 16:41:30 -0000 @@ -9,6 +9,7 @@ * IBM Corporation - initial API and implementation * Brad Reynolds - bug 147515 * Matthew Hall - bug 221704, 226289 + * Ovidio Mallo - bug 300043 *******************************************************************************/ package org.eclipse.core.databinding.observable.masterdetail; @@ -21,6 +22,9 @@ 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.ListDetailValueObservableList; +import org.eclipse.core.internal.databinding.observable.masterdetail.MapDetailValueObservableMap; +import org.eclipse.core.internal.databinding.observable.masterdetail.SetDetailValueObservableMap; /** * Allows for the observation of an attribute, the detail, of an observable @@ -29,7 +33,7 @@ * @since 1.0 */ public class MasterDetailObservables { - + /** * Creates a detail observable value from a master observable value and a * factory. This can be used to create observable values that represent a @@ -145,4 +149,86 @@ return new DetailObservableMap(detailFactory, master, detailKeyType, detailValueType); } + + /** + * Creates an unmodifiable observable list of detail values from an + * observable list of observable values. For every master 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 values that + * represent the same property of a set of selected objects in a table. + * + * @param masterList + * the list of master 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 values, typically of type + * java.lang.Class and can be null + * @return a list of detail 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.3 + */ + public static IObservableList detailListValues(IObservableList masterList, + IObservableFactory detailFactory, Object detailType) { + return new ListDetailValueObservableList(masterList, detailFactory, + detailType); + } + + /** + * Creates an unmodifiable observable map of detail values from an + * observable set of master values. For every master value in the given set, + * the provided factory is used to create the corresponding detail + * observable value which is added to a map whose key is the master value. + * This can e.g. be used to create a list of values that represent the same + * property of a set of selected objects in a table. + * + * @param masterSet + * the set of master 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 values, typically of type + * java.lang.Class and can be null + * @return a map of detail 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.3 + */ + public static IObservableMap detailSetValues(IObservableSet masterSet, + IObservableFactory detailFactory, Object detailType) { + return new SetDetailValueObservableMap(masterSet, detailFactory, + detailType); + } + + /** + * Creates an unmodifiable observable map of detail values from an + * observable map of master values. For every master value in the given map, + * the provided factory is used to create the corresponding detail + * observable value. This can e.g. be used to create a map of values that + * represent the same property of a set of selected objects in a table. + * + * @param masterMap + * the set of master 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 values, typically of type + * java.lang.Class and can be null + * @return a list of detail 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.3 + */ + public static IObservableMap detailMapValues(IObservableMap masterMap, + IObservableFactory detailFactory, Object detailType) { + return new MapDetailValueObservableMap(masterMap, detailFactory, + detailType); + } } Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2010 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 300043) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.RandomAccess; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.DisposeEvent; +import org.eclipse.core.databinding.observable.IDisposeListener; +import org.eclipse.core.databinding.observable.IObserving; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.list.AbstractObservableList; +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.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.internal.databinding.identity.IdentitySet; + +/** + * @since 1.3 + */ +public class ListDetailValueObservableList extends AbstractObservableList + implements IObserving, RandomAccess { + + private IObservableList masterList; + + private IObservableFactory detailFactory; + + private Object detailType; + + private ArrayList detailList; + + private IdentitySet staleDetailObservables = new IdentitySet(); + + private IListChangeListener masterListListener = new IListChangeListener() { + public void handleListChange(ListChangeEvent event) { + adaptMasterListDiff(event.diff); + } + }; + + private IValueChangeListener detailValueListener = new IValueChangeListener() { + public void handleValueChange(ValueChangeEvent event) { + if (!event.getObservable().isStale()) { + staleDetailObservables.remove(event.getObservable()); + } + handleDetailValueChange(event); + } + }; + + private IStaleListener masterStaleListener = new IStaleListener() { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + private IStaleListener detailStaleListener = new IStaleListener() { + public void handleStale(StaleEvent staleEvent) { + boolean wasStale = isStale(); + staleDetailObservables.add((staleEvent.getObservable())); + if (!wasStale) { + fireStale(); + } + } + }; + + /** + * + * @param masterList + * @param detailFactory + * @param detailType + */ + public ListDetailValueObservableList(IObservableList masterList, + IObservableFactory detailFactory, Object detailType) { + super(masterList.getRealm()); + this.masterList = masterList; + this.detailFactory = detailFactory; + this.detailType = detailType; + this.detailList = new ArrayList(); + + masterList.addListChangeListener(masterListListener); + masterList.addStaleListener(masterStaleListener); + masterList.addDisposeListener(new IDisposeListener() { + public void handleDispose(DisposeEvent event) { + ListDetailValueObservableList.this.dispose(); + } + }); + + ListDiff initMasterDiff = Diffs.computeListDiff(Collections.EMPTY_LIST, + masterList); + adaptMasterListDiff(initMasterDiff); + } + + private void adaptMasterListDiff(ListDiff masterListDiff) { + boolean wasStale = isStale(); + + 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(); + + Object detailValue; + if (masterEntry.isAddition()) { + Object masterElement = masterEntry.getElement(); + detailValue = addDetailObservable(masterElement, index); + } else { + detailValue = removeDetailObservable(index); + } + + // Create the corresponding diff for the detail list. + detailEntries[i] = Diffs.createListDiffEntry(index, masterEntry + .isAddition(), detailValue); + } + + if (!wasStale && isStale()) { + fireStale(); + } + + // Fire a list change event with the adapted diffs on the detail list. + fireListChange(Diffs.createListDiff(detailEntries)); + } + + private Object addDetailObservable(Object masterElement, int index) { + IObservableValue detail; + ObservableTracker.setIgnore(true); + try { + detail = (IObservableValue) detailFactory + .createObservable(masterElement); + } finally { + ObservableTracker.setIgnore(false); + } + + detailList.add(index, detail); + + detail.addValueChangeListener(detailValueListener); + + detail.addStaleListener(detailStaleListener); + if (detail.isStale()) { + staleDetailObservables.add(detail); + } + + return detail.getValue(); + } + + private Object removeDetailObservable(int index) { + IObservableValue detail = (IObservableValue) detailList.remove(index); + staleDetailObservables.remove(detail); + Object detailValue = detail.getValue(); + detail.dispose(); + return detailValue; + } + + private void handleDetailValueChange(ValueChangeEvent event) { + IObservableValue detail = event.getObservableValue(); + + BitSet detailIndexes = new BitSet(); + for (int i = 0; i < detailList.size(); i++) { + if (detailList.get(i) == detail) { + detailIndexes.set(i); + } + } + + Object oldValue = event.diff.getOldValue(); + Object newValue = event.diff.getNewValue(); + ListDiffEntry[] diffEntries = new ListDiffEntry[2 * detailIndexes + .cardinality()]; + int diffIndex = 0; + for (int b = 0; b != -1; b = detailIndexes.nextSetBit(b + 1)) { + diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, false, + oldValue); + diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, true, + newValue); + } + fireListChange(Diffs.createListDiff(diffEntries)); + } + + protected int doGetSize() { + return detailList.size(); + } + + public Object get(int index) { + ObservableTracker.getterCalled(this); + return ((IObservableValue) detailList.get(index)).getValue(); + } + + public Object set(int index, Object element) { + IObservableValue detail = (IObservableValue) detailList.get(index); + Object oldElement = detail.getValue(); + detail.setValue(element); + return oldElement; + } + + public Object move(int oldIndex, int newIndex) { + throw new UnsupportedOperationException(); + } + + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public Object getElementType() { + return detailType; + } + + public boolean isStale() { + return super.isStale() + || (masterList != null && masterList.isStale()) + || (staleDetailObservables != null && !staleDetailObservables + .isEmpty()); + } + + public Object getObserved() { + return masterList; + } + + public synchronized void dispose() { + if (masterList != null) { + masterList.removeListChangeListener(masterListListener); + masterList.removeStaleListener(masterStaleListener); + } + + if (detailList != null) { + for (Iterator iter = detailList.iterator(); iter.hasNext();) { + IObservableValue detailValue = (IObservableValue) iter.next(); + detailValue.dispose(); + } + detailList.clear(); + } + + masterList = null; + detailFactory = null; + detailType = null; + masterListListener = null; + detailValueListener = null; + staleDetailObservables = null; + + super.dispose(); + } +} Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2010 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 300043) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.databinding.observable.IObserving; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.map.ComputedObservableMap; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.internal.databinding.identity.IdentitySet; + +/** + * @since 1.3 + */ +public class SetDetailValueObservableMap extends ComputedObservableMap + implements IObserving { + + private IObservableFactory observableValueFactory; + + private Map detailObservableValueMap = new HashMap(); + + private IdentitySet staleDetailObservables = new IdentitySet(); + + private IStaleListener detailStaleListener = new IStaleListener() { + public void handleStale(StaleEvent staleEvent) { + addStaleDetailObservable((IObservableValue) staleEvent + .getObservable()); + } + }; + + /** + * @param masterKeySet + * @param observableValueFactory + * @param detailValueType + */ + public SetDetailValueObservableMap(IObservableSet masterKeySet, + IObservableFactory observableValueFactory, Object detailValueType) { + super(masterKeySet, detailValueType); + this.observableValueFactory = observableValueFactory; + } + + protected void hookListener(final Object addedKey) { + IObservableValue detailValue = getDetailObservableValue(addedKey); + + detailValue.addValueChangeListener(new IValueChangeListener() { + public void handleValueChange(ValueChangeEvent event) { + if (!event.getObservableValue().isStale()) { + staleDetailObservables.remove(event.getObservableValue()); + } + + fireSingleChange(addedKey, event.diff.getOldValue(), event.diff + .getNewValue()); + } + }); + + detailValue.addStaleListener(detailStaleListener); + } + + protected void unhookListener(Object removedKey) { + if (isDisposed()) { + return; + } + + IObservableValue detailValue = (IObservableValue) detailObservableValueMap + .remove(removedKey); + staleDetailObservables.remove(detailValue); + detailValue.dispose(); + } + + private IObservableValue getDetailObservableValue(Object masterKey) { + IObservableValue detailValue = (IObservableValue) detailObservableValueMap + .get(masterKey); + + if (detailValue == null) { + ObservableTracker.setIgnore(true); + try { + detailValue = (IObservableValue) observableValueFactory + .createObservable(masterKey); + } finally { + ObservableTracker.setIgnore(false); + } + + detailObservableValueMap.put(masterKey, detailValue); + + if (detailValue.isStale()) { + addStaleDetailObservable(detailValue); + } + } + + return detailValue; + } + + private void addStaleDetailObservable(IObservableValue detailObservable) { + boolean wasStale = isStale(); + staleDetailObservables.add(detailObservable); + if (!wasStale) { + fireStale(); + } + } + + protected Object doGet(Object key) { + IObservableValue detailValue = getDetailObservableValue(key); + return detailValue.getValue(); + } + + protected Object doPut(Object key, Object value) { + IObservableValue detailValue = getDetailObservableValue(key); + Object oldValue = detailValue.getValue(); + detailValue.setValue(value); + return oldValue; + } + + public boolean isStale() { + return super.isStale() || staleDetailObservables != null + && !staleDetailObservables.isEmpty(); + } + + public Object getObserved() { + return keySet(); + } + + public synchronized void dispose() { + super.dispose(); + + observableValueFactory = null; + detailObservableValueMap = null; + detailStaleListener = null; + staleDetailObservables = null; + } +} Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java =================================================================== RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,360 @@ +/******************************************************************************* + * Copyright (c) 2010 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 300043) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.AbstractSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.DisposeEvent; +import org.eclipse.core.databinding.observable.IDisposeListener; +import org.eclipse.core.databinding.observable.IObserving; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.map.AbstractObservableMap; +import org.eclipse.core.databinding.observable.map.IMapChangeListener; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.map.MapChangeEvent; +import org.eclipse.core.databinding.observable.map.MapDiff; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.internal.databinding.identity.IdentityMap; +import org.eclipse.core.internal.databinding.identity.IdentitySet; +import org.eclipse.core.internal.databinding.observable.Util; + +/** + * @since 1.3 + */ +public class MapDetailValueObservableMap extends AbstractObservableMap + implements IObserving { + + private IObservableMap masterMap; + + private IObservableFactory observableValueFactory; + + private Object detailValueType; + + private Set entrySet; + + private IMapChangeListener masterMapListener = new IMapChangeListener() { + public void handleMapChange(MapChangeEvent event) { + adaptMasterMapDiff(event.diff); + } + }; + + private Map detailObservableValueMap = new HashMap(); + + private IdentitySet staleDetailObservables = new IdentitySet(); + + private IStaleListener masterStaleListener = new IStaleListener() { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + private IStaleListener detailStaleListener = new IStaleListener() { + public void handleStale(StaleEvent staleEvent) { + addStaleDetailObservable((IObservableValue) staleEvent + .getObservable()); + } + }; + + /** + * @param masterMap + * @param observableValueFactory + * @param detailValueType + */ + public MapDetailValueObservableMap(IObservableMap masterMap, + IObservableFactory observableValueFactory, Object detailValueType) { + super(masterMap.getRealm()); + this.masterMap = masterMap; + this.observableValueFactory = observableValueFactory; + this.detailValueType = detailValueType; + + masterMap.addMapChangeListener(masterMapListener); + masterMap.addStaleListener(masterStaleListener); + masterMap.addDisposeListener(new IDisposeListener() { + public void handleDispose(DisposeEvent event) { + MapDetailValueObservableMap.this.dispose(); + } + }); + + MapDiff initMasterDiff = Diffs.computeMapDiff(Collections.EMPTY_MAP, + masterMap); + adaptMasterMapDiff(initMasterDiff); + } + + private void adaptMasterMapDiff(MapDiff diff) { + IdentityMap oldValues = new IdentityMap(); + IdentityMap newValues = new IdentityMap(); + + Set addedKeys = diff.getAddedKeys(); + for (Iterator iter = addedKeys.iterator(); iter.hasNext();) { + Object addedKey = iter.next(); + + hookListener(addedKey); + + IObservableValue detailValue = getDetailObservableValue(addedKey); + newValues.put(addedKey, detailValue.getValue()); + } + + Set removedKeys = diff.getRemovedKeys(); + for (Iterator iter = removedKeys.iterator(); iter.hasNext();) { + Object removedKey = iter.next(); + + unhookListener(removedKey); + + IObservableValue detailValue = getDetailObservableValue(removedKey); + oldValues.put(removedKey, detailValue.getValue()); + } + + Set changedKeys = diff.getChangedKeys(); + for (Iterator iter = changedKeys.iterator(); iter.hasNext();) { + Object changedKey = iter.next(); + + IObservableValue oldDetailValue = getDetailObservableValue(changedKey); + oldValues.put(changedKey, oldDetailValue.getValue()); + + unhookListener(changedKey); + hookListener(changedKey); + + IObservableValue newDetailValue = getDetailObservableValue(changedKey); + newValues.put(changedKey, newDetailValue.getValue()); + } + + fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys, changedKeys, + oldValues, newValues)); + } + + private void hookListener(final Object addedKey) { + IObservableValue detailValue = getDetailObservableValue(addedKey); + + detailValue.addValueChangeListener(new IValueChangeListener() { + public void handleValueChange(ValueChangeEvent event) { + if (!event.getObservableValue().isStale()) { + staleDetailObservables.remove(event.getObservableValue()); + } + + fireMapChange(Diffs.createMapDiffSingleChange(addedKey, + event.diff.getOldValue(), event.diff.getNewValue())); + } + }); + + detailValue.addStaleListener(detailStaleListener); + } + + private void unhookListener(Object removedKey) { + if (isDisposed()) { + return; + } + + IObservableValue detailValue = (IObservableValue) detailObservableValueMap + .remove(removedKey); + staleDetailObservables.remove(detailValue); + detailValue.dispose(); + } + + private IObservableValue getDetailObservableValue(Object masterKey) { + IObservableValue detailValue = (IObservableValue) detailObservableValueMap + .get(masterKey); + + if (detailValue == null) { + ObservableTracker.setIgnore(true); + try { + detailValue = (IObservableValue) observableValueFactory + .createObservable(masterMap.get(masterKey)); + } finally { + ObservableTracker.setIgnore(false); + } + + detailObservableValueMap.put(masterKey, detailValue); + + if (detailValue.isStale()) { + addStaleDetailObservable(detailValue); + } + } + + return detailValue; + } + + private void addStaleDetailObservable(IObservableValue detailObservable) { + boolean wasStale = isStale(); + staleDetailObservables.add(detailObservable); + if (!wasStale) { + fireStale(); + } + } + + public Object get(Object key) { + getterCalled(); + + IObservableValue detailValue = getDetailObservableValue(key); + return detailValue.getValue(); + } + + public Object put(Object key, Object value) { + IObservableValue detailValue = getDetailObservableValue(key); + Object oldValue = detailValue.getValue(); + detailValue.setValue(value); + return oldValue; + } + + public boolean containsKey(Object key) { + getterCalled(); + + return masterMap.containsKey(key); + } + + public Object remove(Object key) { + checkRealm(); + + IObservableValue detailValue = getDetailObservableValue(key); + Object oldValue = detailValue.getValue(); + + masterMap.remove(key); + + return oldValue; + } + + public boolean isStale() { + return super.isStale() + || (masterMap != null && masterMap.isStale()) + || (staleDetailObservables != null && !staleDetailObservables + .isEmpty()); + } + + public Object getKeyType() { + return masterMap.getKeyType(); + } + + public Object getValueType() { + return detailValueType; + } + + public Object getObserved() { + return masterMap; + } + + public synchronized void dispose() { + if (masterMap != null) { + masterMap.removeMapChangeListener(masterMapListener); + masterMap.removeStaleListener(masterStaleListener); + } + + if (detailObservableValueMap != null) { + for (Iterator iter = detailObservableValueMap.values().iterator(); iter + .hasNext();) { + IObservableValue detailValue = (IObservableValue) iter.next(); + detailValue.dispose(); + } + detailObservableValueMap.clear(); + } + + masterMap = null; + observableValueFactory = null; + detailValueType = null; + detailObservableValueMap = null; + masterStaleListener = null; + detailStaleListener = null; + staleDetailObservables = null; + + super.dispose(); + } + + public Set entrySet() { + getterCalled(); + + if (entrySet == null) { + entrySet = new EntrySet(); + } + return entrySet; + } + + private void getterCalled() { + ObservableTracker.getterCalled(this); + } + + private class EntrySet extends AbstractSet { + + public Iterator iterator() { + final Iterator keyIterator = keySet().iterator(); + return new Iterator() { + + public boolean hasNext() { + return keyIterator.hasNext(); + } + + public Object next() { + Object key = keyIterator.next(); + return new MapEntry(key); + } + + public void remove() { + keyIterator.remove(); + } + }; + } + + public int size() { + return MapDetailValueObservableMap.this.size(); + } + } + + private final class MapEntry implements Map.Entry { + + private final Object key; + + private MapEntry(Object key) { + this.key = key; + } + + public Object getKey() { + MapDetailValueObservableMap.this.getterCalled(); + return key; + } + + public Object getValue() { + return MapDetailValueObservableMap.this.get(getKey()); + } + + public Object setValue(Object value) { + return MapDetailValueObservableMap.this.put(getKey(), value); + } + + public boolean equals(Object o) { + MapDetailValueObservableMap.this.getterCalled(); + if (o == this) + return true; + if (o == null) + return false; + if (!(o instanceof Map.Entry)) + return false; + Map.Entry that = (Map.Entry) o; + return Util.equals(this.getKey(), that.getKey()) + && Util.equals(this.getValue(), that.getValue()); + } + + public int hashCode() { + MapDetailValueObservableMap.this.getterCalled(); + Object value = getValue(); + return (getKey() == null ? 0 : getKey().hashCode()) + ^ (value == null ? 0 : value.hashCode()); + } + } +}