blob: 76e9e476b6fb8c223882e4d47ec1da1fa078331b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 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.aql.migration.converters;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Collections;
import org.eclipse.acceleo.AcceleoFactory;
import org.eclipse.acceleo.ExpressionStatement;
import org.eclipse.acceleo.Statement;
import org.eclipse.acceleo.aql.migration.MigrationException;
import org.eclipse.acceleo.aql.migration.converters.utils.OperationUtils;
import org.eclipse.acceleo.aql.migration.converters.utils.TypeUtils;
import org.eclipse.acceleo.model.mtl.MtlPackage;
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.query.ast.AstFactory;
import org.eclipse.acceleo.query.ast.BooleanLiteral;
import org.eclipse.acceleo.query.ast.Call;
import org.eclipse.acceleo.query.ast.CallType;
import org.eclipse.acceleo.query.ast.Conditional;
import org.eclipse.acceleo.query.ast.EnumLiteral;
import org.eclipse.acceleo.query.ast.Expression;
import org.eclipse.acceleo.query.ast.IntegerLiteral;
import org.eclipse.acceleo.query.ast.Lambda;
import org.eclipse.acceleo.query.ast.RealLiteral;
import org.eclipse.acceleo.query.ast.SequenceInExtensionLiteral;
import org.eclipse.acceleo.query.ast.SetInExtensionLiteral;
import org.eclipse.acceleo.query.ast.StringLiteral;
import org.eclipse.acceleo.query.ast.VarRef;
import org.eclipse.acceleo.query.ast.VariableDeclaration;
import org.eclipse.acceleo.query.parser.AstBuilderListener;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.impl.EStructuralFeatureImpl;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ocl.ecore.BooleanLiteralExp;
import org.eclipse.ocl.ecore.CollectionItem;
import org.eclipse.ocl.ecore.CollectionLiteralExp;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.ecore.EcorePackage;
import org.eclipse.ocl.ecore.EnumLiteralExp;
import org.eclipse.ocl.ecore.IfExp;
import org.eclipse.ocl.ecore.IntegerLiteralExp;
import org.eclipse.ocl.ecore.IteratorExp;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.OperationCallExp;
import org.eclipse.ocl.ecore.OrderedSetType;
import org.eclipse.ocl.ecore.PropertyCallExp;
import org.eclipse.ocl.ecore.RealLiteralExp;
import org.eclipse.ocl.ecore.SetType;
import org.eclipse.ocl.ecore.StringLiteralExp;
import org.eclipse.ocl.ecore.TypeExp;
import org.eclipse.ocl.ecore.Variable;
import org.eclipse.ocl.ecore.VariableExp;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
/**
* A converter dedicated to OCLExpressions.
*
* @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
*/
public final class ExpressionConverter extends AbstractConverter {
private final class ServiceMethodRefactorVisitor extends ASTVisitor {
/**
* The {@link IDocument} of the source code.
*/
private final IDocument document;
/**
* The service name.
*/
private final String serviceName;
private ServiceMethodRefactorVisitor(IDocument document, String serviceName) {
this.document = document;
this.serviceName = serviceName;
}
@Override
public boolean visit(MethodDeclaration method) {
if (method.getName().getIdentifier().equals(serviceName) && method.parameters().isEmpty()) {
final ASTRewrite rewrite = ASTRewrite.create(method.getAST());
final MethodDeclaration newMethod = method.getAST().newMethodDeclaration();
// newMethod: <serviceModifiers> <serviceType> <serviceName>JavaService(Object object)
for (Object modifier : method.modifiers()) {
newMethod.modifiers().add(rewrite.createCopyTarget((ASTNode)modifier));
}
newMethod.setReturnType2((Type)rewrite.createCopyTarget(method.getReturnType2()));
newMethod.setName(newMethod.getAST().newSimpleName(serviceName + JAVA_SERVICE));
final SingleVariableDeclaration parameter = newMethod.getAST().newSingleVariableDeclaration();
parameter.setName(newMethod.getAST().newSimpleName("object"));
parameter.setType(newMethod.getAST().newSimpleType(newMethod.getAST().newName("Object")));
newMethod.parameters().add(parameter);
// newMethod body: return <serviceName>();
final Block body = newMethod.getAST().newBlock();
final ReturnStatement returnStatement = newMethod.getAST().newReturnStatement();
final MethodInvocation methodInvocation = newMethod.getAST().newMethodInvocation();
methodInvocation.setName(newMethod.getAST().newSimpleName(serviceName));
returnStatement.setExpression(methodInvocation);
body.statements().add(returnStatement);
newMethod.setBody(body);
// add the newMethod to the containing Class
final TypeDeclaration typeDeclaration = (TypeDeclaration)method.getParent();
final ListRewrite declarations = rewrite.getListRewrite(typeDeclaration,
TypeDeclaration.BODY_DECLARATIONS_PROPERTY);
declarations.insertAfter(newMethod, method, null);
try {
final TextEdit edit = rewrite.rewriteAST(document, Collections.EMPTY_MAP);
edit.apply(document);
} catch (IllegalArgumentException | MalformedTreeException | BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return super.visit(method);
}
}
/**
* The java service suffix.
*/
private static final String JAVA_SERVICE = "JavaService";
/**
* The self variable.
*/
private static final String SELF_VARIABLE_NAME = "self";
/**
* The flag stating whether we should resolve the self variable.
*/
private boolean resolveSelf = true;
/**
* The trget folder {@link Path}.
*/
private final Path targetFolderPath;
/**
* Constructor.
*
* @param targetFolderPath
* the target folder {@link Path}
*/
public ExpressionConverter(Path targetFolderPath) {
this.targetFolderPath = targetFolderPath;
}
/**
* Converts the given {@link OCLExpression} to a {@link Statement}.
*
* @param input
* the expression to convert
* @return the statement
*/
public Statement convertToStatement(OCLExpression input) {
Statement output = AcceleoFactory.eINSTANCE.createExpressionStatement();
((ExpressionStatement)output).setExpression(convertToExpression(input, false));
return output;
}
/**
* Converts the given {@link OCLExpression} to an {@link Expression}.
*
* @param inputExpression
* the expression to convert
* @param allowSelf
* if <true>, won't resolve self for this expression
* @return the Acceleo 4 expression
*/
public org.eclipse.acceleo.Expression convertToExpression(OCLExpression inputExpression,
boolean allowSelf) {
org.eclipse.acceleo.Expression outputExpression = AcceleoFactory.eINSTANCE.createExpression();
if (allowSelf) {
this.resolveSelf = false;
}
outputExpression.setAst(createAstResult((Expression)convert(inputExpression)));
if (allowSelf) {
this.resolveSelf = true;
}
return outputExpression;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.aql.migration.converters.AbstractConverter#convert(org.eclipse.emf.ecore.EObject)
*/
@Override
protected Object convert(EObject input) {
Object output = null;
switch (input.eClass().getClassifierID()) {
case EcorePackage.VARIABLE:
output = caseVariable((Variable)input);
break;
case EcorePackage.PROPERTY_CALL_EXP:
output = casePropertyCallExp((PropertyCallExp)input);
break;
case EcorePackage.VARIABLE_EXP:
output = caseVariableExp((VariableExp)input);
break;
case MtlPackage.TEMPLATE_INVOCATION:
output = caseTemplateInvocation((TemplateInvocation)input);
break;
case MtlPackage.QUERY_INVOCATION:
output = caseQueryInvocation((QueryInvocation)input);
break;
case EcorePackage.ITERATOR_EXP:
output = caseIteratorExp((IteratorExp)input);
break;
case EcorePackage.TYPE_EXP:
output = TypeUtils.createTypeLiteral(((TypeExp)input).getReferredType());
break;
case EcorePackage.OPERATION_CALL_EXP:
output = caseOperationCallExp((OperationCallExp)input);
break;
case EcorePackage.STRING_LITERAL_EXP:
output = AstFactory.eINSTANCE.createStringLiteral();
((StringLiteral)output).setValue(((StringLiteralExp)input).getStringSymbol());
break;
case EcorePackage.INTEGER_LITERAL_EXP:
output = AstFactory.eINSTANCE.createIntegerLiteral();
((IntegerLiteral)output).setValue(((IntegerLiteralExp)input).getIntegerSymbol());
break;
case EcorePackage.BOOLEAN_LITERAL_EXP:
output = AstFactory.eINSTANCE.createBooleanLiteral();
((BooleanLiteral)output).setValue(((BooleanLiteralExp)input).getBooleanSymbol());
break;
case EcorePackage.REAL_LITERAL_EXP:
output = AstFactory.eINSTANCE.createRealLiteral();
((RealLiteral)output).setValue(((RealLiteralExp)input).getRealSymbol());
break;
case EcorePackage.ENUM_LITERAL_EXP:
output = AstFactory.eINSTANCE.createEnumLiteral();
((EnumLiteral)output).setLiteral(((EnumLiteralExp)input).getReferredEnumLiteral());
break;
case EcorePackage.NULL_LITERAL_EXP:
output = AstFactory.eINSTANCE.createNullLiteral();
break;
case EcorePackage.COLLECTION_LITERAL_EXP:
output = convertCollectionLiteralExp((CollectionLiteralExp)input);
break;
case EcorePackage.IF_EXP:
output = caseIfExp((IfExp)input);
break;
case EcorePackage.COLLECTION_ITEM:
output = convert(((CollectionItem)input).getItem());
break;
// case EcorePackage.COLLECTION_RANGE:
// output = convert(((CollectionItem)input).getItem());
// break;
default:
throw new MigrationException(input);
}
return output;
}
private Object caseIfExp(IfExp input) {
Conditional output = AstFactory.eINSTANCE.createConditional();
output.setPredicate((Expression)convert(input.getCondition()));
output.setTrueBranch((Expression)convert(input.getThenExpression()));
output.setFalseBranch((Expression)convert(input.getElseExpression()));
return output;
}
private Object caseVariable(Variable input) {
VariableDeclaration output = AstFactory.eINSTANCE.createVariableDeclaration();
output.setName(input.getName());
if (output.getExpression() != null) {
output.setExpression((Expression)convert(input.getInitExpression()));
}
return output;
}
private Expression caseVariableExp(VariableExp input) {
Expression output = null;
// if (input.getReferredVariable().eContainer() instanceof IteratorExp) {
// IteratorExp iterator = (IteratorExp)input.getReferredVariable().eContainer();
// output = (Expression)convert(iterator.getSource());
// } else {
output = AstFactory.eINSTANCE.createVarRef();
String variableName = input.getReferredVariable().getName();
if (resolveSelf && SELF_VARIABLE_NAME.equals(variableName)) {
Variable variable = findVariable(input);
variableName = variable.getName();
}
((VarRef)output).setVariableName(variableName);
// }
return output;
}
private Expression casePropertyCallExp(PropertyCallExp input) {
Call output = AstFactory.eINSTANCE.createCall();
output.setServiceName(AstBuilderListener.FEATURE_ACCESS_SERVICE_NAME);
if (input.getSource().getType() instanceof CollectionType) {
output.setType(CallType.COLLECTIONCALL);
} else {
output.setType(CallType.CALLORAPPLY);
}
output.getArguments().add((Expression)convert(input.getSource()));
StringLiteral propertyName = AstFactory.eINSTANCE.createStringLiteral();
if (!input.getReferredProperty().eIsProxy()) {
propertyName.setValue(input.getReferredProperty().getName());
} else {
final URI proxyURI = ((EStructuralFeatureImpl)input.getReferredProperty()).eProxyURI();
final String[] segments = proxyURI.fragment().split("/");
propertyName.setValue(segments[segments.length - 1]);
}
output.getArguments().add(propertyName);
return output;
}
private Expression caseOperationCallExp(OperationCallExp input) {
final Expression res;
if ("oclIsUndefined".equals(input.getReferredOperation().getName())) {
Call output = AstFactory.eINSTANCE.createCall();
output.setServiceName(AstBuilderListener.EQUALS_SERVICE_NAME);
output.setType(CallType.CALLSERVICE);
output.getArguments().add((Expression)convert(input.getSource()));
output.getArguments().add(AstFactory.eINSTANCE.createNullLiteral());
res = output;
} else if (isInvokeCall(input)) {
res = convertInvokeCall(input);
} else if (isOclAsSetCall(input)) {
res = convertOclAsSetCall(input);
} else {
Call output = OperationUtils.createCall(input);
output.getArguments().add((Expression)convert(input.getSource()));
map(input.getArgument(), output.getArguments());
res = output;
}
return res;
}
/**
* Converts the given oclAsSet {@link OperationCallExp}.
*
* @param input
* the oclAsSet {@link OperationCallExp}
* @return the converted oclAsSet {@link OperationCallExp}
*/
private Expression convertOclAsSetCall(OperationCallExp input) {
Call res = AstFactory.eINSTANCE.createCall();
res.setServiceName("asSet");
res.setType(CallType.COLLECTIONCALL);
res.getArguments().add((Expression)convert(input.getSource()));
return res;
}
/**
* Tells if the given {@link OperationCallExp} is an oclAsSet call.
*
* @param input
* the {@link OperationCallExp} to check
* @return <code>true</code> if the given {@link OperationCallExp} is an oclAsSet call, <code>false</code>
* otherwise
*/
private boolean isOclAsSetCall(OperationCallExp input) {
final EOperation referredOperation = input.getReferredOperation();
return referredOperation != null && "oclAsSet".equals(referredOperation.getName())
&& referredOperation.eContainer() instanceof EClass && "OclAny_Class".equals(
((EClass)referredOperation.eContainer()).getName());
}
/**
* Converts the given invoke {@link OperationCallExp}.
*
* @param input
* the invoke call
* @return the converted invoke {@link OperationCallExp}
*/
private Expression convertInvokeCall(OperationCallExp input) {
final Call res = OperationUtils.createCall(input);
res.setType(CallType.CALLSERVICE);
final String serviceSignature = ((org.eclipse.ocl.expressions.StringLiteralExp<EClassifier>)input
.getArgument().get(1)).getStringSymbol();
final String serviceName = serviceSignature.substring(0, serviceSignature.indexOf("("));
res.setServiceName(serviceName);
map(((CollectionLiteralExp)input.getArgument().get(2)).getPart(), res.getArguments());
if (res.getArguments().isEmpty()) {
res.setServiceName(serviceName + JAVA_SERVICE);
final String varName = ((Query)input.eContainer()).getParameter().get(0).getName();
final VarRef varRef = AstFactory.eINSTANCE.createVarRef();
varRef.setVariableName(varName);
res.getArguments().add(varRef);
final String serviceClassName = ((org.eclipse.ocl.expressions.StringLiteralExp<EClassifier>)input
.getArgument().get(0)).getStringSymbol();
try {
refactorService(serviceClassName, serviceName);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return res;
}
/**
* Renames the service and add an {@link Object} parameter.
*
* @param serviceClassName
* the service Class name
* @param serviceName
* the service name
* @throws IOException
* if the java file can't be read or written
*/
private void refactorService(String serviceClassName, String serviceName) throws IOException {
ASTParser parser = ASTParser.newParser(AST.JLS10);
final File javaFile = new File(targetFolderPath + FileSystems.getDefault().getSeparator()
+ serviceClassName.replace(".", FileSystems.getDefault().getSeparator()) + ".java");
if (javaFile.exists()) {
final IDocument document = new Document(new String(Files.readAllBytes(javaFile.toPath())));
parser.setSource(document.get().toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
final CompilationUnit cu = (CompilationUnit)parser.createAST(null);
cu.accept(new ServiceMethodRefactorVisitor(document, serviceName));
Files.write(javaFile.toPath(), document.get().getBytes(), StandardOpenOption.CREATE);
}
}
/**
* Tells if the given {@link OperationCallExp} is an invoke() call.
*
* @param input
* the {@link OperationCallExp}
* @return <code>true</code> if the given OperationCallExp is an invoke() call, <code>false</code>
* otherwise
*/
public boolean isInvokeCall(OperationCallExp input) {
final EOperation referredOperation = input.getReferredOperation();
return referredOperation != null && "invoke".equals(referredOperation.getName()) && referredOperation
.eContainer() instanceof EClass && "oclstdlib_OclAny_Class".equals(((EClass)referredOperation
.eContainer()).getName());
}
private Expression caseIteratorExp(IteratorExp input) {
if ("collect".equals(input.getName()) && (input.getSource().getType() instanceof OrderedSetType
|| input.getSource().getType() instanceof SetType)) {
// we add a asSequence before to match A3 behavior
Call asSequence = AstFactory.eINSTANCE.createCall();
asSequence.setServiceName("asSequence");
asSequence.setType(CallType.COLLECTIONCALL);
asSequence.getArguments().add((Expression)convert(input.getSource()));
return convertIterator(input, asSequence);
} else {
return convertIterator(input, (Expression)convert(input.getSource()));
}
}
private Expression convertIterator(IteratorExp input, Expression source) {
Call output = AstFactory.eINSTANCE.createCall();
if (input.getSource().getType() instanceof CollectionType) {
output.setType(CallType.COLLECTIONCALL);
} else {
output.setType(CallType.CALLORAPPLY);
}
output.setServiceName(input.getName());
output.getArguments().add(source);
Lambda lambda = AstFactory.eINSTANCE.createLambda();
map(input.getIterator(), lambda.getParameters());
lambda.setExpression((Expression)convert(input.getBody()));
output.getArguments().add(lambda);
return output;
}
private Expression caseTemplateInvocation(TemplateInvocation input) {
Call output = AstFactory.eINSTANCE.createCall();
output.setType(CallType.CALLORAPPLY);
output.setServiceName(input.getDefinition().getName());
map(input.getArgument(), output.getArguments());
return output;
}
private Expression caseQueryInvocation(QueryInvocation input) {
Call output = AstFactory.eINSTANCE.createCall();
output.setType(CallType.CALLORAPPLY);
output.setServiceName(input.getDefinition().getName());
map(input.getArgument(), output.getArguments());
return output;
}
private Expression convertCollectionLiteralExp(CollectionLiteralExp input) {
Expression output = null;
EClassifier type = input.getEType();
switch (type.eClass().getClassifierID()) {
case EcorePackage.SEQUENCE_TYPE:
output = AstFactory.eINSTANCE.createSequenceInExtensionLiteral();
map(input.getPart(), ((SequenceInExtensionLiteral)output).getValues());
break;
case EcorePackage.BAG_TYPE:
output = AstFactory.eINSTANCE.createSequenceInExtensionLiteral();
map(input.getPart(), ((SequenceInExtensionLiteral)output).getValues());
break;
case EcorePackage.SET_TYPE:
output = AstFactory.eINSTANCE.createSetInExtensionLiteral();
map(input.getPart(), ((SetInExtensionLiteral)output).getValues());
break;
case EcorePackage.ORDERED_SET_TYPE:
output = AstFactory.eINSTANCE.createSetInExtensionLiteral();
map(input.getPart(), ((SetInExtensionLiteral)output).getValues());
break;
default:
throw new MigrationException(type);
}
return output;
}
// TODO use accurate rules here
private static Variable findVariable(EObject context) {
Variable variable = null;
EObject validParent = context.eContainer();
while (validParent != null && !(validParent instanceof Template || validParent instanceof Query)) {
validParent = validParent.eContainer();
}
if (validParent instanceof Template) {
variable = ((Template)validParent).getParameter().get(0);
} else if (validParent instanceof Query) {
variable = ((Query)validParent).getParameter().get(0);
}
return variable;
}
}