blob: bc1bfb26541fc9dca02f33c5b63195844230d6f1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013 THALES GLOBAL SERVICES.
* 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
*******************************************************************************/
package org.eclipse.sirius.business.api.dialect.description;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.sirius.business.api.query.EObjectQuery;
import org.eclipse.sirius.common.tools.api.util.StringUtil;
import org.eclipse.sirius.ext.base.Option;
import org.eclipse.sirius.ext.emf.AllContents;
import org.eclipse.sirius.viewpoint.description.JavaExtension;
import org.eclipse.sirius.viewpoint.description.RepresentationDescription;
import org.eclipse.sirius.viewpoint.description.Viewpoint;
import org.eclipse.sirius.viewpoint.description.tool.AbstractToolDescription;
import org.eclipse.sirius.viewpoint.description.tool.AbstractVariable;
import org.eclipse.sirius.viewpoint.description.tool.CreateInstance;
import org.eclipse.sirius.viewpoint.description.tool.EditMaskVariables;
import org.eclipse.sirius.viewpoint.description.tool.For;
import org.eclipse.sirius.viewpoint.description.tool.ToolPackage;
import org.eclipse.sirius.viewpoint.description.tool.VariableContainer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Query allowing to get the target domain classes and available packages for a
* given Interpreted expression.
*
* @since 0.9.0
* @author alagarde
*
*/
public abstract class AbstractInterpretedExpressionQuery implements IInterpretedExpressionQuery {
/**
* The source tag used in the meta-model for all EAnnotations concerning the
* variables available to interpreted expressions.
*/
protected static final String VARIABLES_ANNOTATION_SOURCE = "http://www.eclipse.org/sirius/interpreted/expression/variables";
/**
* The character used in the documentation annotation of variables to
* separate the optional type name from the actual documentation.
*
* @see AbstractInterpretedExpressionQuery#collectLocalVariablesDefinitions()
*/
protected static final char TYPE_DEFINTION_SEPARATOR = '|';
/**
* The key used in {@link #VARIABLES_ANNOTATION_SOURCE} annotations of
* variable to specify the type of that variable.
*/
protected static final String VARIABLE_TYPE_KEY = "type";
/**
* The type to use for variables for which nothing more specific could be
* found.
*/
protected static final String DEFAULT_VARIABLE_TYPE = "ecore.EObject";
/**
* The target containing the InterpretedExpression (NodeMapping,
* ModelOperation...).
*/
protected EObject target;
/**
* The feature corresponding to the InterpretedExpression to evaluate (
* NodeMapping.semanticCandidatesExpression...).
*/
protected EStructuralFeature feature;
/**
* The expected DomainClass(es) for the given expression.
*/
protected Option<Collection<String>> targetDomainClass;
/**
* The list of EPackages to import to be able to interpret the given
* expression.
*/
protected Collection<EPackage> packagesToImport;
/**
* The list of dependencies to import to be able to interpret the given
* expression.
*/
protected Collection<String> dependencies;
/**
* A map representing all available variables. Keys of the map are the
* variable names, and values of the map their type.
*/
protected Map<String, String> availableVariables;
/**
* The available {@link IInterpretedExpressionTargetSwitch} that will be
* used to calculate target types.
*/
protected IInterpretedExpressionTargetSwitch targetSwitch;
/**
* Default constructor.
*
* @param target
* the target containing the InterpretedExpression (NodeMapping,
* ModelOperation...)
* @param feature
* the feature corresponding to the InterpretedExpression to
* evaluate ( NodeMapping.semanticCandidatesExpression...), can
* be null.
*/
public AbstractInterpretedExpressionQuery(EObject target, EStructuralFeature feature) {
this.target = target;
this.feature = feature;
initializeTargetSwitch();
}
/**
* Initializes the switch that will be used to calculate the target Types.
*/
protected abstract void initializeTargetSwitch();
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.business.api.dialect.description.IInterpretedExpressionQuery#getTargetDomainClasses()
*/
public Option<Collection<String>> getTargetDomainClasses() {
if (targetDomainClass == null) {
// Use the available TargetSwitches to get the domain class
targetDomainClass = targetSwitch.doSwitch(target, feature != null);
}
return targetDomainClass;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.business.api.dialect.description.IInterpretedExpressionQuery#getPackagesToImport()
*/
public Collection<EPackage> getPackagesToImport() {
if (packagesToImport == null) {
packagesToImport = Sets.newLinkedHashSet();
EObject representation = target.eContainer();
// We get the RepresentationDescription of the target
while (representation != null && !(representation instanceof RepresentationDescription)) {
representation = representation.eContainer();
}
if (representation != null) {
// If no EPackage has been explicitly imported
if (((RepresentationDescription) representation).getMetamodel().isEmpty()) {
// We add all available packages
for (String nsURI : Sets.newLinkedHashSet(EPackage.Registry.INSTANCE.keySet())) {
try {
packagesToImport.add(EPackage.Registry.INSTANCE.getEPackage(nsURI));
// CHECKSTYLE:OFF
} catch (Throwable e) {
/*
* anything might happen here depending on the other
* Eclipse tools, and we've seen many time tools
* breaking all the others.
*/
// CHECKSTYLE:ON
}
}
} else {
// Otherwise, we add the imported packages
packagesToImport.addAll(((RepresentationDescription) representation).getMetamodel());
}
}
packagesToImport.add(EcorePackage.eINSTANCE);
}
return packagesToImport;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.business.api.dialect.description.IInterpretedExpressionQuery#getDependencies()
*/
public Collection<String> getDependencies() {
if (dependencies == null) {
dependencies = Sets.newLinkedHashSet();
if (target != null) {
EObject vp = target;
// We get the corresponding viewpoint
while (vp != null && !(vp instanceof Viewpoint)) {
vp = vp.eContainer();
}
if (vp != null) {
for (JavaExtension dep : ((Viewpoint) vp).getOwnedJavaExtensions()) {
if (!StringUtil.isEmpty(dep.getQualifiedClassName())) {
dependencies.add(dep.getQualifiedClassName());
}
}
}
}
}
return dependencies;
}
/**
*
* {@inheritDoc}
*
* @see org.eclipse.sirius.business.api.dialect.description.IInterpretedExpressionQuery#getAvailableVariables()
*/
public Map<String, String> getAvailableVariables() {
if (availableVariables == null) {
availableVariables = Maps.newLinkedHashMap();
}
Option<EObject> toolContext = getToolContext();
if (toolContext.some()) {
collectContextualVariableDefinitions(availableVariables, toolContext.get(), target);
}
collectLocalVariablesDefinitions();
return availableVariables;
}
protected Option<EObject> getToolContext() {
return new EObjectQuery(target).getFirstAncestorOfType(ToolPackage.eINSTANCE.getAbstractToolDescription());
}
/**
* Add all the variables defined locally to the expression, as specified in
* the meta-model annotations. The type of each variable is encoded in the
* documentation string associated to each variable.
* <p>
* For example, on <code>BasicLabelStyleDescription.labelExpression</code>,
* an annotation using {@link #VARIABLES_ANNOTATION_SOURCE} has the
* following entry:
* <p>
* <code>
* diagram -> diagram.DDiagram | the current DSemanticDiagram.
* </code>
* <p>
* where <code>"diagram"</code> is the annotation key, representing the
* variable's name, and
* <code>"diagram.DDiagram | the current DSemanticDiagram"</code> is the
* value, indicating that the type of the variable is
* <code>diagram.DDiagram</code>.
* <p>
* If no type is specified in the documentation,
* {@link #DEFAULT_VARIABLE_TYPE} is assumed.
*
* @see #TYPE_DEFINTION_SEPARATOR
*/
private void collectLocalVariablesDefinitions() {
EAnnotation varAnnotations = feature.getEAnnotation(AbstractInterpretedExpressionQuery.VARIABLES_ANNOTATION_SOURCE);
if (varAnnotations != null) {
for (String varName : varAnnotations.getDetails().keySet()) {
String doc = varAnnotations.getDetails().get(varName);
String typeName = AbstractInterpretedExpressionQuery.DEFAULT_VARIABLE_TYPE;
if (doc != null && doc.indexOf(AbstractInterpretedExpressionQuery.TYPE_DEFINTION_SEPARATOR) != -1) {
typeName = doc.substring(0, doc.indexOf(AbstractInterpretedExpressionQuery.TYPE_DEFINTION_SEPARATOR)).trim();
}
availableVariables.put(varName, typeName);
}
}
}
/**
* Collect and merge all the variables definition in the scope between
* <code>top</code> and <code>bottom</code> (<code>top</code> <em>must</em>
* be an ancestor of <code>bottom</code>) and merges them. In case of name
* shadowing, priority is given to the the value defined closest to
* <code>bottom</code> (lexical scoping).
*/
private void collectContextualVariableDefinitions(Map<String, String> vars, EObject top, EObject bottom) {
// A map with multiple values is not strictly required as we only use
// one value, but it is useful when debugging to have all the
// information.
Map<String, Collection<String>> definitions = Maps.newHashMap();
// Walk up from bottom to top and gather every definition in the scope.
EObject context = bottom;
do {
appendAllLocalVariableDefinitions(definitions, context);
context = precedingSiblingOrContainer(context);
} while (context != null && context != top.eContainer());
// Merge all the definitions, by taking the one closest to
// <code>bottom</code> when there are multiple ones.
for (String var : definitions.keySet()) {
vars.put(var, ((List<String>) definitions.get(var)).get(0));
}
}
private EObject precedingSiblingOrContainer(EObject context) {
EObject container = context.eContainer();
EStructuralFeature containingFeature = context.eContainingFeature();
if (container != null && containingFeature != null) {
Object val = container.eGet(containingFeature);
/*
* if val is not a collection then we have no siblings.
*/
if (val instanceof EList<?>) {
EList<?> childs = (EList<?>) val;
int contextPositionInContainingList = childs.indexOf(context);
if (contextPositionInContainingList > 0) {
/*
* we have at least one sibling, we return the closest one
* to our position going upward.
*/
Object sibling = childs.get(contextPositionInContainingList - 1);
if (sibling instanceof EObject) {
return (EObject)sibling;
}
}
}
}
return container;
}
/**
* Returns the name of the type of the given variable, as defined in the
* meta-model annotations.
*
* @param var
* the variable definition inside a VSM.
* @return the name of the type for this variables, or
* {@link #DEFAULT_VARIABLE_TYPE} if nothing more specified was
* specified in the meta-model.
*/
protected String getVariableTypeName(AbstractVariable var) {
Preconditions.checkNotNull(var);
Preconditions.checkNotNull(var.eContainingFeature());
String typeName = AbstractInterpretedExpressionQuery.DEFAULT_VARIABLE_TYPE;
EAnnotation varAnnotation = var.eContainingFeature().getEAnnotation(AbstractInterpretedExpressionQuery.VARIABLES_ANNOTATION_SOURCE);
if (varAnnotation != null && varAnnotation.getDetails().containsKey(AbstractInterpretedExpressionQuery.VARIABLE_TYPE_KEY)) {
typeName = varAnnotation.getDetails().get(AbstractInterpretedExpressionQuery.VARIABLE_TYPE_KEY);
}
return typeName;
}
/**
* Append all the variable definitions defined locally by
* <code>context</code> to the specified map. Sub-classes should override
* this method (and call <code>super()</code>) to add any variable
* definitions specific to their languages/tasks/tools.
*
* @param definitions
* the map in which to append all the variable definitions (
* <code>name -> type</code>).
* @param context
* the element which may define new variables.
*/
protected void appendAllLocalVariableDefinitions(Map<String, Collection<String>> definitions, EObject context) {
// Tool definitions can contain variables, but they do not share a
// common type/feature name for this containment, so we must do a
// eAllContent(). This is ugly, and possibly broken if AbstractVariables
// exist below the point where the expression we are working on is
// defined.
if (context instanceof AbstractToolDescription) {
for (AbstractVariable var : Iterables.filter(AllContents.of(context, false), AbstractVariable.class)) {
addDefinition(definitions, var.getName(), getVariableTypeName(var));
}
}
// Some variables may contain sub-variables defined by the user: recurse
// on them.
if (context instanceof VariableContainer) {
for (AbstractVariable subVar : ((VariableContainer) context).getSubVariables()) {
appendAllLocalVariableDefinitions(definitions, subVar);
}
}
// Base case for normal variable definitions.
if (context instanceof AbstractVariable) {
AbstractVariable var = (AbstractVariable) context;
addDefinition(definitions, var.getName(), getVariableTypeName(var));
}
// The CreateInstance model operation implicitly defines a variable to
// reference the newly created instance.
if (context instanceof CreateInstance) {
CreateInstance ci = (CreateInstance) context;
addDefinition(definitions, ci.getVariableName(), ci.getTypeName());
}
// The CreateInstance model operation implicitly defines a variable to
// reference the newly created instance.
if (context instanceof For) {
For f = (For) context;
addDefinition(definitions, f.getIteratorName(), AbstractInterpretedExpressionQuery.DEFAULT_VARIABLE_TYPE);
}
}
/**
* Appends to the specified definition list all the implicit variables which
* will/may be defined at runtime according to the specified edit mask. For
* example, an exit mask of <code>{0}:{1}</code> implies the existence of
* two String variables at execution time, <code>arg0</code> and
* <code>arg1</code>.
*
* @param mask
* the edit mask which defines the format string.
* @param definitions
* the set of variables definition to which to append.
*/
protected void appendEditMaskVariables(EditMaskVariables mask, Map<String, Collection<String>> definitions) {
Pattern p = Pattern.compile("\\{\\d\\}");
Matcher m = p.matcher(mask.getMask());
while (m.find()) {
String group = m.group();
String index = group.substring(1, group.length() - 1);
// Old Acceleo 2-style variables.
addDefinition(definitions, index, "String");
// New variable names which should be legal in all query
// languages, including Acceleo 3.
addDefinition(definitions, "arg" + index, "String");
}
}
/**
* Add a new definition for a variable, shadowing any previously added ones.
*
* @param definitions
* the definitions of all the variables.
* @param name
* the name of the variable to (re-)define.
* @param value
* the value of the variable to (re-)define.
*/
protected void addDefinition(Map<String, Collection<String>> definitions, String name, String value) {
Collection<String> defs = definitions.get(name);
if (defs == null) {
defs = Lists.newArrayList();
definitions.put(name, defs);
}
defs.add(value);
}
}