| /******************************************************************************* |
| * Copyright (c) 2010, 2018 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.ocl.examples.impactanalyzer.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.notify.Adapter; |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.InternalEObject; |
| import org.eclipse.emf.ecore.impl.ENotificationImpl; |
| import org.eclipse.ocl.ecore.OCLExpression; |
| import org.eclipse.ocl.ecore.delegate.SettingBehavior; |
| import org.eclipse.ocl.ecore.opposites.DefaultOppositeEndFinder; |
| import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; |
| import org.eclipse.ocl.examples.eventmanager.EventManager; |
| import org.eclipse.ocl.examples.impactanalyzer.DerivedPropertyNotifier; |
| import org.eclipse.ocl.examples.impactanalyzer.ImpactAnalyzer; |
| import org.eclipse.ocl.examples.impactanalyzer.ImpactAnalyzerFactory; |
| import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluator; |
| import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluatorFactory; |
| import org.eclipse.ocl.examples.impactanalyzer.ValueNotFoundException; |
| import org.eclipse.ocl.examples.impactanalyzer.configuration.ActivationOption; |
| import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory; |
| |
| /** |
| * Used to observe and filter for model changes that affect derived properties |
| * defined by OCL expression. Construct an instance for a single |
| * {@link EStructuralFeature} or all properties of an {@link EPackage} and |
| * {@link #subscribe(EventManager) subscribe} for change notifications at an |
| * {@link EventManager}. When changes affecting a derived property's value are |
| * received, the actual changes are determined, and new {@link Notification}s |
| * are created for those objects on which the derived property's value did |
| * change. |
| * <p> |
| * |
| * To achieve this service, this class makes use of an {@link ImpactAnalyzer} |
| * for each derivation expression. This allows us to construct event filters |
| * catching the subset of change notifications which may affect the derivation |
| * expression's value. The {@link ImpactAnalyzer} is then used to further reduce |
| * the number of possible context objects on which to check for actual changes. |
| * The changes observed are then assembled into an according change |
| * {@link Notification} for the derived property itself and forwarded by calling |
| * {@link EObject#eNotify(Notification)} on the object where it changed. |
| * |
| * @author Martin Hanysz, Axel Uhl |
| * |
| */ |
| public class DerivedPropertyNotifierImpl implements DerivedPropertyNotifier { |
| private final OCLFactory oclFactory; |
| private final Set<DerivedPropertyAdapter> adapters = new HashSet<DerivedPropertyAdapter>(); |
| private final OppositeEndFinder oppositeEndFinder; |
| private final ActivationOption impactAnalyzerConfiguration; |
| |
| public DerivedPropertyNotifierImpl(ActivationOption impactAnalyzerConfiguration, |
| OppositeEndFinder oppositeEndFinder, OCLFactory oclFactory, |
| EPackage pkg) { |
| this(impactAnalyzerConfiguration, oppositeEndFinder, oclFactory, getAllDerivedProperties(pkg)); |
| } |
| |
| public DerivedPropertyNotifierImpl(ActivationOption impactAnalyzerConfiguration, |
| OppositeEndFinder oppositeEndFinder, OCLFactory oclFactory, |
| EStructuralFeature... derivedProperties) { |
| this.oclFactory = oclFactory; |
| this.oppositeEndFinder = oppositeEndFinder; |
| this.impactAnalyzerConfiguration = impactAnalyzerConfiguration; |
| for (EStructuralFeature derivedProperty : derivedProperties) { |
| if (!derivedProperty.isDerived()) { |
| throw new IllegalArgumentException( |
| "Given property must be derived"); |
| } |
| adapters.add(new DerivedPropertyAdapter(derivedProperty)); |
| } |
| } |
| |
| private static EStructuralFeature[] getAllDerivedProperties(EPackage pkg) { |
| List<EStructuralFeature> result = new ArrayList<EStructuralFeature>(); |
| for (EClassifier classifier : pkg.getEClassifiers()) { |
| if (classifier instanceof EClass) { |
| EClass cls = (EClass) classifier; |
| for (EStructuralFeature property : cls.getEStructuralFeatures()) { |
| if (property.isDerived()) { |
| result.add(property); |
| } |
| } |
| } |
| } |
| return result.toArray(new EStructuralFeature[result.size()]); |
| } |
| |
| public void subscribe(EventManager eventManager) { |
| for (DerivedPropertyAdapter adapter : adapters) { |
| adapter.subscribe(eventManager); |
| } |
| } |
| |
| public void unsubscribe(EventManager eventManager) { |
| for (DerivedPropertyAdapter adapter : adapters) { |
| eventManager.unsubscribe(adapter); |
| } |
| } |
| |
| /** |
| * From the derived {@link EStructuralFeature} passed to the constructor, |
| * extracts the OCL expression. When {@link #subscribe(EventManager) |
| * subscribing} for events affecting that expression's value, an |
| * {@link ImpactAnalyzer} is created for the expression, and the |
| * {@link ImpactAnalyzer#createFilterForExpression() event filter} is |
| * obtained and subscribed at the {@link EventManager} given as argument. |
| * <p> |
| * |
| * When the respective {@link Notification}s are received, the |
| * {@link ImpactAnalyer}'s |
| * {@link ImpactAnalyzer#getContextObjects(Notification)} method is used to |
| * determine the candidate objects for which the derived property may have |
| * changed value. Then, uses the {@link PartialEvaluator} to compute before |
| * and after image of the property. For those contexts where it really |
| * changed value, a new {@link Notification} is constructed, indicating the |
| * derived property's change. It is then |
| * {@link EObject#eNotify(Notification) notified} by context object that |
| * changed, for each context object where it changed. |
| * |
| * @author Martin Hanysz, Axel Uhl |
| */ |
| private class DerivedPropertyAdapter implements Adapter { |
| private Notifier target; |
| private ImpactAnalyzer ia; |
| private final EStructuralFeature property; |
| private final OCLExpression derivationExp; |
| |
| /** |
| * Creates an adapter that can be {@link #subscribe(EventManager) subscribed} for |
| * events signaling potential changes of the derived property's value. |
| * |
| * @param property a {@link EStructuralFeature#isDerived() derived} property |
| */ |
| public DerivedPropertyAdapter(EStructuralFeature property) { |
| this.property = property; |
| this.derivationExp = SettingBehavior.INSTANCE.getFeatureBody( |
| oclFactory.createOCL(oppositeEndFinder), this.property); |
| } |
| |
| /** |
| * subscribe at the event manager to get notified about changes that |
| * match the created filter (i.e. may have impact on the derivation |
| * expression) |
| */ |
| public void subscribe(EventManager eventManager) { |
| eventManager.subscribe(getImpactAnalyzer().createFilterForExpression(), this); |
| } |
| |
| private ImpactAnalyzer getImpactAnalyzer() { |
| if (ia == null) { |
| // notifications are not created for new context elements; the event |
| // manager implicitly produces change events for elements added to the |
| // resource set, so it's not necessary here |
| ia = ImpactAnalyzerFactory.INSTANCE.createImpactAnalyzer( |
| derivationExp, /* notifyOnNewContextElements */ false, |
| oppositeEndFinder, impactAnalyzerConfiguration, |
| oclFactory); |
| } |
| return ia; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void notifyChanged(Notification notification) { |
| if (notification.isTouch()) { |
| // in case of a "touch" that did not change anything, just do |
| // nothing |
| return; |
| } |
| Collection<EObject> impact = getImpactAnalyzer().getContextObjects(notification); |
| if (impact.size() > 0) { |
| // calculate the exact change |
| for (EObject context : impact) { |
| Object resultPre = null; |
| Object resultPost = null; |
| try { |
| // comment recommends using #getInstance(set), but this |
| // method is not present at the time of this |
| // implementation. |
| PartialEvaluator prePartEv = PartialEvaluatorFactory.INSTANCE |
| .createPartialEvaluator(notification, |
| DefaultOppositeEndFinder.getInstance(), |
| oclFactory); |
| PartialEvaluator postPartEv = PartialEvaluatorFactory.INSTANCE |
| .createPartialEvaluator( |
| DefaultOppositeEndFinder.getInstance(), |
| oclFactory); |
| resultPre = prePartEv.evaluate(context, derivationExp); |
| resultPost = postPartEv |
| .evaluate(context, derivationExp); |
| } catch (ValueNotFoundException e) { |
| // since the self context of the expression is known, |
| // there should be no unknown variables |
| throw new RuntimeException( |
| "During the partial evaluation of a derived property expression, an unknown variable was found."); |
| } |
| |
| // in case of an unset feature, resultPre will be null which |
| // will cause a null pointer exception when calling .equals |
| // therefore we need to check this case explicitly |
| if ((resultPre == null && resultPost != null) |
| || !resultPre.equals(resultPost)) { |
| // calculate the delta between pre change and post |
| // change |
| // to determine which notification to send. |
| Object oldValue = resultPre; |
| Object newValue = resultPost; |
| int eventType; |
| if (property.isMany()) { |
| // handle list features |
| List<EObject> preList; |
| List<EObject> postList; |
| if (resultPre instanceof List<?> |
| && resultPost instanceof List<?>) { |
| // this is as much typechecking as possible -> |
| // supressed warnings for "unchecked" |
| preList = (ArrayList<EObject>) resultPre; |
| postList = (ArrayList<EObject>) resultPost; |
| } else { |
| throw new ClassCastException( |
| "The values of a many valued feature are not type of EList as they should."); |
| } |
| if (preList.size() == postList.size()) { |
| eventType = Notification.MOVE; |
| } else if (preList.size() < postList.size()) { |
| if (preList.size() + 1 == postList.size()) { |
| eventType = Notification.ADD; |
| } else { |
| eventType = Notification.ADD_MANY; |
| } |
| } else { |
| if (preList.size() == postList.size() + 1) { |
| eventType = Notification.REMOVE; |
| } else { |
| eventType = Notification.REMOVE_MANY; |
| } |
| } |
| } else { |
| // handle single valued features |
| if (property.isUnsettable()) { |
| if (resultPost == null) { |
| eventType = Notification.UNSET; |
| } else { |
| eventType = Notification.SET; |
| } |
| } else { |
| eventType = Notification.SET; |
| } |
| } |
| Notification changeNoti = new ENotificationImpl( |
| (InternalEObject) context, eventType, property, |
| oldValue, newValue); |
| context.eNotify(changeNoti); |
| } |
| } |
| } |
| } |
| |
| public Notifier getTarget() { |
| return target; |
| } |
| |
| public void setTarget(Notifier newTarget) { |
| target = newTarget; |
| } |
| |
| public boolean isAdapterForType(Object type) { |
| return type == DerivedPropertyNotifierImpl.class; |
| } |
| } |
| } |