| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |