blob: dc92f5c06277559383f459c3943fc9c10ec0bdbd [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2019 R.Dvorak 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:
* Radek Dvorak - initial API and implementation
* Christopher Gerking - bugs 431082, 537041
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml;
import static org.eclipse.m2m.internal.qvt.oml.emf.util.EmfUtilPlugin.isSuccess;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.m2m.internal.qvt.oml.ast.env.InternalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.ModelExtentContents;
import org.eclipse.m2m.internal.qvt.oml.ast.env.ModelParameterExtent;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtEvaluationResult;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEnvFactory;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEvaluationEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalFileEnv;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalStdLibrary;
import org.eclipse.m2m.internal.qvt.oml.compiler.CompiledUnit;
import org.eclipse.m2m.internal.qvt.oml.evaluator.InternalEvaluator;
import org.eclipse.m2m.internal.qvt.oml.evaluator.ModelInstance;
import org.eclipse.m2m.internal.qvt.oml.evaluator.ModelParameterHelper;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtInterruptedExecutionException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtRuntimeException;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtStackOverFlowError;
import org.eclipse.m2m.internal.qvt.oml.expressions.DirectionKind;
import org.eclipse.m2m.internal.qvt.oml.expressions.ModelParameter;
import org.eclipse.m2m.internal.qvt.oml.expressions.OperationalTransformation;
import org.eclipse.m2m.internal.qvt.oml.library.Context;
import org.eclipse.m2m.internal.qvt.oml.trace.Trace;
import org.eclipse.m2m.qvt.oml.ExecutionContext;
import org.eclipse.m2m.qvt.oml.ExecutionDiagnostic;
import org.eclipse.m2m.qvt.oml.ModelExtent;
import org.eclipse.m2m.qvt.oml.util.IContext;
import org.eclipse.ocl.EvaluationVisitor;
import org.eclipse.ocl.ecore.CallOperationAction;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.SendSignalAction;
/**
* Internal transformation executor
*
* @since 3.0
*/
public class InternalTransformationExecutor {
private ExecutionDiagnostic fLoadDiagnostic;
private OperationalTransformation fOperationalTransformation;
private QvtOperationalEnvFactory fEnvFactory;
private Transformation fTransformation;
/**
* Constructs the executor for the given transformation URI.
* <p>
* No attempt to resolve and load the transformation is done at this step
*
* @param uri
* the URI of an existing transformation
*/
public InternalTransformationExecutor(URI uri) {
this(new CstTransformation(uri));
}
public InternalTransformationExecutor(URI uri, EPackage.Registry registry) {
this(new CstTransformation(uri, registry));
}
public InternalTransformationExecutor(Transformation transformation) {
if (transformation == null) {
throw new IllegalArgumentException("null transformation"); //$NON-NLS-1$
}
fTransformation = transformation;
}
public URI getURI() {
return fTransformation.getURI();
}
public ResourceSet getResourceSet() {
return fTransformation.getResourceSet();
}
/**
* Attempts to load the transformation referred by this executor and checks
* if it is valid for execution.
* <p>
* <b>Remark:</b></br> Only the first performs the actual transformation
* loading, subsequent calls to this method will return the existing
* diagnostic.
*
* @return the diagnostic indicating possible problems of the load action
*/
public Diagnostic loadTransformation(IProgressMonitor monitor) {
try {
if (fLoadDiagnostic == null) {
doLoad(monitor);
}
return fLoadDiagnostic;
}
finally {
monitor.done();
}
}
/**
* Retrieves compiled unit if the referencing URI gets successfully resolved
* <p>
* <b>Remark</b>: This method invocation causes the referenced transformation to
* load if not already done before by direct call to
* {@linkplain #loadTransformation()} or
* {@linkplain #execute(ExecutionContext, ModelExtent...)}
*
* @return compiled unit or <code>null</code> if it failed to be obtained
*/
public CompiledUnit getUnit() {
return fTransformation.getUnit();
}
/**
* Executes the transformation referred by this executor using the given
* model parameters and execution context.
*
* @param executionContext
* the context object keeping the execution environment details
* @param modelParameters
* the actual model arguments to the transformation
*
* @return the diagnostic object indicating the execution result status,
* also keeping the details of possible problems
* @throws IllegalArgumentException
* if the context or any of the model parameters is
* <code>null</code>
*/
public ExecutionDiagnostic execute(ExecutionContext executionContext, ModelExtent... modelParameters) {
// Java API check for nulls etc.
if (executionContext == null) {
throw new IllegalArgumentException();
}
IProgressMonitor monitor = executionContext.getProgressMonitor();
try {
SubMonitor progress = SubMonitor.convert(monitor, NLS.bind(Messages.Executor_Executing, getURI().toString()), 2);
checkLegalModelParams(modelParameters);
// ensure transformation unit is loaded
loadTransformation(progress.split(1));
// check if we have successfully loaded the transformation unit
if (!isSuccess(fLoadDiagnostic)) {
return fLoadDiagnostic;
}
try {
return doExecute(modelParameters,
new Context(executionContext, progress.split(1)));
} catch (QvtRuntimeException e) {
return createExecutionFailure(e);
}
} finally {
if (monitor != null) {
monitor.done();
}
}
}
private ExecutionDiagnostic doExecute(ModelExtent[] args, IContext context) {
QvtOperationalEnvFactory factory = getEnvironmentFactory();
QvtOperationalEvaluationEnv evaluationEnv = factory
.createEvaluationEnvironment(context, null);
ExecutionDiagnostic modelParamsDiagnostic = initArguments(evaluationEnv, fOperationalTransformation, args);
if (!isSuccess(modelParamsDiagnostic)) {
return modelParamsDiagnostic;
}
QvtOperationalFileEnv rootEnv = factory.createEnvironment(getURI());
EvaluationVisitor<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> evaluator = factory
.createEvaluationVisitor(rootEnv, evaluationEnv, null);
// perform the actual execution
assert evaluator instanceof InternalEvaluator : "expecting InternalEvaluator implementation"; //$NON-NLS-1$
InternalEvaluator rawEvaluator = (InternalEvaluator) evaluator;
Object evalResult = rawEvaluator.execute(fOperationalTransformation);
// unpack the internal extents into the passed model parameters
if (evalResult instanceof QvtEvaluationResult) {
int extentIndex = 0;
for (int i = 0; i < fOperationalTransformation.getModelParameter().size(); ++i) {
ModelParameter p = fOperationalTransformation.getModelParameter().get(i);
if (p.getKind() == DirectionKind.IN) {
continue;
}
ModelExtentContents extent = ((QvtEvaluationResult) evalResult).getModelExtents().get(extentIndex++);
args[i].setContents(extent.getAllRootElements());
}
}
else {
List<Object> resultArgs = evaluationEnv.getOperationArgs();
int i = 0;
for (Object nextResultArg : resultArgs) {
ModelInstance modelInstance = (ModelInstance) nextResultArg;
ModelParameterExtent extent = modelInstance.getExtent();
List<EObject> allRootElements = extent.getContents().getAllRootElements();
try {
args[i++].setContents(allRootElements);
} catch (UnsupportedOperationException e) {
return new ExecutionDiagnosticImpl(Diagnostic.ERROR, ExecutionDiagnostic.MODEL_PARAMETER_MISMATCH,
NLS.bind(Messages.ReadOnlyExtentModificationError, i - 1));
}
}
}
// do some handy processing with traces
Trace traces = evaluationEnv.getAdapter(InternalEvaluationEnv.class).getTraces();
handleExecutionTraces(traces);
return ExecutionDiagnosticImpl.createOkInstance();
}
protected void handleExecutionTraces(Trace traces) {
// nothing interesting here
}
private void doLoad(IProgressMonitor monitor) {
fOperationalTransformation = fTransformation.getTransformation(monitor);
fLoadDiagnostic = fTransformation.getDiagnostic();
}
private ExecutionDiagnostic initArguments(
QvtOperationalEvaluationEnv evalEnv,
OperationalTransformation transformationModel, ModelExtent[] args) {
EList<ModelParameter> modelParameters = transformationModel.getModelParameter();
if (modelParameters.size() > args.length) {
return new ExecutionDiagnosticImpl(Diagnostic.ERROR,
ExecutionDiagnostic.MODEL_PARAMETER_MISMATCH, NLS.bind(
Messages.InvalidModelParameterCountError,
args.length, modelParameters.size()));
}
ExecutionDiagnostic result = ExecutionDiagnosticImpl.createOkInstance();
List<ModelParameterExtent> extents = new ArrayList<ModelParameterExtent>(modelParameters.size());
int argCount = 0;
for (ModelParameter modelParam : modelParameters) {
ModelParameterExtent nextExtent;
ModelExtent nextArg = args[argCount++];
if (modelParam.getKind() != org.eclipse.m2m.internal.qvt.oml.expressions.DirectionKind.OUT) {
nextExtent = new ModelParameterExtent(nextArg.getContents(), getResourceSet(), modelParam);
} else {
nextExtent = new ModelParameterExtent(getResourceSet());
}
evalEnv.addModelExtent(nextExtent);
extents.add(nextExtent);
}
List<ModelInstance> modelArgs = ModelParameterHelper
.createModelArguments(transformationModel, extents);
evalEnv.getOperationArgs().addAll(modelArgs);
return result;
}
public OperationalTransformation getTransformation() {
loadTransformation(new NullProgressMonitor());
return fOperationalTransformation;
}
public void setEnvironmentFactory(QvtOperationalEnvFactory factory) {
fEnvFactory = factory;
}
protected QvtOperationalEnvFactory getEnvironmentFactory() {
return fEnvFactory != null ? fEnvFactory : new QvtOperationalEnvFactory();
}
public void cleanup() {
setEnvironmentFactory(null);
fTransformation.cleanup();
}
private static ExecutionDiagnostic createExecutionFailure(
QvtRuntimeException qvtRuntimeException) {
int code = 0;
int severity = Diagnostic.ERROR;
String message = qvtRuntimeException.getLocalizedMessage();
Object[] data = new Object[] {qvtRuntimeException};
if (qvtRuntimeException instanceof QvtException) {
code = ((QvtException) qvtRuntimeException).getExceptionType() == QvtOperationalStdLibrary.INSTANCE.getAssertionFailedClass() ?
ExecutionDiagnostic.FATAL_ASSERTION : ExecutionDiagnostic.EXCEPTION_THROWN;
} else if (qvtRuntimeException instanceof QvtInterruptedExecutionException) {
code = ExecutionDiagnostic.USER_INTERRUPTED;
severity = Diagnostic.CANCEL;
} else {
code = ExecutionDiagnostic.EXCEPTION_THROWN;
if (qvtRuntimeException instanceof QvtStackOverFlowError == false) {
Throwable cause = qvtRuntimeException.getCause();
data = new Object[] { cause != null ? cause : qvtRuntimeException };
} else {
message = Messages.StackTraceOverFlowError;
}
}
if (message == null) {
message = NLS.bind(Messages.QVTRuntimeExceptionCaught,
qvtRuntimeException.getClass().getName());
}
ExecutionDiagnosticImpl diagnostic = new ExecutionDiagnosticImpl(severity,
code, message, data);
diagnostic.setStackTrace(qvtRuntimeException.getQvtStackTrace());
return diagnostic;
}
private void checkLegalModelParams(ModelExtent[] extents)
throws IllegalArgumentException {
if (extents == null) {
throw new IllegalArgumentException("Null model parameters"); //$NON-NLS-1$
}
for (int i = 0; i < extents.length; i++) {
if (extents[i] == null) {
throw new IllegalArgumentException(
"Null model parameter[" + i + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
@Override
public String toString() {
return "QVTO-Executor: " + fTransformation.getURI(); //$NON-NLS-1$
}
public static class TracesAwareExecutor extends InternalTransformationExecutor {
private Trace fTraces;
public TracesAwareExecutor(URI uri, EPackage.Registry registry) {
super(uri, registry);
}
public TracesAwareExecutor(URI uri) {
super(uri);
}
public TracesAwareExecutor(Transformation transformation) {
super(transformation);
}
public Trace getTraces() {
return fTraces;
}
@Override
protected void handleExecutionTraces(Trace traces) {
super.handleExecutionTraces(traces);
fTraces = traces;
}
@Override
public void cleanup() {
super.cleanup();
fTraces = null;
}
}
}