/* * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.groovy.eclipse.refactoring.core.extract; import java.util.ArrayList; import java.util.List; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.DoWhileStatement; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.ast.stmt.ReturnStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.stmt.WhileStatement; import org.codehaus.groovy.eclipse.codebrowsing.requestor.Region; import org.codehaus.groovy.eclipse.refactoring.core.utils.ASTTools; /** * Scans a document to extract all statements which are covered by a * given Selection. A valid selection must meet this criteria: * - Selection is in a Method * - Covers at minimum a whole Statement * - is in a BlockStatement or contains a whole BlockStatement * * @author Michael Klenk mklenk@hsr.ch */ public class StatementFinder extends CodeVisitorSupport { private List<Statement> inSelection, postSelection; /** * The declaration that contains the selection */ private AnnotatedNode actualSelectedDeclaration; /** * The declaration currently being scanned */ private AnnotatedNode currentDeclaration; private final Region selection; private final ModuleNode rootNode; /** * True when we have not found the selection yet */ boolean preCode = true; boolean isInLoopOrClosure = false; boolean internalInLoopOrClosure = false; public StatementFinder(Region selection, ModuleNode rootNode) { this.selection = selection; this.rootNode = rootNode; scanDocument(); } /** * @return true if the selection is in a static Method */ public boolean isStatic() { return actualSelectedDeclaration instanceof MethodNode ? ((MethodNode) actualSelectedDeclaration).isStatic() : ((FieldNode) actualSelectedDeclaration).isStatic(); } /** * @return true if the selection is in the constructor */ public boolean isInConstructor() { return (actualSelectedDeclaration instanceof MethodNode && ((MethodNode) actualSelectedDeclaration).getName().equals("<init>")); } /** * Return true if the selection is in a loop or a closure * @return */ public boolean isInLoopOrClosure() { return isInLoopOrClosure; } /** * @return all Statements in the given Selection */ public List<Statement> getInSelection() { return inSelection; } /** * @return all Statements after the Selection */ public List<Statement> getPostSelection() { return postSelection; } /** * @return the declaration node in which contains the Selection (can be * method or field) */ public AnnotatedNode getSelectedDeclaration() { return actualSelectedDeclaration; } /** * Return a list of all method names, declared in the class which contains the selection * @return List of method names */ public List<String> getMethodNames() { List<String> methods = new ArrayList<String>(); if (actualSelectedDeclaration != null) { ClassNode declaringClass = actualSelectedDeclaration.getDeclaringClass(); for (MethodNode method : declaringClass.getMethods()) { methods.add(method.getName()); } } return methods; } /** * @return the name of the class that contains the current selection */ public String getClassName() { ClassNode declaringClass = actualSelectedDeclaration.getDeclaringClass(); if (declaringClass != null) return declaringClass.getNameWithoutPackage(); return ""; } /** * @return the class which contains the selection */ public ClassNode getClassNode() { return actualSelectedDeclaration.getDeclaringClass(); } /** * Finds all satements in the given editor and the given Selection */ public void scanDocument() { inSelection = new ArrayList<Statement>(); postSelection = new ArrayList<Statement>(); if (rootNode != null) { for (ClassNode cl : rootNode.getClasses()) { for (ConstructorNode method : cl.getDeclaredConstructors()) { scanMethod(cl, method); } for (MethodNode method : cl.getMethods()) { scanMethod(cl, method); } for (FieldNode field : cl.getFields()) { scanField(cl, field); } } } } private void scanField(ClassNode cl, FieldNode field) { if (testSelection(selection, field, SelectionTestKind.SELECTION_IS_COVERED_BY)) { if (field.getInitialExpression() instanceof ClosureExpression) { Statement closureBlock = ((ClosureExpression) field.getInitialExpression()).getCode(); if (closureBlock instanceof BlockStatement) { currentDeclaration = field; visitBlockStatement((BlockStatement) closureBlock); } } } } private void scanMethod(ClassNode cl, MethodNode method) { if (testSelection(selection, method, SelectionTestKind.SELECTION_IS_COVERED_BY)) { if(method.getCode() instanceof BlockStatement) { currentDeclaration = method; visitBlockStatement(((BlockStatement) method.getCode())); } } } @Override public void visitBlockStatement(BlockStatement block) { for (Statement statement : block.getStatements()) { if (testSelection(selection, statement, SelectionTestKind.SELECTION_COVERS)) { inSelection.add(statement); if (internalInLoopOrClosure) { isInLoopOrClosure = true; } preCode = false; actualSelectedDeclaration = currentDeclaration; } else { if (preCode) { if (testSelection(selection, statement, SelectionTestKind.SELECTION_IS_COVERED_BY)) { statement.visit(this); } } else { postSelection.add(0, statement); } } } } @Override public void visitForLoop(ForStatement forLoop) { boolean oldInLoop = internalInLoopOrClosure; internalInLoopOrClosure = true; super.visitForLoop(forLoop); internalInLoopOrClosure = oldInLoop; } @Override public void visitWhileLoop(WhileStatement loop) { boolean oldInLoop = internalInLoopOrClosure; internalInLoopOrClosure = true; super.visitWhileLoop(loop); internalInLoopOrClosure = oldInLoop; } @Override public void visitDoWhileLoop(DoWhileStatement loop) { boolean oldInLoop = internalInLoopOrClosure; internalInLoopOrClosure = true; super.visitDoWhileLoop(loop); internalInLoopOrClosure = oldInLoop; } @Override public void visitClosureExpression(ClosureExpression expression) { boolean oldInLoop = internalInLoopOrClosure; internalInLoopOrClosure = true; super.visitClosureExpression(expression); internalInLoopOrClosure = oldInLoop; } private enum SelectionTestKind { SELECTION_COVERS, SELECTION_IS_COVERED_BY } /** * Test if the given node is in the Selection or if the Selection contains * the Node * * if true the node must be in the selection */ private boolean testSelection(Region sel, ASTNode astNode, SelectionTestKind inSelection) { if (sel.isEmpty()) { return false; } ASTNode node = astNode; if (!ASTTools.hasValidPosition(node) && node instanceof ReturnStatement) { node = ((ReturnStatement) node).getExpression(); } if (inSelection == SelectionTestKind.SELECTION_COVERS) { return sel.regionCoversNode(node); } else { return sel.regionIsCoveredByNode(node); } } }