Home | History | Annotate | Download | only in printer
      1 /*
      2  * Copyright (C) 2007-2010 Jlio Vilmar Gesser.
      3  * Copyright (C) 2011, 2013-2016 The JavaParser Team.
      4  *
      5  * This file is part of JavaParser.
      6  *
      7  * JavaParser can be used either under the terms of
      8  * a) the GNU Lesser General Public License as published by
      9  *     the Free Software Foundation, either version 3 of the License, or
     10  *     (at your option) any later version.
     11  * b) the terms of the Apache License
     12  *
     13  * You should have received a copy of both licenses in LICENCE.LGPL and
     14  * LICENCE.APACHE. Please refer to those files for details.
     15  *
     16  * JavaParser is distributed in the hope that it will be useful,
     17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     19  * GNU Lesser General Public License for more details.
     20  */
     21 
     22 package com.github.javaparser.printer;
     23 
     24 import com.github.javaparser.Position;
     25 import com.github.javaparser.ast.*;
     26 import com.github.javaparser.ast.body.*;
     27 import com.github.javaparser.ast.comments.BlockComment;
     28 import com.github.javaparser.ast.comments.Comment;
     29 import com.github.javaparser.ast.comments.JavadocComment;
     30 import com.github.javaparser.ast.comments.LineComment;
     31 import com.github.javaparser.ast.expr.*;
     32 import com.github.javaparser.ast.modules.*;
     33 import com.github.javaparser.ast.nodeTypes.NodeWithTypeArguments;
     34 import com.github.javaparser.ast.nodeTypes.NodeWithVariables;
     35 import com.github.javaparser.ast.stmt.*;
     36 import com.github.javaparser.ast.type.*;
     37 import com.github.javaparser.ast.visitor.Visitable;
     38 import com.github.javaparser.ast.visitor.VoidVisitor;
     39 
     40 import java.util.*;
     41 import java.util.stream.Collectors;
     42 
     43 import static com.github.javaparser.ast.Node.Parsedness.UNPARSABLE;
     44 import static com.github.javaparser.utils.PositionUtils.sortByBeginPosition;
     45 import static com.github.javaparser.utils.Utils.isNullOrEmpty;
     46 import static com.github.javaparser.utils.Utils.normalizeEolInTextBlock;
     47 import static com.github.javaparser.utils.Utils.trimTrailingSpaces;
     48 
     49 /**
     50  * Outputs the AST as formatted Java source code.
     51  *
     52  * @author Julio Vilmar Gesser
     53  */
     54 public class PrettyPrintVisitor implements VoidVisitor<Void> {
     55     protected final PrettyPrinterConfiguration configuration;
     56     protected final SourcePrinter printer;
     57     private Deque<Position> methodChainPositions = new LinkedList<>();
     58 
     59     public PrettyPrintVisitor(PrettyPrinterConfiguration prettyPrinterConfiguration) {
     60         configuration = prettyPrinterConfiguration;
     61         printer = new SourcePrinter(configuration.getIndent(), configuration.getEndOfLineCharacter());
     62         pushMethodChainPosition(printer.getCursor()); // initialize a default position for methodChainPositions, it is expected by method #resetMethodChainPosition()
     63     }
     64 
     65     public String getSource() {
     66         return printer.getSource();
     67     }
     68 
     69     public void resetMethodChainPosition(Position position) {
     70         this.methodChainPositions.pop();
     71         this.methodChainPositions.push(position);
     72     }
     73 
     74     public void pushMethodChainPosition(Position position) {
     75         this.methodChainPositions.push(position);
     76     }
     77 
     78     public Position peekMethodChainPosition() {
     79         return this.methodChainPositions.peek();
     80     }
     81 
     82     public Position popMethodChainPosition() {
     83         return this.methodChainPositions.pop();
     84     }
     85 
     86     private void printModifiers(final EnumSet<Modifier> modifiers) {
     87         if (modifiers.size() > 0) {
     88             printer.print(modifiers.stream().map(Modifier::asString).collect(Collectors.joining(" ")) + " ");
     89         }
     90     }
     91 
     92     private void printMembers(final NodeList<BodyDeclaration<?>> members, final Void arg) {
     93         for (final BodyDeclaration<?> member : members) {
     94             printer.println();
     95             member.accept(this, arg);
     96             printer.println();
     97         }
     98     }
     99 
    100     private void printMemberAnnotations(final NodeList<AnnotationExpr> annotations, final Void arg) {
    101         if (annotations.isEmpty()) {
    102             return;
    103         }
    104         for (final AnnotationExpr a : annotations) {
    105             a.accept(this, arg);
    106             printer.println();
    107         }
    108     }
    109 
    110     private void printAnnotations(final NodeList<AnnotationExpr> annotations, boolean prefixWithASpace,
    111                                   final Void arg) {
    112         if (annotations.isEmpty()) {
    113             return;
    114         }
    115         if (prefixWithASpace) {
    116             printer.print(" ");
    117         }
    118         for (AnnotationExpr annotation : annotations) {
    119             annotation.accept(this, arg);
    120             printer.print(" ");
    121         }
    122     }
    123 
    124     private void printTypeArgs(final NodeWithTypeArguments<?> nodeWithTypeArguments, final Void arg) {
    125         NodeList<Type> typeArguments = nodeWithTypeArguments.getTypeArguments().orElse(null);
    126         if (!isNullOrEmpty(typeArguments)) {
    127             printer.print("<");
    128             for (final Iterator<Type> i = typeArguments.iterator(); i.hasNext(); ) {
    129                 final Type t = i.next();
    130                 t.accept(this, arg);
    131                 if (i.hasNext()) {
    132                     printer.print(", ");
    133                 }
    134             }
    135             printer.print(">");
    136         }
    137     }
    138 
    139     private void printTypeParameters(final NodeList<TypeParameter> args, final Void arg) {
    140         if (!isNullOrEmpty(args)) {
    141             printer.print("<");
    142             for (final Iterator<TypeParameter> i = args.iterator(); i.hasNext(); ) {
    143                 final TypeParameter t = i.next();
    144                 t.accept(this, arg);
    145                 if (i.hasNext()) {
    146                     printer.print(", ");
    147                 }
    148             }
    149             printer.print(">");
    150         }
    151     }
    152 
    153     private void printArguments(final NodeList<Expression> args, final Void arg) {
    154         printer.print("(");
    155         Position cursorRef = printer.getCursor();
    156         if (!isNullOrEmpty(args)) {
    157             for (final Iterator<Expression> i = args.iterator(); i.hasNext(); ) {
    158                 final Expression e = i.next();
    159                 e.accept(this, arg);
    160                 if (i.hasNext()) {
    161                     printer.print(",");
    162                     if (configuration.isColumnAlignParameters()) {
    163                         printer.wrapToColumn(cursorRef.column);
    164                     } else {
    165                         printer.print(" ");
    166                     }
    167                 }
    168             }
    169         }
    170         printer.print(")");
    171     }
    172 
    173     private void printPrePostFixOptionalList(final NodeList<? extends Visitable> args, final Void arg, String prefix, String separator, String postfix) {
    174         if (!args.isEmpty()) {
    175             printer.print(prefix);
    176             for (final Iterator<? extends Visitable> i = args.iterator(); i.hasNext(); ) {
    177                 final Visitable v = i.next();
    178                 v.accept(this, arg);
    179                 if (i.hasNext()) {
    180                     printer.print(separator);
    181                 }
    182             }
    183             printer.print(postfix);
    184         }
    185     }
    186 
    187     private void printPrePostFixRequiredList(final NodeList<? extends Visitable> args, final Void arg, String prefix, String separator, String postfix) {
    188         printer.print(prefix);
    189         if (!args.isEmpty()) {
    190             for (final Iterator<? extends Visitable> i = args.iterator(); i.hasNext(); ) {
    191                 final Visitable v = i.next();
    192                 v.accept(this, arg);
    193                 if (i.hasNext()) {
    194                     printer.print(separator);
    195                 }
    196             }
    197         }
    198         printer.print(postfix);
    199     }
    200 
    201     private void printComment(final Optional<Comment> comment, final Void arg) {
    202         comment.ifPresent(c -> c.accept(this, arg));
    203     }
    204 
    205     @Override
    206     public void visit(final CompilationUnit n, final Void arg) {
    207         printComment(n.getComment(), arg);
    208         if (n.getParsed() == UNPARSABLE) {
    209             printer.println("???");
    210             return;
    211         }
    212 
    213         if (n.getPackageDeclaration().isPresent()) {
    214             n.getPackageDeclaration().get().accept(this, arg);
    215         }
    216 
    217         n.getImports().accept(this, arg);
    218         if (!n.getImports().isEmpty()) {
    219             printer.println();
    220         }
    221 
    222         for (final Iterator<TypeDeclaration<?>> i = n.getTypes().iterator(); i.hasNext(); ) {
    223             i.next().accept(this, arg);
    224             printer.println();
    225             if (i.hasNext()) {
    226                 printer.println();
    227             }
    228         }
    229 
    230         n.getModule().ifPresent(m -> m.accept(this, arg));
    231 
    232         printOrphanCommentsEnding(n);
    233     }
    234 
    235     @Override
    236     public void visit(final PackageDeclaration n, final Void arg) {
    237         printComment(n.getComment(), arg);
    238         printAnnotations(n.getAnnotations(), false, arg);
    239         printer.print("package ");
    240         n.getName().accept(this, arg);
    241         printer.println(";");
    242         printer.println();
    243 
    244         printOrphanCommentsEnding(n);
    245     }
    246 
    247     @Override
    248     public void visit(final NameExpr n, final Void arg) {
    249         printComment(n.getComment(), arg);
    250         n.getName().accept(this, arg);
    251 
    252         printOrphanCommentsEnding(n);
    253     }
    254 
    255     @Override
    256     public void visit(final Name n, final Void arg) {
    257         printComment(n.getComment(), arg);
    258         if (n.getQualifier().isPresent()) {
    259             n.getQualifier().get().accept(this, arg);
    260             printer.print(".");
    261         }
    262         printAnnotations(n.getAnnotations(), false, arg);
    263         printer.print(n.getIdentifier());
    264 
    265         printOrphanCommentsEnding(n);
    266     }
    267 
    268     @Override
    269     public void visit(SimpleName n, Void arg) {
    270         printer.print(n.getIdentifier());
    271     }
    272 
    273     @Override
    274     public void visit(final ClassOrInterfaceDeclaration n, final Void arg) {
    275         printComment(n.getComment(), arg);
    276         printMemberAnnotations(n.getAnnotations(), arg);
    277         printModifiers(n.getModifiers());
    278 
    279         if (n.isInterface()) {
    280             printer.print("interface ");
    281         } else {
    282             printer.print("class ");
    283         }
    284 
    285         n.getName().accept(this, arg);
    286 
    287         printTypeParameters(n.getTypeParameters(), arg);
    288 
    289         if (!n.getExtendedTypes().isEmpty()) {
    290             printer.print(" extends ");
    291             for (final Iterator<ClassOrInterfaceType> i = n.getExtendedTypes().iterator(); i.hasNext(); ) {
    292                 final ClassOrInterfaceType c = i.next();
    293                 c.accept(this, arg);
    294                 if (i.hasNext()) {
    295                     printer.print(", ");
    296                 }
    297             }
    298         }
    299 
    300         if (!n.getImplementedTypes().isEmpty()) {
    301             printer.print(" implements ");
    302             for (final Iterator<ClassOrInterfaceType> i = n.getImplementedTypes().iterator(); i.hasNext(); ) {
    303                 final ClassOrInterfaceType c = i.next();
    304                 c.accept(this, arg);
    305                 if (i.hasNext()) {
    306                     printer.print(", ");
    307                 }
    308             }
    309         }
    310 
    311         printer.println(" {");
    312         printer.indent();
    313         if (!isNullOrEmpty(n.getMembers())) {
    314             printMembers(n.getMembers(), arg);
    315         }
    316 
    317         printOrphanCommentsEnding(n);
    318 
    319         printer.unindent();
    320         printer.print("}");
    321     }
    322 
    323     @Override
    324     public void visit(final JavadocComment n, final Void arg) {
    325         if (configuration.isPrintComments() && configuration.isPrintJavadoc()) {
    326             printer.println("/**");
    327             final String commentContent = normalizeEolInTextBlock(n.getContent(), configuration.getEndOfLineCharacter());
    328             String[] lines = commentContent.split("\\R");
    329             boolean skippingLeadingEmptyLines = true;
    330             boolean prependEmptyLine = false;
    331             for (String line : lines) {
    332                 final String trimmedLine = line.trim();
    333                 if (trimmedLine.startsWith("*")) {
    334                     line = trimmedLine.substring(1);
    335                 }
    336                 line = trimTrailingSpaces(line);
    337                 if (line.isEmpty()) {
    338                     if (!skippingLeadingEmptyLines) {
    339                         prependEmptyLine = true;
    340                     }
    341                 } else {
    342                     skippingLeadingEmptyLines = false;
    343                     if (prependEmptyLine) {
    344                         printer.println(" *");
    345                         prependEmptyLine = false;
    346                     }
    347                     printer.println(" *" + line);
    348                 }
    349             }
    350             printer.println(" */");
    351         }
    352     }
    353 
    354     @Override
    355     public void visit(final ClassOrInterfaceType n, final Void arg) {
    356         printComment(n.getComment(), arg);
    357         if (n.getScope().isPresent()) {
    358             n.getScope().get().accept(this, arg);
    359             printer.print(".");
    360         }
    361         for (AnnotationExpr ae : n.getAnnotations()) {
    362             ae.accept(this, arg);
    363             printer.print(" ");
    364         }
    365 
    366         n.getName().accept(this, arg);
    367 
    368         if (n.isUsingDiamondOperator()) {
    369             printer.print("<>");
    370         } else {
    371             printTypeArgs(n, arg);
    372         }
    373     }
    374 
    375     @Override
    376     public void visit(final TypeParameter n, final Void arg) {
    377         printComment(n.getComment(), arg);
    378         for (AnnotationExpr ann : n.getAnnotations()) {
    379             ann.accept(this, arg);
    380             printer.print(" ");
    381         }
    382         n.getName().accept(this, arg);
    383         if (!isNullOrEmpty(n.getTypeBound())) {
    384             printer.print(" extends ");
    385             for (final Iterator<ClassOrInterfaceType> i = n.getTypeBound().iterator(); i.hasNext(); ) {
    386                 final ClassOrInterfaceType c = i.next();
    387                 c.accept(this, arg);
    388                 if (i.hasNext()) {
    389                     printer.print(" & ");
    390                 }
    391             }
    392         }
    393     }
    394 
    395     @Override
    396     public void visit(final PrimitiveType n, final Void arg) {
    397         printComment(n.getComment(), arg);
    398         printAnnotations(n.getAnnotations(), true, arg);
    399         printer.print(n.getType().asString());
    400     }
    401 
    402     @Override
    403     public void visit(final ArrayType n, final Void arg) {
    404         final List<ArrayType> arrayTypeBuffer = new LinkedList<>();
    405         Type type = n;
    406         while (type instanceof ArrayType) {
    407             final ArrayType arrayType = (ArrayType) type;
    408             arrayTypeBuffer.add(arrayType);
    409             type = arrayType.getComponentType();
    410         }
    411 
    412         type.accept(this, arg);
    413         for (ArrayType arrayType : arrayTypeBuffer) {
    414             printAnnotations(arrayType.getAnnotations(), true, arg);
    415             printer.print("[]");
    416         }
    417     }
    418 
    419     @Override
    420     public void visit(final ArrayCreationLevel n, final Void arg) {
    421         printAnnotations(n.getAnnotations(), true, arg);
    422         printer.print("[");
    423         if (n.getDimension().isPresent()) {
    424             n.getDimension().get().accept(this, arg);
    425         }
    426         printer.print("]");
    427     }
    428 
    429     @Override
    430     public void visit(final IntersectionType n, final Void arg) {
    431         printComment(n.getComment(), arg);
    432         printAnnotations(n.getAnnotations(), false, arg);
    433         boolean isFirst = true;
    434         for (ReferenceType element : n.getElements()) {
    435             if (isFirst) {
    436                 isFirst = false;
    437             } else {
    438                 printer.print(" & ");
    439             }
    440             element.accept(this, arg);
    441         }
    442     }
    443 
    444     @Override
    445     public void visit(final UnionType n, final Void arg) {
    446         printComment(n.getComment(), arg);
    447         printAnnotations(n.getAnnotations(), true, arg);
    448         boolean isFirst = true;
    449         for (ReferenceType element : n.getElements()) {
    450             if (isFirst) {
    451                 isFirst = false;
    452             } else {
    453                 printer.print(" | ");
    454             }
    455             element.accept(this, arg);
    456         }
    457     }
    458 
    459     @Override
    460     public void visit(final WildcardType n, final Void arg) {
    461         printComment(n.getComment(), arg);
    462         printAnnotations(n.getAnnotations(), false, arg);
    463         printer.print("?");
    464         if (n.getExtendedType().isPresent()) {
    465             printer.print(" extends ");
    466             n.getExtendedType().get().accept(this, arg);
    467         }
    468         if (n.getSuperType().isPresent()) {
    469             printer.print(" super ");
    470             n.getSuperType().get().accept(this, arg);
    471         }
    472     }
    473 
    474     @Override
    475     public void visit(final UnknownType n, final Void arg) {
    476         // Nothing to print
    477     }
    478 
    479     @Override
    480     public void visit(final FieldDeclaration n, final Void arg) {
    481         printOrphanCommentsBeforeThisChildNode(n);
    482 
    483         printComment(n.getComment(), arg);
    484         printMemberAnnotations(n.getAnnotations(), arg);
    485         printModifiers(n.getModifiers());
    486         if (!n.getVariables().isEmpty()) {
    487             Optional<Type> maximumCommonType = n.getMaximumCommonType();
    488             maximumCommonType.ifPresent(t -> t.accept(this, arg));
    489             if (!maximumCommonType.isPresent()) {
    490                 printer.print("???");
    491             }
    492         }
    493 
    494         printer.print(" ");
    495         for (final Iterator<VariableDeclarator> i = n.getVariables().iterator(); i.hasNext(); ) {
    496             final VariableDeclarator var = i.next();
    497             var.accept(this, arg);
    498             if (i.hasNext()) {
    499                 printer.print(", ");
    500             }
    501         }
    502 
    503         printer.print(";");
    504     }
    505 
    506     @Override
    507     public void visit(final VariableDeclarator n, final Void arg) {
    508         printComment(n.getComment(), arg);
    509         n.getName().accept(this, arg);
    510 
    511         n.getAncestorOfType(NodeWithVariables.class).ifPresent(ancestor -> {
    512             ((NodeWithVariables<?>) ancestor).getMaximumCommonType().ifPresent(commonType -> {
    513 
    514                 final Type type = n.getType();
    515 
    516                 ArrayType arrayType = null;
    517 
    518                 for (int i = commonType.getArrayLevel(); i < type.getArrayLevel(); i++) {
    519                     if (arrayType == null) {
    520                         arrayType = (ArrayType) type;
    521                     } else {
    522                         arrayType = (ArrayType) arrayType.getComponentType();
    523                     }
    524                     printAnnotations(arrayType.getAnnotations(), true, arg);
    525                     printer.print("[]");
    526                 }
    527             });
    528         });
    529 
    530         if (n.getInitializer().isPresent()) {
    531             printer.print(" = ");
    532             n.getInitializer().get().accept(this, arg);
    533         }
    534     }
    535 
    536     @Override
    537     public void visit(final ArrayInitializerExpr n, final Void arg) {
    538         printComment(n.getComment(), arg);
    539         printer.print("{");
    540         if (!isNullOrEmpty(n.getValues())) {
    541             printer.print(" ");
    542             for (final Iterator<Expression> i = n.getValues().iterator(); i.hasNext(); ) {
    543                 final Expression expr = i.next();
    544                 expr.accept(this, arg);
    545                 if (i.hasNext()) {
    546                     printer.print(", ");
    547                 }
    548             }
    549             printer.print(" ");
    550         }
    551         printOrphanCommentsEnding(n);
    552         printer.print("}");
    553     }
    554 
    555     @Override
    556     public void visit(final VoidType n, final Void arg) {
    557         printComment(n.getComment(), arg);
    558         printAnnotations(n.getAnnotations(), false, arg);
    559         printer.print("void");
    560     }
    561 
    562     @Override
    563     public void visit(final VarType n, final Void arg) {
    564         printComment(n.getComment(), arg);
    565         printAnnotations(n.getAnnotations(), false, arg);
    566         printer.print("var");
    567     }
    568 
    569     @Override
    570     public void visit(final ArrayAccessExpr n, final Void arg) {
    571         printComment(n.getComment(), arg);
    572         n.getName().accept(this, arg);
    573         printer.print("[");
    574         n.getIndex().accept(this, arg);
    575         printer.print("]");
    576     }
    577 
    578     @Override
    579     public void visit(final ArrayCreationExpr n, final Void arg) {
    580         printComment(n.getComment(), arg);
    581         printer.print("new ");
    582         n.getElementType().accept(this, arg);
    583         for (ArrayCreationLevel level : n.getLevels()) {
    584             level.accept(this, arg);
    585         }
    586         if (n.getInitializer().isPresent()) {
    587             printer.print(" ");
    588             n.getInitializer().get().accept(this, arg);
    589         }
    590     }
    591 
    592     @Override
    593     public void visit(final AssignExpr n, final Void arg) {
    594         printComment(n.getComment(), arg);
    595         n.getTarget().accept(this, arg);
    596         printer.print(" ");
    597         printer.print(n.getOperator().asString());
    598         printer.print(" ");
    599         n.getValue().accept(this, arg);
    600     }
    601 
    602     @Override
    603     public void visit(final BinaryExpr n, final Void arg) {
    604         printComment(n.getComment(), arg);
    605         n.getLeft().accept(this, arg);
    606         printer.print(" ");
    607         printer.print(n.getOperator().asString());
    608         printer.print(" ");
    609         n.getRight().accept(this, arg);
    610     }
    611 
    612     @Override
    613     public void visit(final CastExpr n, final Void arg) {
    614         printComment(n.getComment(), arg);
    615         printer.print("(");
    616         n.getType().accept(this, arg);
    617         printer.print(") ");
    618         n.getExpression().accept(this, arg);
    619     }
    620 
    621     @Override
    622     public void visit(final ClassExpr n, final Void arg) {
    623         printComment(n.getComment(), arg);
    624         n.getType().accept(this, arg);
    625         printer.print(".class");
    626     }
    627 
    628     @Override
    629     public void visit(final ConditionalExpr n, final Void arg) {
    630         printComment(n.getComment(), arg);
    631         n.getCondition().accept(this, arg);
    632         printer.print(" ? ");
    633         n.getThenExpr().accept(this, arg);
    634         printer.print(" : ");
    635         n.getElseExpr().accept(this, arg);
    636     }
    637 
    638     @Override
    639     public void visit(final EnclosedExpr n, final Void arg) {
    640         printComment(n.getComment(), arg);
    641         printer.print("(");
    642         n.getInner().accept(this, arg);
    643         printer.print(")");
    644     }
    645 
    646     @Override
    647     public void visit(final FieldAccessExpr n, final Void arg) {
    648         printComment(n.getComment(), arg);
    649         n.getScope().accept(this, arg);
    650         printer.print(".");
    651         n.getName().accept(this, arg);
    652     }
    653 
    654     @Override
    655     public void visit(final InstanceOfExpr n, final Void arg) {
    656         printComment(n.getComment(), arg);
    657         n.getExpression().accept(this, arg);
    658         printer.print(" instanceof ");
    659         n.getType().accept(this, arg);
    660     }
    661 
    662     @Override
    663     public void visit(final CharLiteralExpr n, final Void arg) {
    664         printComment(n.getComment(), arg);
    665         printer.print("'");
    666         printer.print(n.getValue());
    667         printer.print("'");
    668     }
    669 
    670     @Override
    671     public void visit(final DoubleLiteralExpr n, final Void arg) {
    672         printComment(n.getComment(), arg);
    673         printer.print(n.getValue());
    674     }
    675 
    676     @Override
    677     public void visit(final IntegerLiteralExpr n, final Void arg) {
    678         printComment(n.getComment(), arg);
    679         printer.print(n.getValue());
    680     }
    681 
    682     @Override
    683     public void visit(final LongLiteralExpr n, final Void arg) {
    684         printComment(n.getComment(), arg);
    685         printer.print(n.getValue());
    686     }
    687 
    688     @Override
    689     public void visit(final StringLiteralExpr n, final Void arg) {
    690         printComment(n.getComment(), arg);
    691         printer.print("\"");
    692         printer.print(n.getValue());
    693         printer.print("\"");
    694     }
    695 
    696     @Override
    697     public void visit(final BooleanLiteralExpr n, final Void arg) {
    698         printComment(n.getComment(), arg);
    699         printer.print(String.valueOf(n.getValue()));
    700     }
    701 
    702     @Override
    703     public void visit(final NullLiteralExpr n, final Void arg) {
    704         printComment(n.getComment(), arg);
    705         printer.print("null");
    706     }
    707 
    708     @Override
    709     public void visit(final ThisExpr n, final Void arg) {
    710         printComment(n.getComment(), arg);
    711         if (n.getClassExpr().isPresent()) {
    712             n.getClassExpr().get().accept(this, arg);
    713             printer.print(".");
    714         }
    715         printer.print("this");
    716     }
    717 
    718     @Override
    719     public void visit(final SuperExpr n, final Void arg) {
    720         printComment(n.getComment(), arg);
    721         if (n.getClassExpr().isPresent()) {
    722             n.getClassExpr().get().accept(this, arg);
    723             printer.print(".");
    724         }
    725         printer.print("super");
    726     }
    727 
    728     @Override
    729     public void visit(final MethodCallExpr n, final Void arg) {
    730         printComment(n.getComment(), arg);
    731         if (n.getScope().isPresent()) {
    732             n.getScope().get().accept(this, arg);
    733             if (configuration.isColumnAlignFirstMethodChain()) {
    734                 if (!(n.getScope().get() instanceof MethodCallExpr) || (!((MethodCallExpr) n.getScope().get()).getScope().isPresent())) {
    735                     resetMethodChainPosition(printer.getCursor());
    736                 } else {
    737                     printer.wrapToColumn(peekMethodChainPosition().column);
    738                 }
    739             }
    740             printer.print(".");
    741         }
    742         printTypeArgs(n, arg);
    743         n.getName().accept(this, arg);
    744         pushMethodChainPosition(printer.getCursor());
    745         printArguments(n.getArguments(), arg);
    746         popMethodChainPosition();
    747     }
    748 
    749     @Override
    750     public void visit(final ObjectCreationExpr n, final Void arg) {
    751         printComment(n.getComment(), arg);
    752         if (n.getScope().isPresent()) {
    753             n.getScope().get().accept(this, arg);
    754             printer.print(".");
    755         }
    756 
    757         printer.print("new ");
    758 
    759         printTypeArgs(n, arg);
    760         if (!isNullOrEmpty(n.getTypeArguments().orElse(null))) {
    761             printer.print(" ");
    762         }
    763 
    764         n.getType().accept(this, arg);
    765 
    766         printArguments(n.getArguments(), arg);
    767 
    768         if (n.getAnonymousClassBody().isPresent()) {
    769             printer.println(" {");
    770             printer.indent();
    771             printMembers(n.getAnonymousClassBody().get(), arg);
    772             printer.unindent();
    773             printer.print("}");
    774         }
    775     }
    776 
    777     @Override
    778     public void visit(final UnaryExpr n, final Void arg) {
    779         printComment(n.getComment(), arg);
    780         if (n.getOperator().isPrefix()) {
    781             printer.print(n.getOperator().asString());
    782         }
    783 
    784         n.getExpression().accept(this, arg);
    785 
    786         if (n.getOperator().isPostfix()) {
    787             printer.print(n.getOperator().asString());
    788         }
    789     }
    790 
    791     @Override
    792     public void visit(final ConstructorDeclaration n, final Void arg) {
    793         printComment(n.getComment(), arg);
    794         printMemberAnnotations(n.getAnnotations(), arg);
    795         printModifiers(n.getModifiers());
    796 
    797         printTypeParameters(n.getTypeParameters(), arg);
    798         if (n.isGeneric()) {
    799             printer.print(" ");
    800         }
    801         n.getName().accept(this, arg);
    802 
    803         printer.print("(");
    804         if (!n.getParameters().isEmpty()) {
    805             for (final Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext(); ) {
    806                 final Parameter p = i.next();
    807                 p.accept(this, arg);
    808                 if (i.hasNext()) {
    809                     printer.print(", ");
    810                 }
    811             }
    812         }
    813         printer.print(")");
    814 
    815         if (!isNullOrEmpty(n.getThrownExceptions())) {
    816             printer.print(" throws ");
    817             for (final Iterator<ReferenceType> i = n.getThrownExceptions().iterator(); i.hasNext(); ) {
    818                 final ReferenceType name = i.next();
    819                 name.accept(this, arg);
    820                 if (i.hasNext()) {
    821                     printer.print(", ");
    822                 }
    823             }
    824         }
    825         printer.print(" ");
    826         n.getBody().accept(this, arg);
    827     }
    828 
    829     @Override
    830     public void visit(final MethodDeclaration n, final Void arg) {
    831         printOrphanCommentsBeforeThisChildNode(n);
    832 
    833         printComment(n.getComment(), arg);
    834         printMemberAnnotations(n.getAnnotations(), arg);
    835         printModifiers(n.getModifiers());
    836         printTypeParameters(n.getTypeParameters(), arg);
    837         if (!isNullOrEmpty(n.getTypeParameters())) {
    838             printer.print(" ");
    839         }
    840 
    841         n.getType().accept(this, arg);
    842         printer.print(" ");
    843         n.getName().accept(this, arg);
    844 
    845         printer.print("(");
    846         n.getReceiverParameter().ifPresent(rp -> {
    847             rp.accept(this, arg);
    848             printer.print(", ");
    849         });
    850         if (!isNullOrEmpty(n.getParameters())) {
    851             for (final Iterator<Parameter> i = n.getParameters().iterator(); i.hasNext(); ) {
    852                 final Parameter p = i.next();
    853                 p.accept(this, arg);
    854                 if (i.hasNext()) {
    855                     printer.print(", ");
    856                 }
    857             }
    858         }
    859         printer.print(")");
    860 
    861         if (!isNullOrEmpty(n.getThrownExceptions())) {
    862             printer.print(" throws ");
    863             for (final Iterator<ReferenceType> i = n.getThrownExceptions().iterator(); i.hasNext(); ) {
    864                 final ReferenceType name = i.next();
    865                 name.accept(this, arg);
    866                 if (i.hasNext()) {
    867                     printer.print(", ");
    868                 }
    869             }
    870         }
    871         if (!n.getBody().isPresent()) {
    872             printer.print(";");
    873         } else {
    874             printer.print(" ");
    875             n.getBody().get().accept(this, arg);
    876         }
    877     }
    878 
    879     @Override
    880     public void visit(final Parameter n, final Void arg) {
    881         printComment(n.getComment(), arg);
    882         printAnnotations(n.getAnnotations(), false, arg);
    883         printModifiers(n.getModifiers());
    884         n.getType().accept(this, arg);
    885         if (n.isVarArgs()) {
    886             printAnnotations(n.getVarArgsAnnotations(), false, arg);
    887             printer.print("...");
    888         }
    889         if (!(n.getType() instanceof UnknownType)) {
    890             printer.print(" ");
    891         }
    892         n.getName().accept(this, arg);
    893     }
    894 
    895     @Override
    896     public void visit(final ReceiverParameter n, final Void arg) {
    897         printComment(n.getComment(), arg);
    898         printAnnotations(n.getAnnotations(), false, arg);
    899         n.getType().accept(this, arg);
    900         printer.print(" ");
    901         n.getName().accept(this, arg);
    902     }
    903 
    904     @Override
    905     public void visit(final ExplicitConstructorInvocationStmt n, final Void arg) {
    906         printComment(n.getComment(), arg);
    907         if (n.isThis()) {
    908             printTypeArgs(n, arg);
    909             printer.print("this");
    910         } else {
    911             if (n.getExpression().isPresent()) {
    912                 n.getExpression().get().accept(this, arg);
    913                 printer.print(".");
    914             }
    915             printTypeArgs(n, arg);
    916             printer.print("super");
    917         }
    918         printArguments(n.getArguments(), arg);
    919         printer.print(";");
    920     }
    921 
    922     @Override
    923     public void visit(final VariableDeclarationExpr n, final Void arg) {
    924         printComment(n.getComment(), arg);
    925         printAnnotations(n.getAnnotations(), false, arg);
    926         printModifiers(n.getModifiers());
    927 
    928         if (!n.getVariables().isEmpty()) {
    929             n.getMaximumCommonType().ifPresent(t -> t.accept(this, arg));
    930         }
    931         printer.print(" ");
    932 
    933         for (final Iterator<VariableDeclarator> i = n.getVariables().iterator(); i.hasNext(); ) {
    934             final VariableDeclarator v = i.next();
    935             v.accept(this, arg);
    936             if (i.hasNext()) {
    937                 printer.print(", ");
    938             }
    939         }
    940     }
    941 
    942     @Override
    943     public void visit(final LocalClassDeclarationStmt n, final Void arg) {
    944         printComment(n.getComment(), arg);
    945         n.getClassDeclaration().accept(this, arg);
    946     }
    947 
    948     @Override
    949     public void visit(final AssertStmt n, final Void arg) {
    950         printComment(n.getComment(), arg);
    951         printer.print("assert ");
    952         n.getCheck().accept(this, arg);
    953         if (n.getMessage().isPresent()) {
    954             printer.print(" : ");
    955             n.getMessage().get().accept(this, arg);
    956         }
    957         printer.print(";");
    958     }
    959 
    960     @Override
    961     public void visit(final BlockStmt n, final Void arg) {
    962         printOrphanCommentsBeforeThisChildNode(n);
    963         printComment(n.getComment(), arg);
    964         printer.println("{");
    965         if (n.getStatements() != null) {
    966             printer.indent();
    967             for (final Statement s : n.getStatements()) {
    968                 s.accept(this, arg);
    969                 printer.println();
    970             }
    971             printer.unindent();
    972         }
    973         printOrphanCommentsEnding(n);
    974         printer.print("}");
    975     }
    976 
    977     @Override
    978     public void visit(final LabeledStmt n, final Void arg) {
    979         printComment(n.getComment(), arg);
    980         n.getLabel().accept(this, arg);
    981         printer.print(": ");
    982         n.getStatement().accept(this, arg);
    983     }
    984 
    985     @Override
    986     public void visit(final EmptyStmt n, final Void arg) {
    987         printComment(n.getComment(), arg);
    988         printer.print(";");
    989     }
    990 
    991     @Override
    992     public void visit(final ExpressionStmt n, final Void arg) {
    993         printOrphanCommentsBeforeThisChildNode(n);
    994         printComment(n.getComment(), arg);
    995         n.getExpression().accept(this, arg);
    996         printer.print(";");
    997     }
    998 
    999     @Override
   1000     public void visit(final SwitchStmt n, final Void arg) {
   1001         printComment(n.getComment(), arg);
   1002         printer.print("switch(");
   1003         n.getSelector().accept(this, arg);
   1004         printer.println(") {");
   1005         if (n.getEntries() != null) {
   1006             printer.indent();
   1007             for (final SwitchEntryStmt e : n.getEntries()) {
   1008                 e.accept(this, arg);
   1009             }
   1010             printer.unindent();
   1011         }
   1012         printer.print("}");
   1013     }
   1014 
   1015     @Override
   1016     public void visit(final SwitchEntryStmt n, final Void arg) {
   1017         printComment(n.getComment(), arg);
   1018         if (n.getLabel().isPresent()) {
   1019             printer.print("case ");
   1020             n.getLabel().get().accept(this, arg);
   1021             printer.print(":");
   1022         } else {
   1023             printer.print("default:");
   1024         }
   1025         printer.println();
   1026         printer.indent();
   1027         if (n.getStatements() != null) {
   1028             for (final Statement s : n.getStatements()) {
   1029                 s.accept(this, arg);
   1030                 printer.println();
   1031             }
   1032         }
   1033         printer.unindent();
   1034     }
   1035 
   1036     @Override
   1037     public void visit(final BreakStmt n, final Void arg) {
   1038         printComment(n.getComment(), arg);
   1039         printer.print("break");
   1040         n.getLabel().ifPresent(l -> printer.print(" ").print(l.getIdentifier()));
   1041         printer.print(";");
   1042     }
   1043 
   1044     @Override
   1045     public void visit(final ReturnStmt n, final Void arg) {
   1046         printComment(n.getComment(), arg);
   1047         printer.print("return");
   1048         if (n.getExpression().isPresent()) {
   1049             printer.print(" ");
   1050             n.getExpression().get().accept(this, arg);
   1051         }
   1052         printer.print(";");
   1053     }
   1054 
   1055     @Override
   1056     public void visit(final EnumDeclaration n, final Void arg) {
   1057         printComment(n.getComment(), arg);
   1058         printMemberAnnotations(n.getAnnotations(), arg);
   1059         printModifiers(n.getModifiers());
   1060 
   1061         printer.print("enum ");
   1062         n.getName().accept(this, arg);
   1063 
   1064         if (!n.getImplementedTypes().isEmpty()) {
   1065             printer.print(" implements ");
   1066             for (final Iterator<ClassOrInterfaceType> i = n.getImplementedTypes().iterator(); i.hasNext(); ) {
   1067                 final ClassOrInterfaceType c = i.next();
   1068                 c.accept(this, arg);
   1069                 if (i.hasNext()) {
   1070                     printer.print(", ");
   1071                 }
   1072             }
   1073         }
   1074 
   1075         printer.println(" {");
   1076         printer.indent();
   1077         if (n.getEntries().isNonEmpty()) {
   1078             final boolean alignVertically =
   1079                     // Either we hit the constant amount limit in the configurations, or...
   1080                     n.getEntries().size() > configuration.getMaxEnumConstantsToAlignHorizontally() ||
   1081                             // any of the constants has a comment.
   1082                             n.getEntries().stream().anyMatch(e -> e.getComment().isPresent());
   1083             printer.println();
   1084             for (final Iterator<EnumConstantDeclaration> i = n.getEntries().iterator(); i.hasNext(); ) {
   1085                 final EnumConstantDeclaration e = i.next();
   1086                 e.accept(this, arg);
   1087                 if (i.hasNext()) {
   1088                     if (alignVertically) {
   1089                         printer.println(",");
   1090                     } else {
   1091                         printer.print(", ");
   1092                     }
   1093                 }
   1094             }
   1095         }
   1096         if (!n.getMembers().isEmpty()) {
   1097             printer.println(";");
   1098             printMembers(n.getMembers(), arg);
   1099         } else {
   1100             if (!n.getEntries().isEmpty()) {
   1101                 printer.println();
   1102             }
   1103         }
   1104         printer.unindent();
   1105         printer.print("}");
   1106     }
   1107 
   1108     @Override
   1109     public void visit(final EnumConstantDeclaration n, final Void arg) {
   1110         printComment(n.getComment(), arg);
   1111         printMemberAnnotations(n.getAnnotations(), arg);
   1112         n.getName().accept(this, arg);
   1113 
   1114         if (!n.getArguments().isEmpty()) {
   1115             printArguments(n.getArguments(), arg);
   1116         }
   1117 
   1118         if (!n.getClassBody().isEmpty()) {
   1119             printer.println(" {");
   1120             printer.indent();
   1121             printMembers(n.getClassBody(), arg);
   1122             printer.unindent();
   1123             printer.println("}");
   1124         }
   1125     }
   1126 
   1127     @Override
   1128     public void visit(final InitializerDeclaration n, final Void arg) {
   1129         printComment(n.getComment(), arg);
   1130         if (n.isStatic()) {
   1131             printer.print("static ");
   1132         }
   1133         n.getBody().accept(this, arg);
   1134     }
   1135 
   1136     @Override
   1137     public void visit(final IfStmt n, final Void arg) {
   1138         printComment(n.getComment(), arg);
   1139         printer.print("if (");
   1140         n.getCondition().accept(this, arg);
   1141         final boolean thenBlock = n.getThenStmt() instanceof BlockStmt;
   1142         if (thenBlock) // block statement should start on the same line
   1143             printer.print(") ");
   1144         else {
   1145             printer.println(")");
   1146             printer.indent();
   1147         }
   1148         n.getThenStmt().accept(this, arg);
   1149         if (!thenBlock)
   1150             printer.unindent();
   1151         if (n.getElseStmt().isPresent()) {
   1152             if (thenBlock)
   1153                 printer.print(" ");
   1154             else
   1155                 printer.println();
   1156             final boolean elseIf = n.getElseStmt().orElse(null) instanceof IfStmt;
   1157             final boolean elseBlock = n.getElseStmt().orElse(null) instanceof BlockStmt;
   1158             if (elseIf || elseBlock) // put chained if and start of block statement on a same level
   1159                 printer.print("else ");
   1160             else {
   1161                 printer.println("else");
   1162                 printer.indent();
   1163             }
   1164             if (n.getElseStmt().isPresent())
   1165                 n.getElseStmt().get().accept(this, arg);
   1166             if (!(elseIf || elseBlock))
   1167                 printer.unindent();
   1168         }
   1169     }
   1170 
   1171     @Override
   1172     public void visit(final WhileStmt n, final Void arg) {
   1173         printComment(n.getComment(), arg);
   1174         printer.print("while (");
   1175         n.getCondition().accept(this, arg);
   1176         printer.print(") ");
   1177         n.getBody().accept(this, arg);
   1178     }
   1179 
   1180     @Override
   1181     public void visit(final ContinueStmt n, final Void arg) {
   1182         printComment(n.getComment(), arg);
   1183         printer.print("continue");
   1184         n.getLabel().ifPresent(l -> printer.print(" ").print(l.getIdentifier()));
   1185         printer.print(";");
   1186     }
   1187 
   1188     @Override
   1189     public void visit(final DoStmt n, final Void arg) {
   1190         printComment(n.getComment(), arg);
   1191         printer.print("do ");
   1192         n.getBody().accept(this, arg);
   1193         printer.print(" while (");
   1194         n.getCondition().accept(this, arg);
   1195         printer.print(");");
   1196     }
   1197 
   1198     @Override
   1199     public void visit(final ForeachStmt n, final Void arg) {
   1200         printComment(n.getComment(), arg);
   1201         printer.print("for (");
   1202         n.getVariable().accept(this, arg);
   1203         printer.print(" : ");
   1204         n.getIterable().accept(this, arg);
   1205         printer.print(") ");
   1206         n.getBody().accept(this, arg);
   1207     }
   1208 
   1209     @Override
   1210     public void visit(final ForStmt n, final Void arg) {
   1211         printComment(n.getComment(), arg);
   1212         printer.print("for (");
   1213         if (n.getInitialization() != null) {
   1214             for (final Iterator<Expression> i = n.getInitialization().iterator(); i.hasNext(); ) {
   1215                 final Expression e = i.next();
   1216                 e.accept(this, arg);
   1217                 if (i.hasNext()) {
   1218                     printer.print(", ");
   1219                 }
   1220             }
   1221         }
   1222         printer.print("; ");
   1223         if (n.getCompare().isPresent()) {
   1224             n.getCompare().get().accept(this, arg);
   1225         }
   1226         printer.print("; ");
   1227         if (n.getUpdate() != null) {
   1228             for (final Iterator<Expression> i = n.getUpdate().iterator(); i.hasNext(); ) {
   1229                 final Expression e = i.next();
   1230                 e.accept(this, arg);
   1231                 if (i.hasNext()) {
   1232                     printer.print(", ");
   1233                 }
   1234             }
   1235         }
   1236         printer.print(") ");
   1237         n.getBody().accept(this, arg);
   1238     }
   1239 
   1240     @Override
   1241     public void visit(final ThrowStmt n, final Void arg) {
   1242         printComment(n.getComment(), arg);
   1243         printer.print("throw ");
   1244         n.getExpression().accept(this, arg);
   1245         printer.print(";");
   1246     }
   1247 
   1248     @Override
   1249     public void visit(final SynchronizedStmt n, final Void arg) {
   1250         printComment(n.getComment(), arg);
   1251         printer.print("synchronized (");
   1252         n.getExpression().accept(this, arg);
   1253         printer.print(") ");
   1254         n.getBody().accept(this, arg);
   1255     }
   1256 
   1257     @Override
   1258     public void visit(final TryStmt n, final Void arg) {
   1259         printComment(n.getComment(), arg);
   1260         printer.print("try ");
   1261         if (!n.getResources().isEmpty()) {
   1262             printer.print("(");
   1263             Iterator<Expression> resources = n.getResources().iterator();
   1264             boolean first = true;
   1265             while (resources.hasNext()) {
   1266                 resources.next().accept(this, arg);
   1267                 if (resources.hasNext()) {
   1268                     printer.print(";");
   1269                     printer.println();
   1270                     if (first) {
   1271                         printer.indent();
   1272                     }
   1273                 }
   1274                 first = false;
   1275             }
   1276             if (n.getResources().size() > 1) {
   1277                 printer.unindent();
   1278             }
   1279             printer.print(") ");
   1280         }
   1281         n.getTryBlock().accept(this, arg);
   1282         for (final CatchClause c : n.getCatchClauses()) {
   1283             c.accept(this, arg);
   1284         }
   1285         if (n.getFinallyBlock().isPresent()) {
   1286             printer.print(" finally ");
   1287             n.getFinallyBlock().get().accept(this, arg);
   1288         }
   1289     }
   1290 
   1291     @Override
   1292     public void visit(final CatchClause n, final Void arg) {
   1293         printComment(n.getComment(), arg);
   1294         printer.print(" catch (");
   1295         n.getParameter().accept(this, arg);
   1296         printer.print(") ");
   1297         n.getBody().accept(this, arg);
   1298     }
   1299 
   1300     @Override
   1301     public void visit(final AnnotationDeclaration n, final Void arg) {
   1302         printComment(n.getComment(), arg);
   1303         printMemberAnnotations(n.getAnnotations(), arg);
   1304         printModifiers(n.getModifiers());
   1305 
   1306         printer.print("@interface ");
   1307         n.getName().accept(this, arg);
   1308         printer.println(" {");
   1309         printer.indent();
   1310         if (n.getMembers() != null) {
   1311             printMembers(n.getMembers(), arg);
   1312         }
   1313         printer.unindent();
   1314         printer.print("}");
   1315     }
   1316 
   1317     @Override
   1318     public void visit(final AnnotationMemberDeclaration n, final Void arg) {
   1319         printComment(n.getComment(), arg);
   1320         printMemberAnnotations(n.getAnnotations(), arg);
   1321         printModifiers(n.getModifiers());
   1322 
   1323         n.getType().accept(this, arg);
   1324         printer.print(" ");
   1325         n.getName().accept(this, arg);
   1326         printer.print("()");
   1327         if (n.getDefaultValue().isPresent()) {
   1328             printer.print(" default ");
   1329             n.getDefaultValue().get().accept(this, arg);
   1330         }
   1331         printer.print(";");
   1332     }
   1333 
   1334     @Override
   1335     public void visit(final MarkerAnnotationExpr n, final Void arg) {
   1336         printComment(n.getComment(), arg);
   1337         printer.print("@");
   1338         n.getName().accept(this, arg);
   1339     }
   1340 
   1341     @Override
   1342     public void visit(final SingleMemberAnnotationExpr n, final Void arg) {
   1343         printComment(n.getComment(), arg);
   1344         printer.print("@");
   1345         n.getName().accept(this, arg);
   1346         printer.print("(");
   1347         n.getMemberValue().accept(this, arg);
   1348         printer.print(")");
   1349     }
   1350 
   1351     @Override
   1352     public void visit(final NormalAnnotationExpr n, final Void arg) {
   1353         printComment(n.getComment(), arg);
   1354         printer.print("@");
   1355         n.getName().accept(this, arg);
   1356         printer.print("(");
   1357         if (n.getPairs() != null) {
   1358             for (final Iterator<MemberValuePair> i = n.getPairs().iterator(); i.hasNext(); ) {
   1359                 final MemberValuePair m = i.next();
   1360                 m.accept(this, arg);
   1361                 if (i.hasNext()) {
   1362                     printer.print(", ");
   1363                 }
   1364             }
   1365         }
   1366         printer.print(")");
   1367     }
   1368 
   1369     @Override
   1370     public void visit(final MemberValuePair n, final Void arg) {
   1371         printComment(n.getComment(), arg);
   1372         n.getName().accept(this, arg);
   1373         printer.print(" = ");
   1374         n.getValue().accept(this, arg);
   1375     }
   1376 
   1377     @Override
   1378     public void visit(final LineComment n, final Void arg) {
   1379         if (configuration.isIgnoreComments()) {
   1380             return;
   1381         }
   1382         printer
   1383                 .print("// ")
   1384                 .println(normalizeEolInTextBlock(n.getContent(), "").trim());
   1385     }
   1386 
   1387     @Override
   1388     public void visit(final BlockComment n, final Void arg) {
   1389         if (configuration.isIgnoreComments()) {
   1390             return;
   1391         }
   1392         final String commentContent = normalizeEolInTextBlock(n.getContent(), configuration.getEndOfLineCharacter());
   1393         String[] lines = commentContent.split("\\R", -1); // as BlockComment should not be formatted, -1 to preserve any trailing empty line if present
   1394         printer.print("/*");
   1395         for (int i = 0; i < (lines.length - 1); i++) {
   1396             printer.print(lines[i]);
   1397             printer.print(configuration.getEndOfLineCharacter()); // Avoids introducing indentation in blockcomments. ie: do not use println() as it would trigger indentation at the next print call.
   1398         }
   1399         printer.print(lines[lines.length - 1]); // last line is not followed by a newline, and simply terminated with `*/`
   1400         printer.println("*/");
   1401     }
   1402 
   1403     @Override
   1404     public void visit(LambdaExpr n, Void arg) {
   1405         printComment(n.getComment(), arg);
   1406 
   1407         final NodeList<Parameter> parameters = n.getParameters();
   1408         final boolean printPar = n.isEnclosingParameters();
   1409 
   1410         if (printPar) {
   1411             printer.print("(");
   1412         }
   1413         for (Iterator<Parameter> i = parameters.iterator(); i.hasNext(); ) {
   1414             Parameter p = i.next();
   1415             p.accept(this, arg);
   1416             if (i.hasNext()) {
   1417                 printer.print(", ");
   1418             }
   1419         }
   1420         if (printPar) {
   1421             printer.print(")");
   1422         }
   1423 
   1424         printer.print(" -> ");
   1425         final Statement body = n.getBody();
   1426         if (body instanceof ExpressionStmt) {
   1427             // Print the expression directly
   1428             ((ExpressionStmt) body).getExpression().accept(this, arg);
   1429         } else {
   1430             body.accept(this, arg);
   1431         }
   1432     }
   1433 
   1434     @Override
   1435     public void visit(MethodReferenceExpr n, Void arg) {
   1436         printComment(n.getComment(), arg);
   1437         Expression scope = n.getScope();
   1438         String identifier = n.getIdentifier();
   1439         if (scope != null) {
   1440             n.getScope().accept(this, arg);
   1441         }
   1442 
   1443         printer.print("::");
   1444         printTypeArgs(n, arg);
   1445         if (identifier != null) {
   1446             printer.print(identifier);
   1447         }
   1448     }
   1449 
   1450     @Override
   1451     public void visit(TypeExpr n, Void arg) {
   1452         printComment(n.getComment(), arg);
   1453         if (n.getType() != null) {
   1454             n.getType().accept(this, arg);
   1455         }
   1456     }
   1457 
   1458     @Override
   1459     public void visit(NodeList n, Void arg) {
   1460         if (configuration.isOrderImports() && n.size() > 0 && n.get(0) instanceof ImportDeclaration) {
   1461             //noinspection unchecked
   1462             NodeList<ImportDeclaration> modifiableList = new NodeList<>(n);
   1463             modifiableList.sort((left, right) -> {
   1464                 int sort = Integer.compare(left.isStatic() ? 0 : 1, right.isStatic() ? 0 : 1);
   1465                 if (sort == 0) {
   1466                     sort = left.getNameAsString().compareTo(right.getNameAsString());
   1467                 }
   1468                 return sort;
   1469             });
   1470             for (Object node : modifiableList) {
   1471                 ((Node) node).accept(this, arg);
   1472             }
   1473         } else {
   1474             for (Object node : n) {
   1475                 ((Node) node).accept(this, arg);
   1476             }
   1477         }
   1478     }
   1479 
   1480     @Override
   1481     public void visit(final ImportDeclaration n, final Void arg) {
   1482         printComment(n.getComment(), arg);
   1483         printer.print("import ");
   1484         if (n.isStatic()) {
   1485             printer.print("static ");
   1486         }
   1487         n.getName().accept(this, arg);
   1488         if (n.isAsterisk()) {
   1489             printer.print(".*");
   1490         }
   1491         printer.println(";");
   1492 
   1493         printOrphanCommentsEnding(n);
   1494     }
   1495 
   1496 
   1497     @Override
   1498     public void visit(ModuleDeclaration n, Void arg) {
   1499         printAnnotations(n.getAnnotations(), false, arg);
   1500         printer.println();
   1501         if (n.isOpen()) {
   1502             printer.print("open ");
   1503         }
   1504         printer.print("module ");
   1505         n.getName().accept(this, arg);
   1506         printer.println(" {").indent();
   1507         n.getModuleStmts().accept(this, arg);
   1508         printer.unindent().println("}");
   1509     }
   1510 
   1511     @Override
   1512     public void visit(ModuleRequiresStmt n, Void arg) {
   1513         printer.print("requires ");
   1514         printModifiers(n.getModifiers());
   1515         n.getName().accept(this, arg);
   1516         printer.println(";");
   1517     }
   1518 
   1519     @Override
   1520     public void visit(ModuleExportsStmt n, Void arg) {
   1521         printer.print("exports ");
   1522         n.getName().accept(this, arg);
   1523         printPrePostFixOptionalList(n.getModuleNames(), arg, " to ", ", ", "");
   1524         printer.println(";");
   1525     }
   1526 
   1527     @Override
   1528     public void visit(ModuleProvidesStmt n, Void arg) {
   1529         printer.print("provides ");
   1530         n.getType().accept(this, arg);
   1531         printPrePostFixRequiredList(n.getWithTypes(), arg, " with ", ", ", "");
   1532         printer.println(";");
   1533     }
   1534 
   1535     @Override
   1536     public void visit(ModuleUsesStmt n, Void arg) {
   1537         printer.print("uses ");
   1538         n.getType().accept(this, arg);
   1539         printer.println(";");
   1540     }
   1541 
   1542     @Override
   1543     public void visit(ModuleOpensStmt n, Void arg) {
   1544         printer.print("opens ");
   1545         n.getName().accept(this, arg);
   1546         printPrePostFixOptionalList(n.getModuleNames(), arg, " to ", ", ", "");
   1547         printer.println(";");
   1548     }
   1549 
   1550     @Override
   1551     public void visit(UnparsableStmt n, Void arg) {
   1552         printer.print("???;");
   1553     }
   1554 
   1555     private void printOrphanCommentsBeforeThisChildNode(final Node node) {
   1556         if (configuration.isIgnoreComments()) return;
   1557         if (node instanceof Comment) return;
   1558 
   1559         Node parent = node.getParentNode().orElse(null);
   1560         if (parent == null) return;
   1561         List<Node> everything = new LinkedList<>();
   1562         everything.addAll(parent.getChildNodes());
   1563         sortByBeginPosition(everything);
   1564         int positionOfTheChild = -1;
   1565         for (int i = 0; i < everything.size(); i++) {
   1566             if (everything.get(i) == node) positionOfTheChild = i;
   1567         }
   1568         if (positionOfTheChild == -1) {
   1569             throw new AssertionError("I am not a child of my parent.");
   1570         }
   1571         int positionOfPreviousChild = -1;
   1572         for (int i = positionOfTheChild - 1; i >= 0 && positionOfPreviousChild == -1; i--) {
   1573             if (!(everything.get(i) instanceof Comment)) positionOfPreviousChild = i;
   1574         }
   1575         for (int i = positionOfPreviousChild + 1; i < positionOfTheChild; i++) {
   1576             Node nodeToPrint = everything.get(i);
   1577             if (!(nodeToPrint instanceof Comment))
   1578                 throw new RuntimeException(
   1579                         "Expected comment, instead " + nodeToPrint.getClass() + ". Position of previous child: "
   1580                                 + positionOfPreviousChild + ", position of child " + positionOfTheChild);
   1581             nodeToPrint.accept(this, null);
   1582         }
   1583     }
   1584 
   1585     private void printOrphanCommentsEnding(final Node node) {
   1586         if (configuration.isIgnoreComments()) return;
   1587 
   1588         List<Node> everything = new LinkedList<>();
   1589         everything.addAll(node.getChildNodes());
   1590         sortByBeginPosition(everything);
   1591         if (everything.isEmpty()) {
   1592             return;
   1593         }
   1594 
   1595         int commentsAtEnd = 0;
   1596         boolean findingComments = true;
   1597         while (findingComments && commentsAtEnd < everything.size()) {
   1598             Node last = everything.get(everything.size() - 1 - commentsAtEnd);
   1599             findingComments = (last instanceof Comment);
   1600             if (findingComments) {
   1601                 commentsAtEnd++;
   1602             }
   1603         }
   1604         for (int i = 0; i < commentsAtEnd; i++) {
   1605             everything.get(everything.size() - commentsAtEnd + i).accept(this, null);
   1606         }
   1607     }
   1608 
   1609 }
   1610