/******************************************************************************* * 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.internal.observables; import java.util.HashMap; import java.util.Map; import org.eclipse.core.databinding.observable.Diffs; import org.eclipse.core.databinding.observable.map.ComputedObservableMap; import org.eclipse.core.databinding.observable.map.MapDiff; import org.eclipse.core.databinding.observable.set.IObservableSet; import org.eclipse.core.runtime.Assert; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; /** * Virtual observable map that maps from a set of {@link EObject EObjects} to an attribute value * specified as a chain of structural features. * * @author Tonny Madsen, The RCP Company */ public class MultiLevelEObjectObservableMap extends ComputedObservableMap { /** * Array with the references leading up the last feature. Never empty. */ protected final EStructuralFeature[] myChainReferences; /** * The last feature. */ protected final EStructuralFeature myLastFeature; /** * Constructs and returns a new map for the specified object set and specified feature. * * @param objects the objects of the map * @param feature the feature that map to the map value */ public MultiLevelEObjectObservableMap(IObservableSet objects, EStructuralFeature feature) { this(objects, new EStructuralFeature[] { feature }); } /** * Constructs and returns a new map for the specified object set and the feature array. * * @param objects the objects of the map * @param features the features that map to the map value */ public MultiLevelEObjectObservableMap(IObservableSet objects, EStructuralFeature[] features) { super(objects); final int l = features.length; // Some sanity checks: final Object type = objects.getElementType(); EClass prevEClass = null; if (type instanceof EClass) { prevEClass = (EClass) type; } else if (type instanceof EReference) { prevEClass = ((EReference) type).getEReferenceType(); } else { // Unfortunately ObservableListContentProvider.getKnownElements() // returns null always :-( // // Assert.isLegal(false, "Set element type must be EClass or EReference, was " + type); } for (int i = 0; i < l - 1; i++) { Assert.isTrue(features[i] instanceof EReference, "Intermidiate features must before references"); final EReference ref = (EReference) features[i]; if (prevEClass != null) { Assert.isTrue(ref.getEContainingClass().isSuperTypeOf(prevEClass), "Reference " + ref + " not applicable for class " + prevEClass); } prevEClass = ref.getEReferenceType(); } myLastFeature = features[l - 1]; if (prevEClass != null) { Assert.isTrue(myLastFeature.getEContainingClass().isSuperTypeOf(prevEClass), "Feature " + myLastFeature + " not applicable for class " + prevEClass); } myChainReferences = new EStructuralFeature[l]; System.arraycopy(features, 0, myChainReferences, 0, l); init(); } /** * Private class that represents the a single key value in the map. * <p> * Keeps an array with all the intermediate {@link EObject EObjects} that matches the * myChainReferences array. * <p> * An adapter (<code>this</code>) is added to all of these objects. Changes are perceived to be * seldom, so the complete set of adpters are unhooked with all changes in the surveyed objects. */ protected class ChainAdapter extends AdapterImpl { protected EObject[] chain = new EObject[myChainReferences.length]; @Override public void notifyChanged(Notification msg) { if (msg.isTouch()) return; for (int i = 0; i < myChainReferences.length; i++) { if (chain[i] == msg.getNotifier() && myChainReferences[i] == msg.getFeature()) { /* * TODO: This assumes we only get a SET notification, which isn't a good * assumption. */ final MapDiff diff = Diffs .createMapDiffSingleChange(chain[0], msg.getOldValue(), msg.getNewValue()); getRealm().exec(new Runnable() { @Override public void run() { fireMapChange(diff); } }); unhookListener(chain[0]); hookListener(chain[0]); break; } } } public void hookListener(Object domainElement) { chain[0] = (EObject) domainElement; EObject next = chain[0]; if (next != null) { next.eAdapters().add(this); } for (int i = 0; i < myChainReferences.length - 1 && next != null; i++) { next = (EObject) next.eGet(myChainReferences[i]); chain[i + 1] = next; if (next != null) { next.eAdapters().add(this); } } } public void unhookListener(Object domainElement) { for (final EObject o : chain) { if (o != null) { o.eAdapters().remove(this); } } } public Object getValue() { return chain[chain.length - 1].eGet(myLastFeature); } public Object setValue(Object value) { final EObject eObject = chain[chain.length - 1]; final Object result = eObject.eGet(myLastFeature); eObject.eSet(myLastFeature, value); return result; } }; Map<Object, ChainAdapter> listeners = new HashMap<Object, ChainAdapter>(); @Override protected void hookListener(Object domainElement) { final ChainAdapter vi = new ChainAdapter(); vi.hookListener(domainElement); } @Override protected void unhookListener(Object domainElement) { final ChainAdapter vi = listeners.remove(domainElement); if (vi != null) { vi.unhookListener(domainElement); } } @Override protected Object doGet(Object key) { final ChainAdapter vi = listeners.get(key); if (vi == null) return null; return vi.getValue(); } @Override protected Object doPut(Object key, Object value) { final ChainAdapter vi = listeners.get(key); if (vi == null) return null; return vi.setValue(value); } }