| /******************************************************************************* |
| * Copyright (c) 2000, 2006 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| 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.IBinding; |
| import org.eclipse.jdt.core.dom.IVariableBinding; |
| import org.eclipse.jdt.core.dom.MethodDeclaration; |
| import org.eclipse.jdt.core.dom.SimpleName; |
| import org.eclipse.jdt.core.dom.TypeDeclaration; |
| |
| 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 fNodes; |
| private Map 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.size() == 0) |
| return true; |
| ASTNode parent= node.getParent(); |
| if(((ASTNode)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 (ASTNode[])fNodes.toArray(new ASTNode[fNodes.size()]); |
| } |
| public void addLocal(IVariableBinding org, SimpleName local) { |
| fLocalMappings.put(org, local); |
| } |
| public SimpleName getMappedName(IVariableBinding org) { |
| return (SimpleName)fLocalMappings.get(org); |
| } |
| public IVariableBinding getMappedBinding(IVariableBinding org) { |
| SimpleName name= (SimpleName) fLocalMappings.get(org); |
| return ASTNodes.getVariableBinding(name); |
| } |
| public boolean isEmpty() { |
| return fNodes.isEmpty(); |
| } |
| /** |
| * Tests if the whole duplicate is the full body of a method. If so |
| * don't replace it since we would replace a method body with a new |
| * method body which doesn't make to much sense. |
| * |
| * @return whether the duplicte is the whole method body |
| */ |
| public boolean isMethodBody() { |
| ASTNode first= (ASTNode)fNodes.get(0); |
| if (first.getParent() == null) |
| return false; |
| ASTNode candidate= first.getParent().getParent(); |
| if (candidate == null || candidate.getNodeType() != ASTNode.METHOD_DECLARATION) |
| return false; |
| MethodDeclaration method= (MethodDeclaration)candidate; |
| return method.getBody().statements().size() == fNodes.size(); |
| } |
| public MethodDeclaration getEnclosingMethod() { |
| ASTNode first= (ASTNode)fNodes.get(0); |
| return (MethodDeclaration)ASTNodes.getParent(first, ASTNode.METHOD_DECLARATION); |
| } |
| } |
| |
| private class Matcher extends ASTMatcher { |
| 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 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 Match[] perform(ASTNode start, ASTNode[] snippet) { |
| Assert.isTrue(start instanceof AbstractTypeDeclaration || start instanceof AnonymousClassDeclaration); |
| SnippetFinder finder= new SnippetFinder(snippet); |
| start.accept(finder); |
| for (Iterator iter = finder.fResult.iterator(); iter.hasNext();) { |
| Match 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 (Match[])finder.fResult.toArray(new Match[finder.fResult.size()]); |
| } |
| |
| private static boolean isLeftHandSideOfAssignment(ASTNode node) { |
| ASTNode parent= node.getParent(); |
| return parent != null && parent.getNodeType() == ASTNode.ASSIGNMENT && ((Assignment)parent).getLeftHandSide() == node; |
| } |
| |
| public boolean visit(TypeDeclaration node) { |
| if (++fTypes > 1) |
| return false; |
| return super.visit(node); |
| } |
| |
| public void endVisit(TypeDeclaration node) { |
| --fTypes; |
| super.endVisit(node); |
| } |
| |
| public boolean visit(EnumDeclaration node) { |
| if (++fTypes > 1) |
| return false; |
| return super.visit(node); |
| } |
| |
| public void endVisit(EnumDeclaration node) { |
| --fTypes; |
| super.endVisit(node); |
| } |
| |
| public boolean visit(AnnotationTypeDeclaration node) { |
| if (++fTypes > 1) |
| return false; |
| return super.visit(node); |
| } |
| |
| public void endVisit(AnnotationTypeDeclaration node) { |
| --fTypes; |
| super.endVisit(node); |
| } |
| |
| 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 (int i= 0; i < fSnippet.length; i++) { |
| if (node == fSnippet[i]) |
| return true; |
| } |
| return false; |
| } |
| } |