/** * Copyright (c) 2010, 2013 Darmstadt University of Technology. * 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: * Marcel Bruch - initial API and implementation. */ package org.eclipse.recommenders.internal.calls.rcp; import static com.google.common.base.Optional.fromNullable; import static org.eclipse.recommenders.calls.ICallModel.DefinitionKind.*; import static org.eclipse.recommenders.jdt.AstBindings.toMethodName; import static org.eclipse.recommenders.utils.Checks.*; import java.util.List; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.Assignment; import org.eclipse.jdt.core.dom.CastExpression; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.ConstructorInvocation; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.MethodInvocation; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.SuperConstructorInvocation; import org.eclipse.jdt.core.dom.SuperMethodInvocation; import org.eclipse.jdt.core.dom.ThisExpression; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationExpression; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jdt.internal.corext.util.JdtFlags; import org.eclipse.recommenders.calls.ICallModel.DefinitionKind; import org.eclipse.recommenders.jdt.AstBindings; import org.eclipse.recommenders.utils.Nullable; import org.eclipse.recommenders.utils.names.IMethodName; import com.google.common.base.Optional; import com.google.common.collect.Lists; @SuppressWarnings("restriction") public class AstDefUseFinder extends ASTVisitor { private static final String VAR_THIS = "this"; //$NON-NLS-1$ private static final String VAR_SUPER = "super"; //$NON-NLS-1$ private IMethodName definingMethod; private DefinitionKind defKind = UNKNOWN; private final List<IMethodName> calls = Lists.newLinkedList(); private final MethodDeclaration method; private final String varname; public AstDefUseFinder(final String varname, MethodDeclaration method) { this.varname = ensureIsNotNull(varname); this.method = ensureIsNotNull(method); if (VAR_THIS.equals(varname) || VAR_SUPER.equals(varname)) { defKind = THIS; } method.accept(this); } public List<IMethodName> getCalls() { return calls; } public Optional<IMethodName> getDefiningMethod() { return fromNullable(definingMethod); } public DefinitionKind getDefinitionKind() { return defKind; } @Override public boolean visit(final MethodInvocation node) { Expression expr = node.getExpression(); if (receiverExpressionMatchesVarname(expr) || maybeThis() && isReceiverThis(node)) { final IMethodBinding b = node.resolveMethodBinding(); registerMethodCallOnReceiver(b); } return true; } private boolean receiverExpressionMatchesVarname(@Nullable final Expression exp) { if (exp == null) { return false; } switch (exp.getNodeType()) { case ASTNode.SIMPLE_NAME: case ASTNode.QUALIFIED_NAME: final Name name = cast(exp); // is it the same name we are looking for? return matchesVarName(name); case ASTNode.THIS_EXPRESSION: // do we look for this? return maybeThis(); default: return false; } } private boolean matchesVarName(@Nullable final Name node) { if (node == null) { return false; } final String name = node.getFullyQualifiedName(); boolean equals = varname.equals(name); if (equals && defKind == UNKNOWN) { refineDefKindByBinding(node); } return equals; } private void refineDefKindByBinding(final Name node) { IBinding binding = node.resolveBinding(); if (!(binding instanceof IVariableBinding)) { return; } IVariableBinding b = (IVariableBinding) binding; if (b.isField()) { defKind = FIELD; } else if (b.isParameter()) { defKind = PARAM; } } private boolean maybeThis() { return varname.isEmpty() || VAR_THIS.equals(varname) || VAR_SUPER.equals(varname); } private boolean isReceiverThis(final MethodInvocation mi) { final Expression expression = mi.getExpression(); // standard case: if (expression == null && !isStatic(mi)) { return true; } // qualified call: this.method() if (expression instanceof ThisExpression) { return true; } return false; } private boolean isStatic(final MethodInvocation call) { final IMethodBinding binding = call.resolveMethodBinding(); if (binding != null) { return JdtFlags.isStatic(binding); } // let's assume it's not static... return false; } private void registerMethodCallOnReceiver(final IMethodBinding b) { final Optional<IMethodName> opt = AstBindings.toMethodName(b); if (opt.isPresent()) { calls.add(opt.get()); } } @Override public boolean visit(AnonymousClassDeclaration node) { return false; } @Override public boolean visit(TypeDeclaration node) { return false; } @Override public boolean visit(final Assignment node) { final Expression lhs = node.getLeftHandSide(); final Expression rhs = node.getRightHandSide(); if (isCompletionNode(rhs)) { switch (lhs.getNodeType()) { case ASTNode.SIMPLE_NAME: case ASTNode.QUALIFIED_NAME: // callee is explicit (as in o.$). we don't have to go further break; case ASTNode.THIS_EXPRESSION: defKind = THIS; break; case ASTNode.STRING_LITERAL: defKind = STRING_LITERAL; break; case ASTNode.ARRAY_ACCESS: defKind = ARRAY_ACCESS; break; case ASTNode.FIELD_ACCESS: FieldAccess f = (FieldAccess) lhs; Expression e = f.getExpression(); switch (e.getNodeType()) { case ASTNode.THIS_EXPRESSION: defKind = THIS; break; case ASTNode.ARRAY_ACCESS: defKind = ARRAY_ACCESS; break; default: // when we have completely broken code, this may happen... ignore it. // throwUnreachable( // "Did not expect this LHS expression to be possible here. Pls report this snippet: %s", lhs); } break; default: // when we have completely broken code, this may happen... ignore it. // throwUnreachable("Did not expect this LHS expression to be possible here. Pls report this snippet: // %s", // lhs); } } if (lhs == null) { return true; } switch (lhs.getNodeType()) { case ASTNode.SIMPLE_NAME: case ASTNode.QUALIFIED_NAME: final Name n = cast(lhs); if (!matchesVarName(n)) { return true; } break; default: return true; } evaluateRightHandSideExpression(rhs); return true; } private boolean isCompletionNode(final Expression rhs) { return rhs instanceof SimpleName && ((SimpleName) rhs).getIdentifier().equals("$missing$"); //$NON-NLS-1$ } // called only if left hand side was a match. private void evaluateRightHandSideExpression(final Expression expression) { switch (expression.getNodeType()) { case ASTNode.CAST_EXPRESSION: final CastExpression ce = cast(expression); // re-evaluate using the next expression: evaluateRightHandSideExpression(ce.getExpression()); break; case ASTNode.METHOD_INVOCATION: // x = some().method().call() final MethodInvocation mi = cast(expression); definingMethod = toMethodName(mi.resolveMethodBinding()).orNull(); defKind = RETURN; break; case ASTNode.SUPER_METHOD_INVOCATION: // x = super.some() final SuperMethodInvocation smi = cast(expression); definingMethod = toMethodName(smi.resolveMethodBinding()).orNull(); defKind = RETURN; break; case ASTNode.CLASS_INSTANCE_CREATION: final ClassInstanceCreation cic = cast(expression); definingMethod = toMethodName(cic.resolveConstructorBinding()).orNull(); defKind = NEW; break; case ASTNode.ARRAY_ACCESS: defKind = ARRAY_ACCESS; break; case ASTNode.NULL_LITERAL: defKind = NULL_LITERAL; break; case ASTNode.SIMPLE_NAME: // e.g. int j=anotherValue; // some alias thing... // it might be that we found an assignment before and this simpleName is just "$missing". Then ignore this if (defKind == null) { defKind = LOCAL; } break; default: break; } } // calls like 'this(args)' @Override public boolean visit(final ConstructorInvocation node) { if (maybeThis()) { final IMethodBinding b = node.resolveConstructorBinding(); registerMethodCallOnReceiver(b); } return true; } @Override public boolean visit(final SingleVariableDeclaration node) { if (matchesVarName(node.getName()) && node.getParent() instanceof MethodDeclaration) { defKind = PARAM; definingMethod = toMethodName(method.resolveBinding()).orNull(); } return true; } @Override public boolean visit(final SuperConstructorInvocation node) { if (maybeThis()) { final IMethodBinding b = node.resolveConstructorBinding(); registerMethodCallOnReceiver(b); } return true; } @Override public boolean visit(final SuperMethodInvocation node) { if (maybeThis()) { final IMethodBinding b = node.resolveMethodBinding(); registerMethodCallOnReceiver(b); } return true; } @SuppressWarnings("unchecked") @Override public boolean visit(final VariableDeclarationStatement node) { for (final VariableDeclarationFragment f : (List<VariableDeclarationFragment>) node.fragments()) { evaluateVariableDeclarationFragment(f); } return true; } private void evaluateVariableDeclarationFragment(final VariableDeclarationFragment f) { final SimpleName name = f.getName(); if (matchesVarName(name)) { final Expression expression = f.getInitializer(); if (expression != null) { evaluateRightHandSideExpression(expression); } } } @SuppressWarnings("unchecked") @Override public boolean visit(VariableDeclarationExpression node) { for (VariableDeclarationFragment f : (List<VariableDeclarationFragment>) node.fragments()) { evaluateVariableDeclarationFragment(f); } return true; } }