/*******************************************************************************
* Copyright (c) 2010 Michal Antkiewicz.
* 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:
* Michal Antkiewicz - initial API and implementation
******************************************************************************/
package ca.uwaterloo.gsd.fsml.javaMappingInterpreter.mappings;
import java.util.Collection;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
import ca.uwaterloo.gsd.fsml.core.Cause;
import ca.uwaterloo.gsd.fsml.core.Context;
import ca.uwaterloo.gsd.fsml.core.FSMLMappingException;
import ca.uwaterloo.gsd.fsml.core.Mode;
import ca.uwaterloo.gsd.fsml.core.Parameter;
import ca.uwaterloo.gsd.fsml.ecore.FSMLEcoreUtil;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeQueries;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeTransforms;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.JavaMappingInterpreter;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.VarCollector;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.VarDesc;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.ASTUtils;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.JavaModelUtils;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.Scope;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.impl.JavaImplVariantManagerConstants;
import ca.uwaterloo.gsd.fsml.stats.Stats;
import ca.uwaterloo.gsd.fsml.sync.SyncItem;
public class MethodCallsMapping extends JavaMapping {
/**
* Constructor used in reverse engineering.
* @param element
* @param feature
* @param annotation
* @param concreteChildType
* @param interpreter
* @param progressMonitor
* @throws FSMLMappingException
*/
public MethodCallsMapping(EObject element, EStructuralFeature feature, EAnnotation annotation, EClass concreteChildType, JavaMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
super(element, feature, annotation, concreteChildType, interpreter, progressMonitor);
if ("true".equals(statement))
statementOnly = true;
}
/**
* Constructor used in forward engineering.
* @param syncItem
* @param annotation
* @param interpreter
* @param progressMonitor
* @throws FSMLMappingException
*/
public MethodCallsMapping(SyncItem syncItem, EAnnotation annotation, JavaMappingInterpreter interpreter, IProgressMonitor progressMonitor) throws FSMLMappingException {
super(syncItem, annotation, interpreter, progressMonitor);
if (detailPosition == null || detailPosition.equals("before"))
position = CodeTransforms.BEFORE_ADVICE;
else if (detailPosition.equals("after"))
position = CodeTransforms.AFTER_ADVICE;
else {
try {
position = Integer.parseInt(detailPosition);
} catch (Exception e){
throw new FSMLMappingException(Cause.INCORRECT_VALUE, "for detail " + JavaMappingInterpreter.DETAIL_POSITION);
}
}
if ("true".equals(statement))
statementOnly = true;
}
@Context(mode=Mode.REVERSE)
public IMethod contextIMethod;
@Context(mode=Mode.REVERSE)
public IType contextIType;
@Context(mode=Mode.FORWARD)
public MethodDeclaration contextMethodDeclaration;
@Context(mode=Mode.FORWARD)
public TypeDeclaration contextTypeDeclaration;
@Parameter(name=JavaMappingInterpreter.DETAIL_CLASS, mode=Mode.ALL, required=true)
public String type;
/**
* Name can be empty for constructors
*/
@Parameter(name=JavaMappingInterpreter.DETAIL_NAME, mode=Mode.ALL, required=false, defaultValue="")
public String name;
@Parameter(name=JavaMappingInterpreter.DETAIL_SIGNATURE, mode=Mode.ALL, defaultValue="()V")
public String signature;
@Parameter(name=JavaMappingInterpreter.DETAIL_IN, mode=Mode.REVERSE)
public String scope;
@Parameter(name=JavaMappingInterpreter.DETAIL_RECEIVER, mode=Mode.ALL)
public String receiver;
@Parameter(name=JavaMappingInterpreter.DETAIL_STATEMENT, mode=Mode.ALL)
public String statement;
@Parameter(name=JavaMappingInterpreter.DETAIL_LOCATION_NAME, mode=Mode.FORWARD, required=true)
public String detailLocationName;
@Parameter(name=JavaMappingInterpreter.DETAIL_LOCATION_SIG, mode=Mode.FORWARD, defaultValue="()V")
public String detailLocationSig;
@Parameter(name=JavaMappingInterpreter.DETAIL_POSITION, mode=Mode.FORWARD, defaultValue="after")
public String detailPosition;
@Parameter(name=JavaMappingInterpreter.DETAIL_RECEIVER_EXPR, mode=Mode.FORWARD)
public String receiverExpr;
protected int position = 0;
protected boolean statementOnly = false;
@Override
protected boolean forward() throws FSMLMappingException {
String key = FSMLEcoreUtil.computeAnnotationKey(annotation);
IMethod targetMethod = javaInterpreter.iMethods.get(key);
String[] parameterTypes = targetMethod.getParameterTypes();
// construct a call
String contents = "";
boolean isConstructor = false;
try {
isConstructor = targetMethod.isConstructor();
} catch (JavaModelException e) {
e.printStackTrace();
}
if (isConstructor) {
contents = "new " + Signature.getSimpleName(type) + CodeTransforms.constructDefaultCallParameters(parameterTypes) + ";";
} else {
contents = receiverExpr + ("".equals(receiverExpr) ? "" : ".") + name + CodeTransforms.constructDefaultCallParameters(parameterTypes) + ";";
}
List<ASTNode> result = null;
// method calls can be create in the context method or a context class
if (contextMethodDeclaration != null) {
result = CodeTransforms.weaveAdvice(position, null, contextMethodDeclaration, contents, progressMonitor);
}
// no context method, try context class
if (contextTypeDeclaration != null) {
MethodDeclaration methodDeclaration = null;
methodDeclaration = ASTUtils.findMethod(javaAstManager, detailLocationName, detailLocationSig, contextTypeDeclaration, true, false, progressMonitor);
if (methodDeclaration == null)
// create the method
methodDeclaration = CodeTransforms.createMethod(contextIJavaProject, null, contextTypeDeclaration, "public", detailLocationName, detailLocationSig, null, progressMonitor);
if (receiverExpr.isEmpty()){
VarCollector collector = new VarCollector();
CompilationUnit cu = (CompilationUnit)methodDeclaration.getRoot();
collector.collect(cu, cu.toString(), methodDeclaration.getStartPosition());
List<VarDesc> declarations = collector.getDeclarations();
try {
for (VarDesc varDesc : declarations) {
IType currentVarType=null;
if (varDesc.type.isSimpleType()){
SimpleType simpleType = (SimpleType)varDesc.type;
if (simpleType.resolveBinding()!=null){
currentVarType= contextIJavaProject.findType(simpleType.resolveBinding().getQualifiedName());
}
} else {
currentVarType = contextIJavaProject.findType(varDesc.type.toString());
}
if (currentVarType!=null){
ITypeHierarchy newSupertypeHierarchy = currentVarType.newSupertypeHierarchy(new NullProgressMonitor());
if (!varDesc.name.equals("this") && newSupertypeHierarchy.contains(contextIJavaProject.findType(type))){
contents =varDesc.name+"."+contents;
}
}else {
continue;
}
}
}
catch (JavaModelException e) {
e.printStackTrace();
}
}
result = CodeTransforms.weaveAdvice(position, null, methodDeclaration, contents, progressMonitor);
}
// need to set up the call as a context and put the markers
if (result != null && !result.isEmpty()) {
// should be a single method call. However, in the future, different variants may
// require some preparation statements, e.g., variable declarations, and the call will
// the the last one
ASTNode astNode = result.get(result.size()-1);
if (astNode instanceof ExpressionStatement)
astNode = ((ExpressionStatement) astNode).getExpression();
// ClassInstanceCreation:
// the case where this "method call" is an instantiation of a class
// e.g.
// int x = 0;
// new Thread();
if (astNode instanceof MethodInvocation || astNode instanceof ClassInstanceCreation) {
EObject element = FSMLEcoreUtil.retrieveContextElement(syncItem, JavaMappingInterpreter.CONTEXT_METHOD_CALL);
contextManager.associateContext(element, astNode);
}
}
return result != null;
}
@Override
protected boolean reverse() throws FSMLMappingException {
String key = FSMLEcoreUtil.computeAnnotationKey(annotation);
IMethod targetIMethod = javaInterpreter.iMethods.get(key);
IMember context = contextIMethod != null ? contextIMethod : contextIType;
if (context == null) {
Stats.INSTANCE.logBug("MethodCallsMapping::reverse(): BUG: context is null!");
return FSMLEcoreUtil.isFeaturePresent(element, feature);
}
CompilationUnit contextCompilationUnit = javaAstManager.getCompilationUnit(context);
if (contextCompilationUnit == null)
return FSMLEcoreUtil.isFeaturePresent(element, feature);
if (javaInterpreter.callGraphScope) {
progressMonitor.beginTask("Checking method calls to " + targetIMethod.getElementName() + " in context " + context.getElementName(), 1);
// make sure the compilation unit is added to the analysis
progressMonitor.subTask("Adding type " + context.getElementName() + " to the call graph");
callGraphManager.addCompilationUnit(JavaModelUtils.getTypeRoot(context));
// get the calls reachable from the call graph
List<Expression> methodCalls = callGraphManager.getCalls(targetIMethod, null);
for (Expression call : methodCalls) {
if (statementOnly && call.getParent().getNodeType() != ASTNode.EXPRESSION_STATEMENT)
continue;
if (!receiver.isEmpty()) {
Expression receiverExpression = null;
switch (call.getNodeType()) {
case ASTNode.METHOD_INVOCATION:
receiverExpression = ((MethodInvocation) call).getExpression();
break;
case ASTNode.CLASS_INSTANCE_CREATION:
receiverExpression = ((ClassInstanceCreation) call).getExpression();
break;
}
if (receiverExpression != null) {
ITypeBinding receiverExpressionTypeBinding = receiverExpression.resolveTypeBinding();
try {
if (!typeHierarchy.implementsOrExtendsType(receiverExpressionTypeBinding, receiver))
continue;
} catch (JavaModelException e) {
e.printStackTrace();
}
}
MethodDeclaration methodDeclaration = (MethodDeclaration) ASTUtils.getAncestorOfType(call, ASTNode.METHOD_DECLARATION);
IMethodBinding iMethodBinding = methodDeclaration.resolveBinding();
if (iMethodBinding == null) {
TypeDeclaration typeDeclaration = (TypeDeclaration) ASTUtils.getAncestorOfType(methodDeclaration, ASTNode.TYPE_DECLARATION);
Stats.INSTANCE.logError("MethodCallsMapping::reverse(): Cannot resolve binding for method: " + typeDeclaration.getName() + "." + methodDeclaration.getName() + ". Ignoring - a potential false negative.");
continue;
}
IMethod iMethod = (IMethod) iMethodBinding.getJavaElement();
// if the method call is in a method declaration, check if it can be accessed from the context
if (iMethod != null && iMethod.exists()) {
progressMonitor.subTask(JavaModelUtils.formatMethod(iMethod).toString());
if (! callGraphManager.calls(context, iMethod)) {
//Stats.INSTANCE.log("MethodCallsMapping::reverse(): Method call to " + targetMethod.getElementName()+ "() found in " + enclosingMemberName + "() is unreachable from context: " + context.getElementName());
continue;
}
if (setFeatureContextAndMarker(true, call, call, null) && !feature.isMany()) {
break;
}
}
}
}
progressMonitor.worked(1);
}
else {
// the context can be a type or a method
try {
IType targetType = contextIJavaProject.findType(type);
// default is SOURCE_HIERARCHY for types and NO_HIERARCHY for methods
Scope scope = Scope.SOURCE_HIERARCHY;
if (context instanceof IMethod)
scope = Scope.NO_HIERARCHY;
// search for method calls using eclipse's infrastructure
Collection<SearchMatch> methodCalls;
if (javaInterpreter.projectScope) {
methodCalls = javaInterpreter.callsCache.get(targetIMethod);
if (methodCalls == null) {
methodCalls = JavaModelUtils.getMethodCallsInProject(contextIJavaProject, targetIMethod, progressMonitor);
javaInterpreter.callsCache.put(targetIMethod, methodCalls);
}
}
else
methodCalls = JavaModelUtils.getMethodCalls(contextIJavaProject, typeHierarchy, context, targetType, targetIMethod, scope, progressMonitor);
progressMonitor.beginTask("Checking method calls to " + targetIMethod.getElementName() + " in context " + context.getElementName(), methodCalls.size());
for (SearchMatch match : methodCalls) {
progressMonitor.worked(1);
if (progressMonitor.isCanceled())
return FSMLEcoreUtil.isFeaturePresent(element, feature);
// make sure the compilation unit is added to the analysis
callGraphManager.addCompilationUnit(JavaModelUtils.getTypeRoot(context));
// it will be a method, constructor, field init or static initializer
IJavaElement enclosingMember = ((IJavaElement) match.getElement()).getPrimaryElement();
CompilationUnit matchCompilationUnit = javaAstManager.getCompilationUnit(enclosingMember);
if (matchCompilationUnit == null)
continue;
ASTNode matchAstNode = ASTNodeSearchUtil.findNode(match, matchCompilationUnit);
if (matchAstNode == null) {
Stats.INSTANCE.logError("MethodCallsMapping::reverse(): " + element.eClass().getName() + "::" + feature.getName()+ " ASTNode for match not found: " + match + "\n");
continue;
}
if (matchAstNode.getNodeType() != ASTNode.METHOD_INVOCATION && matchAstNode.getNodeType() != ASTNode.CLASS_INSTANCE_CREATION) {
Stats.INSTANCE.logMessage("MethodCallsMapping::reverse(): matchASTNode which is either method invocation nor class instance creation found: " + matchAstNode.getClass() + "\n");
continue;
}
if (statementOnly && matchAstNode.getParent().getNodeType() != ASTNode.EXPRESSION_STATEMENT)
continue;
// if the method call is in a method declaration, check if it can be accessed from the context
if (enclosingMember instanceof IMethod) {
progressMonitor.subTask(JavaModelUtils.formatMethod((IMethod) enclosingMember).toString());
if (javaInterpreter.analyzeImplVariants()) {
String declaringMethod = ((IMethod)enclosingMember).getKey().replaceFirst("L[^;]*;\\.", "");
Stats.INSTANCE.logImplVariant(element.eClass(), feature, annotation,declaringMethod );
EClass targetClass = javaImplVariantManager.getVariantEClass(JavaImplVariantManagerConstants.VARIANT+
JavaImplVariantManagerConstants.DELIMITER+
annotation.getSource() + JavaImplVariantManagerConstants.DELIMITER
+ JavaImplVariantManagerConstants.IN_METHOD);
EObject child = EcoreUtil.create(targetClass);
((EList) javaImplVariantManager.getModel().eGet(javaImplVariantManager.getVariantsReference())).add(child);
child.eSet(targetClass.getEStructuralFeature(JavaImplVariantManagerConstants.ECLASS), element.eClass().getName());
child.eSet(targetClass.getEStructuralFeature(JavaImplVariantManagerConstants.ESTRUCTURALFEATURE), feature.getName());
child.eSet(targetClass.getEStructuralFeature(JavaImplVariantManagerConstants.METHOD_NAME), declaringMethod);
//createMarkerDescriptor((ResolvedSourceMethod)((MethodReferenceMatch)match).getElement(), JavaImplVariantManager.computeVariantKey(child), "marker for implModel");
createMarkerDescriptor(enclosingMember, child, "marker for implModel");
/*MethodInvocation methodInvocation = (MethodInvocation) astNode;
// analyze the implementation variants
// find the method name
ASTNode currentNode = astNode;
while (currentNode.getNodeType() != ASTNode.METHOD_DECLARATION) {
currentNode = currentNode.getParent();
}
List<Expression> args = methodInvocation.arguments();
for (Expression arg : args)
Stats.INSTANCE.log(element.eClass(), feature, annotation, "Arguments: " + arg.getClass().getSimpleName());
String methodName = ((MethodDeclaration) currentNode).getName().toString();
if (hierarchyConstant == Scope.NO_HIERARCHY)
Stats.INSTANCE.log(element.eClass(), feature, annotation, "inMethod: " + methodName + ", atLocation: " + ASTUtils.getStmtLocationInMethod(astNode));
else {
// we searched beyond the local file
// try to see if we still get the method
// call if we just search locally
Collection<SearchMatch> localResult = CodeQueries.getMethodCalls(contextIType != null ? contextIType : contextIMethod, targetType, targetMethod, progressMonitor, Scope.NO_HIERARCHY);
if (localResult.contains(match))
Stats.INSTANCE.log(element.eClass(), feature, annotation, "inMethod: " + methodName + ", atLocation: " + ASTUtils.getStmtLocationInMethod(astNode));
else if (contextIType != null)
Stats.INSTANCE.log(element.eClass(), feature, annotation, "inheritsFrom: " + contextIType.getSuperclassName());
*/
}
if (! callGraphManager.calls(context, (IMethod) enclosingMember)) {
//Stats.INSTANCE.log("MethodCallsMapping::reverse(): Method call to " + targetMethod.getElementName()+ "() found in " + enclosingMemberName + "() is unreachable from context: " + context.getElementName());
continue;
}
}
// check if expression is assignable to the required
// receiver
if (!receiver.isEmpty()) {
Expression receiverExpression = null;
if (matchAstNode instanceof MethodInvocation)
receiverExpression = ((MethodInvocation) matchAstNode).getExpression();
else if (matchAstNode instanceof ClassInstanceCreation)
receiverExpression = ((ClassInstanceCreation) matchAstNode).getExpression();
if (receiverExpression != null) {
ITypeBinding typeBinding = receiverExpression.resolveTypeBinding();
if (typeBinding == null)
continue;
IType iType = (IType) typeBinding.getJavaElement();
if (! typeHierarchy.implementsOrExtendsType(iType, receiver))
continue;
else {// analyze implementation variant
if (JavaMappingInterpreter.analyzeImplVariants())
Stats.INSTANCE.logImplVariant(element.eClass(), feature, annotation, "receiver is a " + receiverExpression.getClass().getSimpleName());
}
} else {
// the receiver is 'this'
IType receiverIType = CodeQueries.findITypeContainingASTNode(contextIJavaProject, matchAstNode);
if (! typeHierarchy.implementsOrExtendsType(receiverIType, receiver))
continue;
if (JavaMappingInterpreter.analyzeImplVariants())
Stats.INSTANCE.logImplVariant(element.eClass(), feature, annotation, "receiver is this");
}
}
if (setFeatureContextAndMarker(true, matchAstNode, matchAstNode, null) && !feature.isMany()) {
break;
}
}
} catch (JavaModelException e) {
e.printStackTrace();
}
}
progressMonitor.done();
return FSMLEcoreUtil.isFeaturePresent(element, feature);
}
}