blob: db82d8653590c5159a7287459fd1e87163ee56e3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2012 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 com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.acceleo.common.IAcceleoConstants;
import org.eclipse.acceleo.common.internal.utils.workspace.BundleURLConverter;
import org.eclipse.acceleo.common.utils.AcceleoCollections;
import org.eclipse.acceleo.common.utils.CircularArrayDeque;
import org.eclipse.acceleo.common.utils.CompactHashSet;
import org.eclipse.acceleo.common.utils.CompactLinkedHashSet;
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.AcceleoDynamicTemplatesEclipseUtil;
import org.eclipse.acceleo.engine.service.AcceleoDynamicTemplatesRegistry;
import org.eclipse.acceleo.engine.service.AcceleoModulePropertiesAdapter;
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.util.BasicEList;
import org.eclipse.emf.common.util.ECollections;
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.EDataType;
import org.eclipse.emf.ecore.EEnum;
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.BagType;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.ecore.EcoreEvaluationEnvironment;
import org.eclipse.ocl.ecore.OrderedSetType;
import org.eclipse.ocl.ecore.SequenceType;
import org.eclipse.ocl.ecore.SetType;
import org.eclipse.ocl.ecore.Variable;
import org.eclipse.ocl.options.EvaluationOptions;
import org.eclipse.ocl.util.Bag;
/**
* 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 CompactHashSet<Module>();
/** Maps dynamic overrides as registered in the {@link AcceleoDynamicTemplatesRegistry}. */
private final SetMultimap<Template, Template> dynamicOverrides = AcceleoCollections
.newCompactLinkedHashSetMultimap();
/** Maps all overriding templates to their <code>super</code>. */
private final SetMultimap<Template, Template> overridingTemplates = AcceleoCollections
.newCompactLinkedHashSetMultimap();
/** 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 SetMultimap<String, Template> templates = AcceleoCollections
.newCompactLinkedHashSetMultimap();
/**
* 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<ListMultimap<String, Object>> scopedVariableMap = new CircularArrayDeque<ListMultimap<String, Object>>();
/**
* This will contain variables that are global to a generation module.
*/
private final ListMultimap<String, Object> globalVariableMap = AcceleoCollections
.newCircularArrayDequeMultimap();
/**
* 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(AcceleoCollections.<String, Object> newCircularArrayDequeMultimap());
mapAllTemplates(module);
AcceleoModulePropertiesAdapter adapter = (AcceleoModulePropertiesAdapter)EcoreUtil.getAdapter(module
.eAdapters(), AcceleoModulePropertiesAdapter.class);
if (adapter == null || !adapter.getProperties().contains(IAcceleoConstants.DISABLE_DYNAMIC_MODULES)) {
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(AcceleoCollections.<String, Object> newCircularArrayDequeMultimap());
mapAllTemplates(module);
AcceleoModulePropertiesAdapter adapter = (AcceleoModulePropertiesAdapter)EcoreUtil.getAdapter(module
.eAdapters(), AcceleoModulePropertiesAdapter.class);
if (adapter == null || !adapter.getProperties().contains(IAcceleoConstants.DISABLE_DYNAMIC_MODULES)) {
mapDynamicOverrides();
}
setOption(EvaluationOptions.LAX_NULL_HANDLING, Boolean.FALSE);
propertiesLookup = properties;
}
/**
* Returns the last value of the given list.
* <p>
* Makes no effort to try and check whether the argument is valid.
* </p>
*
* @param values
* List we need the last value from.
* @param <V>
* Type of the list's values.
* @return The last value of the given list.
*/
private static <V> V getLast(List<V> values) {
final ListIterator<V> iterator = values.listIterator(values.size());
return iterator.previous();
}
/**
* Removes the last value of the given list.
* <p>
* Makes no effort to try and check whether the argument is valid.
* </p>
*
* @param values
* List we need the last value from.
* @param <V>
* Type of the list's values.
* @return The last value of the given list.
*/
private static <V> V removeLast(List<V> values) {
final ListIterator<V> iterator = values.listIterator(values.size());
final V last = iterator.previous();
iterator.remove();
return last;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#add(java.lang.String, java.lang.Object)
*/
@Override
public void add(String name, Object value) {
ListMultimap<String, Object> variableMap;
if (name.startsWith(TEMPORARY_CONTEXT_VAR_PREFIX) || name.startsWith(TEMPORARY_INVOCATION_ARG_PREFIX)) {
variableMap = globalVariableMap;
} else {
variableMap = scopedVariableMap.getLast();
}
variableMap.put(name, 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;
/*
* 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(AcceleoCollections.<String, Object> newCircularArrayDequeMultimap());
}
/**
* 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, Object[] arguments) {
final List<Object> argumentTypes = new ArrayList<Object>(arguments.length);
for (int i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof EObject) {
argumentTypes.add(((EObject)arguments[i]).eClass());
} else if (arguments[i] != null) {
argumentTypes.add(arguments[i].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);
// overriding templates come first, then namesakes
return Lists.newArrayList(Iterables.concat(dynamicOverriding, overriding, orderedNamesakes));
}
/**
* 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(Iterable<Template> candidates, Object[] arguments) {
final Iterator<Template> candidateIterator = candidates.iterator();
Template mostSpecific = candidateIterator.next();
if (!candidateIterator.hasNext()) {
return mostSpecific;
}
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() {
ListMultimap<String, Object> variableMap = scopedVariableMap.getLast();
Map<String, Object> availableVariables = new HashMap<String, Object>();
for (String key : variableMap.keys()) {
List<Object> values = variableMap.get(key);
availableVariables.put(key, getLast(values));
}
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;
ListMultimap<String, Object> variableMap = scopedVariableMap.getLast();
if (variableMap.containsKey(name)) {
value = getLast(variableMap.get(name));
} else if (globalVariableMap.containsKey(name)) {
value = getLast(globalVariableMap.get(name));
}
return value;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#remove(java.lang.String)
*/
@Override
public Object remove(String name) {
ListMultimap<String, Object> variableMap;
if (scopedVariableMap.getLast().containsKey(name)) {
variableMap = scopedVariableMap.getLast();
} else if (globalVariableMap.containsKey(name)) {
variableMap = globalVariableMap;
} else {
return null;
}
return removeLast(variableMap.get(name));
}
/**
* This will remove and return the last variable scope.
*
* @return Removes and return the last variable scope.
*/
public Map<String, Collection<Object>> removeVariableScope() {
return scopedVariableMap.removeLast().asMap();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#replace(java.lang.String, java.lang.Object)
*/
@Override
public void replace(String name, Object value) {
ListMultimap<String, 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)) {
removeLast(variableMap.get(name));
}
add(name, value);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.ocl.AbstractEvaluationEnvironment#toString()
*/
@Override
public String toString() {
ListMultimap<String, 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, final List<Object> argumentTypes) {
Predicate<Template> argumentSizeMatch = new Predicate<Template>() {
public boolean apply(Template input) {
final List<Variable> parameters = input.getParameter();
return parameters.size() == argumentTypes.size() || parameters.isEmpty()
&& argumentTypes.size() == 1;
}
};
Predicate<Template> argumentsMatch = new Predicate<Template>() {
public boolean apply(Template input) {
final List<Variable> parameters = input.getParameter();
if (parameters.isEmpty() && argumentTypes.size() == 1) {
return true;
}
boolean argumentMatch = true;
for (int i = 0; i < argumentTypes.size() && argumentMatch; i++) {
final Variable param = parameters.get(i);
final Object parameterType = param.getType();
argumentMatch = isApplicableArgument(parameterType, argumentTypes.get(i));
}
return argumentMatch;
}
};
return Sets.filter(candidates, Predicates.and(argumentSizeMatch, argumentsMatch));
}
/**
* 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 CompactLinkedHashSet<Template>();
final Set<Template> candidates = templates.get(call.getName());
final int candidateSize = candidates.size();
if (candidateSize == 0) {
throw new AcceleoEvaluationException(AcceleoEngineMessages
.getString("AcceleoEvaluationEnvironment.ModuleResolutionError")); //$NON-NLS-1$
}
Set<Module> scope = Sets.union(Collections.singleton(origin), getScopeOf(origin));
for (Template candidate : candidates) {
if (scope.contains(candidate.eContainer())) {
namesakes.add(candidate);
}
}
if (namesakes.size() == 1) {
return namesakes;
}
namesakes.retainAll(applicableTemplates(namesakes, 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 CompactHashSet<Module>();
if (module.getExtends().size() > 0) {
// Only supports single inheritance
Module extended = module.getExtends().get(0);
scope.add(extended);
scope = Sets.union(scope, getExtendedScope(extended));
}
List<Module> imports = module.getImports();
for (int i = 0; i < imports.size(); i++) {
final Module importedModule = imports.get(i);
scope = Sets.union(scope, Collections.singleton(importedModule));
scope = Sets.union(scope, 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 CompactHashSet<Module>();
if (module.getExtends().size() > 0) {
// Only supports single inheritance
Module extended = module.getExtends().get(0);
scope.add(extended);
scope = Sets.union(scope, 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 (int i = 0; i < overridenTemplates.size(); i++) {
final Set<Template> candidates = overridingTemplates.get(overridenTemplates.get(i));
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(eContainer) || 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 CompactLinkedHashSet<Template>();
for (int i = 0; i < overridenTemplates.size(); i++) {
final Set<Template> candidates = dynamicOverrides.get(overridenTemplates.get(i));
if (candidates != null && !candidates.isEmpty()) {
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 CompactHashSet<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 == argumentType) {
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<?>) {
if (expectedType instanceof BagType && argumentType instanceof Class<?>) {
Class<?> clazz = (Class<?>)argumentType;
isApplicable = Bag.class.isAssignableFrom(clazz);
} else if (expectedType instanceof OrderedSetType && argumentType instanceof Class<?>) {
Class<?> clazz = (Class<?>)argumentType;
isApplicable = Set.class.isAssignableFrom(clazz);
} else if (expectedType instanceof SetType && argumentType instanceof Class<?>) {
Class<?> clazz = (Class<?>)argumentType;
isApplicable = Set.class.isAssignableFrom(clazz);
} else if (expectedType instanceof SequenceType && argumentType instanceof Class<?>) {
Class<?> clazz = (Class<?>)argumentType;
isApplicable = List.class.isAssignableFrom(clazz);
} else if (expectedType instanceof CollectionType && argumentType instanceof Class<?>) {
Class<?> clazz = (Class<?>)argumentType;
isApplicable = Collection.class.isAssignableFrom(clazz);
} else {
isApplicable = ((EDataType)expectedType).getInstanceClass() == argumentType;
}
} else if (expectedType instanceof EEnum && argumentType instanceof EClass) {
isApplicable = argumentType.equals(EcorePackage.eINSTANCE.getEEnumLiteral());
} 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 = new CompactLinkedHashSet<File>();
final Set<Module> dynamicModules = new CompactLinkedHashSet<Module>();
// shortcut
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));
}
// We have a resource set, let's find out where its module are coming from
List<Resource> resources = resourceSet.getResources();
Iterator<Resource> iterator = resources.iterator();
while (iterator.hasNext()) {
Resource resource = iterator.next();
URI uri = resource.getURI();
String generatorID = uri.toString();
// Chicken sacrifice done right! /!\ Warning voodoo magic /!\
if (uri.isPlatformPlugin() && uri.segments().length > 2) {
generatorID = uri.segment(1);
} else if (uri.isPlatformResource() && uri.segments().length > 2) {
// Not supposed to happen since extension point works only when deployed in eclipse
generatorID = uri.segment(1);
} else if (uri.isPlatform() && uri.segments().length > 2) {
// Not supposed to happen since extension point works only when deployed in eclipse
generatorID = uri.segment(1);
} else if (uri.isFile() || generatorID.startsWith("jar:file:")) { //$NON-NLS-1$
BundleURLConverter converter = new BundleURLConverter(generatorID);
generatorID = converter.resolveAsPlatformPlugin();
// generatorID = AcceleoWorkspaceUtil.resolveAsPlatformPlugin(generatorID);
if (generatorID != null
&& generatorID.startsWith("platform:/plugin/") && URI.createURI(generatorID).segments().length > 2) { //$NON-NLS-1$
URI tmpURI = URI.createURI(generatorID);
generatorID = tmpURI.segment(1);
}
}
final Set<File> dynamicAcceleoModulesFiles = AcceleoDynamicTemplatesRegistry.INSTANCE
.getRegisteredModules(generatorID);
dynamicModuleFiles.addAll(dynamicAcceleoModulesFiles);
}
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) {
templates.put(elem.getName(), (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 CompactLinkedHashSet<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()) {
Template match = null;
final Iterator<Template> templateIterator = templates.get(overriden.getName()).iterator();
while (match == null && templateIterator.hasNext()) {
final Template template = templateIterator.next();
if (EcoreUtil.equals(template, overriden)) {
match = template;
}
}
if (match != null) {
dynamicOverrides.put(match, ownedTemplate);
}
}
templates.put(ownedTemplate.getName(), ownedTemplate);
}
}
currentModules.add(module);
}
/**
* Maps dynamic overriding templates for smoother polymorphic resolution.
*/
private void mapDynamicOverrides() {
if (!AcceleoDynamicTemplatesEclipseUtil.hasDynamicModulesDescriptors()) {
return;
}
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()) {
overridingTemplates.put(overriden, 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, 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.length; i++) {
final Object actualArgumentType = actualArgumentTypes[i];
if (template1.getParameter().size() == 0 && template2.getParameter().size() == 0) {
continue;
}
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 if (actualArgumentType instanceof Collection<?>) {
template1SpecificArgumentCount++;
} else {
// are there any chance the argument would not be an EObject?
throw new AcceleoEvaluationException(AcceleoEngineMessages.getString(
"AcceleoEvaluationVisitor.ArgumentNotEObjectOrCollection", actualArgumentType)); //$NON-NLS-1$
}
}
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) {
final List<Template> reorderedList = new ArrayList<Template>(candidates.size());
// We only support single inheritance. get(0) comes from that.
Iterator<Template> candidateIterator = candidates.iterator();
while (candidateIterator.hasNext()) {
final Template candidate = candidateIterator.next();
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);
candidateIterator.remove();
isOverridingCandidate = true;
}
module = module.getExtends().get(0);
}
}
candidateIterator = candidates.iterator();
while (candidateIterator.hasNext()) {
final Template candidate = candidateIterator.next();
if (candidate.eContainer() == origin) {
reorderedList.add(candidate);
candidateIterator.remove();
}
}
candidateIterator = candidates.iterator();
while (candidateIterator.hasNext()) {
final Template candidate = candidateIterator.next();
for (final Module extended : origin.getExtends()) {
if (candidate.eContainer() == extended) {
reorderedList.add(candidate);
candidateIterator.remove();
}
}
}
candidateIterator = candidates.iterator();
while (candidateIterator.hasNext()) {
final Template candidate = candidateIterator.next();
for (final Module imported : origin.getImports()) {
if (candidate.eContainer() == imported) {
reorderedList.add(candidate);
candidateIterator.remove();
}
}
}
candidateIterator = candidates.iterator();
while (candidateIterator.hasNext()) {
final Template candidate = candidateIterator.next();
for (final Module imported : origin.getImports()) {
Module myImportedModule = imported;
boolean shouldBreak = false;
while (myImportedModule.getExtends().size() > 0) {
if (myImportedModule.getExtends().get(0) == candidate.eContainer()) {
reorderedList.add(candidate);
candidateIterator.remove();
shouldBreak = true;
}
myImportedModule = myImportedModule.getExtends().get(0);
}
if (shouldBreak) {
break;
}
}
}
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, T21, T12, T11}.
*
* @param candidates
* Set of candidates that are to be reordered.
* @return The reordered list of candidates.
*/
private List<Template> reorderDynamicOverrides(Set<Template> candidates) {
Template[] array = candidates.toArray(new Template[candidates.size()]);
Arrays.sort(array, new TemplateComparator());
return Arrays.asList(array);
}
/**
* This comparator will allow us to reorder Templates according to their overrides.
*
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
*/
private class TemplateComparator implements Comparator<Template> {
/**
* {@inheritDoc}
*
* @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
*/
public int compare(Template o1, Template o2) {
// Tries and find which template is "deeper" in the overriding tree.
// We only consider simple overrides, hence the "get(0)" in the following code.
Template currentLevel1 = o1;
Template currentLevel2 = o2;
boolean no1Override = currentLevel1.getOverrides().isEmpty();
boolean no2Override = currentLevel2.getOverrides().isEmpty();
while (!no1Override && !no2Override) {
currentLevel1 = currentLevel1.getOverrides().get(0);
currentLevel2 = currentLevel2.getOverrides().get(0);
no1Override = currentLevel1.getOverrides().isEmpty();
no2Override = currentLevel2.getOverrides().isEmpty();
}
int result = 0;
if (no1Override && no2Override) {
// same "level". Use the name's ordering
result = o1.getName().compareTo(o2.getName());
} else if (no1Override) {
result = 1;
} else if (no2Override) {
result = -1;
}
return result;
}
}
}