/* * 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.rewriter; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Stack; import groovyjarjarasm.asm.Opcodes; import org.codehaus.groovy.antlr.LineColumn; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CodeVisitorSupport; import org.codehaus.groovy.ast.CompileUnit; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.GroovyClassVisitor; import org.codehaus.groovy.ast.ImportNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.Variable; 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.Expression; 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.NamedArgumentListExpression; 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.Statement; 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.core.GroovyCore; import org.codehaus.groovy.eclipse.refactoring.core.utils.FilePartReader; import org.codehaus.groovy.eclipse.refactoring.core.utils.ImportResolver; import org.codehaus.groovy.syntax.Token; import org.codehaus.groovy.syntax.Types; import org.eclipse.jdt.core.Signature; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; /** * @author martin kempf * @author reto kleeb */ public class ASTWriter extends CodeVisitorSupport implements GroovyClassVisitor, Opcodes { protected StringBuilder groovyCode; //protected PrintStream groovyCode; private String lineDelimiter; //private ByteArrayOutputStream buffer; protected Stack<ASTNode> nodeStack = new Stack<ASTNode>(); private final ModuleNode root; private int lineOfPreviousNode = 1; private int lineOfCurrentNode = 1; private int columnOffset = 0; private int caseCount = 0; //to know which case is the first one in switch private boolean inElseBlock = false; private final IDocument currentDocument; // might be null private int lineOffset = 0; private boolean explizitModifier = false; private String modifier = ""; private int linesSinceFirstAnnotation = 0; //to know the startline of a method without the annotations private DeclarationExpression previousDeclaration = null; /** * * @param lineOffset set a startposition for the rewritten Code * @param currentDocument SourceCode, the AST is generated from */ public ASTWriter(ModuleNode root, int lineOffset, IDocument currentDocument) { this(root,currentDocument); setLineOffset(lineOffset); } /** * * @param currentDocument SourceCode, the AST is generated from */ public ASTWriter(ModuleNode root, IDocument currentDocument) { groovyCode = new StringBuilder(); try { if (currentDocument != null) { lineDelimiter = currentDocument.getLineDelimiter(0); } } catch (BadLocationException e) { GroovyCore.logException("Error writing AST", e); } if (lineDelimiter == null) { lineDelimiter = System.getProperty("line.separator"); } this.root = root; this.currentDocument = currentDocument; } public ASTWriter(Expression e) { this(createModuleNode(e), null); } /** * @param e * @return */ private static ModuleNode createModuleNode(Expression e) { ModuleNode module = new ModuleNode((CompileUnit) null); module.addStatement(new ExpressionStatement(e)); module.setDescription("DummyModule.groovy"); return module; } /** * Sets the start offset (horizontal) of the new written code * * @param tabs start offset of new written code */ public void setStartOffset(int startOffset) { this.columnOffset = startOffset; printColumnOffset(); } /** * Sets the line offset to avoid renumber of line information * @param offset */ public void setLineOffset(int offset) { this.lineOffset = offset; } /** * Sets the modifier that, should be used when writing back a MethodNode * @param mod */ public void setModifierToUse(String mod) { this.modifier = mod; explizitModifier = true; } public void insertLineFeed() { groovyCode.append(lineDelimiter); } public String getGroovyCode() { return groovyCode.toString(); } public void visitRoot() { preVisitStatement(root); //write package if (root.getPackageName() != null) { groovyCode.append("package "); String packageName = root.getPackageName(); //packageName ends with ".", chop it groovyCode.append(packageName.substring(0, packageName.length() - 1)); } //write importPackage like import test.* printImports(root.getStarImports()); //write imports like import test.TestClass printImports(root.getImports()); //write imports like import static java.lang.Math.PI printImports(root.getStaticImports().values()); //write imports like import static java.lang.Math.* printImports(root.getStaticStarImports().values()); //write Statements that are not inside a class if (!root.getStatementBlock().isEmpty()) { visitBlockStatement(root.getStatementBlock()); } //write the classes List<ClassNode> classes = root.getClasses(); for (ClassNode classNode : classes) { if (!classNode.isScript()) { visitClass(classNode); } else { List<MethodNode> methods = root.getMethods(); for ( MethodNode method : methods) { visitMethod(method); } } } postVisitStatement(root); } private void printImports(Collection<ImportNode> imports) { for (Iterator<ImportNode> impIter = imports.iterator(); impIter.hasNext();) { ImportNode imp = impIter.next(); groovyCode.append(imp.getText()); if (impIter.hasNext()) { insertLineFeed(); lineOfPreviousNode++; } } } public void visitClass(ClassNode node) { visitAnnotations(node); preVisitStatement(node); linesSinceFirstAnnotation = 0; if(node.getSuperClass().getName().equals("java.lang.Enum")){ writeEnum(node); return; } //TODO::::::::::::::::::reto /* * all modifiers in a run of the AST writer * are written back the same. example: * class that is explicitly public leads to methods * that are explicitly public */ if (node.isInterface()) { groovyCode.append("interface "); } else { groovyCode.append(ASTWriterHelper.getAccModifier(node.getModifiers(), ASTWriterHelper.MOD_CLASS)); groovyCode.append("class "); } printType(node); if (!node.getSuperClass().getNameWithoutPackage().equals("Object")) { groovyCode.append(" extends "); groovyCode.append(node.getSuperClass().getNameWithoutPackage()); } if (node.getInterfaces().length > 0) { groovyCode.append(" implements "); ClassNode[] theInterfaces = node.getInterfaces(); for (int i = 0; i < theInterfaces.length; i++ ) { groovyCode.append(theInterfaces[i].getNameWithoutPackage()); if (i < theInterfaces.length - 1) { groovyCode.append(", "); } } } preVisitStatementOpenBlock(node); node.visitContents(this); postVisitStatementCloseBlock(node); postVisitStatement(node); // Object initializers don't exist at this point /*List list = node.getObjectInitializerStatements(); for (Iterator iter = list.iterator(); iter.hasNext();) { Statement element = (Statement) iter.next(); element.visit(this); }*/ } /* * Enums are written back on ONE line and a lot of * the original parts will get lost. */ private void writeEnum(ClassNode node) { groovyCode.append("enum "); groovyCode.append(node.getName() + " "); groovyCode.append('{'); for(int i = 0; i < node.getFields().size(); i++){ FieldNode fn = node.getFields().get(i); if(i == 0) groovyCode.append(fn.getName()); else{ //ast contains additional variables that start with a '$' //don't write these back if(!fn.getName().startsWith("$")){ groovyCode.append(", " + fn.getName()); } } } groovyCode.append('}'); postVisitStatement(node); } public void visitAnnotations(AnnotatedNode node) { boolean first = true; List<AnnotationNode> annotionMap = node.getAnnotations(); if (annotionMap.isEmpty()) return; Iterator<AnnotationNode> it = annotionMap.iterator(); while (it.hasNext()) { AnnotationNode an = it.next(); // annotations with no member-value pairs are having // an invalid lastLineNumber. It is 1 greater than it should be. // ASC no longer an issue when annotation position is fixed... int extra = 1;//an.getMembers().size() == 0 ? 0 : 1; linesSinceFirstAnnotation += (an.getLastLineNumber() + extra) - an.getLineNumber(); preVisitStatement(an); groovyCode.append("@"); groovyCode.append(an.getClassNode().getNameWithoutPackage()); //skip builtin properties if (an.isBuiltIn()) continue; for (Entry<String, Expression> member : an.getMembers().entrySet()) { Expression memberValue = member.getValue(); preVisitExpression(memberValue); if (first) { first = false; groovyCode.append("(value = "); } else { groovyCode.append(", value = "); } memberValue.visit(this); if (!first) { groovyCode.append(")"); } postVisitExpression(memberValue); } postVisitStatement(an); } } protected void visitClassCodeContainer(Statement code) { if (code != null) { code.visit(this); } } protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { visitAnnotations(node); preVisitStatement(node); linesSinceFirstAnnotation = 0; printMethodHead(node); Statement code = node.getCode(); if (code != null) { code.setSourcePosition(node); visitClassCodeContainer(code); } postVisitStatement(node); } /** * Prints the Head of the Method * @param node MethodNode from which the head shall be printed */ public void printMethodHead(MethodNode node) { if (node.isVoidMethod()) { if (explizitModifier) { groovyCode.append(modifier); groovyCode.append(" "); } groovyCode.append("void "); } else { if (explizitModifier) { groovyCode.append(modifier); groovyCode.append(" "); } else { groovyCode.append(ASTWriterHelper.getAccModifier(node.getModifiers(), ASTWriterHelper.MOD_METHOD)); } if (!node.isDynamicReturnType()) { printType(node.getReturnType()); groovyCode.append(" "); } } groovyCode.append(node.getName()); groovyCode.append("("); Parameter[] parameters = node.getParameters(); printParameters(parameters); groovyCode.append(")"); if(node.getExceptions() != null){ if(node.getExceptions().length > 0){ groovyCode.append(" throws "); for(int i = 0; i < node.getExceptions().length; i++){ if(i==0)groovyCode.append(node.getExceptions()[i].getNameWithoutPackage()); else groovyCode.append(", " + node.getExceptions()[i].getNameWithoutPackage()); } } } } private void printParameters(Parameter[] parameters) { for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; visitAnnotations(parameter); if (!parameter.getAnnotations().isEmpty()) { groovyCode.append(" "); } linesSinceFirstAnnotation = 0; if (!parameter.isDynamicTyped()) { printType(parameter.getOriginType()); groovyCode.append(" "); } groovyCode.append(parameter.getName()); if (parameter.hasInitialExpression()) { groovyCode.append("="); parameter.getInitialExpression().visit(this); } if (i < parameters.length - 1) { groovyCode.append(", "); } } } public void visitConstructor(ConstructorNode node) { visitConstructorOrMethod(node,true); } public void visitMethod(MethodNode node) { visitConstructorOrMethod(node,false); } public void visitField(FieldNode node) { // Do not write fields back which are manually added due to optimization if (!node.getName().startsWith("$")) { visitAnnotations(node); preVisitStatement(node); //properties are stored twice in ClassNode, visitOnlyOnce //visitProperty does nothing if (ASTWriterHelper.isProperty(node)) { if (!node.isDynamicTyped()) { if (node.isStatic()) { groovyCode.append("static "); } printType(node.getOriginType()); } else { groovyCode.append("def"); if (node.isStatic()) { groovyCode.append(" static"); } } groovyCode.append(" "); } else { groovyCode.append(ASTWriterHelper.getAccModifier(node.getModifiers(), ASTWriterHelper.MOD_FIELD)); if (!node.isDynamicTyped()) { printType(node.getOriginType()); groovyCode.append(" "); } } groovyCode.append(node.getName()); Expression init = node.getInitialExpression(); if (init != null) { /* * if lineNumber of init is -1, the initializer * has not been written explicitly */ if (init.getLineNumber() != -1) { groovyCode.append(" = "); init.visit(this); } } postVisitStatement(node); } } public void visitProperty(PropertyNode node) { //do nothing, also visited as FieldNode } @Override public void visitAssertStatement(AssertStatement statement) { preVisitStatement(statement); groovyCode.append("assert "); super.visitAssertStatement(statement); postVisitStatement(statement); } @Override public void visitBlockStatement(BlockStatement block) { //write CaseStatement and defaultStatment without curly brackets //also finally Statement (BlockStatement in BlockStatement) needs to be adapted maybe //TODO:look in file to decide whether write curly brakets or not if ( getTop() instanceof CaseStatement || getTop() instanceof SwitchStatement || getTop() instanceof BlockStatement || getTop() instanceof ModuleNode) { preVisitStatement(block); super.visitBlockStatement(block); postVisitStatement(block); } else { preVisitStatementOpenBlock(block); if (getParent() instanceof ClosureExpression) { ClosureExpression closure = (ClosureExpression)getParent(); Parameter[] parameters = closure.getParameters(); if (parameters.length > 0) { printParameters(parameters); groovyCode.append(" -> "); } } super.visitBlockStatement(block); postVisitStatementCloseBlock(block); } } @Override public void visitBreakStatement(BreakStatement statement) { preVisitStatement(statement); groovyCode.append("break"); if(statement.getLabel() != null){ groovyCode.append(" " + statement.getLabel()); } super.visitBreakStatement(statement); postVisitStatement(statement); } @Override public void visitCaseStatement(CaseStatement statement) { preVisitStatement(statement); groovyCode.append("case "); super.visitCaseStatement(statement); postVisitStatement(statement); } @Override public void visitCatchStatement(CatchStatement statement) { preVisitStatement(statement); groovyCode.append(" catch ("); ClassNode typOfException = statement.getExceptionType(); Parameter eVariable = statement.getVariable(); if (!eVariable.isDynamicTyped()) { printType(typOfException); groovyCode.append(" "); } groovyCode.append(eVariable.getName()); groovyCode.append(")"); super.visitCatchStatement(statement); postVisitStatement(statement); } @Override public void visitContinueStatement(ContinueStatement statement) { preVisitStatement(statement); groovyCode.append("continue"); super.visitContinueStatement(statement); postVisitStatement(statement); } @Override public void visitDoWhileLoop(DoWhileStatement loop) { preVisitStatementOpenBlock(loop); super.visitDoWhileLoop(loop); postVisitStatementCloseBlock(loop); } @Override public void visitExpressionStatement(ExpressionStatement statement) { preVisitStatement(statement); if(statement.getStatementLabel() != null){ groovyCode.append(statement.getStatementLabel() + ": "); } //super.visitExpressionStatement(statement); if (statement.getExpression() instanceof MethodCallExpression){ MethodCallExpression methCallExpr = (MethodCallExpression) statement.getExpression(); if (!methCallExpr.isImplicitThis()) { methCallExpr.getObjectExpression().visit(this); ArgumentListExpression ale = ((ArgumentListExpression)methCallExpr.getArguments()); printArgumentsOfaMethodCall(methCallExpr, ale); }else{ super.visitExpressionStatement(statement); } }else{ super.visitExpressionStatement(statement); } postVisitStatement(statement); } private void printArgumentsOfaMethodCall(MethodCallExpression methCallExpr, ArgumentListExpression ale) { if(ale != null){ groovyCode.append('.'); groovyCode.append(methCallExpr.getMethod().getText()); /* * Methodcall has a certain number of * arguments */ if(ale.getExpressions().size() >=1){ List<Expression> listOfAllExpressions = ale.getExpressions(); ArgumentListExpression listOfMethodArguments = new ArgumentListExpression(); ClosureExpression closure = null; /* * Loop assigns closure, if there's a closure * (There's no difference in the AST whether it is a normal * parameter or a closure) */ for(Expression expr : listOfAllExpressions){ if(expr instanceof ClosureExpression){ closure = (ClosureExpression) expr; }else{ listOfMethodArguments.addExpression(expr); } } /* * Visit the "normal" arguments */ if(listOfMethodArguments.getExpressions().size() >= 1){ printArgumentsOfaMethod(listOfMethodArguments); } if(closure != null) { closure.visit(this); } }else{ groovyCode.append("()"); } } } private void printArgumentsOfaMethod(ArgumentListExpression methCallExpr) { groovyCode.append('('); methCallExpr.visit(this); groovyCode.append(')'); } @Override public void visitForLoop(ForStatement forLoop) { preVisitStatement(forLoop); groovyCode.append("for "); //if its groovy for-loop if (!forLoop.getVariable().getName().equals("forLoopDummyParameter")) { groovyCode.append("("); groovyCode.append(forLoop.getVariable().getName()); groovyCode.append(" in "); forLoop.getCollectionExpression().visit(this); groovyCode.append(")"); forLoop.getLoopBlock().visit(this); } else { forLoop.getCollectionExpression().visit(this); if (forLoop.getLoopBlock() instanceof BlockStatement) { forLoop.getLoopBlock().visit(this); } else { columnOffset++; forLoop.getLoopBlock().visit(this); columnOffset--; } } postVisitStatement(forLoop); } @Override public void visitIfElse(IfStatement ifElse) { preVisitStatement(ifElse); groovyCode.append("if ("); ifElse.getBooleanExpression().visit(this); groovyCode.append(")"); //if without curly braces if (!(ifElse.getIfBlock() instanceof BlockStatement)) { columnOffset++; //format ifElse.getIfBlock().visit(this); columnOffset--; } else { ifElse.getIfBlock().visit(this); } //If ElseBlock exists if (!(ifElse.getElseBlock() instanceof EmptyStatement)) { if (!(ifElse.getElseBlock() instanceof BlockStatement) && !(ifElse.getElseBlock() instanceof IfStatement)) { positioningCursor(); insertLineFeed(); groovyCode.append("else"); lineOfPreviousNode ++; columnOffset++; //format ifElse.getElseBlock().visit(this); columnOffset--; } else { /* * Do not write "else" here to allow writing it later * on the same line as the closing brace of the if-block */ inElseBlock = true; ifElse.getElseBlock().visit(this); } } postVisitStatement(ifElse); } @Override public void visitReturnStatement(ReturnStatement statement) { // ignore return statement when in an empty script if (!shouldIgnoreReturn()) { preVisitStatement(statement); groovyCode.append("return "); super.visitReturnStatement(statement); postVisitStatement(statement); } } /** * Ignore the return statement when visiting an empty script */ private boolean shouldIgnoreReturn() { if (root.getClasses().size() == 1) { ClassNode clazz = root.getClasses().get(0); if (clazz.isScript()) { MethodNode runMethod = clazz.getMethod("run", Parameter.EMPTY_ARRAY); if (runMethod != null) { Statement s = runMethod.getCode(); if (s instanceof BlockStatement) { BlockStatement body = (BlockStatement) s; if (body.getStatements().size() == 1 && body.getStatements().get(0) instanceof ReturnStatement) { ReturnStatement ret = (ReturnStatement) body.getStatements().get(0); return ret.getExpression() instanceof ConstantExpression && ((ConstantExpression) ret.getExpression()).getText().equals("null"); } } } } } return false; } @Override public void visitSwitch(SwitchStatement statement) { preVisitStatement(statement); groovyCode.append("switch ("); statement.getExpression().visit(this); groovyCode.append(")"); List<CaseStatement> list = statement.getCaseStatements(); if (list != null) { for (CaseStatement caseStatement : list) { caseStatement.visit(this); } } statement.getDefaultStatement().visit(this); postVisitStatementCloseBlock(statement); } @Override public void visitSynchronizedStatement(SynchronizedStatement statement) { preVisitStatement(statement); groovyCode.append("synchronized ("); statement.getExpression().visit(this); groovyCode.append(")"); statement.getCode().visit(this); postVisitStatement(statement); } @Override public void visitThrowStatement(ThrowStatement statement) { preVisitStatement(statement); groovyCode.append("throw "); super.visitThrowStatement(statement); postVisitStatement(statement); } @Override public void visitTryCatchFinally(TryCatchStatement statement) { preVisitStatement(statement); groovyCode.append("try"); statement.getTryStatement().visit(this); List<CatchStatement> list = statement.getCatchStatements(); if (list != null) { for (CatchStatement catchStatement : list) { catchStatement.visit(this); } } /* if a finally statement is written * doesn't matter if the finally statement is empty */ if (!statement.getFinallyStatement().isEmpty()) { groovyCode.append(" finally"); statement.getFinallyStatement().visit(this); } postVisitStatement(statement); } @Override public void visitWhileLoop(WhileStatement loop) { preVisitStatement(loop); groovyCode.append("while ("); loop.getBooleanExpression().visit(this); groovyCode.append(")"); if (loop.getLoopBlock() instanceof BlockStatement) { loop.getLoopBlock().visit(this); } else { columnOffset++; loop.getLoopBlock().visit(this); columnOffset--; } postVisitStatement(loop); } /* * Expressions */ @Override public void visitMethodCallExpression(MethodCallExpression call) { preVisitExpression(call); if (!call.getText().contains("this")) { call.getObjectExpression().visit(this); groovyCode.append("."); } call.getMethod().visit(this); groovyCode.append("("); call.getArguments().visit(this); groovyCode.append(")"); postVisitExpression(call); } @Override public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { preVisitExpression(call); groovyCode.append(call.getMethod()); groovyCode.append("("); call.getArguments().visit(this); groovyCode.append(")"); postVisitExpression(call); } @Override public void visitConstructorCallExpression(ConstructorCallExpression call) { preVisitExpression(call); groovyCode.append("new "); printType(call.getType()); groovyCode.append("("); call.getArguments().visit(this); groovyCode.append(")"); postVisitExpression(call); } @Override public void visitBinaryExpression(BinaryExpression expression) { boolean writeParanthesis = false; preVisitExpression(expression); Token operation = expression.getOperation(); if (operation.getType() == Types.LEFT_SQUARE_BRACKET) { expression.getLeftExpression().visit(this); groovyCode.append("["); expression.getRightExpression().visit(this); groovyCode.append("]"); } else { LineColumn coords = new LineColumn(expression.getLineNumber(), expression.getColumnNumber()); try { if (!(getParent() instanceof DeclarationExpression) && FilePartReader.readForwardFromCoordinate(currentDocument, coords).startsWith("(")) { groovyCode.append("("); writeParanthesis = true; } } catch (BadLocationException e) { GroovyCore.logException("Error in refactoring", e); } expression.getLeftExpression().visit(this); if (expression.getRightExpression().getText() != "null") { groovyCode.append(" "); groovyCode.append(operation.getText()); groovyCode.append(" "); expression.getRightExpression().visit(this); } if (writeParanthesis) { groovyCode.append(")"); } } /* printExpression("", expression, ""); * Can't print the expression like this, cause if the leftExpression is of type * VariableExpression the Line expression.getText() would ignore the type of the VariableExpression * This is because the VariableExpression's getText() method ignore the type */ postVisitExpression(expression); } @Override public void visitTernaryExpression(TernaryExpression expression) { expression.getBooleanExpression().visit(this); groovyCode.append(" ? "); expression.getTrueExpression().visit(this); groovyCode.append(" : "); expression.getFalseExpression().visit(this); } @Override public void visitPostfixExpression(PostfixExpression expression) { preVisitExpression(expression); super.visitPostfixExpression(expression); groovyCode.append(expression.getOperation().getText()); postVisitExpression(expression); } @Override public void visitPrefixExpression(PrefixExpression expression) { preVisitExpression(expression); groovyCode.append(expression.getOperation().getText()); super.visitPrefixExpression(expression); postVisitExpression(expression); } @Override public void visitBooleanExpression(BooleanExpression expression) { preVisitExpression(expression); // LineColumn coords = new LineColumn(expression.getLineNumber(), // expression.getColumnNumber()); // if (!(expression.getExpression() instanceof BinaryExpression) // && FilePartReader.readForwardFromCoordinate(currentDocument,coords).startsWith("(")) { // /* // * e.g displayName = (boolVal) ?: f //"(" optional, that causes the read in the original // */ // groovyCode.append("("); // super.visitBooleanExpression(expression); // groovyCode.append(")"); // } else { // /* // * if the BooleanExpression contains a BinaryExpression, // * the parenthesis are written there // * // * e.g if (test == 4) {} // * or displayName = boolVal ?: f // */ // super.visitBooleanExpression(expression); // } super.visitBooleanExpression(expression); postVisitExpression(expression); } @Override public void visitNotExpression(NotExpression expression) { preVisitExpression(expression); printExpression("!", expression, ""); postVisitExpression(expression); } @Override public void visitClosureExpression(ClosureExpression expression) { preVisitExpression(expression); expression.getCode().setSourcePosition(expression); expression.getCode().visit(this); postVisitExpression(expression); } @Override public void visitTupleExpression(TupleExpression expression) { visitListOfExpressions(expression.getExpressions()); } @Override public void visitListExpression(ListExpression expression) { preVisitExpression(expression); groovyCode.append("["); visitListOfExpressions(expression.getExpressions(),","); groovyCode.append("]"); postVisitExpression(expression); } @Override public void visitArrayExpression(ArrayExpression expression) { preVisitExpression(expression); visitListOfExpressions(expression.getExpressions()); groovyCode.append("new "); String typeName = expression.getType().getNameWithoutPackage(); if (typeName.startsWith("[")) { // this is an array signature typeName = Signature.getElementType(typeName); typeName = Signature.getSignatureSimpleName(typeName); } groovyCode.append(typeName); visitListOfExpressions(expression.getSizeExpression(),""); postVisitExpression(expression); } @Override public void visitMapExpression(MapExpression expression) { boolean isMapList = !(expression instanceof NamedArgumentListExpression); preVisitExpression(expression); List<MapEntryExpression> mapEntries = expression.getMapEntryExpressions(); if (isMapList) { groovyCode.append("["); } if (!mapEntries.isEmpty()) { visitListOfExpressions(mapEntries,","); } else { groovyCode.append(":"); } if(isMapList)groovyCode.append("]"); postVisitExpression(expression); } @Override public void visitMapEntryExpression(MapEntryExpression expression) { preVisitExpression(expression); expression.getKeyExpression().visit(this); groovyCode.append(":"); expression.getValueExpression().visit(this); postVisitExpression(expression); } @Override public void visitRangeExpression(RangeExpression expression) { preVisitExpression(expression); expression.getFrom().visit(this); groovyCode.append(".."); if (!expression.isInclusive()) { groovyCode.append("<"); } expression.getTo().visit(this); postVisitExpression(expression); } @Override public void visitSpreadExpression(SpreadExpression expression) { preVisitExpression(expression); groovyCode.append("*"); super.visitSpreadExpression(expression); postVisitExpression(expression); } @Override public void visitSpreadMapExpression(SpreadMapExpression expression) { preVisitExpression(expression); groovyCode.append("*"); //super.visitSpreadMapExpression(expression); postVisitExpression(expression); } @Override public void visitMethodPointerExpression(MethodPointerExpression expression) { preVisitStatement(expression); expression.getExpression().visit(this); groovyCode.append(".&"); expression.getMethodName().visit(this); postVisitStatement(expression); } @Override public void visitBitwiseNegationExpression(BitwiseNegationExpression expression) { preVisitExpression(expression); groovyCode.append("~"); expression.getExpression().visit(this); postVisitExpression(expression); } @Override public void visitCastExpression(CastExpression expression) { preVisitExpression(expression); groovyCode.append("("); printType(expression.getType()); groovyCode.append(")"); super.visitCastExpression(expression); postVisitExpression(expression); } @Override public void visitConstantExpression(ConstantExpression expression) { preVisitExpression(expression); String pre = ""; String post = ""; ASTNode parent = getParent(); if (parent instanceof AssertStatement && expression.getText() != "null") { pre = " , "; } if(parent instanceof DeclarationExpression || parent instanceof FieldNode){ /* * If a const expression has one of these types * the user wrote the suffix in the source. * There could be more of these situations */ if (expression.getType().equals(ClassHelper.Float_TYPE)){ post += "f"; }else if (expression.getType().equals(ClassHelper.Double_TYPE)){ post += "d"; } else if (expression.getType().equals(ClassHelper.BigInteger_TYPE)) { post += "g"; } else if (expression.getType().equals(ClassHelper.Long_TYPE)) { post += "l"; } } //normally write quotes if expression is of type String. //exceptions: methodName in MethodCallExpression //String in a GStringExpression //methodName in MethodPointerExpression if (constExprIsAString(expression)) { LineColumn coords = new LineColumn(expression.getLineNumber(), expression.getColumnNumber()); String stringMarker = ASTWriterHelper.getStringMarker(currentDocument, coords); pre += stringMarker; printExpression(pre,expression, stringMarker); if (stringMarker.length() == 3) { //these strings might be longer than one line -> don't need to add manual linefeeds lineOfPreviousNode += expression.getLastLineNumber() - expression.getLineNumber(); } } else{ printExpression(pre, expression, post); } postVisitExpression(expression); } private boolean constExprIsAString(ConstantExpression expression) { return expression.getType().getName() == "java.lang.String" && !(getParent() instanceof MethodCallExpression) && !(getParent() instanceof GStringExpression) && !(getParent() instanceof MethodPointerExpression); } @Override public void visitClassExpression(ClassExpression expression) { preVisitExpression(expression); printType(expression.getType()); postVisitExpression(expression); } @Override public void visitVariableExpression(VariableExpression expression) { //String pre = ""; //ASTNode parent = getParent(); getParent(); preVisitExpression(expression); LineColumn coords = new LineColumn(expression.getLineNumber(), expression.getColumnNumber()); try { if (FilePartReader.readForwardFromCoordinate(currentDocument, coords).startsWith("(")) { printExpression("(", expression, ")"); } else { printExpression(expression); } } catch (BadLocationException e) { GroovyCore.logException("Error in refactoring", e); } postVisitExpression(expression); } @Override public void visitDeclarationExpression(DeclarationExpression expression) { //Write Variable type if it's the first declaration on the line preVisitExpression(expression); VariableExpression variable = (VariableExpression) expression.getLeftExpression(); Variable accessedVariable = variable.getAccessedVariable() == null ? variable : variable.getAccessedVariable(); if (previousDeclaration != null) { if(previousDeclaration.getVariableExpression().getLineNumber() != expression.getVariableExpression().getLineNumber()) { if (!accessedVariable.isDynamicTyped()) { printType(variable.getOriginType()); } else { groovyCode.append("def"); } } else { groovyCode.append(","); } } else { if (!accessedVariable.isDynamicTyped()) { printType(variable.getOriginType()); } else { groovyCode.append("def"); } } groovyCode.append(" "); visitBinaryExpression(expression); previousDeclaration = expression; postVisitExpression(expression); } /** * Prints the type considering array and generic * @param type */ private void printType(ClassNode type) { if (type.isArray()) { printArray(type); } else { groovyCode.append(ImportResolver.getResolvedClassName(root,type,true)); if (type.isUsingGenerics()) { GenericsType[] genericTypes = type.getGenericsTypes(); if (genericTypes != null) { groovyCode.append("<"); for (GenericsType generic : Arrays.asList(genericTypes)) { printGenericsType(generic); } groovyCode.append(">"); } } } } public void printGenericsType(GenericsType genericType) { ClassNode[] upperBounds = genericType.getUpperBounds(); ClassNode lowerBound = genericType.getLowerBound(); groovyCode.append(genericType.getName()); if (upperBounds != null) { groovyCode.append(" extends "); for (int i = 0; i < upperBounds.length; i++) { printType(upperBounds[i]); if (i+1<upperBounds.length) groovyCode.append(" & "); } } else if (lowerBound!=null) { groovyCode.append(" super "); printType(lowerBound); } } /** * Prints an Array Declaration * * @param componentType componentType which is an Array */ private void printArray(ClassNode compType) { ClassNode componentType = compType; int dimension = 0; while (componentType.isArray()) { dimension++; componentType = componentType.getComponentType(); } printType(componentType); for (int i = 0; i < dimension; i++) { groovyCode.append("[]"); } } @Override public void visitPropertyExpression(PropertyExpression expression) { preVisitExpression(expression); String alias = ImportResolver.asAlias(root,expression.getObjectExpression().getType()); String fieldName = ImportResolver.asFieldName(root, expression.getObjectExpression().getType(), expression.getPropertyAsString()); if (alias != "") { groovyCode.append(alias); } else if (fieldName != ""){ groovyCode.append(fieldName); } else { expression.getObjectExpression().visit(this); groovyCode.append("."); expression.getProperty().visit(this); } postVisitExpression(expression); } @Override public void visitAttributeExpression(AttributeExpression expression) { preVisitExpression(expression); expression.getObjectExpression().visit(this); groovyCode.append(".@"); expression.getProperty().visit(this); postVisitExpression(expression); } /** * A FieldExpression in the AST in phase SEMANTIC_ANALYSIS can only occur * in a static field access in a static method in the class containing the field */ @Override public void visitFieldExpression(FieldExpression expression) { preVisitExpression(expression); ClassNode classNode = expression.getField().getOwner(); groovyCode.append(ImportResolver.getResolvedClassName(root, classNode,true)); groovyCode.append("."); groovyCode.append(expression.getFieldName()); postVisitExpression(expression); } @Override public void visitGStringExpression(GStringExpression expression) { preVisitExpression(expression); List<Expression> values = expression.getValues(); Iterator<Expression> it = values.iterator(); LineColumn coords = new LineColumn(expression.getLineNumber(), expression.getColumnNumber()); String stringMarker = ASTWriterHelper.getStringMarker(currentDocument, coords); groovyCode.append(stringMarker); for (ConstantExpression stringExpression : expression.getStrings()) { stringExpression.visit(this); if (it.hasNext()) { visitValueInGString(it); } } while (it.hasNext()) { visitValueInGString(it); } groovyCode.append(stringMarker); if (stringMarker.length() == 3) { //these strings might be longer than one line -> don't need to add manual linefeeds lineOfPreviousNode += expression.getLastLineNumber() - expression.getLineNumber(); } postVisitExpression(expression); } /* * Values in GStrings are allowed in the following two forms: * "text ${variable}" but also "text $variable" * * The AST writer needs to read the inputfile to determine * how to user wrote his GString */ private void visitValueInGString(Iterator<Expression> it) { Expression valueExpression = it.next(); LineColumn coords = new LineColumn(valueExpression.getLineNumber(), valueExpression.getColumnNumber()); char firstChar; try { firstChar = FilePartReader.readForwardFromCoordinate(currentDocument, coords).charAt(0); } catch (BadLocationException e) { GroovyCore.logException("Error during refactoring...trying to recover", e); firstChar = '\0'; } groovyCode.append("$"); if(firstChar == '{'){ groovyCode.append("{"); (valueExpression).visit(this); groovyCode.append("}"); }else{ (valueExpression).visit(this); } } protected void visitListOfExpressions(List<? extends Expression> list, String separator) { if (list==null) return; for (Iterator<? extends Expression> iterator = list.iterator(); iterator.hasNext();) { Expression expression = iterator.next(); preVisitExpression(expression); if (getParent() instanceof ArrayExpression) { groovyCode.append("["); } expression.visit(this); if (getParent() instanceof ArrayExpression) { groovyCode.append("]"); } else if (iterator.hasNext()) { groovyCode.append(separator + " "); } postVisitExpression(expression); } } @Override public void visitArgumentlistExpression(ArgumentListExpression ale) { visitListOfExpressions(ale.getExpressions(), ","); } @Override public void visitShortTernaryExpression(ElvisOperatorExpression expression) { preVisitExpression(expression); expression.getBooleanExpression().visit(this); groovyCode.append(" ?: "); expression.getFalseExpression().visit(this); postVisitExpression(expression); } @Override public void visitUnaryPlusExpression(UnaryPlusExpression expression) { preVisitExpression(expression); groovyCode.append('+'); super.visitUnaryPlusExpression(expression); postVisitExpression(expression); } @Override public void visitUnaryMinusExpression(UnaryMinusExpression expression) { preVisitExpression(expression); groovyCode.append('-'); super.visitUnaryMinusExpression(expression); postVisitExpression(expression); } @Override public void visitClosureListExpression(ClosureListExpression cle) { preVisitExpression(cle); groovyCode.append("("); visitListOfExpressions(cle.getExpressions(),";"); groovyCode.append(")"); postVisitExpression(cle); } protected void preVisitStatement(ASTNode statement) { ASTNode parent = getTop(); if (statement instanceof CaseStatement) { caseCount++; } if (parent instanceof SwitchStatement && (caseCount == 1) ) { //if we're here we are in the first case statement in a switch statement //and have to print the opening curly bracket for the switch statement nodeStack.pop(); //Pop SwitchStatement preVisitStatementOpenBlock(parent); } nodeStack.push(statement); if (statement.getLineNumber() != -1) { lineOfCurrentNode = statement.getLineNumber()-lineOffset; } /* * Annotiations are included in the startline of Method/Class * -> add diffrence between annotation and class/method to the currentLine */ if (!(statement instanceof AnnotationNode)) { lineOfCurrentNode += linesSinceFirstAnnotation; } positioningCursor(); if (parent instanceof SwitchStatement && !(statement instanceof CaseStatement)) { groovyCode.append("default : "); } else { if (inElseBlock) { groovyCode.append("else "); inElseBlock = false; } } } protected void preVisitStatementOpenBlock(ASTNode statement) { nodeStack.push(statement); lineOfCurrentNode = statement.getLineNumber()-lineOffset; positioningCursor(); columnOffset++; if (inElseBlock) { groovyCode.append("else"); inElseBlock = false; } groovyCode.append(" {"); } protected void postVisitStatement(ASTNode statement) { nodeStack.pop(); lineOfCurrentNode = statement.getLastLineNumber()-lineOffset; /* * Annotations lastline is to long. Workaround: * if LastLineNumber - 1 = LineNumber-> reduce annotation line count */ // if (statement instanceof AnnotationNode) { // if (statement.getLastLineNumber() - 1 == statement.getLineNumber()) { // linesSinceFirstAnnotation--; // } // } if (statement instanceof SwitchStatement) { caseCount = 0; } } protected void postVisitStatementCloseBlock(ASTNode statement) { nodeStack.pop(); lineOfCurrentNode = statement.getLastLineNumber()-lineOffset; columnOffset--; positioningCursor(); groovyCode.append("}"); } protected void preVisitExpression(ASTNode expression) { nodeStack.push(expression); lineOfCurrentNode = expression.getLineNumber()-lineOffset; positioningCursor(); } protected void postVisitExpression(ASTNode expression) { nodeStack.pop(); } protected void printExpression(ASTNode expression) { String pre = ""; String post = ""; if (expression.getText() != "null") { printExpression(pre,expression,post); } } protected void printExpression(String pre, ASTNode expression, String post) { String printExpression = expression.getText(); if (expression.getText() == "null") { printExpression = ""; } //Escape if not RegularExpression or MultiLineString if (pre.length() > 0) { if (pre.charAt(0) != '/' && expression.getLineNumber() == expression.getLastLineNumber()) { printExpression = escapeJava(printExpression); } else { printExpression = printExpression.replaceAll("\r\n|\n", System.getProperty("line.separator")); } } groovyCode.append(pre); groovyCode.append(printExpression); groovyCode.append(post); if (getParent() instanceof CaseStatement) { groovyCode.append(" : "); } } private void positioningCursor() { boolean onNewLine = false; if ((lineOfPreviousNode == lineOfCurrentNode) && (getTop() instanceof BreakStatement)) { groovyCode.append(" ; "); } else { while (lineOfPreviousNode < lineOfCurrentNode) { groovyCode.append(lineDelimiter); lineOfPreviousNode++; onNewLine = true; } } if (onNewLine) { printColumnOffset(); } } /** * */ private void printColumnOffset() { for (int i = 0; i < columnOffset; i++) { groovyCode.append(" "); } } private ASTNode getTop() { if (nodeStack.isEmpty()) { return null; } return nodeStack.peek(); } private ASTNode getParent() { if (nodeStack.size() > 1) { ASTNode topNode = nodeStack.pop(); ASTNode parrentNode = nodeStack.peek(); nodeStack.push(topNode); return parrentNode; } return null; } //-------------------------------------------------------------------------- /** * From org.apache.commons.lang.StringEscapeUtils * <p>Escapes the characters in a <code>String</code> using Java String rules.</p> * * <p>Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p> * * <p>So a tab becomes the characters <code>'\\'</code> and * <code>'t'</code>.</p> * * <p>The only difference between Java strings and JavaScript strings * is that in JavaScript, a single quote must be escaped.</p> * * <p>Example: * <pre> * input string: He didn't say, "Stop!" * output string: He didn't say, \"Stop!\" * </pre> * </p> * * @param str String to escape values in, may be null * @return String with escaped values, <code>null</code> if null string input */ private static String escapeJava(String str) { return escapeJavaStyleString(str, false); } /** * From org.apache.commons.lang.StringEscapeUtils * <p>Worker method for the {@link #escapeJavaScript(String)} method.</p> * * @param str String to escape values in, may be null * @param escapeSingleQuotes escapes single quotes if <code>true</code> * @return the escaped string */ private static String escapeJavaStyleString(String str, boolean escapeSingleQuotes) { if (str == null) { return null; } try { StringWriter writer = new StringWriter(str.length() * 2); escapeJavaStyleString(writer, str, escapeSingleQuotes); return writer.toString(); } catch (IOException ioe) { // this should never ever happen while writing to a StringWriter ioe.printStackTrace(); return null; } } /** * From org.apache.commons.lang.StringEscapeUtils * * <p>Worker method for the {@link #escapeJavaScript(String)} method.</p> * * @param out write to receieve the escaped string * @param str String to escape values in, may be null * @param escapeSingleQuote escapes single quotes if <code>true</code> * @throws IOException if an IOException occurs */ private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote) throws IOException { if (out == null) { throw new IllegalArgumentException("The Writer must not be null"); } if (str == null) { return; } int sz; sz = str.length(); for (int i = 0; i < sz; i++) { char ch = str.charAt(i); // handle unicode if (ch > 0xfff) { out.write("\\u" + hex(ch)); } else if (ch > 0xff) { out.write("\\u0" + hex(ch)); } else if (ch > 0x7f) { out.write("\\u00" + hex(ch)); } else if (ch < 32) { switch (ch) { case '\b': out.write('\\'); out.write('b'); break; case '\n': out.write('\\'); out.write('n'); break; case '\t': out.write('\\'); out.write('t'); break; case '\f': out.write('\\'); out.write('f'); break; case '\r': out.write('\\'); out.write('r'); break; default : if (ch > 0xf) { out.write("\\u00" + hex(ch)); } else { out.write("\\u000" + hex(ch)); } break; } } else { switch (ch) { case '\'': if (escapeSingleQuote) { out.write('\\'); } out.write('\''); break; case '"': out.write('\\'); out.write('"'); break; case '\\': out.write('\\'); out.write('\\'); break; default : out.write(ch); break; } } } } /** * From org.apache.commons.lang.StringEscapeUtils * * <p>Returns an upper case hexadecimal <code>String</code> for the given * character.</p> * * @param ch The character to convert. * @return An upper case hexadecimal <code>String</code> */ private static String hex(char ch) { return Integer.toHexString(ch).toUpperCase(); } }