/******************************************************************************* * Copyright (c) 2005, 2015 Zend Technologies 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: * Zend Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.php.refactoring.core.extract.function; import java.util.*; import org.eclipse.core.runtime.Assert; import org.eclipse.php.core.ast.match.ASTMatcher; import org.eclipse.php.core.ast.nodes.*; import org.eclipse.php.core.ast.visitor.AbstractVisitor; /* package */class SnippetFinder extends AbstractVisitor { 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.getType(); return nodeType == ASTNode.BLOCK || nodeType == ASTNode.SWITCH_STATEMENT; } public ASTNode[] getNodes() { return (ASTNode[]) fNodes.toArray(new ASTNode[fNodes.size()]); } public void addLocal(IVariableBinding org, Identifier local) { fLocalMappings.put(org, local); } public Identifier getMappedName(IVariableBinding org) { return (Identifier) fLocalMappings.get(org); } public IVariableBinding getMappedBinding(IVariableBinding org) { // Identifier name = (Identifier) fLocalMappings.get(org); // TODO:??? return null; } public boolean isEmpty() { return fNodes.isEmpty() && fLocalMappings.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.getType() != ASTNode.METHOD_DECLARATION) return false; MethodDeclaration method = (MethodDeclaration) candidate; return method.getFunction().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(Identifier candidate, Object s) { if (!(s instanceof Identifier)) return false; Identifier snippet = (Identifier) s; if (!(candidate.getParent() instanceof Variable) || !(snippet.getParent() instanceof Variable)) { return false; } Variable cv = (Variable) candidate.getParent(); Variable sv = (Variable) snippet.getParent(); IVariableBinding cb = cv.resolveVariableBinding(); IVariableBinding sb = sv.resolveVariableBinding(); if (cb == null || sb == null) return false; if (!cb.isField() && !sb.isField()) { Identifier mapped = fMatch.getMappedName(sb); if (mapped != null) { Variable parent = (Variable) mapped.getParent(); IVariableBinding mappedBinding = parent.resolveVariableBinding(); if (!cb.equals(mappedBinding)) return false; } fMatch.addLocal(sb, candidate); return true; } return cb.equals(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(); fSnippet = snippet; fMatcher = new Matcher(); reset(); } public static Match[] perform(ASTNode start, ASTNode[] snippet) { Assert.isTrue( start instanceof ClassDeclaration || start instanceof FunctionDeclaration || start instanceof Program); 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.getType() == ASTNode.ASSIGNMENT && ((Assignment) parent).getLeftHandSide() == node; } // public boolean visit(ClassDeclaration node) { // if (++fTypes > 1) // return false; // return visitNode(node); // } public boolean visit(ExpressionStatement node) { return visitNode(node); } // public void endVisit(ClassDeclaration node) { // --fTypes; // super.endVisit(node); // } public boolean visit(FunctionDeclaration node) { if (++fTypes > 1) return false; return super.visit(node); } public void endVisit(FunctionDeclaration 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; } }