/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company 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: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.observables; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.Diffs; import org.eclipse.core.databinding.observable.IChangeListener; import org.eclipse.core.databinding.observable.IObserving; import org.eclipse.core.databinding.observable.value.AbstractObservableValue; import org.eclipse.core.databinding.observable.value.IObservableValue; import org.eclipse.core.runtime.Assert; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EObjectContainmentEList; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.edit.command.AddCommand; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emf.edit.domain.EditingDomain; import com.rcpcompany.uibindings.model.utils.BasicUtils; /** * {@link IObservableValue Observable value} for a value from an element of an EMF {@link EList} * where the element have a specific key value. * <p> * The primary use of this is for objects with associated name-value lists. Using this observable * value, you can bind to an element in the list that has a specific key value. The element does not * have to exist before the bind, as it will be created automatically if set with * {@link #setValue(Object)}. * <p> * E.g. assume the data structures: * * <pre> * class NV { * String key; * String value; * } * * class B { * EList<NV> NVs; * } * </pre> * * then this class can be used to find the value attribute of the NV element of * <code>B.getNVs()</code> where the key equals "abc" with * * <code>new EListKeyedElementObservableValue(ed, b.getNVs(), "abc", ...Package.Literals.NV__VALUE);</code> * * @author Tonny Madsen, The RCP Company * @param <T> the type of the elements of the list */ public class EListKeyedElementObservableValue<T extends EObject> extends AbstractObservableValue implements IKeyedObservable, IObserving { private final EditingDomain myEditingDomain; private final IObservableValue mySourceOV; private EObject mySource = null; private final EReference myListRef; private EList<T> myList; private final EStructuralFeature myKeySF; private final Object myKey; private int myIndex; private T myElement; private final EStructuralFeature myValueSF; /** * The current value of the observable value. */ private Object myValue; private IObservableValue myListenSourceOV = null; private EObject myListenSource = null; private EList<T> myListenList = null; private Object myReportedValue = null; /** * General listener used on al {@link IObservableValue} and {@link EObject}. */ private final Listener myListener = new Listener(); private final EClass myElemenEClass; /** * Constructs and returns a new {@link IObservableValue} for the specified element in the list * of the object. * * @param editingDomain the editing domain to use * @param ov observable value with the {@link EObject} * @param listRef the structural feature with the list * @param keySF the structural feature for the key value * @param key the key value * @param valueSF the structural feature for the value */ public EListKeyedElementObservableValue(EditingDomain editingDomain, IObservableValue ov, EReference listRef, EStructuralFeature keySF, Object key, EStructuralFeature valueSF) { myEditingDomain = editingDomain; mySourceOV = ov; myListRef = listRef; myElemenEClass = myListRef.getEReferenceType(); myKeySF = keySF; myKey = key; myValueSF = valueSF; init(); } /** * Constructs and returns a new {@link IObservableValue} for the specified element in the list * of the object. * * @param editingDomain the editing domain to use * @param object the source object * @param listRef the structural feature with the list * @param elemenEClass the class of the wanted element type * @param keySF the structural feature for the key value * @param key the key value * @param valueSF the structural feature for the value */ public EListKeyedElementObservableValue(EditingDomain editingDomain, EObject object, EReference listRef, EClass elemenEClass, EStructuralFeature keySF, Object key, EStructuralFeature valueSF) { myEditingDomain = editingDomain; mySourceOV = null; mySource = object; myListRef = listRef; myElemenEClass = elemenEClass; myKeySF = keySF; myKey = key; myValueSF = valueSF; init(); } /** * Constructs and returns a new {@link IObservableValue} for the specified element in the list * of the object. * * @param editingDomain the editing domain to use * @param object the source object * @param listRef the structural feature with the list * @param keySF the structural feature for the key value * @param key the key value * @param valueSF the structural feature for the value */ public EListKeyedElementObservableValue(EditingDomain editingDomain, EObject object, EReference listRef, EStructuralFeature keySF, Object key, EStructuralFeature valueSF) { this(editingDomain, object, listRef, listRef.getEReferenceType(), keySF, key, valueSF); } /** * Constructs and returns a new {@link IObservableValue} for the specified element in the list * of the object. * * @param editingDomain the editing domain to use * @param list the list with elements - must be an <code>EObjectContainmentEList</code> * @param keySF the structural feature for the key value * @param key the key value * @param valueSF the structural feature for the value */ public EListKeyedElementObservableValue(EditingDomain editingDomain, EList<T> list, EStructuralFeature keySF, Object key, EStructuralFeature valueSF) { Assert.isTrue(list instanceof EObjectContainmentEList); final EObjectContainmentEList<T> clist = (EObjectContainmentEList<T>) list; myEditingDomain = editingDomain; mySourceOV = null; mySource = clist.getEObject(); myListRef = (EReference) clist.getEStructuralFeature(); myElemenEClass = myListRef.getEReferenceType(); myKeySF = keySF; myKey = key; myValueSF = valueSF; init(); } private void init() { /* * Consistency tests */ // listRef is to many Assert.isTrue(myListRef == null || myListRef.isMany()); // keySF is based on listRef.getReferenceType() // valueSF is based on listRef.getReferenceType() updateValue(); } @Override public EObject getObserved() { return mySourceOV != null ? (EObject) mySourceOV.getValue() : mySource; } @Override public Object getObservableKey() { return myIndex; } /** * Updates the current state. */ private void updateValue() { /* * If based on the an IOV for the base object, then first resolve this. */ if (mySourceOV != null) { Object o = null; if (!mySourceOV.isDisposed()) { o = mySourceOV.getValue(); } if (o instanceof EObject) { mySource = (EObject) o; } else { mySource = null; } } /* * Get the list of myObject */ if (mySource != null) { final Object l = mySource.eGet(myListRef); if (l instanceof EList) { myList = (EList<T>) l; } else { myList = null; } } myIndex = -1; myElement = null; if (myList != null) { for (int i = 0; i < myList.size(); i++) { final T e = myList.get(i); if (e == null) { continue; } if (!myElemenEClass.isSuperTypeOf(e.eClass())) { continue; } if (BasicUtils.equals(e.eGet(myKeySF), myKey)) { myIndex = i; myElement = e; break; } } } myValue = null; if (myElement != null) { myValue = myElement.eGet(myValueSF); } /* * Adds or removes listeners depending on the wanted listener state. */ if (myListenSourceOV != mySourceOV || !hasListeners()) { if (myListenSourceOV != null) { myListenSourceOV.removeChangeListener(myListener); myListenSourceOV = null; } if (mySourceOV != null && hasListeners()) { myListenSourceOV = mySourceOV; myListenSourceOV.addChangeListener(myListener); } } if (myListenSource != mySource || !hasListeners()) { if (myListenSource != null) { myListenSource.eAdapters().remove(myListener); myListenSource = null; } if (mySource != null && hasListeners()) { myListenSource = mySource; myListenSource.eAdapters().add(myListener); } } if (myListenList != myList || !hasListeners()) { if (myListenList != null) { for (final T e : myListenList) { e.eAdapters().remove(myListener); } myListenList = null; } if (myElement != null && hasListeners()) { myListenList = myList; for (final T e : myListenList) { e.eAdapters().add(myListener); } } } if (!BasicUtils.equals(myValue, myReportedValue)) { fireValueChange(Diffs.createValueDiff(myReportedValue, myReportedValue = myValue)); } } @Override public synchronized void dispose() { if (hasListeners()) { lastListenerRemoved(); } super.dispose(); } @Override protected void firstListenerAdded() { super.firstListenerAdded(); updateValue(); } @Override protected void lastListenerRemoved() { super.lastListenerRemoved(); updateValue(); } @Override protected final Object doGetValue() { updateValue(); return myValue; } @Override protected void doSetValue(final Object value) { if (BasicUtils.equals(value, myValue)) return; if (myList == null) throw new IndexOutOfBoundsException("No list in source"); final Command command; /* * If the element already exists, then change the value. Otherwise, create a new element * with the correct key and value. */ if (myElement != null) { Assert.isTrue(myIndex != -1); command = new SetCommand(myEditingDomain, myElement, myValueSF, value); } else { final EObject object = EcoreUtil.create(myElemenEClass); object.eSet(myKeySF, myKey); object.eSet(myValueSF, value); command = new AddCommand(myEditingDomain, myList, object); } myEditingDomain.getCommandStack().execute(command); updateValue(); } @Override public Object getValueType() { return myValueSF; } private class Listener extends AdapterImpl implements IChangeListener { @Override public void notifyChanged(Notification msg) { if (msg.isTouch()) return; if (msg.getFeature() == myListRef) { /* * Only list changes from the source object */ if (msg.getNotifier() != mySource) return; switch (msg.getEventType()) { case Notification.REMOVE: case Notification.SET: final EObject oldObj = (EObject) msg.getOldValue(); oldObj.eAdapters().remove(myListener); break; default: break; } switch (msg.getEventType()) { case Notification.ADD: case Notification.SET: final EObject newObj = (EObject) msg.getNewValue(); newObj.eAdapters().add(myListener); break; default: break; } } else if (msg.getFeature() == myValueSF) { /* * Only value changes from the element object */ if (msg.getNotifier() != myElement) return; } else if (msg.getFeature() == myKeySF) { /* * Key changes from all elements in the list */ } else /* * No other changes are interesting */ return; updateValue(); }; @Override public void handleChange(ChangeEvent event) { updateValue(); } } }