blob: 2b9f5bc683c6038726f6a81d561c766ac0ef9c9b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 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
*******************************************************************************/
package org.eclipse.acceleo.internal.parser.ast.ocl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Stack;
import org.eclipse.acceleo.common.IAcceleoConstants;
import org.eclipse.acceleo.internal.parser.ast.ocl.environment.AcceleoEnvironment;
import org.eclipse.acceleo.internal.parser.ast.ocl.environment.AcceleoEnvironmentFactory;
import org.eclipse.acceleo.model.mtl.Macro;
import org.eclipse.acceleo.model.mtl.MacroInvocation;
import org.eclipse.acceleo.model.mtl.Module;
import org.eclipse.acceleo.model.mtl.ModuleElement;
import org.eclipse.acceleo.model.mtl.MtlFactory;
import org.eclipse.acceleo.model.mtl.Query;
import org.eclipse.acceleo.model.mtl.QueryInvocation;
import org.eclipse.acceleo.model.mtl.Template;
import org.eclipse.acceleo.model.mtl.TemplateInvocation;
import org.eclipse.acceleo.model.mtl.VisibilityKind;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.CallExp;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.EcoreFactory;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.OperationCallExp;
import org.eclipse.ocl.ecore.Variable;
import org.eclipse.ocl.ecore.VariableExp;
import org.eclipse.ocl.helper.Choice;
import org.eclipse.ocl.helper.ConstraintKind;
import org.eclipse.ocl.utilities.ASTNode;
import org.eclipse.ocl.utilities.CallingASTNode;
import org.eclipse.ocl.utilities.TypedASTNode;
/**
* To parse an OCL Expression. Make sure to call dispose() once you're done with the parser.
*
* @author <a href="mailto:jonathan.musset@obeo.fr">Jonathan Musset</a>
*/
public class OCLParser {
/**
* To set the value of the 'Source' attribute for each Acceleo MTL annotation.
*/
public static final String ANNOTATION_SOURCE = "MTL"; //$NON-NLS-1$
/**
* To set the value of the 'Source' attribute for each Acceleo MTL non-standard annotation.
*/
public static final String ANNOTATION_NON_STANDARD_SOURCE = "MTL non-standard"; //$NON-NLS-1$
/**
* To set the value of the 'type' key for each Acceleo annotation.
*/
public static final String ANNOTATION_KEY_TYPE = "type"; //$NON-NLS-1$
/**
* A stack to push and pop the current context of an OCL expression.
*/
private Stack<EClassifier> contextStack = new Stack<EClassifier>();
/**
* Helper for OCL expression parsing.
*/
private OCL.Helper helper;
/**
* Environment for OCL expression parsing.
*/
private AcceleoEnvironment environment;
/**
* To save the container of one variable before to add it in the OCL environment.
* <p>
* Remark : The following code changes the container of the object 'newVariable' :
* <p>
* <b>helper.getOCL().getEnvironment()).addElement(newVariable)</b>
*/
private Map<Variable, EObject> saveVariableContainer = new HashMap<Variable, EObject>();
/**
* To save the containing feature of one variable before to add it in the OCL environment. Remark : The
* following code changes the container of the object 'newVariable' :
* <p>
* <b>helper.getOCL().getEnvironment()).addElement(newVariable)</b>
*/
private Map<Variable, EStructuralFeature> saveVariableContainingFeature = new HashMap<Variable, EStructuralFeature>();
/**
* To save the EOperation element created by the OCL environment for each module element.
*/
private Map<EOperation, ModuleElement> eOperation2ModuleElement = new HashMap<EOperation, ModuleElement>();
/**
* Create a new OCL parser for Acceleo.
*
* @param oclEnvironmentResource
* resource used to keep the OCL environment.
*/
public OCLParser(Resource oclEnvironmentResource) {
init(oclEnvironmentResource);
}
/**
* Initializes and clears the OCL parser.
*
* @param oclEnvironmentResource
* resource used to keep the OCL environment.
*/
public void init(Resource oclEnvironmentResource) {
helper = OCL.newInstance(AcceleoEnvironmentFactory.ACCELEO_INSTANCE, oclEnvironmentResource)
.createOCLHelper();
environment = (AcceleoEnvironment)helper.getEnvironment();
helper.setContext(EcorePackage.eINSTANCE.getEObject());
contextStack.clear();
saveVariableContainer.clear();
saveVariableContainingFeature.clear();
eOperation2ModuleElement.clear();
}
/**
* Next OCL expressions will have this new context.
*
* @param newContext
* is the new context
*/
public void pushContext(EClassifier newContext) {
helper.setContext(newContext);
contextStack.push(newContext);
}
/**
* Restores the previous context.
*/
public void popContext() {
contextStack.pop();
if (contextStack.isEmpty()) {
helper.setContext(EcorePackage.eINSTANCE.getEObject());
} else {
helper.setContext(contextStack.peek());
}
}
/**
* Add a new variable in the current parsing scope.
*
* @param newVariable
* a new ocl variable to consider while parsing.
*/
public void addVariableToScope(Variable newVariable) {
if (newVariable != null && newVariable.getName() != null && newVariable.getName().length() > 0) {
if (newVariable.getType() == null) {
resolveType(newVariable);
}
EObject eContainer = newVariable.eContainer();
EStructuralFeature eContainingFeature = newVariable.eContainingFeature();
saveVariableContainer.put(newVariable, eContainer);
saveVariableContainingFeature.put(newVariable, eContainingFeature);
environment.addElement(newVariable.getName(), newVariable, true);
}
}
/**
* Resolves the type of the variable using the current OCL environment, and the "Acceleo.type"
* EAnnotation.
*
* @param newVariable
* We have to resolve the type of this variable.
*/
private void resolveType(Variable newVariable) {
EAnnotation eAnnotation = newVariable.getEAnnotation(ANNOTATION_SOURCE);
if (eAnnotation != null) {
String type = eAnnotation.getDetails().get(ANNOTATION_KEY_TYPE);
if (type != null) {
EClassifier eType = lookupClassifier(type);
if (eType != null && eType != getInvalidType()) {
newVariable.getEAnnotations().remove(eAnnotation);
}
newVariable.setType(eType);
}
}
}
/**
* Add the accessible variables (at the given index) in the current parsing scope. It browses the given
* module, and its children that are accessible at the given index. It returns the context classifier. It
* is defined by a variable (a loop variable, the first parameter of a template...)
*
* @param oModule
* is the current module
* @param offset
* is the offset in the text
* @return the context classifier, or null if the current block doesn't define a new context
*/
public EClassifier addRecursivelyVariablesToScopeAndGetContextClassifierAt(
org.eclipse.acceleo.model.mtl.Module oModule, int offset) {
Variable eContext = null;
Iterator<org.eclipse.acceleo.model.mtl.ModuleElement> ownedModuleElementIt = oModule
.getOwnedModuleElement().iterator();
while (ownedModuleElementIt.hasNext()) {
org.eclipse.acceleo.model.mtl.ModuleElement oModuleElement = ownedModuleElementIt.next();
if (offset > oModuleElement.getStartPosition() && offset < oModuleElement.getEndPosition()) {
if (oModuleElement instanceof org.eclipse.acceleo.model.mtl.Template) {
org.eclipse.acceleo.model.mtl.Template oTemplate = (org.eclipse.acceleo.model.mtl.Template)oModuleElement;
if (oTemplate.getParameter().size() > 0) {
eContext = oTemplate.getParameter().get(0);
}
addVariablesToScope(oTemplate.getParameter());
} else if (oModuleElement instanceof org.eclipse.acceleo.model.mtl.Query) {
org.eclipse.acceleo.model.mtl.Query oQuery = (org.eclipse.acceleo.model.mtl.Query)oModuleElement;
if (oQuery.getParameter().size() > 0) {
eContext = oQuery.getParameter().get(0);
}
addVariablesToScope(oQuery.getParameter());
} else if (oModuleElement instanceof org.eclipse.acceleo.model.mtl.Macro) {
org.eclipse.acceleo.model.mtl.Macro oMacro = (org.eclipse.acceleo.model.mtl.Macro)oModuleElement;
if (oMacro.getParameter().size() > 0) {
eContext = oMacro.getParameter().get(0);
}
addVariablesToScope(oMacro.getParameter());
}
if (oModuleElement instanceof org.eclipse.acceleo.model.mtl.Block) {
Variable oContext = addVariablesToScope(
(org.eclipse.acceleo.model.mtl.Block)oModuleElement, offset);
if (oContext != null) {
eContext = oContext;
}
}
break;
}
}
if (eContext != null) {
return eContext.getType();
} else {
return null;
}
}
/**
* Add a list of variables in the current parsing scope.
*
* @param newVariables
* are new variables to consider
*/
private void addVariablesToScope(List<Variable> newVariables) {
Iterator<Variable> newVariablesIt = new ArrayList<Variable>(newVariables).iterator();
while (newVariablesIt.hasNext()) {
addVariableToScope(newVariablesIt.next());
}
}
/**
* Add the variables of a 'Block' in the current parsing scope, and return the contextual variable (a loop
* variable for example).
*
* @param block
* is a block to consider
* @param offset
* is the offset in the text
* @return the contextual variable, or null if the block doesn't define a new context
*/
private Variable addVariablesToScope(org.eclipse.acceleo.model.mtl.Block block, int offset) {
Variable eContext = null;
if (offset > block.getStartPosition() && offset < block.getEndPosition()) {
if (block instanceof org.eclipse.acceleo.model.mtl.ForBlock) {
// We have to get the variable before
eContext = ((org.eclipse.acceleo.model.mtl.ForBlock)block).getLoopVariable();
addVariableToScope(((org.eclipse.acceleo.model.mtl.ForBlock)block).getLoopVariable());
}
if (block instanceof org.eclipse.acceleo.model.mtl.LetBlock) {
// We have to get the variable before
eContext = ((org.eclipse.acceleo.model.mtl.LetBlock)block).getLetVariable();
addVariableToScope(((org.eclipse.acceleo.model.mtl.LetBlock)block).getLetVariable());
}
if (block.getInit() != null) {
addVariablesToScope(block.getInit().getVariable());
}
Iterator<EObject> eContentsIt = block.eContents().iterator();
while (eContentsIt.hasNext()) {
EObject eContent = eContentsIt.next();
if (eContent instanceof org.eclipse.acceleo.model.mtl.Block) {
Variable oContext = addVariablesToScope((org.eclipse.acceleo.model.mtl.Block)eContent,
offset);
if (oContext != null) {
eContext = oContext;
break;
}
}
}
}
return eContext;
}
/**
* Add the behavioral features of a module in the current parsing scope. It browses the given module, its
* children, and the imported modules...
*
* @param oModule
* is the current module
*/
public void addRecursivelyBehavioralFeaturesToScope(org.eclipse.acceleo.model.mtl.Module oModule) {
addRecursivelyBehavioralFeaturesToScope(oModule, true, true, null);
}
/**
* Add the behavioral features of a module in the current parsing scope. It browses the given module, its
* children, and the imported modules...
*
* @param oModule
* is the current module
* @param operationWithReceiver
* indicates if we add the operations for which the first parameter is the receiver
* @param operationWithoutReceiver
* indicates if we add the operations for which there is a default receiver, the first
* parameter isn't the receiver
* @param startsWith
* is a filter for the named elements to keep
*/
public void addRecursivelyBehavioralFeaturesToScope(org.eclipse.acceleo.model.mtl.Module oModule,
boolean operationWithReceiver, boolean operationWithoutReceiver, String startsWith) {
addBehavioralFeaturesToScope(oModule, VisibilityKind.PRIVATE, operationWithReceiver,
operationWithoutReceiver, startsWith);
Iterator<org.eclipse.acceleo.model.mtl.Module> itOtherModules = oModule.getImports().iterator();
while (itOtherModules.hasNext()) {
org.eclipse.acceleo.model.mtl.Module oOtherModule = itOtherModules.next();
addBehavioralFeaturesToScope(oOtherModule, VisibilityKind.PUBLIC, operationWithReceiver,
operationWithoutReceiver, startsWith);
// We add the operation from the modules extended by the import.
this.addRecursivelyBehavioralFeaturesFromImports(oOtherModule, operationWithReceiver,
operationWithoutReceiver, startsWith);
}
List<org.eclipse.acceleo.model.mtl.Module> allExtends = new ArrayList<org.eclipse.acceleo.model.mtl.Module>();
computeAllExtends(allExtends, oModule);
itOtherModules = allExtends.iterator();
while (itOtherModules.hasNext()) {
org.eclipse.acceleo.model.mtl.Module oOtherModule = itOtherModules.next();
addBehavioralFeaturesToScope(oOtherModule, VisibilityKind.PROTECTED, operationWithReceiver,
operationWithoutReceiver, startsWith);
}
}
/**
* Add the behavioral features of the extended modules in the current parsing scope.
*
* @param oModule
* is the current module
* @param operationWithReceiver
* indicates if we add the operations for which the first parameter is the receiver
* @param operationWithoutReceiver
* indicates if we add the operations for which there is a default receiver, the first
* parameter isn't the receiver
* @param startsWith
* is a filter for the named elements to keep
*/
private void addRecursivelyBehavioralFeaturesFromImports(org.eclipse.acceleo.model.mtl.Module oModule,
boolean operationWithReceiver, boolean operationWithoutReceiver, String startsWith) {
List<Module> extendsList = oModule.getExtends();
if (extendsList != null && extendsList.size() > 0) {
for (Module module : extendsList) {
addBehavioralFeaturesToScope(module, VisibilityKind.PUBLIC, operationWithReceiver,
operationWithoutReceiver, startsWith);
}
}
}
/**
* It creates the list of all inherited modules.
*
* @param allExtends
* the list to compute
* @param oModule
* is the current module
*/
private void computeAllExtends(List<org.eclipse.acceleo.model.mtl.Module> allExtends,
org.eclipse.acceleo.model.mtl.Module oModule) {
List<org.eclipse.acceleo.model.mtl.Module> toBrowse = new ArrayList<org.eclipse.acceleo.model.mtl.Module>();
Iterator<org.eclipse.acceleo.model.mtl.Module> itOtherModules = oModule.getExtends().iterator();
while (itOtherModules.hasNext()) {
org.eclipse.acceleo.model.mtl.Module oOtherModule = itOtherModules.next();
if (!allExtends.contains(oOtherModule)) {
allExtends.add(oOtherModule);
toBrowse.add(oOtherModule);
}
}
itOtherModules = toBrowse.iterator();
while (itOtherModules.hasNext()) {
org.eclipse.acceleo.model.mtl.Module oOtherModule = itOtherModules.next();
computeAllExtends(allExtends, oOtherModule);
}
}
/**
* Add the behavioral features of a module in the current parsing scope. It browses the given module and
* its children.
*
* @param oModule
* is the current module
* @param lowestVisibilityKind
* is the lowest visibility value, VisibilityKind.PRIVATE indicates that private members are
* included
* @param operationWithReceiver
* indicates if we add the operations for which the first parameter is the receiver
* @param operationWithoutReceiver
* indicates if we add the operations for which there is a default receiver, the first
* parameter isn't the receiver
* @param startsWith
* is a filter for the named elements to keep
*/
private void addBehavioralFeaturesToScope(org.eclipse.acceleo.model.mtl.Module oModule,
VisibilityKind lowestVisibilityKind, boolean operationWithReceiver,
boolean operationWithoutReceiver, String startsWith) {
Iterator<EObject> itObjects = oModule.eAllContents();
while (itObjects.hasNext()) {
EObject eObject = itObjects.next();
if (eObject instanceof org.eclipse.acceleo.model.mtl.ModuleElement) {
org.eclipse.acceleo.model.mtl.ModuleElement eModuleElement = (org.eclipse.acceleo.model.mtl.ModuleElement)eObject;
if (eModuleElement.getVisibility().getValue() >= lowestVisibilityKind.getValue()
&& (startsWith == null || (eModuleElement.getName() != null && eModuleElement
.getName().toLowerCase().startsWith(startsWith.toLowerCase())))) {
if (eObject instanceof org.eclipse.acceleo.model.mtl.Template) {
org.eclipse.acceleo.model.mtl.Template eTemplate = (org.eclipse.acceleo.model.mtl.Template)eObject;
addTemplateToScope(eTemplate, operationWithReceiver, operationWithoutReceiver);
} else if (eObject instanceof org.eclipse.acceleo.model.mtl.Query) {
org.eclipse.acceleo.model.mtl.Query eQuery = (org.eclipse.acceleo.model.mtl.Query)eObject;
addQueryToScope(eQuery, operationWithReceiver, operationWithoutReceiver);
} else if (eObject instanceof org.eclipse.acceleo.model.mtl.Macro) {
org.eclipse.acceleo.model.mtl.Macro eMacro = (org.eclipse.acceleo.model.mtl.Macro)eObject;
addMacroToScope(eMacro, operationWithReceiver, operationWithoutReceiver);
}
}
}
}
}
/**
* Add a new template in the current parsing scope.
*
* @param newTemplate
* a new template to consider while parsing.
* @param operationWithReceiver
* indicates if we add the operations for which the first parameter is the receiver
* @param operationWithoutReceiver
* indicates if we add the operations for which there is a default receiver, the first
* parameter isn't the receiver
*/
@SuppressWarnings({"rawtypes", "unchecked" })
private void addTemplateToScope(Template newTemplate, boolean operationWithReceiver,
boolean operationWithoutReceiver) {
EClassifier owner = EcorePackage.eINSTANCE.getEObject();
if (newTemplate != null && newTemplate.getName() != null && newTemplate.getName().length() > 0) {
EClassifier type = environment.getOCLStandardLibrary().getString();
List params = newTemplate.getParameter();
Iterator<Variable> paramsIt = params.iterator();
while (paramsIt.hasNext()) {
resolveType(paramsIt.next());
}
if (operationWithoutReceiver) {
Constraint constraint = EcoreFactory.eINSTANCE.createConstraint();
EOperation eOperation = environment.defineOperation(owner, newTemplate.getName(), type,
params, constraint);
if (eOperation != null) {
eOperation2ModuleElement.put(eOperation, newTemplate);
EAnnotation eAnnotation = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE
.createEAnnotation();
eAnnotation.setSource(ANNOTATION_SOURCE);
eAnnotation.getReferences().add(newTemplate);
eOperation.getEAnnotations().add(eAnnotation);
}
}
// We create a new EOperation for the template, but the first parameter is the owner...
if (operationWithReceiver && params.size() > 0 && ((Variable)params.get(0)).getType() != null) {
owner = ((Variable)params.get(0)).getType();
params = new BasicEList<Variable>(params);
params.remove(0);
Constraint constraint = EcoreFactory.eINSTANCE.createConstraint();
EOperation eOperation = environment.defineOperation(owner, newTemplate.getName(), type,
params, constraint);
if (eOperation != null) {
eOperation2ModuleElement.put(eOperation, newTemplate);
EAnnotation eAnnotation = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE
.createEAnnotation();
eAnnotation.setSource(ANNOTATION_SOURCE);
eAnnotation.getReferences().add(newTemplate);
eOperation.getEAnnotations().add(eAnnotation);
}
}
}
}
/**
* Add a new query in the current parsing scope.
*
* @param newQuery
* a new query to consider while parsing.
* @param operationWithReceiver
* indicates if we add the operations for which the first parameter is the receiver
* @param operationWithoutReceiver
* indicates if we add the operations for which there is a default receiver, the first
* parameter isn't the receiver
*/
@SuppressWarnings({"rawtypes", "unchecked" })
private void addQueryToScope(Query newQuery, boolean operationWithReceiver,
boolean operationWithoutReceiver) {
EClassifier owner = EcorePackage.eINSTANCE.getEObject();
if (newQuery != null && newQuery.getName() != null && newQuery.getName().length() > 0) {
resolveType(newQuery);
List params = newQuery.getParameter();
Iterator<Variable> paramsIt = params.iterator();
while (paramsIt.hasNext()) {
resolveType(paramsIt.next());
}
if (operationWithoutReceiver) {
Constraint constraint = EcoreFactory.eINSTANCE.createConstraint();
EOperation eOperation = environment.defineOperation(owner, newQuery.getName(), newQuery
.getType(), params, constraint);
if (eOperation != null) {
eOperation2ModuleElement.put(eOperation, newQuery);
EAnnotation eAnnotation = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE
.createEAnnotation();
eAnnotation.setSource(ANNOTATION_SOURCE);
eAnnotation.getReferences().add(newQuery);
eOperation.getEAnnotations().add(eAnnotation);
}
}
// We create a new EOperation for the query, but the first parameter is the owner...
if (operationWithReceiver && params.size() > 0 && ((Variable)params.get(0)).getType() != null) {
owner = ((Variable)params.get(0)).getType();
params = new BasicEList<Variable>(params);
params.remove(0);
Constraint constraint = EcoreFactory.eINSTANCE.createConstraint();
EOperation eOperation = environment.defineOperation(owner, newQuery.getName(), newQuery
.getType(), params, constraint);
if (eOperation != null) {
eOperation2ModuleElement.put(eOperation, newQuery);
EAnnotation eAnnotation = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE
.createEAnnotation();
eAnnotation.setSource(ANNOTATION_SOURCE);
eAnnotation.getReferences().add(newQuery);
eOperation.getEAnnotations().add(eAnnotation);
}
}
}
}
/**
* Resolves the type of the query using the current OCL environment, and the "Acceleo.type" EAnnotation.
*
* @param newQuery
* We have to resolve the type of this query.
*/
private void resolveType(Query newQuery) {
EAnnotation eAnnotation = newQuery.getEAnnotation(ANNOTATION_SOURCE);
if (eAnnotation != null) {
String type = eAnnotation.getDetails().get(ANNOTATION_KEY_TYPE);
if (type != null) {
EClassifier eType = lookupClassifier(type);
if (eType != null && eType != getInvalidType()) {
newQuery.getEAnnotations().remove(eAnnotation);
}
newQuery.setType(eType);
}
}
}
/**
* Add a new macro in the current parsing scope.
*
* @param newMacro
* a new macro to consider while parsing.
* @param operationWithReceiver
* indicates if we add the operations for which the first parameter is the receiver
* @param operationWithoutReceiver
* indicates if we add the operations for which there is a default receiver, the first
* parameter isn't the receiver
*/
@SuppressWarnings({"rawtypes", "unchecked" })
private void addMacroToScope(Macro newMacro, boolean operationWithReceiver,
boolean operationWithoutReceiver) {
EClassifier owner = EcorePackage.eINSTANCE.getEObject();
if (newMacro != null && newMacro.getName() != null && newMacro.getName().length() > 0) {
resolveType(newMacro);
List params = newMacro.getParameter();
Iterator<Variable> paramsIt = params.iterator();
while (paramsIt.hasNext()) {
resolveType(paramsIt.next());
}
if (operationWithoutReceiver) {
Constraint constraint = EcoreFactory.eINSTANCE.createConstraint();
EOperation eOperation = environment.defineOperation(owner, newMacro.getName(), newMacro
.getType(), params, constraint);
if (eOperation != null) {
eOperation2ModuleElement.put(eOperation, newMacro);
EAnnotation eAnnotation = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE
.createEAnnotation();
eAnnotation.setSource(ANNOTATION_SOURCE);
eAnnotation.getReferences().add(newMacro);
eOperation.getEAnnotations().add(eAnnotation);
}
}
// We create a new EOperation for the query, but the first parameter is the owner...
if (operationWithReceiver && params.size() > 0 && ((Variable)params.get(0)).getType() != null) {
owner = ((Variable)params.get(0)).getType();
params = new BasicEList<Variable>(params);
params.remove(0);
Constraint constraint = EcoreFactory.eINSTANCE.createConstraint();
EOperation eOperation = environment.defineOperation(owner, newMacro.getName(), newMacro
.getType(), params, constraint);
if (eOperation != null) {
eOperation2ModuleElement.put(eOperation, newMacro);
EAnnotation eAnnotation = org.eclipse.emf.ecore.EcoreFactory.eINSTANCE
.createEAnnotation();
eAnnotation.setSource(ANNOTATION_SOURCE);
eAnnotation.getReferences().add(newMacro);
eOperation.getEAnnotations().add(eAnnotation);
}
}
}
}
/**
* Resolves the type of the macro using the current OCL environment, and the "Acceleo.type" EAnnotation.
*
* @param newMacro
* We have to resolve the type of this macro.
*/
private void resolveType(Macro newMacro) {
EAnnotation eAnnotation = newMacro.getEAnnotation(ANNOTATION_SOURCE);
if (eAnnotation != null) {
String type = eAnnotation.getDetails().get(ANNOTATION_KEY_TYPE);
if (type != null) {
EClassifier eType = lookupClassifier(type);
if (eType != null && eType != getInvalidType()) {
newMacro.getEAnnotations().remove(eAnnotation);
}
newMacro.setType(eType);
}
}
}
/**
* Remove the behavioral features in the current parsing scope. It browses the given module, its children,
* and the imported modules...
*
* @param oModule
* is the current module
*/
@SuppressWarnings("unused")
public void removeRecursivelyBehavioralFeaturesToScope(org.eclipse.acceleo.model.mtl.Module oModule) {
/* Do nothing */
}
/**
* Add all {@link EPackage} of a module in the list of the metamodels considered during the parsing.
*
* @param oModule
* is the current module
*/
public void addRecursivelyMetamodelsToScope(org.eclipse.acceleo.model.mtl.Module oModule) {
Iterator<org.eclipse.acceleo.model.mtl.TypedModel> inputIt = oModule.getInput().iterator();
while (inputIt.hasNext()) {
org.eclipse.acceleo.model.mtl.TypedModel typedModel = inputIt.next();
Iterator<EPackage> takesTypesFromIt = typedModel.getTakesTypesFrom().iterator();
while (takesTypesFromIt.hasNext()) {
addMetamodel(takesTypesFromIt.next());
}
}
}
/**
* Add a new {@link EPackage} in the list of the metamodels considered during the parsing.
*
* @param metamodel
* {@link EPackage} to add in the current {@link EPackage}'s known by the parser.
*/
public void addMetamodel(EPackage metamodel) {
environment.addMetamodel(metamodel);
}
/**
* Remove a {@link EPackage} in the list of the metamodels considered during the parsing.
*
* @param metamodel
* {@link EPackage} to remove in the current {@link EPackage}'s known by the parser.
*/
public void removeMetamodel(EPackage metamodel) {
environment.removeMetamodel(metamodel);
}
/**
* Selects the meta-model object for the given name in the current module.
*
* @param name
* is the name of the type to search
* @return the meta-model object, or null if it doesn't exist
*/
public EClassifier lookupClassifier(String name) {
return environment.lookupClassifier(name);
}
/**
* Returns the underlying OCL environment.
*
* @return The underlying OCL environment.
* @noreference This method is not intended to be referenced by clients.
*/
public Environment<?, EClassifier, EOperation, EStructuralFeature, ?, ?, ?, ?, ?, ?, ?, ?> getOCLEnvironment() {
return helper.getEnvironment();
}
/**
* Gets the meta-model types of the current module.
*
* @return the meta-model objects, or an empty list
*/
public List<EClassifier> getTypes() {
return environment.getTypes();
}
/**
* Obtains the single instance of the OCL invalid type.
*
* @return the OCL invalid type
*/
public EClassifier getInvalidType() {
return environment.getOCLStandardLibraryReflection().getOCLInvalid();
}
/**
* Returns the standard library's integer classifier.
*
* @return The standard library's integer classifier.
*/
public EClassifier getIntegerType() {
return environment.getOCLStandardLibrary().getInteger();
}
/**
* Returns the standard library's string classifier.
*
* @return The standard library's string classifier.
*/
public EClassifier getStringType() {
return environment.getOCLStandardLibrary().getString();
}
/**
* Parse an OCL Expression and return the corresponding instance. Make sure to declare all the needed
* contextual elements (variables or contexts) before trying to parse.
*
* @param text
* is the OCL text to parse, just a part of the file
* @param realOffsetInTheBuffer
* beginning index of the given text in the file, this index is used to compute the real offset
* of a problem...
* @param iModelExpression
* The model expression (only used to solve a recursivity problem
* @return an {@link OCLExpression} instance.
* @throws ParserException
* if the given text is not conform to what we expect.
*/
public OCLExpression parseOCLExpression(String text, int realOffsetInTheBuffer,
org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) throws ParserException {
if (text.trim().equals(IAcceleoConstants.SUPER)) {
TemplateInvocation eTemplateInvocation = MtlFactory.eINSTANCE.createTemplateInvocation();
eTemplateInvocation.setDefinition(null);
eTemplateInvocation.setSuper(true);
eTemplateInvocation.setStartPosition(realOffsetInTheBuffer);
eTemplateInvocation.setEndPosition(realOffsetInTheBuffer + text.length());
return eTemplateInvocation;
} else {
OCLExpression eOCLExpression = createQuery(text);
shiftOCLExpressionPositions(realOffsetInTheBuffer, eOCLExpression);
Iterator<EObject> eAllContents = eOCLExpression.eAllContents();
while (eAllContents.hasNext()) {
EObject eContent = eAllContents.next();
shiftOCLExpressionPositions(realOffsetInTheBuffer, eContent);
}
OCLExpression newOCLExpression = createAcceleoInvocation(eOCLExpression, iModelExpression);
if (newOCLExpression == null) {
Iterator<EObject> eAllContentsIt = eOCLExpression.eAllContents();
while (eAllContentsIt.hasNext()) {
createAcceleoInvocation(eAllContentsIt.next(), iModelExpression);
}
newOCLExpression = eOCLExpression;
}
return newOCLExpression;
}
}
/**
* Parse an OCL Expression and return the corresponding instance. We try 2 contexts : 'self.type' and
* 'EObject'.
*
* @param text
* is the OCL text to parse, just a part of the file
* @return an {@link OCLExpression} instance.
* @throws ParserException
* if the given text is not conform to what we expect.
*/
private OCLExpression createQuery(String text) throws ParserException {
environment.deleteFirstProblemObject();
try {
OCLExpression eOCLExpression = helper.createQuery(text);
return eOCLExpression;
} catch (ParserException e1) {
Object firstProblemObject = environment.getFirstProblemObject();
pushContext(org.eclipse.emf.ecore.EcorePackage.eINSTANCE.getEObject());
try {
OCLExpression eOCLExpression = helper.createQuery(text);
return eOCLExpression;
} catch (ParserException e2) {
if (firstProblemObject != null) {
throw new WrappedOCLException(e1, firstProblemObject);
} else {
throw e1;
}
} finally {
popContext();
}
}
}
/**
* Replaces the current 'OCL' object with an invocation of one Acceleo module element.
*
* @param eObject
* is the detected OCL element
* @param iModelExpression
* The CST model expression (used only for solving a recusive problem)
* @return the new OCL expression, which is an invocation of the Acceleo module element
*/
private OCLExpression createAcceleoInvocation(EObject eObject,
org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
// Check the source of this object and replace it if needed
if (eObject instanceof CallExp) {
createAcceleoInvocation(((CallExp)eObject).getSource(), iModelExpression);
}
if (eObject instanceof OperationCallExp) {
OperationCallExp eCall = (OperationCallExp)eObject;
if (eCall.getReferredOperation() != null
&& eCall.getReferredOperation().getEAnnotation(ANNOTATION_SOURCE) != null) {
Iterator<EObject> referencesIt = eCall.getReferredOperation().getEAnnotation(
ANNOTATION_SOURCE).getReferences().iterator();
if (referencesIt.hasNext()) {
EObject eModuleElement = referencesIt.next();
OCLExpression acceleoInvocation = createAcceleoInvocation(eCall, eModuleElement,
iModelExpression);
return handleArguments(acceleoInvocation, iModelExpression);
}
} else if (eCall.getReferredOperation() != null
&& eCall.getReferredOperation().getEAnnotation(ANNOTATION_NON_STANDARD_SOURCE) != null) {
// try to see if the arguments of the non standard operation contains an Acceleo invocation
// example: 'a string' + ('another string' + aQueryInvocation())
List<org.eclipse.ocl.expressions.OCLExpression<EClassifier>> argument = eCall.getArgument();
for (org.eclipse.ocl.expressions.OCLExpression<EClassifier> oclExpression : argument) {
createAcceleoInvocation(oclExpression, iModelExpression);
}
}
}
return null;
}
/**
* Transform the arguments into Acceleo invocations.
*
* @param acceleoInvocation
* The acceleoInvocation
* @param iModelExpression
* The CST model expression (used only for solving a recusive problem)
* @return The new Acceleo invocation with its new arguments
*/
private OCLExpression handleArguments(OCLExpression acceleoInvocation,
org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
OCLExpression result = acceleoInvocation;
if (acceleoInvocation instanceof TemplateInvocation) {
TemplateInvocation templateInvocation = (TemplateInvocation)acceleoInvocation;
List<OCLExpression> arguments = templateInvocation.getArgument();
for (OCLExpression argument : arguments) {
createAcceleoInvocationArguments(argument, iModelExpression);
}
} else if (acceleoInvocation instanceof QueryInvocation) {
QueryInvocation queryInvocation = (QueryInvocation)acceleoInvocation;
List<OCLExpression> arguments = queryInvocation.getArgument();
for (OCLExpression argument : arguments) {
createAcceleoInvocationArguments(argument, iModelExpression);
}
} else if (acceleoInvocation instanceof MacroInvocation) {
MacroInvocation macroInvocation = (MacroInvocation)acceleoInvocation;
List<OCLExpression> arguments = macroInvocation.getArgument();
for (OCLExpression argument : arguments) {
createAcceleoInvocationArguments(argument, iModelExpression);
}
}
return result;
}
/**
* Transform the arguments into Acceleo invocations.
*
* @param argument
* The arguments of the Acceleo invocation
* @param iModelExpression
* The CST model expression (used only for solving a recusive problem)
*/
private void createAcceleoInvocationArguments(org.eclipse.ocl.expressions.OCLExpression<?> argument,
org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
if (argument instanceof org.eclipse.ocl.expressions.CollectionLiteralExp<?>) {
org.eclipse.ocl.expressions.CollectionLiteralExp<?> collectionLiteralExp = (org.eclipse.ocl.expressions.CollectionLiteralExp<?>)argument;
List<?> parts = collectionLiteralExp.getPart();
for (Object collectionLiteralPart : parts) {
if (collectionLiteralPart instanceof org.eclipse.ocl.expressions.CollectionItem) {
org.eclipse.ocl.expressions.CollectionItem<?> collectionItem = (org.eclipse.ocl.expressions.CollectionItem<?>)collectionLiteralPart;
org.eclipse.ocl.expressions.OCLExpression<?> item = collectionItem.getItem();
if (item instanceof org.eclipse.ocl.expressions.CollectionLiteralExp<?>) {
createAcceleoInvocationArguments(item, iModelExpression);
} else {
createAcceleoInvocation(item, iModelExpression);
}
}
}
}
}
/**
* Replaces the given OperationCallExp object with an invocation of the Acceleo module element.
*
* @param eCall
* is the detected OCL operation call
* @param eModuleElement
* is the referred module element
* @param iModelExpression
* The model expression of the CST (only used to solve a recursivity problem)
* @return the new OCL expression, which is an invocation of the Acceleo module element
*/
private OCLExpression createAcceleoInvocation(OperationCallExp eCall, EObject eModuleElement,
org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
OCLExpression eOCLExpression;
if (eModuleElement instanceof Template) {
// We have a template invocation
Template eTemplate = (Template)eModuleElement;
TemplateInvocation eTemplateInvocation = MtlFactory.eINSTANCE.createTemplateInvocation();
eOCLExpression = eTemplateInvocation;
eTemplateInvocation.setDefinition(eTemplate);
eTemplateInvocation.setStartPosition(eCall.getStartPosition());
eTemplateInvocation.setEndPosition(eCall.getEndPosition());
if (receiverIsArgument(eCall, eTemplate, eTemplate.getParameter(), iModelExpression)) {
eTemplateInvocation.getArgument().add((OCLExpression)eCall.getSource());
}
eTemplateInvocation.setType(eCall.getType());
move(eCall.getArgument(), eTemplateInvocation.getArgument());
checkArgumentInvocations(eTemplateInvocation.getArgument(), iModelExpression);
EcoreUtil.replace(eCall, eTemplateInvocation);
} else if (eModuleElement instanceof Query) {
Query eQuery = (Query)eModuleElement;
QueryInvocation eQueryInvocation = MtlFactory.eINSTANCE.createQueryInvocation();
eOCLExpression = eQueryInvocation;
eQueryInvocation.setDefinition(eQuery);
eQueryInvocation.setStartPosition(eCall.getStartPosition());
eQueryInvocation.setEndPosition(eCall.getEndPosition());
if (receiverIsArgument(eCall, eQuery, eQuery.getParameter(), iModelExpression)) {
eQueryInvocation.getArgument().add((OCLExpression)eCall.getSource());
}
eQueryInvocation.setType(eCall.getType());
move(eCall.getArgument(), eQueryInvocation.getArgument());
checkArgumentInvocations(eQueryInvocation.getArgument(), iModelExpression);
EcoreUtil.replace(eCall, eQueryInvocation);
} else if (eModuleElement instanceof Macro) {
Macro eMacro = (Macro)eModuleElement;
MacroInvocation eMacroInvocation = MtlFactory.eINSTANCE.createMacroInvocation();
eOCLExpression = eMacroInvocation;
eMacroInvocation.setDefinition(eMacro);
eMacroInvocation.setStartPosition(eCall.getStartPosition());
eMacroInvocation.setEndPosition(eCall.getEndPosition());
if (receiverIsArgument(eCall, eMacro, eMacro.getParameter(), iModelExpression)) {
eMacroInvocation.getArgument().add((OCLExpression)eCall.getSource());
}
eMacroInvocation.setType(eCall.getType());
move(eCall.getArgument(), eMacroInvocation.getArgument());
checkArgumentInvocations(eMacroInvocation.getArgument(), iModelExpression);
EcoreUtil.replace(eCall, eMacroInvocation);
} else {
eOCLExpression = null;
}
return eOCLExpression;
}
/**
* Indicates if the receiver of the given operation call should be added to the arguments.
*
* @param eCall
* is an operation call
* @param astModuleElement
* The module element from the AST
* @param variables
* The list of variables of the template, query or macro called.
* @param iModelExpression
* The CST model expression (used only for solving a recusive problem)
* @return true if the receiver of the given operation call should be added to the arguments
*/
private boolean receiverIsArgument(OperationCallExp eCall, ModuleElement astModuleElement,
List<Variable> variables, org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
boolean result = false;
if (eCall.getSource() != null) {
if (eCall.getSource() instanceof VariableExp
&& ((VariableExp)eCall.getSource()).getReferredVariable() != null) {
// No arguments for the call: the source is the argument
if (eCall.getArgument().size() == 0) {
result = true;
} else if (variables.size() == 0 && iModelExpression != null) {
// We are in a recursive call of a template or a query
result = handleRecursiveCall(eCall, astModuleElement, variables, iModelExpression);
} else {
// We are in a call where the source may be the argument
result = eCall.getArgument().size() < variables.size();
result = result && variables.size() != 0;
}
} else {
result = true;
}
} else {
result = false;
}
return result;
}
/**
* Handle the recursive call.
*
* @param eCall
* is an operation call
* @param astModuleElement
* The module element from the AST
* @param variables
* The list of variables of the template, query or macro called.
* @param iModelExpression
* The CST model expression (used only for solving a recusive problem)
* @return true if the receiver of the given operation call should be added to the arguments
*/
private boolean handleRecursiveCall(OperationCallExp eCall, ModuleElement astModuleElement,
List<Variable> variables, org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
boolean result = false;
EObject eContainer = iModelExpression.eContainer();
while (eContainer != null && !(eContainer instanceof org.eclipse.acceleo.parser.cst.ModuleElement)) {
eContainer = eContainer.eContainer();
}
if (eContainer instanceof org.eclipse.acceleo.parser.cst.Template) {
// We have the module of the CST, we need to look for the correct template or query
org.eclipse.acceleo.parser.cst.Template cstTemplate = (org.eclipse.acceleo.parser.cst.Template)eContainer;
if (cstTemplate.getStartPosition() == astModuleElement.getStartPosition()
&& cstTemplate.getEndPosition() == astModuleElement.getEndPosition()) {
result = eCall.getArgument().size() < cstTemplate.getParameter().size();
result = result && cstTemplate.getParameter().size() != 0;
}
} else if (eContainer instanceof org.eclipse.acceleo.parser.cst.Query) {
// We have the module of the CST, we need to look for the correct template or query
org.eclipse.acceleo.parser.cst.Query cstQuery = (org.eclipse.acceleo.parser.cst.Query)eContainer;
if (cstQuery.getStartPosition() == astModuleElement.getStartPosition()
&& cstQuery.getEndPosition() == astModuleElement.getEndPosition()) {
result = eCall.getArgument().size() < cstQuery.getParameter().size();
result = result && variables.size() != 0;
}
} else if (eContainer instanceof org.eclipse.acceleo.parser.cst.Macro) {
// We have the module of the CST, we need to look for the correct template or query
org.eclipse.acceleo.parser.cst.Macro cstMacro = (org.eclipse.acceleo.parser.cst.Macro)eContainer;
if (cstMacro.getStartPosition() == astModuleElement.getStartPosition()
&& cstMacro.getEndPosition() == astModuleElement.getEndPosition()) {
result = eCall.getArgument().size() < cstMacro.getParameter().size();
result = result && variables.size() != 0;
}
}
return result;
}
/**
* Moves the elements of the first list in the second list.
*
* @param in
* is the first list
* @param out
* is the second list
*/
@SuppressWarnings({"rawtypes", "unchecked" })
private void move(List in, List out) {
out.addAll(in);
}
/**
* Creates the invocations of the arguments list.
*
* @param arguments
* is the arguments list
* @param iModelExpression
* The CST model expression (used only for solving a recusive problem)
*/
private void checkArgumentInvocations(List<OCLExpression> arguments,
org.eclipse.acceleo.parser.cst.ModelExpression iModelExpression) {
ListIterator<OCLExpression> it = arguments.listIterator();
while (it.hasNext()) {
OCLExpression next = it.next();
OCLExpression newArgument = createAcceleoInvocation(next, iModelExpression);
if (newArgument != null) {
it.set(newArgument);
}
}
}
/**
* Shifts the position of an OCL expression and its children with the given value that corresponds to the
* beginning index of the expression in the full buffer.
*
* @param posBegin
* is the beginning index of the expression in the full buffer
* @param eContent
* is the expression to be updated
*/
private void shiftOCLExpressionPositions(int posBegin, EObject eContent) {
if (eContent instanceof ASTNode && ((ASTNode)eContent).getStartPosition() >= 0) {
((ASTNode)eContent).setStartPosition(posBegin + ((ASTNode)eContent).getStartPosition());
((ASTNode)eContent).setEndPosition(posBegin + ((ASTNode)eContent).getEndPosition());
}
if (eContent instanceof CallingASTNode && ((CallingASTNode)eContent).getPropertyStartPosition() >= 0) {
((CallingASTNode)eContent).setPropertyStartPosition(posBegin
+ ((CallingASTNode)eContent).getPropertyStartPosition());
((CallingASTNode)eContent).setPropertyEndPosition(posBegin
+ ((CallingASTNode)eContent).getPropertyEndPosition());
}
if (eContent instanceof TypedASTNode && ((TypedASTNode)eContent).getTypeStartPosition() >= 0) {
((TypedASTNode)eContent).setTypeStartPosition(posBegin
+ ((TypedASTNode)eContent).getTypeStartPosition());
((TypedASTNode)eContent).setTypeEndPosition(posBegin
+ ((TypedASTNode)eContent).getTypeEndPosition());
}
}
/**
* Clear all contextual information.
*/
public void dispose() {
saveVariableContainer.clear();
saveVariableContainingFeature.clear();
}
/**
* Remove an accessible variable from the current parser scope.
*
* @param oldVariable
* oldVariable is the variable to remove.
*/
@SuppressWarnings("unchecked")
public void removeVariableFromScope(Variable oldVariable) {
if (oldVariable == null) {
return;
}
EObject eContainer = saveVariableContainer.get(oldVariable);
EStructuralFeature eContainingFeature = saveVariableContainingFeature.get(oldVariable);
if (oldVariable.getName() != null && eContainer != null && eContainingFeature != null) {
helper.getOCL().getEnvironment().deleteElement(oldVariable.getName());
if (eContainingFeature.getUpperBound() == 1) {
eContainer.eSet(eContainingFeature, oldVariable);
} else {
Object eGet = eContainer.eGet(eContainingFeature);
if (eGet instanceof Collection<?>) {
Collection<Variable> list = (Collection<Variable>)eGet;
list.add(oldVariable);
}
}
}
saveVariableContainer.remove(oldVariable);
saveVariableContainingFeature.remove(oldVariable);
}
/**
* Obtains syntax completion choices for the specified fragment of an OCL expression.
*
* @param text
* a partial OCL expression for which to seek choices that could be appended to it
* @return a list of {@link Choice}s, possibly empty. The ordering of the list may or may not indicate
* relative relevance or frequency of a choice
*/
public List<Choice> getSyntaxHelp(String text) {
List<Choice> result = new ArrayList<Choice>();
result.addAll(helper.getSyntaxHelp(ConstraintKind.INVARIANT, text));
pushContext(org.eclipse.emf.ecore.EcorePackage.eINSTANCE.getEObject());
try {
result.addAll(helper.getSyntaxHelp(ConstraintKind.INVARIANT, text));
} finally {
popContext();
}
return result;
}
/**
* Gets the module element used to create the given operation, or null if it doen't exist.
*
* @param eOperation
* is an EOperation created by the OCL parser
* @return the corresponding Acceleo module element
*/
public ModuleElement getModuleElement(EOperation eOperation) {
ModuleElement result = eOperation2ModuleElement.get(eOperation);
if (result == null && eOperation != null) {
Iterator<Map.Entry<EOperation, ModuleElement>> entries = eOperation2ModuleElement.entrySet()
.iterator();
while (entries.hasNext()) {
Map.Entry<EOperation, ModuleElement> entry = entries.next();
EOperation operation = entry.getKey();
if (equals(eOperation, operation)) {
return entry.getValue();
}
}
}
return result;
}
/**
* Compares these EOperations. The result is true if the operations have the same name and the same
* parameters.
*
* @param o1
* is the first one
* @param o2
* is the second one
* @return true if the operations have the same name and the same parameters
*/
private boolean equals(EOperation o1, EOperation o2) {
boolean result;
if (o1 == o2) {
result = true;
} else if (o1 == null || o2 == null) {
result = false;
} else {
if (o1.getName() != null && o1.getName().equals(o2.getName())
&& o1.getEParameters().size() == o2.getEParameters().size()) {
Iterator<EParameter> params1 = o1.getEParameters().iterator();
Iterator<EParameter> params2 = o2.getEParameters().iterator();
while (params1.hasNext() && params2.hasNext()) {
EParameter param1 = params1.next();
EParameter param2 = params2.next();
if (param1.getEType() != null && param2.getEType() != null
&& param1.getEType().getName() != null
&& !param1.getEType().getName().equals(param2.getEType().getName())) {
return false;
}
}
result = true;
} else {
result = false;
}
}
return result;
}
}