blob: 4cf80b094e32a372e47dd749827e7cf3e33a436c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Benjamin Muskalla <bmuskalla@eclipsesource.com> - [extract method] extracting return value results in compile error - https://bugs.eclipse.org/bugs/show_bug.cgi?id=264606
* Samrat Dhillon <samrat.dhillon@gmail.com> - [extract method] Extracted method should be declared static if extracted expression is also used in another static method https://bugs.eclipse.org/bugs/show_bug.cgi?id=393098
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.dom.ASTMatcher;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.EnumDeclaration;
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.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.internal.core.manipulation.dom.ASTResolving;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
/* package */ class SnippetFinder extends GenericVisitor {
public static class Match {
private List<ASTNode> fNodes;
private Map<IVariableBinding, SimpleName> fLocalMappings;
public Match() {
fNodes= new ArrayList<>(10);
fLocalMappings= new HashMap<>();
}
public void add(ASTNode node) {
fNodes.add(node);
}
public boolean hasCorrectNesting(ASTNode node) {
if (fNodes.isEmpty())
return true;
ASTNode parent= node.getParent();
if(fNodes.get(0).getParent() != parent)
return false;
// Here we know that we have two elements. In this case the
// parent must be a block or a switch statement. Otherwise a
// snippet like "if (true) foo(); else foo();" would match
// the pattern "foo(); foo();"
int nodeType= parent.getNodeType();
return nodeType == ASTNode.BLOCK || nodeType == ASTNode.SWITCH_STATEMENT;
}
public ASTNode[] getNodes() {
return fNodes.toArray(new ASTNode[fNodes.size()]);
}
public void addLocal(IVariableBinding org, SimpleName local) {
fLocalMappings.put(org, local);
}
public SimpleName getMappedName(IVariableBinding org) {
return fLocalMappings.get(org);
}
public IVariableBinding getMappedBinding(IVariableBinding org) {
SimpleName name= fLocalMappings.get(org);
return ASTNodes.getVariableBinding(name);
}
public boolean isEmpty() {
return fNodes.isEmpty() && fLocalMappings.isEmpty();
}
/**
* Tests whether the node to be replaced is invalid.
*
* @return true if the node is invalid, false otherwise
*
*/
public boolean isInvalidNode() {
ASTNode first= fNodes.get(0);
ASTNode candidate= first.getParent();
if (candidate == null)
return false;
// return invalid if the opening and closing parenthesis of the method signature is part of the node to be replaced
if (candidate.getNodeType() == ASTNode.METHOD_DECLARATION)
return true;
return false;
}
public MethodDeclaration getEnclosingMethod() {
ASTNode first= fNodes.get(0);
return (MethodDeclaration)ASTNodes.getParent(first, ASTNode.METHOD_DECLARATION);
}
public boolean isNodeInStaticContext(){
ASTNode first= fNodes.get(0);
return ASTResolving.isInStaticContext(first);
}
}
private class Matcher extends ASTMatcher {
@Override
public boolean match(SimpleName candidate, Object s) {
if (!(s instanceof SimpleName))
return false;
SimpleName snippet= (SimpleName)s;
if (candidate.isDeclaration() != snippet.isDeclaration())
return false;
IBinding cb= candidate.resolveBinding();
IBinding sb= snippet.resolveBinding();
if (cb == null || sb == null)
return false;
IVariableBinding vcb= ASTNodes.getVariableBinding(candidate);
IVariableBinding vsb= ASTNodes.getVariableBinding(snippet);
if (vcb == null || vsb == null)
return Bindings.equals(cb, sb);
if (!vcb.isField() && !vsb.isField() && Bindings.equals(vcb.getType(), vsb.getType())) {
SimpleName mapped= fMatch.getMappedName(vsb);
if (mapped != null) {
IVariableBinding mappedBinding= ASTNodes.getVariableBinding(mapped);
if (!Bindings.equals(vcb, mappedBinding))
return false;
}
fMatch.addLocal(vsb, candidate);
return true;
}
return Bindings.equals(cb, sb);
}
}
private List<Match> fResult= new ArrayList<>(2);
private Match fMatch;
private ASTNode[] fSnippet;
private int fIndex;
private Matcher fMatcher;
private int fTypes;
private SnippetFinder(ASTNode[] snippet) {
super(true);
fSnippet= snippet;
fMatcher= new Matcher();
reset();
}
public static List<Match> perform(ASTNode start, ASTNode[] snippet) {
Assert.isTrue(start instanceof AbstractTypeDeclaration || start instanceof AnonymousClassDeclaration);
SnippetFinder finder= new SnippetFinder(snippet);
start.accept(finder);
for (Iterator<Match> iter = finder.fResult.iterator(); iter.hasNext();) {
Match match = iter.next();
ASTNode[] nodes= match.getNodes();
// doesn't match if the candidate is the left hand side of an
// assignment and the snippet consists of a single node.
// Otherwise y= i; i= z; results in y= e(); e()= z;
if (nodes.length == 1 && isLeftHandSideOfAssignment(nodes[0])) {
iter.remove();
}
}
return finder.fResult;
}
static boolean isLeftHandSideOfAssignment(ASTNode node) {
Assignment assignment= (Assignment)ASTNodes.getParent(node, ASTNode.ASSIGNMENT);
if (assignment != null) {
Expression leftHandSide= assignment.getLeftHandSide();
if (leftHandSide == node) {
return true;
}
if (ASTNodes.isParent(node, leftHandSide)) {
switch (leftHandSide.getNodeType()) {
case ASTNode.SIMPLE_NAME:
return true;
case ASTNode.FIELD_ACCESS:
return node == ((FieldAccess)leftHandSide).getName();
case ASTNode.QUALIFIED_NAME:
return node == ((QualifiedName)leftHandSide).getName();
case ASTNode.SUPER_FIELD_ACCESS:
return node == ((SuperFieldAccess)leftHandSide).getName();
default:
return false;
}
}
}
return false;
}
@Override
public boolean visit(TypeDeclaration node) {
if (++fTypes > 1)
return false;
return super.visit(node);
}
@Override
public void endVisit(TypeDeclaration node) {
--fTypes;
super.endVisit(node);
}
@Override
public boolean visit(EnumDeclaration node) {
if (++fTypes > 1)
return false;
return super.visit(node);
}
@Override
public void endVisit(EnumDeclaration node) {
--fTypes;
super.endVisit(node);
}
@Override
public boolean visit(AnnotationTypeDeclaration node) {
if (++fTypes > 1)
return false;
return super.visit(node);
}
@Override
public void endVisit(AnnotationTypeDeclaration node) {
--fTypes;
super.endVisit(node);
}
@Override
protected boolean visitNode(ASTNode node) {
if (matches(node)) {
return false;
} else if (!isResetted()){
reset();
if (matches(node))
return false;
}
return true;
}
private boolean matches(ASTNode node) {
if (isSnippetNode(node))
return false;
if (node.subtreeMatch(fMatcher, fSnippet[fIndex]) && fMatch.hasCorrectNesting(node)) {
fMatch.add(node);
fIndex++;
if (fIndex == fSnippet.length) {
fResult.add(fMatch);
reset();
}
return true;
}
return false;
}
private boolean isResetted() {
return fIndex == 0 && fMatch.isEmpty();
}
private void reset() {
fIndex= 0;
fMatch= new Match();
}
private boolean isSnippetNode(ASTNode node) {
for (ASTNode a : fSnippet) {
if (node == a) {
return true;
}
}
return false;
}
}