/*******************************************************************************
* Copyright (c) 2008, 2013 Borland Software Corporation and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribQVTution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Borland Software Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.m2m.internal.qvt.oml.runtime.util;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
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.impl.EPackageRegistryImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.m2m.internal.qvt.oml.ast.binding.ASTBindingHelper;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QvtOperationalEnv;
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.QvtOperationalStdLibrary;
import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalParserUtil;
import org.eclipse.m2m.internal.qvt.oml.common.MdaException;
import org.eclipse.m2m.internal.qvt.oml.compiler.CompiledUnit;
import org.eclipse.m2m.internal.qvt.oml.compiler.CompilerUtils;
import org.eclipse.m2m.internal.qvt.oml.compiler.QVTOCompiler;
import org.eclipse.m2m.internal.qvt.oml.evaluator.ModuleInstance;
import org.eclipse.m2m.internal.qvt.oml.expressions.ExpressionsPackage;
import org.eclipse.m2m.internal.qvt.oml.expressions.Helper;
import org.eclipse.m2m.internal.qvt.oml.expressions.ImportKind;
import org.eclipse.m2m.internal.qvt.oml.expressions.Library;
import org.eclipse.m2m.internal.qvt.oml.expressions.Module;
import org.eclipse.m2m.internal.qvt.oml.stdlib.CallHandler;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.EnvironmentFactory;
import org.eclipse.ocl.EvaluationEnvironment;
import org.eclipse.ocl.EvaluationVisitor;
import org.eclipse.ocl.EvaluationVisitorDecorator;
import org.eclipse.ocl.ecore.CallOperationAction;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.EcoreEnvironment;
import org.eclipse.ocl.ecore.EcoreEnvironmentFactory;
import org.eclipse.ocl.ecore.EcoreEvaluationEnvironment;
import org.eclipse.ocl.ecore.SendSignalAction;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.expressions.VariableExp;
import org.eclipse.ocl.lpg.AbstractParser;
import org.eclipse.ocl.lpg.ProblemHandler;
import org.eclipse.ocl.options.ParsingOptions;
import org.eclipse.ocl.parser.OCLProblemHandler;
import org.eclipse.ocl.utilities.TypedElement;
/**
* This class represents ecore based OCL environment factory capable of resolving
* operation, properties from the given set of imported libraries.
* <p>
* Module owned operations, properties are resolved via implicit source of calls
*/
public final class OCLEnvironmentWithQVTAccessFactory extends EcoreEnvironmentFactory {
private static final String DIAGNOSTIC_SOURCE = "OCLEnvironmentWithQVTAccessFactory"; //$NON-NLS-1$
private NonTransformationExecutionContext fExecCtx;
private final Set<Module> fImportedModules;
private final QvtOperationalEnvFactory fQVTEnvFactory;
private Diagnostic fDiagnostic = Diagnostic.OK_INSTANCE;
/**
* Creates environment factory importing the given QVT unit via referencing
* URIs. </p> The global package registry is used for meta-model resolution.
*
* @param imports
* URI referencing QVTO library units to be imported to the scope
* of OCL expressions
*/
public OCLEnvironmentWithQVTAccessFactory(List<URI> imports) {
this(imports, EPackage.Registry.INSTANCE);
}
/**
* Creates environment factory importing the given QVT unit via referencing
* URIs
*
* @param imports
* URI referencing QVTO library units to be imported to the scope
* of OCL expressions
* @param registry
* custom meta-model registry
*/
public OCLEnvironmentWithQVTAccessFactory(List<URI> imports, EPackage.Registry registry) {
super(setupRegistry(registry));
if(registry == null || imports == null || imports.contains(null)) {
throw new IllegalArgumentException("null in constructor argments"); //$NON-NLS-1$
}
HashSet<Module> modules = new HashSet<Module>();
try {
CompiledUnit[] compiledUnits = QVTOCompiler.compile(new HashSet<URI>(imports), registry);
for (CompiledUnit unit : compiledUnits) {
// TODO perhaps we should skip units with errors?
modules.addAll(unit.getModules());
}
fDiagnostic = createDiagnostic(imports, compiledUnits);
} catch (MdaException e) {
fDiagnostic = new BasicDiagnostic(Diagnostic.ERROR, DIAGNOSTIC_SOURCE, 0, e.getMessage(), new Object[] { e });
}
this.fQVTEnvFactory = new QvtOperationalEnvFactory(registry);
this.fImportedModules = modules;
}
/**
* Constructs environment with QVT imports and metamodel registry.
* @parameter imports set of imported QVT libraries
* @parameter registry metamodel registry
*/
public OCLEnvironmentWithQVTAccessFactory(Set<Module> imports, EPackage.Registry registry) {
super(registry);
if(imports == null || registry == null) {
throw new IllegalArgumentException();
}
this.fQVTEnvFactory = new QvtOperationalEnvFactory(registry);
this.fImportedModules = imports;
// mark is OK, the caller is responsible for error analysis
this.fDiagnostic = Diagnostic.OK_INSTANCE;
}
public Diagnostic getDiagnostic() {
return fDiagnostic;
}
public Set<Module> getQVTModules() {
return Collections.unmodifiableSet(fImportedModules);
}
public OCLEnvironmentWithQVTAccessFactory(Set<Module> importedModules) {
this(importedModules, EPackage.Registry.INSTANCE);
}
@Override
public EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> createEvaluationEnvironment() {
return new EvalEnvImpl(execContext(), null);
}
@Override
public EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> createEvaluationEnvironment(
EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent) {
return new EvalEnvImpl(execContext(), parent);
}
@Override
public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEnvironment() {
EnvImpl result = new EnvImpl(this.getEPackageRegistry());
result.setFactory(this);
return result;
}
@Override
public Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEnvironment(
Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
if (!(parent instanceof EcoreEnvironment)) {
throw new IllegalArgumentException("Parent environment must be an Ecore environment: " + parent); //$NON-NLS-1$
}
EnvImpl result = new EnvImpl(parent);
result.setFactory(this);
return result;
}
@Override
@SuppressWarnings("unchecked")
public EvaluationVisitor<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> createEvaluationVisitor(
Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> env,
EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> evalEnv,
Map<? extends EClass, ? extends Set<? extends EObject>> extentMap) {
EvaluationVisitorDecorator<EPackage, EClassifier, EOperation,
EStructuralFeature, EEnumLiteral, EParameter, EObject,
CallOperationAction, SendSignalAction, Constraint, EClass, EObject> decorator =
new EvaluationVisitorDecorator<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject>(super.createEvaluationVisitor(env, evalEnv, extentMap))
{
@Override
public Object visitVariableExp(VariableExp variableExp) {
Variable var = variableExp.getReferredVariable();
if(var != null && var.getType() != null) {
if(ExpressionsPackage.eINSTANCE.getModule().isInstance(var.getType())) {
return execContext().getEvaluator().visitVariableExp(variableExp);
}
}
return super.visitVariableExp(variableExp);
}
};
return decorator;
}
private NonTransformationExecutionContext execContext() {
if(fExecCtx == null) {
this.fExecCtx = new ReusableCallCtx(fImportedModules);
}
return fExecCtx;
}
private static class ReusableCallCtx extends NonTransformationExecutionContext {
private Map<EOperation, HelperOperationCall> fCallMap = new HashMap<EOperation, HelperOperationCall>();
private ReusableCallCtx(Set<Module> libraryImports) {
super(libraryImports);
}
@Override
public HelperOperationCall createHelperCall(Helper operation) {
HelperOperationCall call = fCallMap.get(operation);
if(call == null) {
call = super.createHelperCall(operation);
fCallMap.put(operation, call);
}
return call;
}
}
private class EnvImpl extends EcoreEnvironment {
private QvtOperationalEnv fQVTdelegate;
protected EnvImpl(Environment<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral, EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> parent) {
super(parent);
if(parent instanceof EnvImpl) {
EnvImpl qvtParentEnv = (EnvImpl)parent;
fQVTdelegate = qvtParentEnv.fQVTdelegate;
} else {
fQVTdelegate = OCLEnvironmentWithQVTAccessFactory.this.fQVTEnvFactory.createEnvironment();
initiliazeImports();
}
}
protected EnvImpl(EPackage.Registry reg, Resource resource) {
super(reg, resource);
fQVTdelegate = OCLEnvironmentWithQVTAccessFactory.this.fQVTEnvFactory.createEnvironment();
initiliazeImports();
}
protected EnvImpl(EPackage.Registry reg) {
super(reg);
fQVTdelegate = OCLEnvironmentWithQVTAccessFactory.this.fQVTEnvFactory.createEnvironment();
initiliazeImports();
}
@Override
protected ProblemHandler createDefaultProblemHandler(AbstractParser parser) {
return new OCLProblemHandler(parser) {
@Override
public void handleProblem(Severity problemSeverity,
Phase processingPhase, String problemMessage,
String processingContext, int startOffset, int endOffset) {
boolean allowCsUnboundValidationProblems = true;
// Note: We are an OCL based environment, but still need relax some restrictions
// imposed by pending compatibility issues
if(QvtOperationalEnv.isMDTOCLCompatibilityFalseProblem(allowCsUnboundValidationProblems,
problemSeverity, processingPhase, problemMessage, processingContext,
startOffset, endOffset)) {
return;
}
super.handleProblem(problemSeverity, processingPhase, problemMessage,
processingContext, startOffset, endOffset);
}
};
}
@Override
public Variable<EClassifier, EParameter> lookupImplicitSourceForOperation(String name, List<? extends TypedElement<EClassifier>> args) {
Variable<EClassifier, EParameter> result = super.lookupImplicitSourceForOperation(name, args);
if(result == null) {
result = fQVTdelegate.lookupImplicitSourceForOperation(name, args);
}
return result;
}
@Override
public Variable<EClassifier, EParameter> lookupImplicitSourceForProperty(String name) {
Variable<EClassifier, EParameter> result = super.lookupImplicitSourceForProperty(name);
if(result == null) {
result = fQVTdelegate.lookupImplicitSourceForProperty(name);
}
return result;
}
@Override
public List<EOperation> getAdditionalOperations(EClassifier classifier) {
List<EOperation> result = super.getAdditionalOperations(classifier);
List<EOperation> qvtAdditionals = fQVTdelegate.getAdditionalOperations(classifier);
if(qvtAdditionals != null && !qvtAdditionals.isEmpty()) {
if(result == null) {
return qvtAdditionals;
}
// FIXME - using LinkedHashset to skip duplicates from Stdlib, which is imported to
// every QVT-Environment.
LinkedHashSet<EOperation> completeResult = new LinkedHashSet<EOperation>(result.size() + qvtAdditionals.size());
completeResult.addAll(result);
completeResult.addAll(qvtAdditionals);
result = new ArrayList<EOperation>(completeResult);
}
return result;
}
@Override
protected void setFactory(EnvironmentFactory<EPackage, EClassifier, EOperation, EStructuralFeature, EEnumLiteral,
EParameter, EObject, CallOperationAction, SendSignalAction, Constraint, EClass, EObject> factory) {
super.setFactory(factory);
}
private void initiliazeImports() {
assert fQVTdelegate != null;
setOption(ParsingOptions.implicitRootClass(this), QvtOperationalStdLibrary.INSTANCE.getElementType());
QvtOperationalStdLibrary.INSTANCE.importTo(fQVTdelegate);
for (Module nextImport : OCLEnvironmentWithQVTAccessFactory.this.fImportedModules) {
fQVTdelegate.addImport(ImportKind.ACCESS, (QvtOperationalEnv)ASTBindingHelper.resolveEnvironment(nextImport));
}
}
}
private static class EvalEnvImpl extends EcoreEvaluationEnvironment {
private NonTransformationExecutionContext fExecCtx;
public EvalEnvImpl(NonTransformationExecutionContext execCtx,
EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent) {
super(parent == null ? new EcoreEvaluationEnvironment((EcoreEnvironmentFactory) null) : parent);
fExecCtx = execCtx;
}
@Override
public boolean overrides(EOperation operation, int opcode) {
if(QvtOperationalParserUtil.getOwningModule(operation) != null) {
// the operation must have come from QVT
return true;
}
return false;
}
@Override
public Object callOperation(EOperation operation, int opcode, Object source, Object[] args) throws IllegalArgumentException {
CallHandler callHandler = CallHandler.Access.getHandler(operation);
if(callHandler != null) {
if(source == null || source == getInvalidResult()) {
return getInvalidResult();
}
QvtOperationalEvaluationEnv qvtEvalEnv = fExecCtx.getEvaluator().getOperationalEvaluationEnv();
ModuleInstance moduleInstance = qvtEvalEnv.getThisOfType(QvtOperationalParserUtil.getOwningModule(operation));
assert moduleInstance != null;
return callHandler.invoke(moduleInstance, source, args, qvtEvalEnv);
}
if(operation instanceof Helper) {
Helper helper = (Helper) operation;
HelperOperationCall call = fExecCtx.createHelperCall((Helper)operation);
try {
Object result = null;
if(QvtOperationalParserUtil.isContextual(helper)) {
result = call.invoke(source, args);
} else {
result = call.invoke(args);
}
return result;
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return getInvalidResult();
}
}
return super.callOperation(operation, opcode, source, args);
}
@Override
public Object navigateProperty(EStructuralFeature property, List<?> qualifiers, Object target) throws IllegalArgumentException {
return fExecCtx.getEvaluator().getEvaluationEnvironment()
.navigateProperty(property, qualifiers, target);
}
}
private static EPackage.Registry setupRegistry(EPackage.Registry registry) {
EPackageRegistryImpl result = new EPackageRegistryImpl();
result.putAll(registry);
Library stdLibPackage = QvtOperationalStdLibrary.INSTANCE.getStdLibModule();
result.put(stdLibPackage.getNsURI(), stdLibPackage);
return result;
}
private static Diagnostic createDiagnostic(List<URI> imports, CompiledUnit[] compiledUnits) {
List<Diagnostic> children = new LinkedList<Diagnostic>();
for (CompiledUnit unit : compiledUnits) {
Diagnostic unitDiagnostic = CompilerUtils.createUnitProblemDiagnostic(unit);
if(unitDiagnostic.getSeverity() != Diagnostic.OK) {
children.add(unitDiagnostic);
}
}
if(imports.size() != compiledUnits.length) {
List<URI> unresolved = new ArrayList<URI>();
for (URI uri : imports) {
boolean found = false;
for (CompiledUnit unit : compiledUnits) {
if(unit.getURI().equals(uri)) {
found = true;
break;
}
}
if(!found) {
unresolved.add(uri);
}
}
children.add(new BasicDiagnostic(Diagnostic.ERROR,
DIAGNOSTIC_SOURCE, 0,
"Unresolved compilation units", unresolved.toArray()));
}
// TODO - move to the qvto core plugin => externalize String
if(!children.isEmpty()) {
return new BasicDiagnostic(
DIAGNOSTIC_SOURCE, 0, children,
"QVT imports diagnostic", null);
}
return Diagnostic.OK_INSTANCE;
}
}