blob: b5c7d22d442c03d3d60c55f7430ee6fc06c46a7b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2011 Obeo.
* 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:
* Obeo - initial API and implementation
* Jerome Benois - eInverse initial implementation
* Goulwen Le Fur - caching of the eInverse cross referencer
*******************************************************************************/
package org.eclipse.acceleo.engine.internal.environment;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.acceleo.common.utils.CircularArrayDeque;
import org.eclipse.acceleo.common.utils.Deque;
import org.eclipse.acceleo.common.utils.ModelUtils;
import org.eclipse.acceleo.engine.AcceleoEngineMessages;
import org.eclipse.acceleo.engine.AcceleoEnginePlugin;
import org.eclipse.acceleo.engine.AcceleoEvaluationException;
import org.eclipse.acceleo.engine.internal.utils.AcceleoOverrideAdapter;
import org.eclipse.acceleo.engine.service.AcceleoDynamicTemplatesRegistry;
import org.eclipse.acceleo.model.mtl.Module;
import org.eclipse.acceleo.model.mtl.ModuleElement;
import org.eclipse.acceleo.model.mtl.Template;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.EvaluationEnvironment;
import org.eclipse.ocl.ecore.AnyType;
import org.eclipse.ocl.ecore.EcoreEvaluationEnvironment;
import org.eclipse.ocl.options.EvaluationOptions;
import org.eclipse.ocl.utilities.PredefinedType;
/**
* This will allow us to accurately evaluate custom operations defined in the Acceleo standard library and
* resolve the right template for each call (guards, overrides, namesakes, ...).
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
public class AcceleoEvaluationEnvironment extends EcoreEvaluationEnvironment {
/** This will be used as a place holder when trying to call templates with a <code>null</code> argument. */
private static final Object NULL_ARGUMENT = new Object();
/** Holds the prefix we'll use for the temporary context variables created to hold context values. */
private static final String TEMPORARY_CONTEXT_VAR_PREFIX = "context$"; //$NON-NLS-1$
/** Holds the prefix we'll use for the temporary variables created to hold argument values. */
private static final String TEMPORARY_INVOCATION_ARG_PREFIX = "temporaryInvocationVariable$"; //$NON-NLS-1$
/** This will allow the environment to know of the modules currently in the generation context. */
private final Set<Module> currentModules = new HashSet<Module>();
/** Maps dynamic overrides as registered in the {@link AcceleoDynamicTemplatesRegistry}. */
private final Map<Template, Set<Template>> dynamicOverrides = new HashMap<Template, Set<Template>>();
/** Maps all overriding templates to their <code>super</code>. */
private final Map<Template, Set<Template>> overridingTemplates = new HashMap<Template, Set<Template>>();
/** This will hold a reference to the class allowing for properties lookup. */
private AcceleoPropertiesLookup propertiesLookup;
/** This will allow us to map all accessible templates to their name. */
private final Map<String, Set<Template>> templates = new HashMap<String, Set<Template>>();
/**
* Allows us to totally get rid of the inherited map. This will mainly serve the purpose of allowing
* multiple bindings against the same variable name.
*/
private final Deque<Map<String, Deque<Object>>> scopedVariableMap = new CircularArrayDeque<Map<String, Deque<Object>>>();
/**
* This will contain variables that are global to a generation module.
*/
private final Map<String, Deque<Object>> globalVariableMap = new HashMap<String, Deque<Object>>();
/**
* This constructor is needed by the factory.
*
* @param parent
* Parent evaluation environment.
* @param module
* We will resolve dependencies for this module and keep references to all accessible
* templates.
* @param properties
* The class allowing for properties lookup for this generation.
*/
public AcceleoEvaluationEnvironment(
EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent,
Module module, AcceleoPropertiesLookup properties) {
super(parent);
scopedVariableMap.add(new HashMap<String, Deque<Object>>());
mapAllTemplates(module);
mapDynamicOverrides();
setOption(EvaluationOptions.LAX_NULL_HANDLING, Boolean.FALSE);
propertiesLookup = properties;
}
/**
* This constructor will create our environment given the module from which to resolve dependencies.
*
* @param module
* We will resolve dependencies for this module and keep references to all accessible
* templates.
* @param properties
* The class allowing for properties lookup for this generation.
*/
public AcceleoEvaluationEnvironment(Module module, AcceleoPropertiesLookup properties) {
super();
scopedVariableMap.add(new HashMap<String, Deque<Object>>());
mapAllTemplates(module);
mapDynamicOverrides();
setOption(EvaluationOptions.LAX_NULL_HANDLING, Boolean.FALSE);
propertiesLookup = properties;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#add(java.lang.String, java.lang.Object)
*/
@Override
public void add(String name, Object value) {
Map<String, Deque<Object>> variableMap;
if (name.startsWith(TEMPORARY_CONTEXT_VAR_PREFIX) || name.startsWith(TEMPORARY_INVOCATION_ARG_PREFIX)) {
variableMap = globalVariableMap;
} else {
variableMap = scopedVariableMap.getLast();
}
Deque<Object> values = variableMap.get(name);
if (values == null) {
values = new CircularArrayDeque<Object>();
variableMap.put(name, values);
}
values.add(value);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.ecore.EcoreEvaluationEnvironment#callOperation(org.eclipse.emf.ecore.EOperation,
* int, java.lang.Object, java.lang.Object[])
*/
@Override
public Object callOperation(EOperation operation, int opcode, Object source, Object[] args) {
Object result = null;
if (operation.getEAnnotation("MTL") != null) { //$NON-NLS-1$
result = AcceleoLibraryOperationVisitor.callStandardOperation(this, operation, source, args);
} else if (operation.getEAnnotation("MTL non-standard") != null) { //$NON-NLS-1$
result = AcceleoLibraryOperationVisitor.callNonStandardOperation(this, operation, source, args);
} else {
/*
* Shortcut OCL for operations returning "EJavaObject" : these could be collections, but OCL would
* discard any value other than the first in such cases. See bug 287052.
*/
if (operation.getEType() == EcorePackage.eINSTANCE.getEJavaObject()) {
result = callOperationWorkaround287052(operation, opcode, source, args);
} else {
result = super.callOperation(operation, opcode, source, args);
}
}
return result;
}
/**
* Copied from
* {@link org.eclipse.ocl.AbstractEvaluationEnvironment#callOperation(Object, int, Object, Object[])} to
* bypass the {@link EcoreEvaluationEnvironment} "coerce" behavior as it is the cause of bug 287052.This
* will only be called for EOperations which return type is EJavaObject.
*
* @param operation
* Operation we are to call.
* @param opcode
* OCL code for this operation.
* @param source
* Source of the call.
* @param args
* Arguments of the call.
* @return Result of the invocation.
* @throws IllegalArgumentException
* Thrown if we couldn't find an appropriate method to call.
*/
private Object callOperationWorkaround287052(EOperation operation, int opcode, Object source,
Object[] args) throws IllegalArgumentException {
if (getParent() != null) {
return getParent().callOperation(operation, opcode, source, args);
}
Method method = getJavaMethodFor(operation, source);
Object noResult = new Object();
Object result = noResult;
if (method != null) {
try {
// coerce any collection arguments to EList as necessary
Class<?>[] parmTypes = method.getParameterTypes();
for (int i = 0; i < parmTypes.length; i++) {
if (EList.class.isAssignableFrom(parmTypes[i])) {
if (args[i] == null) {
args[i] = ECollections.EMPTY_ELIST;
} else if (!(args[i] instanceof Collection<?>)) {
EList<Object> list = new BasicEList.FastCompare<Object>(1);
list.add(args[i]);
args[i] = list;
} else if (!(args[i] instanceof EList<?>)) {
args[i] = new BasicEList.FastCompare<Object>((Collection<?>)args[i]);
}
}
}
result = method.invoke(source, args);
// CHECKSTYLE:OFF
// This is mostly copied from AbstractEvaluationEnvironment, which catches Exception.
} catch (Exception e) {
// CHECKSTYLE:ON
AcceleoEnginePlugin.log(e, false);
result = getInvalidResult();
}
}
if (result == noResult) {
throw new IllegalArgumentException();
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#clear()
*/
@Override
public void clear() {
super.clear();
scopedVariableMap.clear();
globalVariableMap.clear();
}
/**
* Creates a new variable scope. This will typically be called when we enter a new TemplateInvocation or
* QueryInvocation.
*/
public void createVariableScope() {
scopedVariableMap.add(new HashMap<String, Deque<Object>>());
}
/**
* This will return the List of all applicable candidates for the given template call with the given
* arguments. These will be ordered as described on {@link #reorderCandidatesPriority(Module, Set)}.
*
* @param origin
* Origin of the template call.
* @param call
* The called element.
* @param arguments
* Arguments of the call.
* @return The set of all applicable templates for these arguments
*/
public List<Template> getAllCandidates(Module origin, Template call, List<Object> arguments) {
final List<Object> argumentTypes = new ArrayList<Object>(arguments.size());
for (final Object arg : arguments) {
if (arg instanceof EObject) {
argumentTypes.add(((EObject)arg).eClass());
} else if (arg != null) {
argumentTypes.add(arg.getClass());
} else {
argumentTypes.add(NULL_ARGUMENT);
}
}
/*
* NOTE : we depend on the ordering offered by List types. Do not change implementation without
* testing.
*/
final List<Template> orderedNamesakes = reorderCandidatesPriority(origin, getAllCandidateNamesakes(
origin, call, argumentTypes));
final List<Template> dynamicOverriding = reorderDynamicOverrides(getAllDynamicCandidateOverriding(
orderedNamesakes, argumentTypes));
final List<Template> overriding = getAllCandidateOverriding(origin, orderedNamesakes, argumentTypes);
final List<Template> applicableCandidates = new ArrayList<Template>();
// overriding templates come first, then namesakes
applicableCandidates.addAll(dynamicOverriding);
applicableCandidates.addAll(overriding);
applicableCandidates.addAll(orderedNamesakes);
return applicableCandidates;
}
/**
* Returns the most specific template for the given arguments in the given list.
*
* @param candidates
* List of templates candidates to be substituted.
* @param arguments
* Arguments of the call.
* @return The most specific templates for <code>arguments</code>.
*/
public Template getMostSpecificTemplate(List<Template> candidates, List<Object> arguments) {
final Iterator<Template> candidateIterator = candidates.iterator();
if (candidates.size() == 1) {
return candidateIterator.next();
}
final List<Object> argumentTypes = new ArrayList<Object>(arguments.size());
for (final Object arg : arguments) {
if (arg instanceof EObject) {
argumentTypes.add(((EObject)arg).eClass());
} else if (arg != null) {
argumentTypes.add(arg.getClass());
}
}
Template mostSpecific = candidateIterator.next();
while (candidateIterator.hasNext()) {
mostSpecific = mostSpecificTemplate(mostSpecific, candidateIterator.next(), arguments);
}
return mostSpecific;
}
/**
* Returns the list of available properties files.
*
* @return The list of available properties files.
*/
public AcceleoPropertiesLookup getPropertiesLookup() {
return propertiesLookup;
}
/**
* This will return the map of currently available variables. Take note that this is not meant to be used
* to alter the variables (which won't work since what we return is a copy of the variable map) but more
* to query the variable state.
*
* @return The map of currently available variables.
*/
public Map<String, Object> getCurrentVariables() {
Map<String, Deque<Object>> variableMap = scopedVariableMap.getLast();
Map<String, Object> availableVariables = new HashMap<String, Object>();
for (Map.Entry<String, Deque<Object>> var : variableMap.entrySet()) {
if (!var.getValue().isEmpty()) {
availableVariables.put(var.getKey(), var.getValue().getLast());
}
}
return availableVariables;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.ecore.EcoreEvaluationEnvironment#getInvalidResult()
*/
@Override
public Object getInvalidResult() {
return super.getInvalidResult();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#getValueOf(java.lang.String)
*/
@Override
public Object getValueOf(String name) {
Object value = null;
Map<String, Deque<Object>> variableMap = scopedVariableMap.getLast();
if (variableMap.containsKey(name)) {
value = variableMap.get(name).getLast();
} else if (globalVariableMap.containsKey(name)) {
value = globalVariableMap.get(name).getLast();
}
return value;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#overrides(org.eclipse.emf.ecore.EOperation, int)
*/
@Override
public boolean overrides(EOperation operation, int opcode) {
boolean result = false;
if (operation.getEAnnotation("MTL") != null) { //$NON-NLS-1$
result = true;
} else if (operation.getEAnnotation("MTL non-standard") != null) { //$NON-NLS-1$
if (opcode == PredefinedType.PLUS) {
Adapter adapter = EcoreUtil.getAdapter(operation.eAdapters(), AcceleoOverrideAdapter.class);
result = adapter != null;
operation.eAdapters().remove(adapter);
} else {
result = true;
}
} else {
result = super.overrides(operation, opcode);
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#remove(java.lang.String)
*/
@Override
public Object remove(String name) {
Map<String, Deque<Object>> variableMap;
if (scopedVariableMap.getLast().containsKey(name)) {
variableMap = scopedVariableMap.getLast();
} else if (globalVariableMap.containsKey(name)) {
variableMap = globalVariableMap;
} else {
return null;
}
final Object removedValue = variableMap.get(name).removeLast();
if (variableMap.get(name).size() == 0) {
variableMap.remove(name);
}
return removedValue;
}
/**
* This will remove and return the last variable scope.
*
* @return Removes and return the last variable scope.
*/
public Map<String, Deque<Object>> removeVariableScope() {
return scopedVariableMap.removeLast();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#replace(java.lang.String, java.lang.Object)
*/
@Override
public void replace(String name, Object value) {
Map<String, Deque<Object>> variableMap;
if (name.startsWith(TEMPORARY_CONTEXT_VAR_PREFIX) || name.startsWith(TEMPORARY_INVOCATION_ARG_PREFIX)) {
variableMap = globalVariableMap;
} else {
variableMap = scopedVariableMap.getLast();
}
if (variableMap.containsKey(name)) {
variableMap.get(name).removeLast();
}
add(name, value);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#toString()
*/
@Override
public String toString() {
Map<String, Deque<Object>> variableMap = scopedVariableMap.getLast();
return variableMap.toString();
}
/**
* Filters non-applicable templates out of the candidates list.
*
* @param candidates
* List of templates that needs to be filtered out.
* @param argumentTypes
* Types of the arguments for the call.
* @return The set of applicable templates.
*/
private Set<Template> applicableTemplates(Set<Template> candidates, List<Object> argumentTypes) {
final Set<Template> applicableCandidates = new LinkedHashSet<Template>(candidates);
for (final Template candidate : candidates) {
if (candidate.getParameter().size() != argumentTypes.size()) {
applicableCandidates.remove(candidate);
}
}
for (int i = 0; i < argumentTypes.size(); i++) {
for (final Template candidate : new LinkedHashSet<Template>(applicableCandidates)) {
final Object parameterType = candidate.getParameter().get(i).getType();
if (!isApplicableArgument(parameterType, argumentTypes.get(i))) {
applicableCandidates.remove(candidate);
}
}
}
return applicableCandidates;
}
/**
* This will return the list of all namesakes of the template <code>call</code> applicable for
* <code>arguments</code>.
*
* @param origin
* Origin of the template call.
* @param call
* The called element.
* @param argumentTypes
* Types of the arguments of the call.
* @return All of the applicable templates of this name in the current context.
*/
private Set<Template> getAllCandidateNamesakes(Module origin, Template call, List<Object> argumentTypes) {
final Set<Template> namesakes = new LinkedHashSet<Template>();
final Set<Template> candidates = templates.get(call.getName());
if (candidates == null) {
throw new AcceleoEvaluationException(AcceleoEngineMessages
.getString("AcceleoEvaluationEnvironment.ModuleResolutionError")); //$NON-NLS-1$
}
Set<Module> scope = new HashSet<Module>();
scope.add(origin);
scope.addAll(getScopeOf(origin));
for (Template candidate : candidates) {
if (scope.contains(candidate.eContainer())) {
namesakes.add(candidate);
}
}
if (namesakes.size() == 1) {
return namesakes;
}
namesakes.retainAll(applicableTemplates(candidates, argumentTypes));
return namesakes;
}
/**
* Returns the whole scope of modules visible from <code>module</code>.
*
* @param module
* Module of which we need the scope.
* @return The whole scope of modules visible from <code>module</code>.
*/
private Set<Module> getScopeOf(Module module) {
Set<Module> scope = new HashSet<Module>();
if (module.getExtends().size() > 0) {
// Only supports single inheritance
Module extended = module.getExtends().get(0);
scope.add(extended);
scope.addAll(getExtendedScope(extended));
}
List<Module> imports = module.getImports();
scope.addAll(imports);
for (Module importedModule : imports) {
scope.addAll(getExtendedScope(importedModule));
}
return scope;
}
/**
* Returns the whole scope of modules visible thanks to an extends.
*
* @param module
* Module of which we need the scope.
* @return The whole scope of modules visible thanks to an extends.
*/
private Set<Module> getExtendedScope(Module module) {
Set<Module> scope = new HashSet<Module>();
if (module.getExtends().size() > 0) {
// Only supports single inheritance
Module extended = module.getExtends().get(0);
scope.add(extended);
scope.addAll(getExtendedScope(extended));
}
return scope;
}
/**
* This will return the list of all templates overriding one of <code>overridenTemplates</code> that are
* applicable for <code>arguments</code>. These will be ordered as specified on
* {@link #reorderCandidatesPriority(Module, Set)}.
*
* @param origin
* Origin of the template call.
* @param overridenTemplates
* List of templates we seek overriding templates of.
* @param argumentTypes
* Types of the arguments of the call.
* @return All of the applicable templates overriding one of <code>overridenTemplates</code> in the
* current context.
*/
private List<Template> getAllCandidateOverriding(Module origin, List<Template> overridenTemplates,
List<Object> argumentTypes) {
final List<Template> candidateOverriding = new ArrayList<Template>();
for (final Template overriden : overridenTemplates) {
final Set<Template> candidates = overridingTemplates.get(overriden);
if (candidates != null) {
final Set<Template> applicableCandidates = applicableTemplates(candidates, argumentTypes);
for (Template template : applicableCandidates) {
EObject eContainer = template.eContainer();
if (eContainer instanceof Module
&& (getScopeOf(origin).contains((Module)eContainer) || ((Module)eContainer)
.equals(origin))) {
candidateOverriding.add(template);
}
}
// no need to order this, it'll be ordered later on
candidateOverriding.addAll(getAllCandidateOverriding(origin, new ArrayList<Template>(
applicableCandidates), argumentTypes));
}
}
Collections.reverse(candidateOverriding);
return candidateOverriding;
}
/**
* This will return the list of all templates dynamically overriding one of
* <code>overridenTemplates</code> that are applicable for <code>arguments</code>.
*
* @param overridenTemplates
* List of templates we seek overriding templates of.
* @param argumentTypes
* Types of the arguments of the call.
* @return All of the applicable templates dynamically overriding one of <code>overridenTemplates</code>.
*/
private Set<Template> getAllDynamicCandidateOverriding(List<Template> overridenTemplates,
List<Object> argumentTypes) {
final Set<Template> dynamicOverriding = new LinkedHashSet<Template>();
for (final Template overriden : overridenTemplates) {
final Set<Template> candidates = dynamicOverrides.get(overriden);
if (candidates != null) {
final Set<Template> applicableCandidates = applicableTemplates(candidates, argumentTypes);
dynamicOverriding.addAll(applicableCandidates);
}
}
return dynamicOverriding;
}
/**
* This is meant to be used in the Dynamic URI converter only.
*
* @return The set of all currently accessible modules.
*/
Set<Module> getCurrentModules() {
return new HashSet<Module>(currentModules);
}
/**
* Returns <code>true</code> if the value is applicable to the given type, <code>false</code> otherwise.
*
* @param expectedType
* Expected type of the argument.
* @param argumentType
* type of the argument we're trying to use as an argument.
* @return <code>true</code> if the value is applicable to the given type, <code>false</code> otherwise.
*/
private boolean isApplicableArgument(Object expectedType, Object argumentType) {
boolean isApplicable = false;
if (argumentType == NULL_ARGUMENT) {
isApplicable = true;
} else if (expectedType instanceof EClass && argumentType instanceof EClass) {
isApplicable = expectedType == argumentType || isSubTypeOf(expectedType, argumentType);
} else if (expectedType instanceof Class<?> && argumentType instanceof Class<?>) {
isApplicable = ((Class<?>)expectedType).isAssignableFrom((Class<?>)argumentType);
} else if (expectedType instanceof EDataType && argumentType instanceof Class<?>) {
isApplicable = ((EDataType)expectedType).getInstanceClass() == argumentType;
} else if (expectedType instanceof AnyType) {
isApplicable = true;
} else {
isApplicable = expectedType.getClass().isInstance(argumentType);
}
return isApplicable;
}
/**
* Returns <code>true</code> if <code>eClass</code> is a sub-type of <code>superType</code>,
* <code>false</code> otherwise.
*
* @param superType
* Expected super type of <code>eClass</code>.
* @param eClass
* EClass to consider.
* @return <code>true</code> if <code>eClass</code> is a sub-type of <code>superType</code>,
* <code>false</code> otherwise.
*/
private boolean isSubTypeOf(Object superType, Object eClass) {
// if both types are EClass(es) then do the usual stuff
boolean result = false;
if (superType instanceof EClass && eClass instanceof EClass) {
for (final EClass candidate : ((EClass)eClass).getEAllSuperTypes()) {
if (candidate == superType) {
result = true;
break;
}
}
} else if (superType instanceof AnyType) {
result = true;
}
return result;
}
/**
* This will load all dynamic modules in the first {@link ResourceSet} found by iterating over the
* {@link #currentModules}.
*
* @return The set of loaded modules.
*/
private Set<Module> loadDynamicModules() {
final Set<File> dynamicModuleFiles = AcceleoDynamicTemplatesRegistry.INSTANCE.getRegisteredModules();
final Set<Module> dynamicModules = new LinkedHashSet<Module>();
// shortcut
if (dynamicModuleFiles.size() > 0) {
ResourceSet resourceSet = null;
for (Module module : currentModules) {
if (module.eResource() != null && module.eResource().getResourceSet() != null) {
resourceSet = module.eResource().getResourceSet();
break;
}
}
// If we couldn't find a resourceSet, break the loading loop and log an exception
if (resourceSet == null) {
// set as a blocker so that it is logged as an error
AcceleoEnginePlugin.log(AcceleoEngineMessages
.getString("AcceleoEvaluationEnvironment.DynamicModulesLoadingFailure"), true); //$NON-NLS-1$
return dynamicModules;
}
if (!(resourceSet.getURIConverter() instanceof DynamicModulesURIConverter)) {
resourceSet.setURIConverter(new DynamicModulesURIConverter(resourceSet.getURIConverter(),
this));
}
for (File moduleFile : dynamicModuleFiles) {
if (moduleFile.exists() && moduleFile.canRead()) {
try {
Resource res = ModelUtils.load(moduleFile, resourceSet).eResource();
for (EObject root : res.getContents()) {
if (root instanceof Module) {
dynamicModules.add((Module)root);
}
}
} catch (IOException e) {
AcceleoEnginePlugin.log(e, false);
}
}
}
}
return dynamicModules;
}
/**
* This will resolve all dependencies of the given module and keep references to all accessible templates.
*
* @param module
* We will resolve dependencies for this module and keep references to all accessible
* templates.
*/
private void mapAllTemplates(Module module) {
if (currentModules.contains(module)) {
return;
}
currentModules.add(module);
for (final ModuleElement elem : module.getOwnedModuleElement()) {
if (elem instanceof Template) {
Set<Template> namesakes = templates.get(elem.getName());
if (namesakes == null) {
namesakes = new LinkedHashSet<Template>();
templates.put(elem.getName(), namesakes);
}
namesakes.add((Template)elem);
mapOverridingTemplate((Template)elem);
}
}
for (final Module extended : module.getExtends()) {
mapAllTemplates(extended);
}
for (final Module imported : module.getImports()) {
mapAllTemplates(imported);
}
}
/**
* Handles the mapping of a single dynamic module.
*
* @param module
* Module that is to be mapped as a dynamic module.
* @param dynamicModules
* The set of all dynamic modules as returned by {@link #loadDynamicModules()}.
*/
private void mapDynamicModule(Module module, Set<Module> dynamicModules) {
boolean map = false;
final Set<Module> unMappedRequiredModules = new LinkedHashSet<Module>();
for (Module extended : module.getExtends()) {
if (dynamicModules.contains(extended)) {
mapDynamicModule(extended, dynamicModules);
}
if (currentModules.contains(extended)) {
map = true;
} else {
unMappedRequiredModules.add(extended);
}
}
// This module shouldn't be added to the context. Go to next.
if (!map) {
return;
}
for (Module imported : module.getImports()) {
if (!currentModules.contains(imported)) {
unMappedRequiredModules.add(imported);
}
}
for (Module required : unMappedRequiredModules) {
mapAllTemplates(required);
}
for (final ModuleElement elem : module.getOwnedModuleElement()) {
if (elem instanceof Template) {
final Template ownedTemplate = (Template)elem;
for (final Template overriden : ownedTemplate.getOverrides()) {
Set<Template> overriding = dynamicOverrides.get(overriden);
if (overriding == null && templates.containsKey(overriden.getName())) {
overriding = new LinkedHashSet<Template>();
Template match = overriden;
Set<Template> candidates = templates.get(overriden.getName());
for (Template template : candidates) {
if (EcoreUtil.equals(template, overriden)) {
match = template;
break;
}
}
dynamicOverrides.put(match, overriding);
}
if (overriding != null) {
overriding.add(ownedTemplate);
}
}
if (ownedTemplate.getOverrides().size() == 0) {
Set<Template> namesakes = templates.get(ownedTemplate.getName());
if (namesakes == null) {
namesakes = new LinkedHashSet<Template>();
templates.put(ownedTemplate.getName(), namesakes);
}
namesakes.add(ownedTemplate);
}
}
}
currentModules.add(module);
}
/**
* Maps dynamic overriding templates for smoother polymorphic resolution.
*/
private void mapDynamicOverrides() {
Set<Module> dynamicModules = loadDynamicModules();
for (Module module : dynamicModules) {
mapDynamicModule(module, dynamicModules);
}
}
/**
* This will create entries for the given template in the overriding map as needed.
*
* @param elem
* The template which we need to map if overriding.
*/
private void mapOverridingTemplate(Template elem) {
for (final Template overriden : elem.getOverrides()) {
Set<Template> overriding = overridingTemplates.get(overriden);
if (overriding == null) {
overriding = new LinkedHashSet<Template>();
overridingTemplates.put(overriden, overriding);
}
overriding.add(elem);
}
}
/**
* Returns the most specific template of the given two for the given arguments.
*
* @param template1
* First of the two compared templates.
* @param template2
* Second of the compared templates.
* @param actualArgumentTypes
* Types of the actual arguments of the call.
* @return The most specific templates for <code>actualArgumentTypes</code>.
*/
private Template mostSpecificTemplate(Template template1, Template template2,
List<Object> actualArgumentTypes) {
Template mostSpecific;
// number of arguments which are more specific on template1 as compared to template2
int template1SpecificArgumentCount = 0;
// ...
int template2SpecificArgumentCount = 0;
for (int i = 0; i < actualArgumentTypes.size(); i++) {
final Object actualArgumentType = actualArgumentTypes.get(i);
final EClassifier template1Type = template1.getParameter().get(i).getType();
final EClassifier template2Type = template2.getParameter().get(i).getType();
if (template1Type == template2Type) {
continue;
}
if (actualArgumentType instanceof EObject) {
if (isSubTypeOf(template1Type, template2Type)) {
template2SpecificArgumentCount++;
} else {
template1SpecificArgumentCount++;
}
} else {
// TODO are there any chance the argument would not be an EObject?
assert false;
}
}
if (template1SpecificArgumentCount >= template2SpecificArgumentCount) {
mostSpecific = template1;
} else {
mostSpecific = template2;
}
return mostSpecific;
}
/**
* Reorders the <code>candidates</code> list so that templates overriding <code>origin</code> come first.
* Templates in the same module as <code>call</code> come second, then templates in extended modules, and
* finally templates in imported modules.
*
* @param origin
* The originating module.
* @param candidates
* List that is to be reordered.
* @return The reordered list.
*/
private List<Template> reorderCandidatesPriority(Module origin, Set<Template> candidates) {
// FIXME Performance bottleneck. How can we order candidates of a template call more effectively?
final List<Template> reorderedList = new ArrayList<Template>(candidates.size());
// We only support single inheritance. get(0) comes from that.
for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
boolean isOverridingCandidate = false;
Module module = (Module)candidate.eContainer();
while (!isOverridingCandidate && module != null && module.getExtends().size() > 0) {
if (module.getExtends().get(0) == origin) {
reorderedList.add(candidate);
candidates.remove(candidate);
isOverridingCandidate = true;
}
module = module.getExtends().get(0);
}
}
for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
if (candidate.eContainer() == origin) {
reorderedList.add(candidate);
candidates.remove(candidate);
}
}
for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
for (final Module extended : origin.getExtends()) {
if (candidate.eContainer() == extended) {
reorderedList.add(candidate);
candidates.remove(candidate);
}
}
}
for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
for (final Module imported : origin.getImports()) {
if (candidate.eContainer() == imported) {
reorderedList.add(candidate);
candidates.remove(candidate);
}
}
}
for (final Template candidate : new LinkedHashSet<Template>(candidates)) {
for (final Module imported : origin.getImports()) {
Module myImportedModule = imported;
while (myImportedModule.getExtends().size() > 0) {
if (myImportedModule.getExtends().get(0) == candidate.eContainer()) {
reorderedList.add(candidate);
candidates.remove(candidate);
}
myImportedModule = myImportedModule.getExtends().get(0);
}
}
}
return reorderedList;
}
/**
* Reorders the given list of candidates by order of overriding. For example if the set contains T11
* overriding T1, T21 overriding T11, T31 overriding T11 and T12 overriding T1, The returned list will
* contain in this order : {T31, T12, T21, T11}.
*
* @param candidates
* Set of candidates that are to be reordered.
* @return The reordered list of candidates.
*/
private List<Template> reorderDynamicOverrides(Set<Template> candidates) {
final List<Template> reorderedList = new ArrayList<Template>(candidates.size());
final Set<Template> lowest = new LinkedHashSet<Template>(candidates);
while (!lowest.isEmpty()) {
for (final Template candidate : new LinkedHashSet<Template>(lowest)) {
for (final Template overriden : candidate.getOverrides()) {
if (lowest.contains(overriden)) {
lowest.remove(overriden);
}
}
}
if (lowest.isEmpty()) {
List<Template> remainingCandidates = new ArrayList<Template>(candidates);
remainingCandidates.removeAll(reorderedList);
reorderedList.addAll(remainingCandidates);
} else {
reorderedList.addAll(lowest);
lowest.clear();
lowest.addAll(candidates);
lowest.removeAll(reorderedList);
}
}
return reorderedList;
}
}