blob: f3eee37b174ab7121d9493de7b4ccc02470441bd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 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.editor;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.ui.MarkerHelper;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.ui.EMFEditUIPlugin;
import org.eclipse.emf.edit.ui.action.ValidateAction;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.impactanalyzer.ImpactAnalyzer;
import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory;
import org.eclipse.ui.PlatformUI;
/**
* Validates a specific constraint on one or more of the context elements that are instance of the constaint's context class or a
* subclass thereof.
* <p>
*
* There are {@link Diagnostic} objects maintained as {@link Resource#getErrors() errors} and {@link Resource#getWarnings()
* warnings} of an EMF resource, and {@link IMarker} objects managed by {@link IResource}s and stored persistently in the
* workspace, representing validation errors. {@link MarkerHelper} and its subclasses can turn {@link Diagnostic} objects into
* {@link IMarker}s which form the basis for the problem view display. A {@link MarkerHelper} is used to create {@link IMarker
* markers} from {@link Diagnostic} objects. Specializations of {@link MarkerHelper} can annotate the markers with additional
* information stored, e.g., in {@link Diagnostic#getData()} and storing them in {@link IMarker#getAttribute(String) attributes}
* of the marker. By default, this process is triggered in the <code>updateProblemIndication</code> method of a generated EMF
* sample editor, where first all markers for the entire {@link ResourceSet} (this seems too broad a scope) are deleted and then
* created again based on the {@link Diagnostic}s returned from {@link Resource#getErrors()} and {@link Resource#getWarnings()}
* for the resources contained by the editing domain's resource set.
* <p>
*
* EMF resources, upon loading, don't perform any constraint validation. Therefore, their errors and warnings list turns out empty
* by default. Filling those has to happen by explicitly performing a {@link ValidateAction} on selected resources or individual
* elements. Only then will a {@link Diagnostician} be used to fetch the {@link EValidator} from the validator registry with which
* a validation is performed on a single {@link EObject}. Such a validation run will produce {@link Diagnostic} objects which the
* {@link ValidateAction} then converts into {@link IMarker}s again. Note that these {@link Diagnostic} objects are <em>not</em>
* entered into the errors/warnings of the {@link Resource} on which the validation happened.
* <p>
*
* When a change {@link Notification} triggers this revalidation action, the constraint is re-evaluated on the context objects
* determined by the {@link ImpactAnalyzer OCL Impact Analysis}. For any constraint which now evaluates to <code>true</code>, any
* existing {@link Diagnostic} needs to be removed. FOr any constraint evaluating to <code>false</code> a {@link Diagnostic}
* object needs to be created, and an {@link IMarker} needs to be created and displayed in the problem view.
* <p>
*
* During re-validation, the resources of other objects on which to evaluate the constraint may be loaded into the surrounding
* editing domain's resource set. Markers are managed by a <code>MarkerManager</code> which persistently stores markers in the
* workspace, keyed by the resources to which they belong.
*
* @author Axel Uhl (D043530)
*
*/
public class RevalidateAction extends ValidateAction {
private static final String MARKER_TYPE = "org.eclipse.core.resources.problemmarker";
private static final String ELEMENT_URI = "elementuri";
private static final String CONSTRAINT_NAME = "constraintname";
private final Collection<EObject> contextObjects;
private final String constraintName;
private final OCLExpression invariant;
private final OppositeEndFinder oppositeEndFinder;
private final OCLFactory oclFactory;
public RevalidateAction(String constraintName, Collection<EObject> contextObjects, OCLExpression invariant, OCLFactory oclFactory, OppositeEndFinder oppositeEndFinder) {
super();
this.contextObjects = contextObjects;
this.constraintName = constraintName;
this.invariant = invariant;
this.oclFactory = oclFactory;
this.oppositeEndFinder = oppositeEndFinder;
}
@Override
public void run() {
IRunnableWithProgress runnableWithProgress = new IRunnableWithProgress() {
public void run(final IProgressMonitor progressMonitor) throws InvocationTargetException, InterruptedException {
try {
validateConstraints(progressMonitor);
} catch (CoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
if (eclipseResourcesUtil != null) {
runnableWithProgress = eclipseResourcesUtil.getWorkspaceModifyOperation(runnableWithProgress);
}
try {
// This runs the operation, and shows progress.
// (It appears to be a bad thing to fork this onto another thread.)
//
PlatformUI.getWorkbench().getProgressService().run(/* fork */ true, /* cancelable */ true, runnableWithProgress);
} catch (Exception exception) {
EMFEditUIPlugin.INSTANCE.log(exception);
}
}
/**
* This simply execute the command.
*/
private void validateConstraints(IProgressMonitor progressMonitor) throws CoreException {
int selectionSize = contextObjects.size();
progressMonitor.beginTask("", selectionSize);
OCL ocl = oclFactory.createOCL(oppositeEndFinder);
for (EObject eObject : contextObjects) {
System.out.println("Re-validating "+constraintName+" on "+EcoreUtil.getURI(eObject));
IFile fileForContext = getFile(eObject);
URI eObjectURI = EcoreUtil.getURI(eObject);
removeMarker(eObjectURI, fileForContext);
boolean valid = (Boolean) ocl.evaluate(eObject, invariant);
if (!valid) {
if (fileForContext != null) {
IMarker marker = fileForContext.createMarker(MARKER_TYPE);
// TODO here's the place to talk about severities of constraint violations
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.MESSAGE, "Constraint "+constraintName+" violated on "+eObject);
marker.setAttribute(CONSTRAINT_NAME, constraintName);
marker.setAttribute(ELEMENT_URI, eObjectURI.toString());
}
}
}
}
private void removeMarker(URI eObjectURI, IFile fileForContext) throws CoreException {
IMarker[] markers = fileForContext.findMarkers(MARKER_TYPE, /* includeSubtypes */ false, IResource.DEPTH_ZERO);
if (markers != null) {
for (IMarker marker : markers) {
if (marker.getAttribute(CONSTRAINT_NAME, "").equals(constraintName) &&
marker.getAttribute(ELEMENT_URI, "").equals(eObjectURI.toString())) {
marker.delete();
}
}
}
}
private IFile getFile(EObject eObject) {
URI uri = EcoreUtil.getURI(eObject);
String platformResourceString = uri.toPlatformString(true);
return platformResourceString != null ? ResourcesPlugin.getWorkspace().getRoot()
.getFile(new Path(platformResourceString)) : null;
}
protected String composeMessage(Diagnostic diagnostic, Diagnostic parentDiagnostic) {
String message = diagnostic.getMessage();
if (parentDiagnostic != null) {
String parentMessage = parentDiagnostic.getMessage();
if (parentMessage != null) {
message = message != null ? parentMessage + ". " + message : parentMessage;
}
}
return message;
}
protected void createMarkers(IResource resource, Diagnostic diagnostic, Diagnostic parentDiagnostic) throws CoreException {
if (resource != null && resource.exists()) {
IMarker marker = resource.createMarker(MARKER_TYPE);
int severity = diagnostic.getSeverity();
if (severity < Diagnostic.WARNING) {
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
} else if (severity < Diagnostic.ERROR) {
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_WARNING);
} else {
marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR);
}
String message = composeMessage(diagnostic, parentDiagnostic);
if (message != null) {
marker.setAttribute(IMarker.MESSAGE, message);
}
}
}
}