blob: 689dee3125a03558937fbc29a007b67c32abf3d9 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}