/*
* Copyright (c) 2005, 2010 Sven Efftinge 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 distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sven Efftinge - Initial API and implementation
* Artem Tikhomirov (Borland) - Migration to OCL expressions
*/
package org.eclipse.gmf.internal.xpand.model;
import java.util.Arrays;
import java.util.Collection;
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.ecore.EClassifier;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.gmf.internal.xpand.BuiltinMetaModel;
import org.eclipse.gmf.internal.xpand.ResourceMarker;
import org.eclipse.gmf.internal.xpand.util.PolymorphicResolver;
import org.eclipse.gmf.internal.xpand.util.TypeNameUtil;
import org.eclipse.gmf.internal.xpand.xtend.ast.QvtExtension;
import org.eclipse.gmf.internal.xpand.xtend.ast.QvtResource;
import org.eclipse.m2m.internal.qvt.oml.ast.env.QVTParsingOptions;
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.evaluator.ImportToNonTransformCtxHelper;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitor;
import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtOperationalEvaluationVisitorImpl;
import org.eclipse.m2m.internal.qvt.oml.expressions.Module;
import org.eclipse.m2m.internal.qvt.oml.library.Context;
import org.eclipse.m2m.internal.qvt.oml.runtime.util.OCLEnvironmentWithQVTAccessFactory;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.ecore.EcoreEnvironment;
import org.eclipse.ocl.ecore.EcoreEvaluationEnvironment;
import org.eclipse.ocl.ecore.EcoreFactory;
import org.eclipse.ocl.options.ParsingOptions;
/**
* @author Sven Efftinge
* @author Arno Haase
*/
@SuppressWarnings("restriction")
public final class ExecutionContextImpl implements ExecutionContext {
private final Map<String, Variable> variables = new HashMap<String, Variable> ();
// never null
private final Scope scope;
private final ResourceMarker currentResource;
private Set<QvtExtension> allExtensions;
// private ImportToNonTransformCtxHelper modulesImportHelper;
public ExecutionContextImpl(Scope rootScope) {
this (rootScope, null, null);
}
public ExecutionContextImpl(Scope rootScope, ResourceMarker resource, Collection<Variable> variables) {
assert rootScope != null;
this.scope = rootScope;
this.currentResource = resource;
if (variables != null) {
for (Variable v : variables) {
this.variables.put(v.getName(), v);
}
}
}
public Scope getScope() {
return scope;
}
public Variable getImplicitVariable() {
return variables.get(IMPLICIT_VARIABLE);
}
public ExecutionContext cloneWithVariable(final Variable... vars) {
final ExecutionContextImpl result = new ExecutionContextImpl(scope, currentResource, variables.values());
// cached values that depend on resource only may be kept
result.envFactory = envFactory;
result.allExtensions = allExtensions;
// result.modulesImportHelper = modulesImportHelper;
result.environment = null; // XXX or create new, delegating?
for (Variable v : vars) {
// adding to the set of original variables because of e.g. nested let statements
result.variables.put(v.getName(), v);
}
return result;
}
public ExecutionContext cloneWithResource(final ResourceMarker ns) {
if (ns == currentResource) {
return this;
}
// XXX is it reasonable to pass variables if it's another resource?
final ExecutionContextImpl result = new ExecutionContextImpl(scope, ns, variables.values());
result.envFactory = null; // need to make sure resource's imports are read into registry.
result.environment = null;
result.allExtensions = null;
// result.modulesImportHelper = null;
return result;
}
public ResourceMarker currentResource() {
return currentResource;
}
private String[] getImportedExtensions() {
return currentResource == null ? new String[0] : currentResource.getImportedExtensions();
}
public Set<QvtExtension> getAllExtensions() {
if (allExtensions == null) {
allExtensions = new HashSet<QvtExtension>();
final String[] extensions = getImportedExtensions();
for (String extension : extensions) {
final QvtResource qvtResource = getScope().findExtension(extension);
if (qvtResource == null) {
continue;
}
final ExecutionContext ctx = cloneWithResource(qvtResource);
final List<QvtExtension> extensionList = qvtResource.getExtensions();
if (extensionList == null) {
continue;
}
for (QvtExtension element : extensionList) {
element.init(ctx);
allExtensions.add(element);
}
}
}
return allExtensions;
}
public XpandDefinition findDefinition(String name, EClassifier target, EClassifier[] paramTypes) throws AmbiguousDefinitionException {
String templateName;
boolean localCall = !TypeNameUtil.isQualifiedName(name);
if (localCall) {
// [artem] the reason can't just use currentResource() as it might be part of composite?
// otherwise, see no reason to load it once again in findTemplate()
templateName = ((XpandResource) currentResource()).getFullyQualifiedName(); // need an enclosing resource in case of composite
} else {
templateName = TypeNameUtil.withoutLastSegment(name);
}
XpandResource tpl = findTemplate(templateName);
if (tpl == null) {
if (localCall) {
tpl = (XpandResource) currentResource();
} else {
return null;
}
}
final ExecutionContext ctx = cloneWithResource(tpl);
XpandDefinition def = findDefinition(tpl.getDefinitions(), name, target, paramTypes, ctx);
if (def == null) {
return null;
}
XpandAdvice[] advicesInResource = tpl.getAdvices();
for (int x = advicesInResource.length - 1; x >= 0; x--) {
final XpandAdvice adv = advicesInResource[x];
if (adv.matches(def, this)) {
def = new AdvicedDefinition(adv, def);
}
}
for (int x = scope.getAdvices().size() - 1; x >= 0; x--) {
final XpandAdvice adv = scope.getAdvices().get(x);
if (adv.matches(def, this)) {
def = new AdvicedDefinition(adv, def);
}
}
return def;
}
// XXX completely rewritten, NEEDS TESTS!
// getPossibleNames(getImportedNamespaces()), along with FQN added in #getImportedNamespaces(), was stupid hack anyway.
private XpandResource findTemplate(final String templateName) {
if (currentResource() instanceof XpandResource) {
String contextTemplate = ((XpandResource) currentResource()).getFullyQualifiedName();
return scope.findTemplate(templateName, contextTemplate);
} else {
return scope.findTemplate(templateName);
}
}
/**
* resolves the correct definition (using parametric polymorphism)
* XXX: get rid of the ctx argument and redeclare as non-static?
* @param definitions
* @param target
* @param paramTypes
* @return
* @throws AmbiguousDefinitionException
*/
private static XpandDefinition findDefinition(final XpandDefinition[] definitions, final String name, final EClassifier target,
EClassifier[] paramTypes, final ExecutionContext ctx) throws AmbiguousDefinitionException {
if (paramTypes == null) {
paramTypes = new EClassifier[0];
}
final String unqualifiedName = TypeNameUtil.getLastSegment(name);
// XXX Instead of using map as a mere pair storage, do it like Extension does with init(ctx)
// to resolve and keep typed arguments
HashMap<XpandDefinition, List<EClassifier>> resolvedDefs = new HashMap<XpandDefinition, List<EClassifier>>();
for (final XpandDefinition def : definitions) {
if (!def.getName().equals(unqualifiedName)) {
continue;
}
if (def.getParams().length == paramTypes.length) {
final LinkedList<EClassifier> defsParamTypes = new LinkedList<EClassifier>();
EClassifier t = null;
boolean complete = true;
for (int j = 0; (j < paramTypes.length) && complete; j++) {
t = def.getParams()[j].getTypeForName(ctx);
if (t == null) {
complete = false;
}
defsParamTypes.add(t);
}
t = def.getTargetType().getTypeForName(ctx);
if (t == null) {
complete = false;
} else {
defsParamTypes.addFirst(t);
}
if (complete) {
resolvedDefs.put(def, defsParamTypes);
}
}
}
return PolymorphicResolver.filterDefinition(resolvedDefs, target, Arrays.asList(paramTypes), ctx.getOCLEnvironment());
}
private OCLEnvironmentWithQVTAccessFactory envFactory; // null-ified when context's resource is changed
private EcoreEnvironment environment;
public EcoreEnvironment getOCLEnvironment() {
if (environment != null) {
return environment;
}
if (envFactory == null) {
envFactory = new OCLEnvironmentWithQVTAccessFactory(getImportedModules(), getAllVisibleModels());
}
environment = (EcoreEnvironment) envFactory.createEnvironment();
ParsingOptions.setOption(environment, QVTParsingOptions.ENFORCE_EXPLICIT_SELF_VARIABLE, Boolean.FALSE);
ParsingOptions.setOption(environment, ParsingOptions.implicitRootClass(environment), EcorePackage.Literals.EOBJECT);
Variable that = getImplicitVariable();
for (Variable v : variables.values()) {
if (that != v) {
// XXX alternative: environment.getOCLFactory().createVariable()
org.eclipse.ocl.ecore.Variable oclVar = EcoreFactory.eINSTANCE.createVariable();
oclVar.setName(v.getName());
if (v.getType() == null) {
oclVar.setType(BuiltinMetaModel.getType(this, v.getValue()));
} else {
oclVar.setType(v.getType());
}
environment.addElement(oclVar.getName(), oclVar, true);
}
}
if (that != null) {
EClassifier type = that.getType() == null ? BuiltinMetaModel.getType(this, that.getValue()) : that.getType();
environment = (EcoreEnvironment) envFactory.createClassifierContext(environment, type);
}
return environment;
}
public QvtOperationalEvaluationVisitor createEvaluationVisitor() {
QvtOperationalEvaluationEnv evaluationEnv = (QvtOperationalEvaluationEnv) createEvaluationEnvironment();
ImportToNonTransformCtxHelper importsHelper = scope.getImportsHelper();
for (Module module : getImportedModules()) {
importsHelper.addImportedModule(module);
}
return QvtOperationalEvaluationVisitorImpl.createNonTransformationExecutionContextVisitor(QvtOperationalEnvFactory.INSTANCE.createEnvironment(), evaluationEnv, importsHelper);
}
private Set<Module> getImportedModules() {
LinkedHashSet<Module> importedModules = new LinkedHashSet<Module>();
final String[] extensions = getImportedExtensions();
for (String extension : extensions) {
final QvtResource qvtResource = getScope().findExtension(extension);
if (qvtResource != null) {
importedModules.addAll(qvtResource.getModules());
}
}
return importedModules;
}
private EcoreEvaluationEnvironment createEvaluationEnvironment() {
if (envFactory == null) {
getOCLEnvironment();
}
EcoreEvaluationEnvironment ee = QvtOperationalEnvFactory.INSTANCE.createEvaluationEnvironment(new Context(), null);
Variable that = getImplicitVariable();
for (Variable v : variables.values()) {
if (that != v) {
ee.add(v.getName(), v.getValue());
}
}
if (that != null) {
ee.add(Environment.SELF_VARIABLE_NAME, that.getValue());
}
return ee;
}
private String[] getImportedNamespaces() {
return currentResource == null ? new String[0] : currentResource.getImportedNamespaces();
}
public EPackage.Registry getAllVisibleModels() {
return getScope().createPackageRegistry(getImportedNamespaces());
}
}