/* * Copyright 2009-2016 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.codebrowsing.selection; import java.util.Stack; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.PackageNode; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.ArrayExpression; import org.codehaus.groovy.ast.expr.AttributeExpression; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; import org.codehaus.groovy.ast.expr.BooleanExpression; import org.codehaus.groovy.ast.expr.CastExpression; import org.codehaus.groovy.ast.expr.ClassExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ClosureListExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.ElvisOperatorExpression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.GStringExpression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MapExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.MethodPointerExpression; import org.codehaus.groovy.ast.expr.NotExpression; import org.codehaus.groovy.ast.expr.PostfixExpression; import org.codehaus.groovy.ast.expr.PrefixExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.RangeExpression; import org.codehaus.groovy.ast.expr.SpreadExpression; import org.codehaus.groovy.ast.expr.SpreadMapExpression; import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.TernaryExpression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.expr.UnaryMinusExpression; import org.codehaus.groovy.ast.expr.UnaryPlusExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.AssertStatement; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.BreakStatement; import org.codehaus.groovy.ast.stmt.CaseStatement; import org.codehaus.groovy.ast.stmt.CatchStatement; import org.codehaus.groovy.ast.stmt.ContinueStatement; import org.codehaus.groovy.ast.stmt.DoWhileStatement; import org.codehaus.groovy.ast.stmt.EmptyStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.ast.stmt.IfStatement; import org.codehaus.groovy.ast.stmt.ReturnStatement; import org.codehaus.groovy.ast.stmt.SwitchStatement; import org.codehaus.groovy.ast.stmt.SynchronizedStatement; import org.codehaus.groovy.ast.stmt.ThrowStatement; import org.codehaus.groovy.ast.stmt.TryCatchStatement; import org.codehaus.groovy.ast.stmt.WhileStatement; import org.codehaus.groovy.eclipse.codebrowsing.fragments.ASTFragmentFactory; import org.codehaus.groovy.eclipse.codebrowsing.fragments.IASTFragment; import org.codehaus.groovy.eclipse.codebrowsing.requestor.ASTNodeFinder; import org.codehaus.groovy.eclipse.codebrowsing.requestor.Region; import org.codehaus.groovy.eclipse.core.util.VisitCompleteException; import org.eclipse.core.runtime.Assert; /** * Finds the AST node that completely encloses the selection that is passed in. * * @author Andrew Eisenberg * @created May 7, 2010 */ public class FindSurroundingNode extends ASTNodeFinder { public static enum VisitKind { SURROUNDING_NODE, // Find the surrounding node EXTRA_EXPAND, // Do an extra expand after finding the surrounding node // if the region is exactly the same as the surrounding PARENT_STACK // do not filter out co-located or empty nodes. Keep the entire parent // stack as-is } private final VisitKind visitKind; private Stack<IASTFragment> nodeStack; private ASTFragmentFactory factory; // GRECLIPSE-781 must visit the run method of a script last private MethodNode runMethod; public FindSurroundingNode(Region r) { this(r, VisitKind.EXTRA_EXPAND); } public FindSurroundingNode(Region r, VisitKind visitKind) { super(r); nodeStack = new Stack<IASTFragment>(); this.visitKind = visitKind; factory = new ASTFragmentFactory(); } public IASTFragment doVisitSurroundingNode(ModuleNode module) { nodeStack.push(factory.createFragment(module)); Region sloc = this.sloc; ASTNode node = super.doVisit(module); if (node == null && runMethod != null) { // GRECLIPSE-781 visit run method if it exists try { internalVisitConstructorOrMethod(runMethod, false); } catch (VisitCompleteException e) { // do nothing } } IASTFragment maybeNode = factory.createFragment(node); if (visitKind != VisitKind.PARENT_STACK) { // remove nodes that are colocated with the region as well as nodes // that have no source position while (!nodeStack.isEmpty()) { maybeNode = nodeStack.peek(); if (maybeNode.getEnd() == 0 || (visitKind == VisitKind.EXTRA_EXPAND && sloc.isSame(maybeNode))) { nodeStack.pop(); } else { break; } } } return nodeStack.isEmpty() ? maybeNode : nodeStack.peek(); } @Override public ASTNode doVisit(ModuleNode module) { Assert.isLegal(false, "Use doVisitSurroundingNode() instead"); return super.doVisit(module); } public Stack<IASTFragment> getParentStack() { return nodeStack; } protected void reset() { nodeStack.clear(); runMethod = null; } @Override public void visitAnnotations(AnnotatedNode node) { nodeStack.push(factory.createFragment(node)); super.visitAnnotations(node); nodeStack.pop(); } @Override public void visitAssertStatement(AssertStatement node) { nodeStack.push(factory.createFragment(node)); super.visitAssertStatement(node); check(node); nodeStack.pop(); } @Override public void visitBlockStatement(BlockStatement node) { nodeStack.push(factory.createFragment(node)); super.visitBlockStatement(node); check(node); nodeStack.pop(); } @Override public void visitBreakStatement(BreakStatement node) { nodeStack.push(factory.createFragment(node)); super.visitBreakStatement(node); check(node); nodeStack.pop(); } @Override public void visitCaseStatement(CaseStatement node) { nodeStack.push(factory.createFragment(node)); super.visitCaseStatement(node); check(node); nodeStack.pop(); } @Override public void visitContinueStatement(ContinueStatement node) { nodeStack.push(factory.createFragment(node)); super.visitContinueStatement(node); check(node); nodeStack.pop(); } @Override public void visitDoWhileLoop(DoWhileStatement node) { nodeStack.push(factory.createFragment(node)); super.visitDoWhileLoop(node); check(node); nodeStack.pop(); } @Override public void visitExpressionStatement(ExpressionStatement node) { nodeStack.push(factory.createFragment(node)); super.visitExpressionStatement(node); nodeStack.pop(); } @Override public void visitIfElse(IfStatement node) { nodeStack.push(factory.createFragment(node)); super.visitIfElse(node); check(node); nodeStack.pop(); } @Override public void visitPackage(PackageNode node) { if (node != null) { nodeStack.push(factory.createFragment(node)); super.visitPackage(node); // check(node); nodeStack.pop(); } } // @Override // public void visitImports(ModuleNode module) { // for (ImportNode importNode : new ImportNodeCompatibilityWrapper(module).getAllImportNodes()) { // if (importNode.getType() != null) { // nodeStack.push(factory.createFragment(importNode)); // check(importNode.getType()); // nodeStack.pop(); // } // } // } @Override protected void visitImport(ImportNode node) { nodeStack.push(factory.createFragment(node)); super.visitImport(node); nodeStack.pop(); } @Override public void visitSwitch(SwitchStatement node) { nodeStack.push(factory.createFragment(node)); super.visitSwitch(node); check(node); nodeStack.pop(); } @Override public void visitSynchronizedStatement(SynchronizedStatement node) { nodeStack.push(factory.createFragment(node)); super.visitSynchronizedStatement(node); check(node); nodeStack.pop(); } @Override public void visitThrowStatement(ThrowStatement node) { nodeStack.push(factory.createFragment(node)); super.visitThrowStatement(node); check(node); nodeStack.pop(); } @Override public void visitTryCatchFinally(TryCatchStatement node) { nodeStack.push(factory.createFragment(node)); super.visitTryCatchFinally(node); check(node); nodeStack.pop(); } @Override public void visitWhileLoop(WhileStatement node) { nodeStack.push(factory.createFragment(node)); super.visitWhileLoop(node); check(node); nodeStack.pop(); } @Override public void visitArgumentlistExpression(ArgumentListExpression node) { nodeStack.push(factory.createFragment(node)); super.visitArgumentlistExpression(node); check(node); nodeStack.pop(); } @Override public void visitAttributeExpression(AttributeExpression node) { nodeStack.push(factory.createFragment(node)); super.visitAttributeExpression(node); check(node); nodeStack.pop(); } @Override public void visitBinaryExpression(BinaryExpression node) { nodeStack.push(factory.createFragment(node)); if (sloc.regionIsCoveredByNode(node)) { // create an extra stack frame to precisely describe the region being covered nodeStack.push(factory.createFragment(node, sloc.getOffset(), sloc.getEnd())); } super.visitBinaryExpression(node); check(node); nodeStack.pop(); } @Override public void visitBitwiseNegationExpression(BitwiseNegationExpression node) { nodeStack.push(factory.createFragment(node)); super.visitBitwiseNegationExpression(node); check(node); nodeStack.pop(); } @Override public void visitBooleanExpression(BooleanExpression node) { nodeStack.push(factory.createFragment(node)); // we want to check the inner node // if the source locations are the same if (!isColocated(node.getExpression(), node)) { check(node); } super.visitBooleanExpression(node); nodeStack.pop(); } /** * Checks to see if the two nodes have the same source location */ private boolean isColocated(ASTNode node1, ASTNode node2) { return node1.getStart() == node2.getStart() && node1.getEnd() == node2.getEnd(); } @Override public void visitClosureListExpression(ClosureListExpression node) { nodeStack.push(factory.createFragment(node)); super.visitClosureListExpression(node); check(node); nodeStack.pop(); } @Override protected void visitEmptyStatement(EmptyStatement node) { nodeStack.push(factory.createFragment(node)); check(node); nodeStack.pop(); } @Override public void visitListExpression(ListExpression node) { nodeStack.push(factory.createFragment(node)); super.visitListExpression(node); check(node); nodeStack.pop(); } @Override public void visitMapEntryExpression(MapEntryExpression node) { nodeStack.push(factory.createFragment(node)); super.visitMapEntryExpression(node); check(node); nodeStack.pop(); } @Override public void visitMapExpression(MapExpression node) { nodeStack.push(factory.createFragment(node)); super.visitMapExpression(node); check(node); nodeStack.pop(); } @Override public void visitMethodCallExpression(MethodCallExpression node) { nodeStack.push(factory.createFragment(node)); if (sloc.regionIsCoveredByNode(node)) { // create an extra stack frame to precisely describe the region being covered nodeStack.push(factory.createFragment(node, sloc.getOffset(), sloc.getEnd())); } super.visitMethodCallExpression(node); check(node); nodeStack.pop(); } @Override public void visitMethodPointerExpression(MethodPointerExpression node) { nodeStack.push(factory.createFragment(node)); if (sloc.regionIsCoveredByNode(node)) { // create an extra stack frame to precisely describe the region being covered nodeStack.push(factory.createFragment(node, sloc.getOffset(), sloc.getEnd())); } super.visitMethodPointerExpression(node); check(node); nodeStack.pop(); } @Override public void visitNotExpression(NotExpression node) { nodeStack.push(factory.createFragment(node)); super.visitNotExpression(node); check(node); nodeStack.pop(); } @Override public void visitPostfixExpression(PostfixExpression node) { nodeStack.push(factory.createFragment(node)); super.visitPostfixExpression(node); check(node); nodeStack.pop(); } @Override public void visitPrefixExpression(PrefixExpression node) { nodeStack.push(factory.createFragment(node)); super.visitPrefixExpression(node); check(node); nodeStack.pop(); } @Override public void visitPropertyExpression(PropertyExpression node) { nodeStack.push(factory.createFragment(node)); super.visitPropertyExpression(node); check(node); nodeStack.pop(); } @Override public void visitRangeExpression(RangeExpression node) { nodeStack.push(factory.createFragment(node)); super.visitRangeExpression(node); check(node); nodeStack.pop(); } @Override public void visitShortTernaryExpression(ElvisOperatorExpression node) { nodeStack.push(factory.createFragment(node)); super.visitShortTernaryExpression(node); check(node); nodeStack.pop(); } @Override public void visitSpreadExpression(SpreadExpression node) { nodeStack.push(factory.createFragment(node)); super.visitSpreadExpression(node); check(node); nodeStack.pop(); } @Override public void visitSpreadMapExpression(SpreadMapExpression node) { nodeStack.push(factory.createFragment(node)); super.visitSpreadMapExpression(node); check(node); nodeStack.pop(); } @Override public void visitTernaryExpression(TernaryExpression node) { nodeStack.push(factory.createFragment(node)); super.visitTernaryExpression(node); check(node); nodeStack.pop(); } @Override public void visitTupleExpression(TupleExpression node) { nodeStack.push(factory.createFragment(node)); super.visitTupleExpression(node); check(node); nodeStack.pop(); } @Override public void visitUnaryMinusExpression(UnaryMinusExpression node) { nodeStack.push(factory.createFragment(node)); super.visitUnaryMinusExpression(node); check(node); nodeStack.pop(); } @Override public void visitUnaryPlusExpression(UnaryPlusExpression node) { nodeStack.push(factory.createFragment(node)); super.visitUnaryPlusExpression(node); check(node); nodeStack.pop(); } @Override public void visitArrayExpression(ArrayExpression node) { nodeStack.push(factory.createFragment(node)); super.visitArrayExpression(node); check(node); nodeStack.pop(); } @Override public void visitCastExpression(CastExpression node) { nodeStack.push(factory.createFragment(node)); super.visitCastExpression(node); check(node); nodeStack.pop(); } @Override public void visitCatchStatement(CatchStatement node) { nodeStack.push(factory.createFragment(node)); super.visitCatchStatement(node); check(node); nodeStack.pop(); } @Override public void visitClass(ClassNode node) { nodeStack.push(factory.createFragment(node)); super.visitClass(node); // explicitly check the class itself since super only // checks the class name // but only check if the node is not a script because // the script's run method will be checked separately if (!node.isScript()) { check(node); } nodeStack.pop(); } @Override public void visitClassExpression(ClassExpression node) { nodeStack.push(factory.createFragment(node)); super.visitClassExpression(node); nodeStack.pop(); } @Override public void visitClosureExpression(ClosureExpression node) { nodeStack.push(factory.createFragment(node)); super.visitClosureExpression(node); check(node); nodeStack.pop(); } @Override public void visitConstantExpression(ConstantExpression node) { nodeStack.push(factory.createFragment(node)); super.visitConstantExpression(node); nodeStack.pop(); } @Override public void visitConstructorCallExpression(ConstructorCallExpression node) { nodeStack.push(factory.createFragment(node)); super.visitConstructorCallExpression(node); check(node); nodeStack.pop(); } @Override protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { // GRECLIPSE-781 must visit run method last. if (isRunMethod(node)) { runMethod = node; return; } internalVisitConstructorOrMethod(node, isConstructor); } private void internalVisitConstructorOrMethod(MethodNode node, boolean isConstructor) { nodeStack.push(factory.createFragment(node)); super.visitConstructorOrMethod(node, isConstructor); // explicitly check the method itself since super only // checks the method name check(node); nodeStack.pop(); } private boolean isRunMethod(MethodNode node) { return node.getDeclaringClass().isScript() && node.getName().equals("run") && (node.getParameters() == null || node.getParameters().length == 0); } @Override public void visitDeclarationExpression(DeclarationExpression node) { nodeStack.push(factory.createFragment(node)); if (sloc.regionIsCoveredByNode(node)) { // create an extra stack frame to precisely describe the region // being covered nodeStack.push(factory.createFragment(node, sloc.getOffset(), sloc.getEnd())); } super.visitDeclarationExpression(node); check(node); nodeStack.pop(); } @Override public void visitField(FieldNode node) { if (node.getName().startsWith("$")) { // synthetic return; } nodeStack.push(factory.createFragment(node)); super.visitField(node); check(node); nodeStack.pop(); } @Override public void visitProperty(PropertyNode node) { // don't visit properties } @Override public void visitFieldExpression(FieldExpression node) { nodeStack.push(factory.createFragment(node)); super.visitFieldExpression(node); nodeStack.pop(); } @Override public void visitForLoop(ForStatement node) { nodeStack.push(factory.createFragment(node)); super.visitForLoop(node); check(node); nodeStack.pop(); } @Override public void visitGStringExpression(GStringExpression node) { nodeStack.push(factory.createFragment(node)); super.visitGStringExpression(node); check(node); nodeStack.pop(); } @Override public void visitReturnStatement(ReturnStatement node) { nodeStack.push(factory.createFragment(node)); super.visitReturnStatement(node); check(node); nodeStack.pop(); } @Override public void visitStaticMethodCallExpression(StaticMethodCallExpression node) { nodeStack.push(factory.createFragment(node)); super.visitStaticMethodCallExpression(node); nodeStack.pop(); } @Override public void visitVariableExpression(VariableExpression node) { nodeStack.push(factory.createFragment(node)); super.visitVariableExpression(node); nodeStack.pop(); } }