/******************************************************************************* * 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 * Thiago Tonelli Bartolomei - extensions ******************************************************************************/ package ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IInitializer; 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.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.BooleanLiteral; import org.eclipse.jdt.core.dom.CharacterLiteral; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.IfStatement; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.NumberLiteral; import org.eclipse.jdt.core.dom.QualifiedName; import org.eclipse.jdt.core.dom.QualifiedType; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.TypeLiteral; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.internal.core.util.Util; import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil; import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeQueries; import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.OccurrencesFinder; import ca.uwaterloo.gsd.fsml.stats.Stats; /** * Utility methods for manipulating AST nodes * * @author Thiago Tonelli Bartolomei <ttonelli@gsd.uwaterloo.ca> */ public class ASTUtils { /** * Returns the AST node relative to the search match, if it is inside this compilation unit. * * This is just to remove this "discouraged access" from other classes. * * @param match * @param cu * @return */ public static ASTNode getASTNode(SearchMatch match, CompilationUnit cu) { return ASTNodeSearchUtil.findNode(match, cu); } /** * Returns the AST node relative to this member, if it is inside this compilation unit. The member * can be an static initializer, a field initializer, a method (constructor) or a type declaration. * * @param member * @param cu * @return * @throws JavaModelException */ public static BodyDeclaration getASTNode(IMember member, CompilationUnit cu) throws JavaModelException { if (member instanceof IField) { return ASTNodeSearchUtil.getFieldDeclarationNode((IField) member, cu); } if (member instanceof IInitializer) { return ASTNodeSearchUtil.getInitializerNode((IInitializer) member, cu); } if (member instanceof IMethod) { return ASTNodeSearchUtil.getMethodDeclarationNode((IMethod) member, cu); } if (member instanceof IType) { return ASTNodeSearchUtil.getTypeDeclarationNode((IType) member, cu); } return null; } // types public static TypeDeclaration getTypeDeclarationNode(ITypeBinding typeBinding, CompilationUnit compilationUnit) { TypeDeclaration result = (TypeDeclaration) compilationUnit.findDeclaringNode(typeBinding); if (result == null) return getTypeDeclarationNode((IType) typeBinding.getJavaElement(), compilationUnit); return result; } public static TypeDeclaration getTypeDeclarationNode(IType iType, CompilationUnit compilationUnit) { TypeDeclaration result = (TypeDeclaration) compilationUnit.findDeclaringNode(iType.getKey()); if (result == null) { try { return ASTNodeSearchUtil.getTypeDeclarationNode(iType, compilationUnit); } catch (JavaModelException e) { } } return result; } // methods public static MethodDeclaration getMethodDeclarationNode(IMethodBinding methodBinding, CompilationUnit compilationUnit) { MethodDeclaration result = (MethodDeclaration) compilationUnit.findDeclaringNode(methodBinding); if (result == null) return getMethodDeclarationNode((IMethod) methodBinding.getJavaElement(), compilationUnit); return result; } public static MethodDeclaration getMethodDeclarationNode(IMethod iMethod, CompilationUnit compilationUnit) { MethodDeclaration result = (MethodDeclaration) compilationUnit.findDeclaringNode(iMethod.getKey()); if (result == null) { try { return ASTNodeSearchUtil.getMethodDeclarationNode(iMethod, compilationUnit); } catch (JavaModelException e) { } } return result; } // fields public static VariableDeclarationFragment getVariableDeclarationFragmentNode(IVariableBinding iVariableBinding, CompilationUnit compilationUnit) { VariableDeclarationFragment result = (VariableDeclarationFragment) compilationUnit.findDeclaringNode(iVariableBinding); if (result == null) return getVariableDeclarationFragmentNode((IField) iVariableBinding.getJavaElement(), compilationUnit); return result; } public static VariableDeclarationFragment getVariableDeclarationFragmentNode(IField iField, CompilationUnit compilationUnit) { VariableDeclarationFragment result = (VariableDeclarationFragment) compilationUnit.findDeclaringNode(iField.getKey()); if (result == null) { try { return ASTNodeSearchUtil.getFieldDeclarationFragmentNode(iField, compilationUnit); } catch (JavaModelException e) { } } return result; } /** * Gets the first statement of this method declaration * * @param decl a method (or constructor) declaration * @return the first statement, or null if nothing found */ public static Statement getFirstStatement(MethodDeclaration decl) { try { return (Statement) decl.getBody().statements().get(0); } catch (Exception e) { return null; } } /** * @param expression is any literal expression: BOOLEAN_LITERAL, CHARACTER_LITERAL, NULL_LITERAL, NUMBER_LITERAL, STRING_LITERAL, and TYPE_LITERAL. * @return string representation */ public static String getStringValue(Expression expression) { switch (expression.getNodeType()) { case ASTNode.BOOLEAN_LITERAL: return ((BooleanLiteral) expression).booleanValue() ? "true" : "false"; case ASTNode.CHARACTER_LITERAL: return ((CharacterLiteral) expression).getEscapedValue(); case ASTNode.NULL_LITERAL: return "null"; case ASTNode.NUMBER_LITERAL: return ((NumberLiteral)expression).getToken(); case ASTNode.STRING_LITERAL: return ((StringLiteral) expression).getLiteralValue(); case ASTNode.TYPE_LITERAL: TypeLiteral typeLiteral = (TypeLiteral) expression; if (typeLiteral.getType().resolveBinding() != null) return typeLiteral.getType().resolveBinding().getQualifiedName(); else return getFullyQualifiedName(typeLiteral.getType()); } return null; } public static Expression getInitializer(CompilationUnit compilationUnit, IVariableBinding iVariableBinding) { IVariableBinding variableDeclarationBinding = iVariableBinding.getVariableDeclaration(); VariableDeclarationFragment variableDeclaration = getVariableDeclarationFragmentNode(variableDeclarationBinding, compilationUnit); if (variableDeclaration == null) { Stats.INSTANCE.logError("ASTUtils.getInitializer(): cannot find variable declaration for " + variableDeclarationBinding.getKey()); return null; } return variableDeclaration.getInitializer(); } /** * @return the closes parent (ancestor) of the given @param astNode of given @param nodeType. * The node type constants are located in the ASTNode class, e.g., ASTNode.METHOD_DECLARATION. */ public static ASTNode getAncestorOfType(ASTNode astNode, int nodeType) { ASTNode aux = astNode; while (aux != null && aux.getNodeType() != nodeType) aux = aux.getParent(); return aux; } /** * Returns the type declaration where this node was declared. * Note that if node was declared on a nested type, the closest type * is returned. * * @param node * @return */ public static TypeDeclaration getDeclaringType(ASTNode astNode) { return (TypeDeclaration) getAncestorOfType(astNode, ASTNode.TYPE_DECLARATION); } public static List<ASTNode> findMathodInvocations(CompilationUnit compilationUnit, MethodDeclaration methodDeclaration) { return ASTUtils.findMathodInvocations(compilationUnit, methodDeclaration, methodDeclaration.resolveBinding()); } public static List<ASTNode> findMathodInvocations(CompilationUnit compilationUnit, MethodDeclaration methodDeclaration, IMethodBinding iMethodBinding) { OccurrencesFinder occurrencesFinder = new OccurrencesFinder(); occurrencesFinder.initialize(compilationUnit, methodDeclaration.getName()); List<ASTNode> result = occurrencesFinder.performGlobally(); for (Iterator<ASTNode> i = result.iterator(); i.hasNext(); ) { ASTNode node = i.next(); switch (node.getParent().getNodeType()) { case ASTNode.METHOD_DECLARATION: case ASTNode.METHOD_REF: case ASTNode.METHOD_REF_PARAMETER: i.remove(); break; } } return result; } public static List<ASTNode> findWriteAccesses(CompilationUnit compilationUnit, SimpleName variable) { IBinding iBinding = variable.resolveBinding(); if (iBinding instanceof IVariableBinding) return findWriteAccesses(compilationUnit, variable, (IVariableBinding) iBinding); else return null; } /** * @param compilationUnit * @param variable a field, a local variable, a parameter * @param iVariableBinding * @return */ public static List<ASTNode> findWriteAccesses(CompilationUnit compilationUnit, SimpleName variable, IVariableBinding iVariableBinding) { OccurrencesFinder occurrencesFinder = new OccurrencesFinder(); occurrencesFinder.initialize(compilationUnit, variable); if (iVariableBinding.isField()) occurrencesFinder.performGlobally(); else occurrencesFinder.performLocally(getAncestorOfType(variable, ASTNode.METHOD_DECLARATION)); List<ASTNode> result = occurrencesFinder.getWriteUsages(); for (Iterator<ASTNode> i = result.iterator(); i.hasNext(); ) { ASTNode node = i.next(); switch (node.getParent().getNodeType()) { case ASTNode.SINGLE_VARIABLE_DECLARATION: case ASTNode.VARIABLE_DECLARATION_FRAGMENT: i.remove(); break; } } return result; } public static String getFullyQualifiedName(Type type) { switch (type.getNodeType()) { case ASTNode.SIMPLE_TYPE: //try simple way first String returnValue = null; // Michal: in forward engineering bindings are null ITypeBinding iTypeBinding = type.resolveBinding(); if (iTypeBinding != null) returnValue = iTypeBinding.getQualifiedName(); if (returnValue == null){ String name = ((SimpleType) type).getName().getFullyQualifiedName(); // find appropriate import declaration CompilationUnit compilationUnit = (CompilationUnit) type.getRoot(); for (Object importObject : compilationUnit.imports()) { ImportDeclaration importDeclaration = (ImportDeclaration) importObject; QualifiedName importQualifiedName = (QualifiedName) importDeclaration.getName(); SimpleName importLastName = importQualifiedName.getName(); if (importLastName.getIdentifier().equals(name)) { returnValue=importQualifiedName.getFullyQualifiedName(); } } } return returnValue; case ASTNode.QUALIFIED_TYPE: return ((QualifiedType) type).getName().getFullyQualifiedName(); } return null; } public static String getFullyQualifiedName(TypeDeclaration typeDeclaration) { ITypeBinding iTypeBinding = typeDeclaration.resolveBinding(); if (iTypeBinding != null) return iTypeBinding.getQualifiedName(); else { String className = typeDeclaration.getName().getIdentifier(); String packageName = ((CompilationUnit) typeDeclaration.getRoot()).getPackage().getName().getFullyQualifiedName(); return packageName != null && !packageName.isEmpty() ? packageName + "." + className : className; } } public static MethodDeclaration findMethod(IJavaASTManager javaAstManager, String lookupName, String lookupSignature, TypeDeclaration type, boolean includeLocal, boolean includeInherited, IProgressMonitor progressMonitor) { String[] lookupParameterNames = Signature.getParameterTypes(lookupSignature); String[] lookupParameterSimpleNames = new String[lookupParameterNames.length]; for (int i = 0; i < lookupParameterNames.length; i++) { String erasure = Signature.getTypeErasure(lookupParameterNames[i]); lookupParameterSimpleNames[i] = Signature.getSimpleName(Signature.toString(erasure)); } if (includeLocal) { for (MethodDeclaration method : type.getMethods()) { if (method.getName().getIdentifier().equals(lookupName)) { // check the signature IMethodBinding methodBinding = method.resolveBinding(); if (methodBinding != null) { IMethod iMethod = (IMethod) methodBinding.getJavaElement(); String key = iMethod.getKey(); String auxSignature = key.substring(key.indexOf('(')); auxSignature = auxSignature.replace('/', '.'); if (auxSignature.equals(lookupSignature)) return method; } else { // need to match without the binding... int methodParameterCount = method.parameters().size(); if (methodParameterCount == lookupParameterNames.length) { String[] methodParameterNames = new String[lookupParameterNames.length]; int i = 0; for (Object aux : method.parameters()) { SingleVariableDeclaration parameter = (SingleVariableDeclaration) aux; methodParameterNames[i] = Util.getSignature(parameter.getType()); } if (CodeQueries.areSimilarMethods(lookupName, lookupParameterNames, lookupName, methodParameterNames, lookupParameterSimpleNames)) return method; } } } } } if (includeInherited) { // look in supertype IType superIType = (IType) type.getSuperclassType().resolveBinding().getJavaElement(); CompilationUnit cu = javaAstManager.getCompilationUnit(superIType); TypeDeclaration superType = null; try { superType = ASTNodeSearchUtil.getTypeDeclarationNode(superIType, cu); } catch (JavaModelException e) { e.printStackTrace(); } if (superType != null) return findMethod(javaAstManager, lookupName, lookupSignature, superType, true, includeInherited, progressMonitor); } return null; } public static String getStmtLocationInMethod(ASTNode node) { ASTNode currentNode = node; Statement stmt=null; while (!(currentNode instanceof Statement)) { currentNode=currentNode.getParent(); } stmt=(Statement)currentNode; Block block=null; while (currentNode.getNodeType()!= ASTNode.METHOD_DECLARATION) { currentNode=currentNode.getParent(); if (currentNode instanceof IfStatement) { return "middle"; } if (currentNode instanceof Block) { block = (Block)currentNode; } } if (block.statements().size()==1 && block.statements().get(0).equals(stmt)){ return "single"; } else if (block.statements().get(0).equals(stmt)) { return "start"; } else if (block.statements().get(block.statements().size()-1).equals(stmt)) { return "end"; } else { return "middle"; } } public static List<VariableDeclarationFragment> getFieldsTypedWithType(TypeDeclaration typeDeclaration, String fullyQualifiedName, IIncrementalTypeHierarchy typeHierarchy, IProgressMonitor progressMonitor) { List<VariableDeclarationFragment> fields = new ArrayList<VariableDeclarationFragment>(); for (FieldDeclaration field : typeDeclaration.getFields()) { for (Object auxFragment : field.fragments()) { VariableDeclarationFragment variableDeclarationFragment = (VariableDeclarationFragment) auxFragment; ITypeBinding iTypeBinding = variableDeclarationFragment.resolveBinding().getType(); if (fullyQualifiedName.equals(iTypeBinding.getQualifiedName())) fields.add(variableDeclarationFragment); else { // maybe the type is a subtype? try { IType iType = (IType) iTypeBinding.getJavaElement(); if (typeHierarchy.implementsOrExtendsType(iType, fullyQualifiedName)) fields.add(variableDeclarationFragment); } catch (JavaModelException e) { e.printStackTrace(); } } } } return fields; } public static List<VariableDeclarationFragment> getFieldsAnnotatedWithType(TypeDeclaration typeDeclaration, String fullyQualifiedName, IProgressMonitor progressMonitor) { List<VariableDeclarationFragment> fields = new ArrayList<VariableDeclarationFragment>(); for (FieldDeclaration field : typeDeclaration.getFields()) { for (Object modifier : field.modifiers()) { if (modifier instanceof Annotation){ Annotation annot = (Annotation) modifier; if (annot.resolveTypeBinding().getQualifiedName().equalsIgnoreCase(fullyQualifiedName)){ for (Object auxFragment : field.fragments()) { VariableDeclarationFragment variableDeclarationFragment = (VariableDeclarationFragment) auxFragment; fields.add(variableDeclarationFragment); } } } } } return fields; } public static List<MethodDeclaration> getMethodsAnnotatedWithType(TypeDeclaration typeDeclaration, String fullyQualifiedName, IProgressMonitor progressMonitor) { List<MethodDeclaration> methods = new ArrayList<MethodDeclaration>(); for (MethodDeclaration method : typeDeclaration.getMethods()) { for (Object modifier : method.modifiers()) { if (modifier instanceof Annotation){ Annotation annot = (Annotation) modifier; if (annot.resolveTypeBinding().getQualifiedName().equalsIgnoreCase(fullyQualifiedName)) methods.add(method); } } } return methods; } /** * @param expression * @return true iff the expression 1) is a literal, 2) static final (both array and non-array) variable, 3) enum literal */ public static boolean hasStaticValue(Expression expression) { if (ASTUtils.getStringValue(expression) != null) // the expression is a literal return true; if (expression.resolveConstantExpressionValue() != null) // the expression is a non-array static final variable or enum literal return true; switch (expression.getNodeType()) { case ASTNode.SIMPLE_NAME: SimpleName simpleName = (SimpleName) expression; IBinding iBinding = simpleName.resolveBinding(); if (iBinding instanceof IVariableBinding) { IVariableBinding iVariableBinding = (IVariableBinding) iBinding; if (Modifier.isStatic(iVariableBinding.getModifiers()) && Modifier.isFinal(iVariableBinding.getModifiers()) && iVariableBinding.getType().isArray()) // static final array return true; } break; case ASTNode.ARRAY_CREATION: case ASTNode.ARRAY_INITIALIZER: return true; } return false; } /** * @param javaAstManager * @param simpleName variable use or declaration. * @return variable declaration: VariableDeclarationFragment for local variables or fields, SingleVariableDeclaration for parameters. */ public static ASTNode getVariableDeclarationNode(IJavaASTManager javaAstManager, SimpleName simpleName) { IBinding iBinding = simpleName.resolveBinding(); if (iBinding instanceof IVariableBinding) { IVariableBinding iVariableBinding = (IVariableBinding) iBinding; IVariableBinding iVariableDeclarationBinding = iVariableBinding.getVariableDeclaration(); CompilationUnit variableCompilationUnit = null; if (iVariableBinding.isField()) { IJavaElement iJavaElement = iVariableDeclarationBinding.getJavaElement(); variableCompilationUnit = javaAstManager.getCompilationUnit(iJavaElement); if (variableCompilationUnit != null) return getVariableDeclarationFragmentNode(iVariableDeclarationBinding, variableCompilationUnit); } else { variableCompilationUnit = (CompilationUnit) simpleName.getRoot(); ASTNode result = variableCompilationUnit.findDeclaringNode(iVariableDeclarationBinding); return result; } } return null; } public static boolean isStaticFinalField(IVariableBinding iVariableBinding) { return iVariableBinding.isField() && Modifier.isStatic(iVariableBinding.getModifiers()) && Modifier.isFinal(iVariableBinding.getModifiers()); } public static boolean isStaticFinalArrayField(IVariableBinding iVariableBinding) { return iVariableBinding.isField() && Modifier.isStatic(iVariableBinding.getModifiers()) && Modifier.isFinal(iVariableBinding.getModifiers()) && iVariableBinding.getType().isArray(); } }