/******************************************************************************* * Copyright (c) 2004-2011 Abel Hegedus and Daniel Varro * 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: * pvmellor - original code (http://wiki.eclipse.org/EMF/Recipes#Recipe:_Derived_Attribute_Notifier) * Abel Hegedus - initial API and implementation *******************************************************************************/ package org.eclipse.incquery.querybasedfeatures.runtime; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.ENotificationImpl; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.incquery.runtime.api.IncQueryEngine; import org.eclipse.incquery.runtime.base.comprehension.EMFModelComprehension; import org.eclipse.incquery.runtime.base.comprehension.EMFVisitor; /** * @author Abel Hegedus * * TODO: move to incquery code * * */ public class DerivedFeatureAdapter extends AdapterImpl { private final InternalEObject source; private final EStructuralFeature derivedFeature; private final DerivedFeatureEMFVisitor visitor = new DerivedFeatureEMFVisitor(); /** * Only used for single reference! */ private Object currentValue; private Object oldValue; private EClassifier type; private final List<EStructuralFeature> localFeatures = new ArrayList<EStructuralFeature>(); private final List<DependentFeaturePath> featurePaths = new ArrayList<DerivedFeatureAdapter.DependentFeaturePath>(); /* * Convenience constructor for a local and navigated dependency */ public DerivedFeatureAdapter(EObject source, EStructuralFeature derivedFeature, EStructuralFeature navigationFeature, EStructuralFeature dependantFeature, EStructuralFeature localFeature) { this(source, derivedFeature); addNavigatedDependencyInternal(navigationFeature, dependantFeature); addLocalDependencyInternal(localFeature); } /* * Convenience constructor for a navigated dependency */ public DerivedFeatureAdapter(EObject source, EStructuralFeature derivedFeature, EStructuralFeature navigationFeature, EStructuralFeature dependantFeature) { this(source, derivedFeature); addNavigatedDependencyInternal(navigationFeature, dependantFeature); } /* * Convenience constructor for a local dependency */ public DerivedFeatureAdapter(EObject source, EStructuralFeature derivedFeature, EStructuralFeature localFeature) { this(source, derivedFeature); addLocalDependencyInternal(localFeature); } public DerivedFeatureAdapter(EObject source, EStructuralFeature derivedFeature) { super(); this.source = (InternalEObject) source; this.derivedFeature = derivedFeature; source.eAdapters().add(this); } public void addNavigatedDependency(EStructuralFeature navigationFeature, EStructuralFeature dependantFeature) { if (navigationFeature == null || dependantFeature == null) { return; } if (!source.eClass().getEAllStructuralFeatures().contains(navigationFeature)) { return; } if (!(navigationFeature.getEType() instanceof EClass) || !dependantFeature.getEContainingClass().isSuperTypeOf((EClass) navigationFeature.getEType())) { return; } addNavigatedDependencyInternal(navigationFeature, dependantFeature); } public void addLocalDependency(EStructuralFeature localFeature) { if (localFeature == null) { return; } if (!source.eClass().getEAllStructuralFeatures().contains(localFeature)) { return; } addLocalDependencyInternal(localFeature); } private void addNavigatedDependencyInternal(EStructuralFeature navigationFeature, EStructuralFeature dependantFeature) { featurePaths.add(new DependentFeaturePath(navigationFeature, dependantFeature)); } private void addLocalDependencyInternal(EStructuralFeature localFeature) { localFeatures.add(localFeature); } @Override public void notifyChanged(Notification notification) { // System.err.println("[Source: " + derivedFeature.getName() + "] New notification: " + notification); for (DependentFeaturePath path : featurePaths) { if (notification.getFeature().equals(path.getNavigationFeature())) { // System.err.println("Handling notification."); switch (notification.getEventType()) { case Notification.SET: EObject newValue = (EObject) notification.getNewValue(); EObject tempOldValue = (EObject) notification.getOldValue(); if (tempOldValue != null) { tempOldValue.eAdapters().remove(path.getDependantAdapter()); } else { IncQueryEngine.getDefaultLogger().debug("[DerivedFeatureAdapter] oldValue is not set"); } if (newValue != null) { newValue.eAdapters().add(path.getDependantAdapter()); } else { IncQueryEngine.getDefaultLogger().debug("[DerivedFeatureAdapter] new value is not set"); } break; case Notification.ADD: EObject added = (EObject) notification.getNewValue(); added.eAdapters().add(path.getDependantAdapter()); break; case Notification.ADD_MANY: EObject newValueCollection = (EObject) notification.getNewValue(); for (Object newElement : (Collection<?>) newValueCollection) { ((Notifier) newElement).eAdapters().add(path.getDependantAdapter()); } break; case Notification.REMOVE: EObject removed = (EObject) notification.getOldValue(); removed.eAdapters().remove(path.getDependantAdapter()); break; case Notification.REMOVE_MANY: EObject oldValueCollection = (EObject) notification.getOldValue(); for (Object oldElement : (Collection<?>) oldValueCollection) { ((Notifier) oldElement).eAdapters().remove(path.getDependantAdapter()); } break; case Notification.UNSET: EObject unset = (EObject) notification.getOldValue(); unset.eAdapters().remove(path.getDependantAdapter()); break; case Notification.CREATE: case Notification.MOVE: // currently no support for ordering case Notification.RESOLVE: // TODO is it safe to ignore all of them? case Notification.REMOVING_ADAPTER: break; default: IncQueryEngine.getDefaultLogger().debug( "[DerivedFeatureAdapter] Unhandled notification: " + notification.getEventType()); return; // No notification } refreshDerivedFeature(); } } if (localFeatures.contains(notification.getFeature())) { // System.err.println("Handling notification."); refreshDerivedFeature(); } } @SuppressWarnings("unchecked") private void refreshDerivedFeature() { // System.err.println("[Notify: " + derivedFeature.getName() + "] Derived refresh."); try { if (source.eNotificationRequired()) { if (type == null) { type = derivedFeature.getEType(); } if (derivedFeature.isMany()) { if (currentValue != null) { oldValue = new HashSet<EObject>((Collection<EObject>) currentValue); } else { oldValue = new HashSet<EObject>(); } // if(currentValue == null) { currentValue = new HashSet<EObject>(); // } else { // ((Collection<EObject>) currentValue).clear(); // } Collection<? extends Object> targets = (Collection<? extends Object>) source.eGet(derivedFeature); for (Object target : targets) { EMFModelComprehension.traverseFeature(visitor, source, derivedFeature, target); } if (currentValue instanceof Collection<?> && oldValue instanceof Collection<?>) { ((Collection<?>) oldValue).removeAll((Collection<?>) currentValue); if (((Collection<?>) oldValue).size() > 0) { sendRemoveManyNotification(source, derivedFeature, oldValue); } } } else { Object target = source.eGet(derivedFeature); EMFModelComprehension.traverseFeature(visitor, source, derivedFeature, target); } } } catch (Exception ex) { IncQueryEngine.getDefaultLogger().error( "The derived feature adapter encountered an error in processing the EMF model. " + "This happened while maintaining the derived feature " + derivedFeature.getName() + " of object " + source, ex); } } private class DependentFeaturePath { private EStructuralFeature dependantFeature = null; private EStructuralFeature navigationFeature = null; private final AdapterImpl dependantAdapter = new AdapterImpl() { @Override public void notifyChanged(Notification msg) { // System.err.println("[Dependant: " + derivedFeature.getName() + "] New notification: " + msg); if (msg.getFeature().equals(dependantFeature)) { refreshDerivedFeature(); } } }; /** * */ public DependentFeaturePath(EStructuralFeature navigationFeature, EStructuralFeature dependantFeature) { this.dependantFeature = dependantFeature; this.navigationFeature = navigationFeature; } /** * @return the dependantAdapter */ public AdapterImpl getDependantAdapter() { return dependantAdapter; } /** * @return the dependantFeature */ public EStructuralFeature getDependantFeature() { return dependantFeature; } /** * @return the navigationFeature */ public EStructuralFeature getNavigationFeature() { return navigationFeature; } } /** * @param direction * @return */ protected class DerivedFeatureEMFVisitor extends EMFVisitor { @Override public void visitAttribute(EObject source, EAttribute feature, Object target) { // System.err.println("Attribute refresh."); // send set notification sendSetNotification(source, feature, currentValue, target); storeSingleValue(feature, target); } @Override public void visitElement(EObject source) { return; } // @Override // public void visitExternalReference(EObject source, EReference feature, EObject target) { // } @Override public void visitNonContainmentReference(EObject source, EReference feature, EObject target) { // System.err.println("Non-containment reference refresh."); sendNotificationForEReference(source, feature, target); } @Override public void visitInternalContainment(EObject source, EReference feature, EObject target) { // System.err.println("Containment reference refresh."); sendNotificationForEReference(source, feature, target); } @Override public boolean pruneSubtrees(EObject source) { return true; } } /** * @param source * @param feature * @param target */ private void sendSetNotification(EObject source, EStructuralFeature feature, Object oldTarget, Object target) { source.eNotify(new ENotificationImpl((InternalEObject) source, Notification.SET, feature, oldTarget, target)); } /** * @param source * @param feature * @param target */ private void sendAddNotification(EObject source, EStructuralFeature feature, Object target) { source.eNotify(new ENotificationImpl((InternalEObject) source, Notification.ADD, feature, null, target)); } /** * @param source * @param feature * @param target */ private void sendRemoveManyNotification(EObject source, EStructuralFeature feature, Object oldTarget) { source.eNotify(new ENotificationImpl((InternalEObject) source, Notification.REMOVE_MANY, feature, oldTarget, null)); } /** * @param source * @param feature * @param target */ /* * private void sendRemoveNotification(EObject source, EStructuralFeature feature, Object oldTarget) { * source.eNotify(new ENotificationImpl((InternalEObject) source, Notification.REMOVE, feature, oldTarget, null)); } */ @SuppressWarnings("unchecked") private void sendNotificationForEReference(EObject source, EReference feature, EObject target) { if (feature.isMany() && oldValue instanceof Collection<?> && currentValue instanceof Collection<?>) { // send ADD notification if (!((Collection<?>) oldValue).contains(target)) { sendAddNotification(source, feature, target); // add to currentValue } ((Collection<EObject>) currentValue).add(target); } else { sendSetNotification(source, feature, currentValue, target); } } /** * @param feature * @param target */ private void storeSingleValue(EAttribute feature, Object target) { // store current value if (feature.isChangeable()) { if (target instanceof EObject) { currentValue = EcoreUtil.copy((EObject) target); } } else { currentValue = target; } } }