### Eclipse Workspace Patch 1.0 #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 10 Mar 2010 17:31:20 -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 10 Mar 2010 17:31:21 -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,178 @@ +/******************************************************************************* + * 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) { + final IObservableValue detailValue = getDetailObservableValue(addedKey); + + detailValue.addValueChangeListener(new IValueChangeListener() { + public void handleValueChange(ValueChangeEvent event) { + if (!event.getObservableValue().isStale()) { + staleDetailObservables.remove(detailValue); + } + + 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 containsKey(Object key) { + getterCalled(); + + return keySet().contains(key); + } + + public Object remove(Object key) { + checkRealm(); + + if (!containsKey(key)) { + return null; + } + + IObservableValue detailValue = getDetailObservableValue(key); + Object oldValue = detailValue.getValue(); + + keySet().remove(key); + + return oldValue; + } + + public int size() { + getterCalled(); + + return keySet().size(); + } + + 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; + } + + private void getterCalled() { + ObservableTracker.getterCalled(this); + } +} 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,385 @@ +/******************************************************************************* + * 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) { + final IObservableValue detailValue = getDetailObservableValue(addedKey); + + detailValue.addValueChangeListener(new IValueChangeListener() { + public void handleValueChange(ValueChangeEvent event) { + if (!event.getObservableValue().isStale()) { + staleDetailObservables.remove(detailValue); + } + + 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 Set keySet() { + getterCalled(); + + return masterMap.keySet(); + } + + public Object get(Object key) { + getterCalled(); + + if (!containsKey(key)) { + return null; + } + + IObservableValue detailValue = getDetailObservableValue(key); + return detailValue.getValue(); + } + + public Object put(Object key, Object value) { + if (!containsKey(key)) { + // TODO omallo: Better throw an exception? + return null; + } + + 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(); + + if (!containsKey(key)) { + return null; + } + + IObservableValue detailValue = getDetailObservableValue(key); + Object oldValue = detailValue.getValue(); + + masterMap.remove(key); + + return oldValue; + } + + public int size() { + getterCalled(); + + return masterMap.size(); + } + + 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()); + } + } +} #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.121 diff -u -r1.121 BindingTestSuite.java --- src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 9 Mar 2010 08:54:51 -0000 1.121 +++ src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 10 Mar 2010 17:31:22 -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,9 @@ 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.observable.masterdetail.MapDetailValueObservableMapTest; +import org.eclipse.core.tests.internal.databinding.observable.masterdetail.SetDetailValueObservableMapTest; 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; @@ -380,6 +383,9 @@ addTestSuite(DetailObservableMapTest.class); addTest(DetailObservableSetTest.suite()); addTest(DetailObservableValueTest.suite()); + addTest(ListDetailValueObservableListTest.suite()); + addTest(MapDetailValueObservableMapTest.suite()); + addTest(SetDetailValueObservableMapTest.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,351 @@ +/******************************************************************************* + * 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 testGetElementType() { + 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 masterList = new WritableList(); + SimplePerson person = new SimplePerson(); + person.setName("name"); + masterList.add(person); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, BeansObservables.valueFactory("name"), String.class); + + // Make sure that a non-empty master list is initialized correctly. + assertEquals(masterList.size(), ldol.size()); + assertEquals(person.getName(), ldol.get(0)); + } + + public void testAddRemove() { + WritableList masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, BeansObservables.valueFactory("name"), String.class); + + // Initially, the detail list is empty. + assertTrue(ldol.isEmpty()); + + // Add a first person and check that its name is in the detail list. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterList.add(p1); + assertEquals(masterList.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"); + masterList.add(p2); + assertEquals(masterList.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. + masterList.remove(0); + assertEquals(masterList.size(), ldol.size()); + assertEquals(p2.getName(), ldol.get(0)); + + // Remove the second person as well. + masterList.remove(0); + assertTrue(ldol.isEmpty()); + } + + public void testChangeDetail() { + WritableList masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, BeansObservables.valueFactory("name"), String.class); + + // Change the detail attribute explicitly. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterList.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"); + masterList.set(0, p2); + assertEquals(p2.getName(), ldol.get(0)); + } + + public void testSet() { + WritableList masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, BeansObservables.valueFactory("name"), String.class); + + // Change the detail attribute explicitly. + SimplePerson person = new SimplePerson(); + person.setName("name1"); + masterList.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 masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, 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. + masterList.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); + 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); + assertEquals(1, ldol.size()); + 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 masterList = new WritableList(); + ListDetailValueObservableList ldol = new ListDetailValueObservableList( + masterList, detailValueFactory, null); + + masterList.add(new Object()); + masterList.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. + masterList.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 masterList = new WritableList(realm); + for (int i = 0; i < elementCount; i++) { + masterList.add(new SimplePerson()); + } + + return new TestListDetailValueObservableList(masterList, + BeansObservables.valueFactory(realm, "name"), String.class); + } + + public void change(IObservable observable) { + TestListDetailValueObservableList ldol = (TestListDetailValueObservableList) observable; + ldol.masterList.add(new SimplePerson()); + } + + public Object getElementType(IObservableCollection collection) { + return String.class; + } + } + + private static class TestListDetailValueObservableList extends + ListDetailValueObservableList { + final IObservableList masterList; + + public TestListDetailValueObservableList(IObservableList masterList, + IObservableFactory detailValueFactory, Object detailType) { + super(masterList, detailValueFactory, detailType); + this.masterList = masterList; + } + } +} Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java =================================================================== RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,309 @@ +/******************************************************************************* + * 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.HashMap; +import java.util.Map; + +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.map.WritableMap; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.WritableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.MapDetailValueObservableMap; +import org.eclipse.jface.databinding.conformance.util.MapChangeEventTracker; +import org.eclipse.jface.examples.databinding.model.SimplePerson; +import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; + +/** + * @since 1.3 + */ +public class MapDetailValueObservableMapTest extends + AbstractDefaultRealmTestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(MapDetailValueObservableMapTest.class + .getName()); + suite.addTestSuite(MapDetailValueObservableMapTest.class); + return suite; + } + + public void testGetKeyType() { + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + new WritableMap(SimplePerson.class, SimplePerson.class), + BeansObservables.valueFactory("name"), String.class); + + assertSame(SimplePerson.class, mdom.getKeyType()); + } + + public void testGetValueType() { + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + new WritableMap(), BeansObservables.valueFactory("name"), + String.class); + + assertSame(String.class, mdom.getValueType()); + } + + public void testGetObserved() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // The observed object is the master key set. + assertSame(masterMap, mdom.getObserved()); + } + + public void testMasterSetInitiallyNotEmpty() { + WritableMap masterMap = new WritableMap(); + SimplePerson person = new SimplePerson(); + person.setName("name"); + masterMap.put(person, person); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Make sure that a non-empty master key set is initialized correctly. + assertEquals(masterMap.size(), mdom.size()); + assertEquals(person.getName(), mdom.get(person)); + } + + public void testAddRemove() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Initially, the detail map is empty. + assertTrue(mdom.isEmpty()); + + // Add a first person and check that its name is in the detail list. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterMap.put(p1, p1); + assertEquals(masterMap.size(), mdom.size()); + assertEquals(p1.getName(), mdom.get(p1)); + + // Add a second person and check that it's name is in the detail list. + SimplePerson p2 = new SimplePerson(); + p2.setName("name2"); + masterMap.put(p2, p2); + assertEquals(masterMap.size(), mdom.size()); + assertEquals(p2.getName(), mdom.get(p2)); + + // Remove the first person from the master list and check that we still + // have the name of the second person in the detail list. + masterMap.remove(p1); + assertEquals(masterMap.size(), mdom.size()); + assertEquals(p2.getName(), mdom.get(p2)); + + // Remove the second person as well. + masterMap.remove(p2); + assertTrue(mdom.isEmpty()); + } + + public void testChangeDetail() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Change the detail attribute explicitly. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterMap.put(p1, p1); + assertEquals(p1.getName(), mdom.get(p1)); + p1.setName("name2"); + assertEquals(p1.getName(), mdom.get(p1)); + + // Change the detail attribute by changing the master. + SimplePerson p2 = new SimplePerson(); + p2.setName("name3"); + masterMap.put(p1, p2); + assertEquals(p2.getName(), mdom.get(p1)); + } + + public void testPut() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Change the detail attribute explicitly. + SimplePerson person = new SimplePerson(); + person.setName("name1"); + masterMap.put(person, person); + assertEquals(person.getName(), mdom.get(person)); + + // Set a new name on the detail map. + mdom.put(person, "name2"); + // Check that the name has been propagated to the master. + assertEquals("name2", person.getName()); + assertEquals(person.getName(), mdom.get(person)); + } + + public void testContainsValue() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Add a person with a given name. + SimplePerson person = new SimplePerson(); + person.setName("name"); + masterMap.put(person, person); + + // Make sure the name of the person is contained. + assertTrue(mdom.containsValue(person.getName())); + + // Remove the person and make sure that it's name cannot be found + // anymore. + masterMap.remove(person); + assertFalse(mdom.containsValue(person.getName())); + } + + public void testRemove() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Add two person objects to the map. + SimplePerson p1 = new SimplePerson(); + SimplePerson p2 = new SimplePerson(); + masterMap.put(p1, p1); + masterMap.put(p2, p2); + + // Initially, both person objects should be contained in the detail map. + assertTrue(mdom.containsKey(p1)); + assertTrue(mdom.containsKey(p2)); + + // Remove one person and check that it is not contained anymore. + mdom.remove(p1); + assertFalse(mdom.containsKey(p1)); + assertTrue(mdom.containsKey(p2)); + + // Trying to remove a non-existent is allowed but has no effect. + mdom.remove(p1); + assertFalse(mdom.containsKey(p1)); + assertTrue(mdom.containsKey(p2)); + } + + public void testDetailObservableChangeEvent() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + MapChangeEventTracker changeTracker = MapChangeEventTracker + .observe(mdom); + + 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. + masterMap.put(person, person); + assertEquals(1, changeTracker.count); + assertEquals(1, changeTracker.event.diff.getAddedKeys().size()); + assertEquals(0, changeTracker.event.diff.getRemovedKeys().size()); + assertEquals(0, changeTracker.event.diff.getChangedKeys().size()); + assertSame(person, changeTracker.event.diff.getAddedKeys().iterator() + .next()); + assertNull(changeTracker.event.diff.getOldValue(person)); + assertEquals("old name", changeTracker.event.diff.getNewValue(person)); + + // Change the detail property and check that we receive a replace + person.setName("new name"); + assertEquals(2, changeTracker.count); + assertEquals(0, changeTracker.event.diff.getAddedKeys().size()); + assertEquals(0, changeTracker.event.diff.getRemovedKeys().size()); + assertEquals(1, changeTracker.event.diff.getChangedKeys().size()); + assertSame(person, changeTracker.event.diff.getChangedKeys().iterator() + .next()); + assertEquals("old name", changeTracker.event.diff.getOldValue(person)); + assertEquals("new name", changeTracker.event.diff.getNewValue(person)); + } + + public void testMasterNull() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Make sure null values are handled gracefully. + masterMap.put(null, null); + assertEquals(1, mdom.size()); + assertNull(mdom.get(null)); + } + + public void testDetailObservableValuesAreDisposed() { + final Map detailObservables = new HashMap(); + IObservableFactory detailValueFactory = new IObservableFactory() { + public IObservable createObservable(Object target) { + WritableValue detailObservable = new WritableValue(); + // Remember the created observables. + detailObservables.put(target, detailObservable); + return detailObservable; + } + }; + + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, detailValueFactory, null); + + Object master1 = new Object(); + Object master2 = new Object(); + masterMap.put(master1, master1); + masterMap.put(master2, master2); + + // Attach a listener in order to ensure that all detail observables are + // actually created. + MapChangeEventTracker.observe(mdom); + + assertEquals(mdom.size(), detailObservables.size()); + + // No detail observables should be disposed yet. + assertFalse(((WritableValue) detailObservables.get(master1)) + .isDisposed()); + assertFalse(((WritableValue) detailObservables.get(master2)) + .isDisposed()); + + // Only the detail observable for the removed master should be disposed. + masterMap.remove(master2); + assertFalse(((WritableValue) detailObservables.get(master1)) + .isDisposed()); + assertTrue(((WritableValue) detailObservables.get(master2)) + .isDisposed()); + + // After disposing the detail map, all detail observables should be + // disposed. + mdom.dispose(); + assertTrue(((WritableValue) detailObservables.get(master1)) + .isDisposed()); + assertTrue(((WritableValue) detailObservables.get(master2)) + .isDisposed()); + } + + public void testDisposeOnMasterDisposed() { + WritableMap masterMap = new WritableMap(); + MapDetailValueObservableMap mdom = new MapDetailValueObservableMap( + masterMap, BeansObservables.valueFactory("name"), String.class); + + // Initially, nothing should be disposed. + assertFalse(masterMap.isDisposed()); + assertFalse(mdom.isDisposed()); + + // Upon disposing the master list, the detail list should be disposed as + // well. + masterMap.dispose(); + assertTrue(masterMap.isDisposed()); + assertTrue(mdom.isDisposed()); + } +} Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java =================================================================== RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,311 @@ +/******************************************************************************* + * 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.HashMap; +import java.util.Map; + +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.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.set.WritableSet; +import org.eclipse.core.databinding.observable.value.WritableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.SetDetailValueObservableMap; +import org.eclipse.jface.databinding.conformance.util.MapChangeEventTracker; +import org.eclipse.jface.examples.databinding.model.SimplePerson; +import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase; + +/** + * @since 1.3 + */ +public class SetDetailValueObservableMapTest extends + AbstractDefaultRealmTestCase { + + public static Test suite() { + TestSuite suite = new TestSuite(SetDetailValueObservableMapTest.class + .getName()); + suite.addTestSuite(SetDetailValueObservableMapTest.class); + return suite; + } + + public void testGetValueType() { + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + new WritableSet(), BeansObservables.valueFactory("name"), + String.class); + + assertSame(String.class, sdom.getValueType()); + } + + public void testGetObserved() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // The observed object is the master key set. + assertSame(masterKeySet, sdom.getObserved()); + } + + public void testMasterSetInitiallyNotEmpty() { + WritableSet masterKeySet = new WritableSet(); + SimplePerson person = new SimplePerson(); + person.setName("name"); + masterKeySet.add(person); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Make sure that a non-empty master key set is initialized correctly. + assertEquals(masterKeySet.size(), sdom.size()); + assertEquals(person.getName(), sdom.get(person)); + } + + public void testAddRemove() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Initially, the detail map is empty. + assertTrue(sdom.isEmpty()); + + // Add a first person and check that its name is in the detail list. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterKeySet.add(p1); + assertEquals(masterKeySet.size(), sdom.size()); + assertEquals(p1.getName(), sdom.get(p1)); + + // Add a second person and check that it's name is in the detail list. + SimplePerson p2 = new SimplePerson(); + p2.setName("name2"); + masterKeySet.add(p2); + assertEquals(masterKeySet.size(), sdom.size()); + assertEquals(p2.getName(), sdom.get(p2)); + + // Remove the first person from the master list and check that we still + // have the name of the second person in the detail list. + masterKeySet.remove(p1); + assertEquals(masterKeySet.size(), sdom.size()); + assertEquals(p2.getName(), sdom.get(p2)); + + // Remove the second person as well. + masterKeySet.remove(p2); + assertTrue(sdom.isEmpty()); + } + + public void testChangeDetail() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Change the detail attribute explicitly. + SimplePerson p1 = new SimplePerson(); + p1.setName("name1"); + masterKeySet.add(p1); + assertEquals(p1.getName(), sdom.get(p1)); + p1.setName("name2"); + assertEquals(p1.getName(), sdom.get(p1)); + + // Change the detail attribute by changing the master. + SimplePerson p2 = new SimplePerson(); + p2.setName("name3"); + masterKeySet.add(p2); + assertEquals(p2.getName(), sdom.get(p2)); + } + + public void testPut() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Change the detail attribute explicitly. + SimplePerson person = new SimplePerson(); + person.setName("name1"); + masterKeySet.add(person); + assertEquals(person.getName(), sdom.get(person)); + + // Set a new name on the detail map. + sdom.put(person, "name2"); + // Check that the name has been propagated to the master. + assertEquals("name2", person.getName()); + assertEquals(person.getName(), sdom.get(person)); + } + + public void testContainsValue() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Add a person with a given name. + SimplePerson person = new SimplePerson(); + person.setName("name"); + masterKeySet.add(person); + + // Make sure the name of the person is contained. + assertTrue(sdom.containsValue(person.getName())); + + // Remove the person and make sure that it's name cannot be found + // anymore. + masterKeySet.remove(person); + assertFalse(sdom.containsValue(person.getName())); + } + + public void testRemove() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Add two person objects to the map. + SimplePerson p1 = new SimplePerson(); + SimplePerson p2 = new SimplePerson(); + masterKeySet.add(p1); + masterKeySet.add(p2); + + // Initially, both person objects should be contained in the detail map. + assertTrue(sdom.containsKey(p1)); + assertTrue(sdom.containsKey(p2)); + + // Remove one person and check that it is not contained anymore. + sdom.remove(p1); + assertFalse(sdom.containsKey(p1)); + assertTrue(sdom.containsKey(p2)); + + // Trying to remove a non-existent is allowed but has no effect. + sdom.remove(p1); + assertFalse(sdom.containsKey(p1)); + assertTrue(sdom.containsKey(p2)); + } + + public void testDetailObservableChangeEvent() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + MapChangeEventTracker changeTracker = MapChangeEventTracker + .observe(sdom); + + 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. + masterKeySet.add(person); + assertEquals(1, changeTracker.count); + assertEquals(1, changeTracker.event.diff.getAddedKeys().size()); + assertEquals(0, changeTracker.event.diff.getRemovedKeys().size()); + assertEquals(0, changeTracker.event.diff.getChangedKeys().size()); + assertSame(person, changeTracker.event.diff.getAddedKeys().iterator() + .next()); + assertNull(changeTracker.event.diff.getOldValue(person)); + assertEquals("old name", changeTracker.event.diff.getNewValue(person)); + + // Change the detail property and check that we receive a replace + person.setName("new name"); + assertEquals(2, changeTracker.count); + assertEquals(0, changeTracker.event.diff.getAddedKeys().size()); + assertEquals(0, changeTracker.event.diff.getRemovedKeys().size()); + assertEquals(1, changeTracker.event.diff.getChangedKeys().size()); + assertSame(person, changeTracker.event.diff.getChangedKeys().iterator() + .next()); + assertEquals("old name", changeTracker.event.diff.getOldValue(person)); + assertEquals("new name", changeTracker.event.diff.getNewValue(person)); + } + + public void testMasterNull() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Make sure null values are handled gracefully. + masterKeySet.add(null); + assertEquals(1, sdom.size()); + assertNull(sdom.get(null)); + } + + public void testDetailObservableValuesAreDisposed() { + final Map detailObservables = new HashMap(); + IObservableFactory detailValueFactory = new IObservableFactory() { + public IObservable createObservable(Object target) { + WritableValue detailObservable = new WritableValue(); + // Remember the created observables. + detailObservables.put(target, detailObservable); + return detailObservable; + } + }; + + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, detailValueFactory, null); + + Object master1 = new Object(); + Object master2 = new Object(); + masterKeySet.add(master1); + masterKeySet.add(master2); + + // Attach a listener in order to ensure that all detail observables are + // actually created. + MapChangeEventTracker.observe(sdom); + + assertEquals(sdom.size(), detailObservables.size()); + + // No detail observables should be disposed yet. + assertFalse(((WritableValue) detailObservables.get(master1)) + .isDisposed()); + assertFalse(((WritableValue) detailObservables.get(master2)) + .isDisposed()); + + // Only the detail observable for the removed master should be disposed. + masterKeySet.remove(master2); + assertFalse(((WritableValue) detailObservables.get(master1)) + .isDisposed()); + assertTrue(((WritableValue) detailObservables.get(master2)) + .isDisposed()); + + // After disposing the detail map, all detail observables should be + // disposed. + sdom.dispose(); + assertTrue(((WritableValue) detailObservables.get(master1)) + .isDisposed()); + assertTrue(((WritableValue) detailObservables.get(master2)) + .isDisposed()); + } + + public void testDisposeOnMasterDisposed() { + WritableSet masterKeySet = new WritableSet(); + SetDetailValueObservableMap sdom = new SetDetailValueObservableMap( + masterKeySet, BeansObservables.valueFactory("name"), + String.class); + + // Initially, nothing should be disposed. + assertFalse(masterKeySet.isDisposed()); + assertFalse(sdom.isDisposed()); + + // Upon disposing the master list, the detail list should be disposed as + // well. + masterKeySet.dispose(); + assertTrue(masterKeySet.isDisposed()); + assertTrue(sdom.isDisposed()); + } +} #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.4 diff -u -r1.4 ValueProperty.java --- src/org/eclipse/core/databinding/property/value/ValueProperty.java 10 Mar 2010 07:20:10 -0000 1.4 +++ src/org/eclipse/core/databinding/property/value/ValueProperty.java 10 Mar 2010 17:31:23 -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; @@ -112,6 +115,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.4 diff -u -r1.4 IListProperty.java --- src/org/eclipse/core/databinding/property/list/IListProperty.java 10 Mar 2010 07:20:10 -0000 1.4 +++ src/org/eclipse/core/databinding/property/list/IListProperty.java 10 Mar 2010 17:31:23 -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; @@ -161,4 +161,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.4 diff -u -r1.4 ListProperty.java --- src/org/eclipse/core/databinding/property/list/ListProperty.java 10 Mar 2010 07:20:10 -0000 1.4 +++ src/org/eclipse/core/databinding/property/list/ListProperty.java 10 Mar 2010 17:31:23 -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; @@ -26,6 +26,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. @@ -139,4 +140,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: 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(); + } + }; + } +}