blob: f9f9fcf3cf2068d24d892e4196afee4cd032058e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2019 Willink Transformations 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:
* E.D.Willink - initial API and implementation
******************************************************************************/
package org.eclipse.qvtd.pivot.qvtimperative.evaluation;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.CallExp;
import org.eclipse.ocl.pivot.Model;
import org.eclipse.ocl.pivot.NamedElement;
import org.eclipse.ocl.pivot.OCLExpression;
import org.eclipse.ocl.pivot.Operation;
import org.eclipse.ocl.pivot.OperationCallExp;
import org.eclipse.ocl.pivot.Parameter;
import org.eclipse.ocl.pivot.PivotFactory;
import org.eclipse.ocl.pivot.Property;
import org.eclipse.ocl.pivot.Type;
import org.eclipse.ocl.pivot.TypedElement;
import org.eclipse.ocl.pivot.Variable;
import org.eclipse.ocl.pivot.evaluation.AbstractModelManager;
import org.eclipse.ocl.pivot.evaluation.EvaluationEnvironment;
import org.eclipse.ocl.pivot.evaluation.EvaluationVisitor;
import org.eclipse.ocl.pivot.internal.complete.StandardLibraryInternal;
import org.eclipse.ocl.pivot.internal.evaluation.AbstractExecutor;
import org.eclipse.ocl.pivot.internal.messages.PivotMessagesInternal;
import org.eclipse.ocl.pivot.labels.ILabelGenerator;
import org.eclipse.ocl.pivot.resource.ASResource;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.pivot.utilities.NameUtil;
import org.eclipse.ocl.pivot.utilities.PivotUtil;
import org.eclipse.ocl.pivot.utilities.XMIUtil;
import org.eclipse.ocl.pivot.values.InvalidValueException;
import org.eclipse.ocl.pivot.values.NullValue;
import org.eclipse.qvtd.pivot.qvtbase.Function;
import org.eclipse.qvtd.pivot.qvtbase.Transformation;
import org.eclipse.qvtd.pivot.qvtbase.utilities.QVTbaseUtil;
import org.eclipse.qvtd.pivot.qvtimperative.AppendParameter;
import org.eclipse.qvtd.pivot.qvtimperative.AppendParameterBinding;
import org.eclipse.qvtd.pivot.qvtimperative.GuardParameter;
import org.eclipse.qvtd.pivot.qvtimperative.GuardParameterBinding;
import org.eclipse.qvtd.pivot.qvtimperative.ImperativeTransformation;
import org.eclipse.qvtd.pivot.qvtimperative.ImperativeTypedModel;
import org.eclipse.qvtd.pivot.qvtimperative.LoopParameterBinding;
import org.eclipse.qvtd.pivot.qvtimperative.Mapping;
import org.eclipse.qvtd.pivot.qvtimperative.MappingCall;
import org.eclipse.qvtd.pivot.qvtimperative.MappingParameter;
import org.eclipse.qvtd.pivot.qvtimperative.MappingParameterBinding;
import org.eclipse.qvtd.pivot.qvtimperative.NewStatement;
import org.eclipse.qvtd.pivot.qvtimperative.SetStatement;
import org.eclipse.qvtd.pivot.qvtimperative.SimpleParameterBinding;
import org.eclipse.qvtd.pivot.qvtimperative.Statement;
import org.eclipse.qvtd.pivot.qvtimperative.utilities.QVTimperativeUtil;
import org.eclipse.qvtd.runtime.evaluation.AbstractTransformer;
import org.eclipse.qvtd.runtime.evaluation.Connection;
import org.eclipse.qvtd.runtime.evaluation.Interval;
import org.eclipse.qvtd.runtime.evaluation.InvocationFailedException;
import org.eclipse.qvtd.runtime.evaluation.ModeFactory;
import org.eclipse.qvtd.runtime.evaluation.TypedModelInstance;
public abstract class BasicQVTiExecutor extends AbstractExecutor implements QVTiExecutor
{
/**
* WrappedModelManager enables the unhelpful model access API to be observed without infecting the
* more streamlined QVTi accesses.
*/
private class WrappedModelManager extends AbstractModelManager
{
@Override
public @NonNull Set<@NonNull ? extends Object> get(org.eclipse.ocl.pivot.@NonNull Class type) {
return modelsManager.get(type);
}
}
protected final @NonNull ImperativeTransformation transformation;
protected final @NonNull QVTiTransformationAnalysis transformationAnalysis;
protected final @NonNull QVTiModelsManager modelsManager;
private @Nullable WrappedModelManager wrappedModelManager = null;
protected final boolean debugExceptions = AbstractTransformer.EXCEPTIONS.isActive();
protected final boolean debugInvocations = AbstractTransformer.INVOCATIONS.isActive();
private final @NonNull Map<@NonNull Mapping, @NonNull Interval> mapping2interval = new HashMap<>();;
public BasicQVTiExecutor(@NonNull QVTiEnvironmentFactory environmentFactory, @NonNull ImperativeTransformation transformation) {
super(environmentFactory);
this.transformation = transformation;
this.transformationAnalysis = environmentFactory.createTransformationAnalysis(transformation);
transformationAnalysis.analyzeTransformation();
this.modelsManager = environmentFactory.createModelsManager(transformationAnalysis);
}
/**
* Adds the model.
*/
public void addModel(@NonNull ImperativeTypedModel typedModel, @NonNull Resource resource) {
modelsManager.addModel(typedModel, resource);
}
@Override
public void checkModels() throws CoreException {
StringBuilder s = null;
for (@NonNull Resource resource : environmentFactory.getResourceSet().getResources()) {
String messages = PivotUtil.formatResourceDiagnostics(resource.getErrors(), "Errors in " + resource.getURI().toString(), "\n");
if (messages != null) {
if (s == null) {
s = new StringBuilder();
}
s.append(messages);
}
}
if (s != null) {
IStatus status = new Status(IStatus.ERROR, "getPluginId()", s.toString());
throw new CoreException(status);
}
}
@Override
protected EvaluationVisitor.@NonNull EvaluationVisitorExtension createEvaluationVisitor() {
IQVTiEvaluationVisitor visitor = new QVTiEvaluationVisitor(this);
if (environmentFactory.isEvaluationTracingEnabled()) {
// decorate the evaluation visitor with tracing support
visitor = new QVTiTracingEvaluationVisitor(visitor);
// ((QVTiTracingEvaluationVisitor)visitor).setVerboseLevel(QVTiTracingEvaluationVisitor.VERBOSE_LEVEL_HIGH);
}
return visitor;
}
public @Nullable Resource createModel(@NonNull String name, @NonNull URI modelURI) {
return createModel(name, modelURI, null);
}
@Override
public @Nullable Resource createModel(@NonNull String name, @NonNull URI modelURI, @Nullable String contentType) {
ImperativeTypedModel typedModel = getTypedModel(name);
Resource resource = environmentFactory.getResourceSet().createResource(modelURI, contentType);
if (resource != null) {
modelsManager.addModel(typedModel, resource);
}
return resource;
}
@Override
protected EvaluationEnvironment.@NonNull EvaluationEnvironmentExtension createNestedEvaluationEnvironment(EvaluationEnvironment.@NonNull EvaluationEnvironmentExtension evaluationEnvironment, @NonNull NamedElement executableObject, @Nullable Object caller) {
if (evaluationEnvironment instanceof QVTiEvaluationEnvironment) {
return new QVTiNestedEvaluationEnvironment((QVTiEvaluationEnvironment) evaluationEnvironment, executableObject, caller);
}
else{
return super.createNestedEvaluationEnvironment(evaluationEnvironment, executableObject, caller);
}
}
@Override
protected EvaluationEnvironment.@NonNull EvaluationEnvironmentExtension createRootEvaluationEnvironment(@NonNull NamedElement executableObject) {
if (executableObject instanceof Transformation) {
return new QVTiRootEvaluationEnvironment(this, (Transformation) executableObject);
}
else {
return super.createRootEvaluationEnvironment(executableObject);
}
}
@Override
public void dispose() {
modelsManager.dispose();
super.dispose();
}
@Override
public Boolean execute() {
initializeEvaluationEnvironment(transformation);
getRootEvaluationEnvironment();
StandardLibraryInternal standardLibrary = environmentFactory.getStandardLibrary();
Variable ownedContext = QVTbaseUtil.getContextVariable(standardLibrary, transformation);
add(ownedContext, modelsManager.getTransformationInstance(transformation));
for (@NonNull ImperativeTypedModel typedModel : QVTimperativeUtil.getOwnedTypedModels(transformation)) {
ownedContext = QVTbaseUtil.getContextVariable(standardLibrary, typedModel);
add(ownedContext, modelsManager.getTypedModelInstance(typedModel));
}
return executeInternal();
}
protected Boolean executeInternal() {
return (Boolean) getEvaluationVisitor().visit(transformation);
}
@Override
public @NonNull QVTiEnvironmentFactory getEnvironmentFactory() {
return (QVTiEnvironmentFactory) super.getEnvironmentFactory();
}
@Override
public @NonNull QVTiEvaluationEnvironment getEvaluationEnvironment() {
return (QVTiEvaluationEnvironment) super.getEvaluationEnvironment();
}
@Override
public @NonNull Interval getInterval(@NonNull Mapping asMapping) {
Interval interval = mapping2interval.get(asMapping);
return ClassUtil.nonNullState(interval);
}
/**
* Gets the named TypedModel.
*/
@Override
public @Nullable Resource getModel(@NonNull String name) {
ImperativeTypedModel typedModel = getTypedModel(name);
return modelsManager.getModel(typedModel);
}
@Override
public @NonNull WrappedModelManager getModelManager() {
WrappedModelManager wrappedModelManager2 = wrappedModelManager;
if (wrappedModelManager2 == null) {
wrappedModelManager2 = wrappedModelManager = new WrappedModelManager();
}
return wrappedModelManager2;
}
@Override
public @NonNull QVTiModelsManager getModelsManager() {
return modelsManager;
}
public @NonNull Collection<@NonNull EObject> getRootObjects(@NonNull String name) {
return modelsManager.getRootObjects(getTypedModel(name));
}
public @NonNull Transformation getTransformation() {
return transformation;
}
public @NonNull QVTiTransformationAnalysis getTransformationAnalysis() {
return transformationAnalysis;
}
public @NonNull ImperativeTypedModel getTypedModel(@NonNull String name) {
ImperativeTypedModel typedModel = NameUtil.getNameable(QVTimperativeUtil.getOwnedTypedModels(transformation), name);
if (typedModel == null) {
// if (QVTbaseUtil.TRACE_TYPED_MODEL_NAME.equals(name)) {
// typedModel = NameUtil.getNameable(QVTimperativeUtil.getOwnedTypedModels(transformation), QVTscheduleConstants.MIDDLE_DOMAIN_NAME); // FIXME unify name
// }
// if (typedModel == null) {
throw new IllegalStateException("Unknown TypedModel '" + name + "'");
// }
}
return typedModel;
}
protected @Nullable Object internalExecuteFunctionCallExp(@NonNull OperationCallExp operationCallExp,
@NonNull Function referredFunction, @Nullable Object @NonNull [] boxedSourceAndArgumentValues) {
// PivotUtil.checkExpression(expressionInOCL);
EvaluationEnvironment nestedEvaluationEnvironment = pushEvaluationEnvironment(referredFunction, (TypedElement)operationCallExp);
// nestedEvaluationEnvironment.add(ClassUtil.nonNullModel(expressionInOCL.getOwnedContext()), sourceValue);
List<Parameter> parameters = referredFunction.getOwnedParameters();
if (!parameters.isEmpty()) {
for (int i = 0; i < parameters.size(); i++) {
Object value = boxedSourceAndArgumentValues[i+1];
nestedEvaluationEnvironment.add(ClassUtil.nonNullModel(parameters.get(i)), value);
}
}
try {
OCLExpression bodyExpression = referredFunction.getQueryExpression();
assert bodyExpression != null;
Object result = evaluate(bodyExpression);
assert !(result instanceof NullValue);
return result;
}
catch (InvalidValueException e) {
throw e;
}
catch (Exception e) {
// This is a backstop. Library operations should catch their own exceptions
// and produce a better reason as a result.
throw new InvalidValueException(e, PivotMessagesInternal.FailedToEvaluate_ERROR_, referredFunction, ILabelGenerator.Registry.INSTANCE.labelFor(null), operationCallExp);
}
finally {
popEvaluationEnvironment();
}
}
@Override
public @Nullable Object internalExecuteMappingCall(@NonNull MappingCall mappingCall, @NonNull Object @NonNull [] boundValues, @NonNull EvaluationVisitor undecoratedVisitor) {
Mapping calledMapping = mappingCall.getReferredMapping();
if (calledMapping != null) {
pushEvaluationEnvironment(calledMapping, mappingCall);
try {
int index = 0;
for (@NonNull MappingParameterBinding binding : QVTimperativeUtil.getOwnedMappingParameterBindings(mappingCall)) {
MappingParameter boundVariable = ClassUtil.nonNullState(binding.getBoundVariable());
Object boundValue = boundValues[index++];
if (binding instanceof AppendParameterBinding) { // FIXME visit the bindings
if (!replace(boundVariable, boundValue, false)) {
return false;
}
}
else if (binding instanceof GuardParameterBinding) {
// if (boundValue instanceof Connection) {
// boundValue = ((Connection)boundValue).
// }
if (!replace(boundVariable, boundValue, ((GuardParameterBinding)binding).isIsCheck())) {
return false;
}
}
else if (binding instanceof LoopParameterBinding) {
if (!replace(boundVariable, boundValue, ((LoopParameterBinding)binding).isIsCheck())) {
return false;
}
}
else if (binding instanceof SimpleParameterBinding) {
if (!replace(boundVariable, boundValue, ((SimpleParameterBinding)binding).isIsCheck())) {
return false;
}
}
else {
assert false;
}
}
calledMapping.accept(undecoratedVisitor);
}
finally {
popEvaluationEnvironment();
}
}
return true;
}
@Override
public @Nullable Object internalExecuteMapping(@NonNull Mapping mapping, @NonNull EvaluationVisitor undecoratedVisitor) {
boolean success = false;
try {
for (Statement statement : mapping.getOwnedStatements()) {
Object result = statement.accept(undecoratedVisitor);
if (result != Boolean.TRUE) {
return success;
}
}
success = true;
return success;
}
catch (InvocationFailedException e) {
throw e;
}
catch (Throwable e) {
// Mapping failures are just mappings that never happened.
if (debugExceptions) {
AbstractTransformer.EXCEPTIONS.println("Execution failure in " + mapping.getName() + " : " + e);
}
throw e;
// return success;
}
finally {
for (@NonNull MappingParameter mappingParameter : QVTimperativeUtil.getOwnedMappingParameters(mapping)) {
if (mappingParameter instanceof GuardParameter) {
Property successProperty = ((GuardParameter)mappingParameter).getSuccessProperty();
if (successProperty != null) {
Object guardVariable = getValueOf(mappingParameter);
if (guardVariable != null) {
successProperty.initValue(guardVariable, success);
}
}
}
}
}
}
@Override
public @Nullable Object internalExecuteOperationCallExp(@NonNull OperationCallExp operationCallExp,
@Nullable Object @NonNull [] boxedSourceAndArgumentValues) {
Operation referredOperation = operationCallExp.getReferredOperation();
if (referredOperation instanceof Function) {
return internalExecuteFunctionCallExp(operationCallExp, (Function)referredOperation, boxedSourceAndArgumentValues);
}
else {
return super.internalExecuteOperationCallExp(operationCallExp, boxedSourceAndArgumentValues);
}
}
@Override
public @Nullable Object internalExecuteNewStatement(@NonNull NewStatement newStatement, @NonNull EvaluationVisitor undecoratedVisitor) {
OCLExpression ownedExpression = newStatement.getOwnedExpression();
if (ownedExpression != null) {
Object initValue = ownedExpression.accept(undecoratedVisitor);
getEvaluationEnvironment().add(newStatement, initValue);
replace(newStatement, initValue);
ImperativeTypedModel typedModel = newStatement.getReferredTypedModel();
assert typedModel != null;
Object ecoreValue = getIdResolver().ecoreValueOf(null, initValue);
assert ecoreValue != null;
modelsManager.addModelElement(typedModel, ecoreValue);
return ecoreValue;
}
else {
// Realized variables are in the mapping's target bottom pattern
// and create elements in the target model. The realized variables
// are being visited for each binding of variable in the mapping.
Type type = newStatement.getType();
if (!(type instanceof org.eclipse.ocl.pivot.Class)) {
return null;
}
ImperativeTypedModel typedModel = newStatement.getReferredTypedModel();
assert typedModel != null;
Object element = ((org.eclipse.ocl.pivot.Class)type).createInstance();
// Add the realize variable binding to the environment
if (!replace(newStatement, element, false)) {
return null;
}
modelsManager.addModelElement(typedModel, element);
return element;
}
}
@Deprecated /* @deprecated childKey not used */
@Override
public void internalExecuteSetStatement(@NonNull SetStatement setStatement, @NonNull Object slotObject, @Nullable Object ecoreValue, @Nullable Object childKey) {
internalExecuteSetStatement(setStatement, slotObject, ecoreValue);
}
@Override
public void internalExecuteSetStatement(@NonNull SetStatement setStatement, @NonNull Object slotObject, @Nullable Object ecoreValue) {
Property targetProperty = QVTimperativeUtil.getTargetProperty(setStatement);
targetProperty.initValue(slotObject, ecoreValue);
Integer cacheIndex = modelsManager.getTransformationAnalysis().getCacheIndex(setStatement);
if (cacheIndex != null) {
modelsManager.setUnnavigableOpposite(cacheIndex, slotObject, ecoreValue);
}
}
@Override
public @Nullable Object internalExecuteTransformation(@NonNull ImperativeTransformation transformation, @NonNull EvaluationVisitor undecoratedVisitor) {
Mapping rule = QVTimperativeUtil.getRootMapping(transformation);
CallExp callExp = PivotFactory.eINSTANCE.createOperationCallExp(); // FIXME TransformationCallExp
pushEvaluationEnvironment(rule, (TypedElement)callExp);
try {
Interval rootInterval = getInvocationManager().getRootInterval();
mapping2interval.put(rule, rootInterval);
TypedModelInstance modelInstance = null;
for (@NonNull ImperativeTypedModel typedModel : QVTimperativeUtil.getOwnedTypedModels(transformation)) {
if (typedModel.isIsChecked()) {
modelInstance = getModelsManager().getTypedModelInstance(typedModel);
break;
}
}
assert modelInstance != null;
for (@NonNull MappingParameter mappingParameter : QVTimperativeUtil.getOwnedMappingParameters(rule)) {
if (mappingParameter instanceof AppendParameter) {
org.eclipse.ocl.pivot.Class type = QVTimperativeUtil.getClassType(mappingParameter);
Connection connection = rootInterval.createConnection(QVTimperativeUtil.getName(mappingParameter), type.getTypeId(), false, ModeFactory.NON_INCREMENTAL);
Iterable<@NonNull ? extends Object> objectsOfKind = modelInstance.getObjectsOfKind(type);
for (@NonNull Object object : objectsOfKind) {
connection.appendElement(object);
}
getEvaluationEnvironment().add(mappingParameter, connection);
}
}
rule.accept(undecoratedVisitor); // Use an outer InvocationConstructor?
getInvocationManager().flush();
}
finally {
popEvaluationEnvironment();
}
return true;
}
/**
* Loads the modelURI and binds it to the named TypedModel.
*/
public @Nullable Resource loadModel(@NonNull String name, @NonNull URI modelURI) {
ImperativeTypedModel typedModel = getTypedModel(name);
Resource resource = environmentFactory.getResourceSet().getResource(modelURI, true);
if (resource != null) {
modelsManager.addModel(typedModel, resource);
}
return resource;
}
@Override
public void loadModel(@NonNull String name, @NonNull URI modelURI, @Nullable String contentType) {
ImperativeTypedModel typedModel = getTypedModel(name);
Resource resource;
ResourceSet resourceSet = environmentFactory.getResourceSet();
if (contentType == null) {
resource = resourceSet.getResource(modelURI, true);
}
else {
resource = resourceSet.createResource(modelURI, contentType);
try {
resource.load(null);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (resource != null) {
modelsManager.addModel(typedModel, resource);
}
}
@Override
public void replace(@NonNull TypedElement asVariable, @Nullable Object value) {
if (value == null) {
if (asVariable.isIsRequired()) {
throw new InvalidValueException("Attempted to assign null value to " + asVariable);
}
}
super.replace(asVariable, value);
}
@Override
public boolean replace(@NonNull TypedElement asVariable, @Nullable Object value, boolean checkType) {
if (value == null) {
if (asVariable.isIsRequired()) {
// throw new InvalidValueException("Attempted to assign null value to " + asVariable);
return false;
}
}
else if (checkType) {
Type valueType = getIdResolver().getDynamicTypeOf(value);
// Type valueType2 = getIdResolver().getStaticTypeOf(value);
// Type valueType = valueType1;
Type variableType = ClassUtil.nonNullState(asVariable.getType());
if (!valueType.conformsTo(getStandardLibrary(), variableType)) {
// throw new InvalidValueException("Attempted to assign incompatible value to " + asVariable);
return false;
}
}
super.replace(asVariable, value);
return true;
}
// @Override
public void saveContents() {
modelsManager.saveContents();
}
public Resource saveModel(@NonNull String name, @NonNull URI modelURI, String contentType, @Nullable Map<?, ?> savingOptions) throws IOException {
ImperativeTypedModel typedModel = getTypedModel(name);
Resource resource = modelsManager.getModel(typedModel);
if (resource == null) {
resource = environmentFactory.getResourceSet().createResource(modelURI, contentType);
}
if (resource != null) {
resource.save(savingOptions);
}
return resource;
}
@Override
public void saveModels() {
modelsManager.saveModels();
}
public void saveModels(@Nullable Map<?, ?> savingOptions) {
modelsManager.saveModels(savingOptions);
}
public void saveModels(@Nullable URI traceURI) {
Map<Object, Object> saveOptions = XMIUtil.createSaveOptions();
saveOptions.put(XMIResource.OPTION_SCHEMA_LOCATION_IMPLEMENTATION, Boolean.TRUE);
this.saveModels(traceURI, saveOptions);
}
public void saveModels(@Nullable URI traceURI, @Nullable Map<?, ?> savingOptions) {
this.saveModels(savingOptions);
if (traceURI != null) {
modelsManager.saveMiddleModel(traceURI, savingOptions);
}
}
@Deprecated /* @deprecated supply URI */
public void saveTransformation(Map<?,?> options) throws IOException {
saveTransformation(null, options);
}
public void saveTransformation(URI saveURI, Map<?,?> options) throws IOException {
XMLResource resource = (XMLResource) transformation.eResource();
// new AS2ID().assignIds(resource.getResourceSet());
if (saveURI != null) {
resource.setURI(saveURI);
}
((ASResource)resource).setSaveable(true);
resource.save(options);
}
public void setExternalURI(@NonNull String name, @NonNull URI modelURI) throws IOException {
ImperativeTypedModel typedModel = getTypedModel(name);
Resource resource = modelsManager.getModel(typedModel);
if (resource != null) {
for (EObject eObject : resource.getContents()) {
if (eObject instanceof Model) {
((Model)eObject).setExternalURI(modelURI.toString());
}
}
}
}
}