• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2022 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.api.generator.engine.writer;
16 
17 import com.google.api.generator.engine.ast.AnnotationNode;
18 import com.google.api.generator.engine.ast.AnonymousClassExpr;
19 import com.google.api.generator.engine.ast.ArithmeticOperationExpr;
20 import com.google.api.generator.engine.ast.ArrayExpr;
21 import com.google.api.generator.engine.ast.AssignmentExpr;
22 import com.google.api.generator.engine.ast.AssignmentOperationExpr;
23 import com.google.api.generator.engine.ast.AstNodeVisitor;
24 import com.google.api.generator.engine.ast.BlockComment;
25 import com.google.api.generator.engine.ast.BlockStatement;
26 import com.google.api.generator.engine.ast.BreakStatement;
27 import com.google.api.generator.engine.ast.CastExpr;
28 import com.google.api.generator.engine.ast.ClassDefinition;
29 import com.google.api.generator.engine.ast.CommentStatement;
30 import com.google.api.generator.engine.ast.ConcreteReference;
31 import com.google.api.generator.engine.ast.EmptyLineStatement;
32 import com.google.api.generator.engine.ast.EnumRefExpr;
33 import com.google.api.generator.engine.ast.Expr;
34 import com.google.api.generator.engine.ast.ExprStatement;
35 import com.google.api.generator.engine.ast.ForStatement;
36 import com.google.api.generator.engine.ast.GeneralForStatement;
37 import com.google.api.generator.engine.ast.IdentifierNode;
38 import com.google.api.generator.engine.ast.IfStatement;
39 import com.google.api.generator.engine.ast.InstanceofExpr;
40 import com.google.api.generator.engine.ast.JavaDocComment;
41 import com.google.api.generator.engine.ast.LambdaExpr;
42 import com.google.api.generator.engine.ast.LineComment;
43 import com.google.api.generator.engine.ast.LogicalOperationExpr;
44 import com.google.api.generator.engine.ast.MethodDefinition;
45 import com.google.api.generator.engine.ast.MethodInvocationExpr;
46 import com.google.api.generator.engine.ast.NewObjectExpr;
47 import com.google.api.generator.engine.ast.OperatorKind;
48 import com.google.api.generator.engine.ast.PackageInfoDefinition;
49 import com.google.api.generator.engine.ast.Reference;
50 import com.google.api.generator.engine.ast.ReferenceConstructorExpr;
51 import com.google.api.generator.engine.ast.RelationalOperationExpr;
52 import com.google.api.generator.engine.ast.ReturnExpr;
53 import com.google.api.generator.engine.ast.ScopeNode;
54 import com.google.api.generator.engine.ast.Statement;
55 import com.google.api.generator.engine.ast.SynchronizedStatement;
56 import com.google.api.generator.engine.ast.TernaryExpr;
57 import com.google.api.generator.engine.ast.ThrowExpr;
58 import com.google.api.generator.engine.ast.TryCatchStatement;
59 import com.google.api.generator.engine.ast.TypeNode;
60 import com.google.api.generator.engine.ast.TypeNode.TypeKind;
61 import com.google.api.generator.engine.ast.UnaryOperationExpr;
62 import com.google.api.generator.engine.ast.ValueExpr;
63 import com.google.api.generator.engine.ast.VaporReference;
64 import com.google.api.generator.engine.ast.Variable;
65 import com.google.api.generator.engine.ast.VariableExpr;
66 import com.google.api.generator.engine.ast.WhileStatement;
67 import com.google.api.generator.gapic.model.RegionTag;
68 import java.util.Arrays;
69 import java.util.Iterator;
70 import java.util.List;
71 import java.util.Map;
72 import java.util.stream.Collectors;
73 import java.util.stream.IntStream;
74 
75 public class JavaWriterVisitor implements AstNodeVisitor {
76   private static final String SPACE = " ";
77   private static final String NEWLINE = "\n";
78 
79   private static final String AT = "@";
80 
81   private static final String COLON = ":";
82   private static final String COMMA = ",";
83   private static final String BLOCK_COMMENT_START = "/*";
84   private static final String BLOCK_COMMENT_END = "*/";
85   private static final String DOT = ".";
86   private static final String EQUALS = "=";
87   private static final String LEFT_ANGLE = "<";
88   private static final String LEFT_BRACE = "{";
89   private static final String LEFT_PAREN = "(";
90   private static final String JAVADOC_COMMENT_START = "/**";
91   private static final String QUESTION_MARK = "?";
92   private static final String RIGHT_ANGLE = ">";
93   private static final String RIGHT_BRACE = "}";
94   private static final String RIGHT_PAREN = ")";
95   private static final String SEMICOLON = ";";
96   private static final String ASTERISK = "*";
97 
98   private static final String ABSTRACT = "abstract";
99   private static final String CATCH = "catch";
100   private static final String CLASS = "class";
101   private static final String ELSE = "else";
102   private static final String EXTENDS = "extends";
103   private static final String FINAL = "final";
104   private static final String FOR = "for";
105   private static final String IF = "if";
106   private static final String INSTANCEOF = "instanceof";
107   private static final String IMPLEMENTS = "implements";
108   private static final String NEW = "new";
109   private static final String RETURN = "return";
110   private static final String SYNCHRONIZED = "synchronized";
111   private static final String STATIC = "static";
112   private static final String THROW = "throw";
113   private static final String THROWS = "throws";
114   private static final String TRY = "try";
115   private static final String VOLATILE = "volatile";
116   private static final String WHILE = "while";
117   private static final String BREAK = "break";
118 
119   // Operators
120   private static final String OPERATOR_ADDITION = "+";
121   private static final String OPERATOR_EQUAL_TO = "==";
122   private static final String OPERATOR_NOT_EQUAL_TO = "!=";
123   private static final String OPERATOR_LESS_THAN = "<";
124   private static final String OPERATOR_INCREMENT = "++";
125   private static final String OPERATOR_LOGICAL_NOT = "!";
126   private static final String OPERATOR_LOGICAL_AND = "&&";
127   private static final String OPERATOR_LOGICAL_OR = "||";
128   private static final String OPERATOR_XOR = "^=";
129   private static final String OPERATOR_MULTIPLE_AND_ASSIGNMENT = "*=";
130 
131   private final StringBuffer buffer = new StringBuffer();
132   private final ImportWriterVisitor importWriterVisitor = new ImportWriterVisitor();
133 
JavaWriterVisitor()134   public JavaWriterVisitor() {}
135 
clear()136   public void clear() {
137     buffer.setLength(0);
138     importWriterVisitor.clear();
139   }
140 
write()141   public String write() {
142     return buffer.toString();
143   }
144 
145   @Override
visit(IdentifierNode identifier)146   public void visit(IdentifierNode identifier) {
147     buffer.append(identifier.name());
148   }
149 
150   @Override
visit(TypeNode type)151   public void visit(TypeNode type) {
152     TypeKind typeKind = type.typeKind();
153     if (type.isPrimitiveType()) {
154       buffer.append(typeKind.toString().toLowerCase());
155     } else {
156       type.reference().accept(this);
157     }
158 
159     if (type.isArray()) {
160       buffer.append("[]");
161     }
162   }
163 
164   @Override
visit(ScopeNode scope)165   public void visit(ScopeNode scope) {
166     buffer.append(scope.toString());
167   }
168 
169   @Override
visit(ArrayExpr expr)170   public void visit(ArrayExpr expr) {
171     buffer.append(LEFT_BRACE);
172     for (int i = 0; i < expr.exprs().size(); i++) {
173       expr.exprs().get(i).accept(this);
174       if (i < expr.exprs().size() - 1) {
175         buffer.append(COMMA);
176         buffer.append(SPACE);
177       }
178     }
179     buffer.append(RIGHT_BRACE);
180   }
181 
182   @Override
visit(AnnotationNode annotation)183   public void visit(AnnotationNode annotation) {
184     buffer.append(AT);
185     annotation.type().accept(this);
186     if (annotation.descriptionExprs() != null) {
187       leftParen();
188       for (int i = 0; i < annotation.descriptionExprs().size(); i++) {
189         annotation.descriptionExprs().get(i).accept(this);
190         if (i < annotation.descriptionExprs().size() - 1) {
191           buffer.append(COMMA);
192           buffer.append(SPACE);
193         }
194       }
195       rightParen();
196     }
197     newline();
198   }
199 
200   @Override
visit(ConcreteReference reference)201   public void visit(ConcreteReference reference) {
202     if (reference.isWildcard()) {
203       buffer.append(QUESTION_MARK);
204       if (reference.wildcardUpperBound() != null) {
205         // Handle the upper bound.
206         buffer.append(SPACE);
207         buffer.append(EXTENDS);
208         buffer.append(SPACE);
209         reference.wildcardUpperBound().accept(this);
210       }
211       return;
212     }
213     String pakkage = reference.pakkage();
214     String shortName = reference.name();
215     if (reference.useFullName() || importWriterVisitor.collidesWithImport(pakkage, shortName)) {
216       buffer.append(pakkage);
217       buffer.append(DOT);
218     }
219 
220     if (reference.hasEnclosingClass() && !reference.isStaticImport()) {
221       buffer.append(String.join(DOT, reference.enclosingClassNames()));
222       buffer.append(DOT);
223     }
224 
225     buffer.append(reference.simpleName());
226 
227     if (!reference.generics().isEmpty()) {
228       buffer.append(LEFT_ANGLE);
229       for (int i = 0; i < reference.generics().size(); i++) {
230         Reference r = reference.generics().get(i);
231         r.accept(this);
232         if (i < reference.generics().size() - 1) {
233           buffer.append(COMMA);
234           buffer.append(SPACE);
235         }
236       }
237       buffer.append(RIGHT_ANGLE);
238     }
239   }
240 
241   @Override
visit(VaporReference reference)242   public void visit(VaporReference reference) {
243     // This implementation should be more extensive, but there are no existing use cases that
244     // exercise the edge cases.
245     // TODO(miraleung): Give this behavioral parity with ConcreteReference.
246     String pakkage = reference.pakkage();
247     String shortName = reference.name();
248 
249     if (reference.useFullName() || importWriterVisitor.collidesWithImport(pakkage, shortName)) {
250       buffer.append(pakkage);
251       buffer.append(DOT);
252       if (reference.hasEnclosingClass()) {
253         buffer.append(String.join(DOT, reference.enclosingClassNames()));
254         buffer.append(DOT);
255       }
256     }
257 
258     // A null pointer exception will be thrown if reference is null, which is WAI.
259     buffer.append(shortName);
260   }
261 
262   /** =============================== EXPRESSIONS =============================== */
263   @Override
visit(ValueExpr valueExpr)264   public void visit(ValueExpr valueExpr) {
265     buffer.append(valueExpr.value().value());
266   }
267 
268   @Override
visit(VariableExpr variableExpr)269   public void visit(VariableExpr variableExpr) {
270     Variable variable = variableExpr.variable();
271     TypeNode type = variable.type();
272     ScopeNode scope = variableExpr.scope();
273 
274     // VariableExpr will handle isDecl and exprReferenceExpr edge cases.
275     if (variableExpr.isDecl()) {
276       // Annotations, if any.
277       annotations(variableExpr.annotations());
278 
279       if (!scope.equals(ScopeNode.LOCAL)) {
280         scope.accept(this);
281         space();
282       }
283 
284       if (variableExpr.isStatic()) {
285         buffer.append(STATIC);
286         space();
287       }
288 
289       if (variableExpr.isFinal()) {
290         buffer.append(FINAL);
291         space();
292       }
293 
294       if (variableExpr.isVolatile()) {
295         buffer.append(VOLATILE);
296         space();
297       }
298 
299       type.accept(this);
300       if (!variableExpr.templateNodes().isEmpty()) {
301         leftAngle();
302         IntStream.range(0, variableExpr.templateNodes().size())
303             .forEach(
304                 i -> {
305                   variableExpr.templateNodes().get(i).accept(this);
306                   if (i < variableExpr.templateNodes().size() - 1) {
307                     buffer.append(COMMA);
308                     space();
309                   }
310                 });
311         rightAngle();
312       }
313       space();
314     } else {
315       // Expression or static reference.
316       if (variableExpr.exprReferenceExpr() != null) {
317         variableExpr.exprReferenceExpr().accept(this);
318         buffer.append(DOT);
319       } else if (variableExpr.staticReferenceType() != null) {
320         variableExpr.staticReferenceType().accept(this);
321         buffer.append(DOT);
322       }
323     }
324 
325     variable.identifier().accept(this);
326   }
327 
328   @Override
visit(TernaryExpr ternaryExpr)329   public void visit(TernaryExpr ternaryExpr) {
330     ternaryExpr.conditionExpr().accept(this);
331     space();
332     buffer.append(QUESTION_MARK);
333     space();
334     ternaryExpr.thenExpr().accept(this);
335     space();
336     buffer.append(COLON);
337     space();
338     ternaryExpr.elseExpr().accept(this);
339   }
340 
341   @Override
visit(AssignmentExpr assignmentExpr)342   public void visit(AssignmentExpr assignmentExpr) {
343     assignmentExpr.variableExpr().accept(this);
344     space();
345     buffer.append(EQUALS);
346     space();
347     assignmentExpr.valueExpr().accept(this);
348   }
349 
350   @Override
visit(MethodInvocationExpr methodInvocationExpr)351   public void visit(MethodInvocationExpr methodInvocationExpr) {
352     // Expression or static reference.
353     if (methodInvocationExpr.exprReferenceExpr() != null) {
354       methodInvocationExpr.exprReferenceExpr().accept(this);
355       buffer.append(DOT);
356     } else if (methodInvocationExpr.staticReferenceType() != null) {
357       methodInvocationExpr.staticReferenceType().accept(this);
358       buffer.append(DOT);
359     }
360 
361     if (methodInvocationExpr.isGeneric()) {
362       leftAngle();
363       int numGenerics = methodInvocationExpr.generics().size();
364       for (int i = 0; i < numGenerics; i++) {
365         buffer.append(methodInvocationExpr.generics().get(i).name());
366         if (i < numGenerics - 1) {
367           buffer.append(COMMA);
368           space();
369         }
370       }
371       rightAngle();
372     }
373 
374     methodInvocationExpr.methodIdentifier().accept(this);
375     leftParen();
376     int numArguments = methodInvocationExpr.arguments().size();
377     for (int i = 0; i < numArguments; i++) {
378       Expr argExpr = methodInvocationExpr.arguments().get(i);
379       argExpr.accept(this);
380       if (i < numArguments - 1) {
381         buffer.append(COMMA);
382         space();
383       }
384     }
385     rightParen();
386   }
387 
388   @Override
visit(CastExpr castExpr)389   public void visit(CastExpr castExpr) {
390     leftParen();
391     leftParen();
392     castExpr.type().accept(this);
393     rightParen();
394     space();
395     castExpr.expr().accept(this);
396     rightParen();
397   }
398 
399   @Override
visit(AnonymousClassExpr anonymousClassExpr)400   public void visit(AnonymousClassExpr anonymousClassExpr) {
401     buffer.append(NEW);
402     space();
403     anonymousClassExpr.type().accept(this);
404     leftParen();
405     rightParen();
406     space();
407     leftBrace();
408     newline();
409     statements(anonymousClassExpr.statements());
410     methods(anonymousClassExpr.methods());
411     rightBrace();
412   }
413 
414   @Override
visit(ThrowExpr throwExpr)415   public void visit(ThrowExpr throwExpr) {
416     buffer.append(THROW);
417     space();
418     // If throwExpr is present, then messageExpr and causeExpr will not be present. Relies on AST
419     // build-time checks.
420     if (throwExpr.throwExpr() != null) {
421       throwExpr.throwExpr().accept(this);
422       return;
423     }
424 
425     buffer.append(NEW);
426     space();
427     throwExpr.type().accept(this);
428     leftParen();
429     if (throwExpr.messageExpr() != null) {
430       throwExpr.messageExpr().accept(this);
431     }
432     if (throwExpr.causeExpr() != null) {
433       if (throwExpr.messageExpr() != null) {
434         buffer.append(COMMA);
435         space();
436       }
437       throwExpr.causeExpr().accept(this);
438     }
439     rightParen();
440   }
441 
442   @Override
visit(InstanceofExpr instanceofExpr)443   public void visit(InstanceofExpr instanceofExpr) {
444     instanceofExpr.expr().accept(this);
445     space();
446     buffer.append(INSTANCEOF);
447     space();
448     instanceofExpr.checkType().accept(this);
449   }
450 
451   @Override
visit(NewObjectExpr newObjectExpr)452   public void visit(NewObjectExpr newObjectExpr) {
453     buffer.append(NEW);
454     space();
455     newObjectExpr.type().accept(this);
456     // If isGeneric() is true, but generic list is empty, we will append `<>` to the buffer.
457     if (newObjectExpr.isGeneric() && newObjectExpr.type().reference().generics().isEmpty()) {
458       leftAngle();
459       rightAngle();
460     }
461     leftParen();
462     int numArguments = newObjectExpr.arguments().size();
463     for (int i = 0; i < numArguments; i++) {
464       newObjectExpr.arguments().get(i).accept(this);
465       if (i < numArguments - 1) {
466         buffer.append(COMMA);
467         space();
468       }
469     }
470     rightParen();
471   }
472 
473   @Override
visit(EnumRefExpr enumRefExpr)474   public void visit(EnumRefExpr enumRefExpr) {
475     enumRefExpr.type().accept(this);
476     buffer.append(DOT);
477     enumRefExpr.identifier().accept(this);
478   }
479 
480   @Override
visit(ReturnExpr returnExpr)481   public void visit(ReturnExpr returnExpr) {
482     buffer.append(RETURN);
483     space();
484     returnExpr.expr().accept(this);
485   }
486 
487   @Override
visit(ReferenceConstructorExpr referenceConstructorExpr)488   public void visit(ReferenceConstructorExpr referenceConstructorExpr) {
489     buffer.append(referenceConstructorExpr.keywordKind().name().toLowerCase());
490     leftParen();
491     IntStream.range(0, referenceConstructorExpr.arguments().size())
492         .forEach(
493             i -> {
494               referenceConstructorExpr.arguments().get(i).accept(this);
495               if (i < referenceConstructorExpr.arguments().size() - 1) {
496                 buffer.append(COMMA);
497                 space();
498               }
499             });
500     rightParen();
501   }
502 
503   @Override
visit(ArithmeticOperationExpr arithmeticOperationExpr)504   public void visit(ArithmeticOperationExpr arithmeticOperationExpr) {
505     arithmeticOperationExpr.lhsExpr().accept(this);
506     space();
507     operator(arithmeticOperationExpr.operatorKind());
508     space();
509     arithmeticOperationExpr.rhsExpr().accept(this);
510   }
511 
512   @Override
visit(UnaryOperationExpr unaryOperationExpr)513   public void visit(UnaryOperationExpr unaryOperationExpr) {
514     if (unaryOperationExpr.operatorKind().isPrefixOperator()) {
515       operator(unaryOperationExpr.operatorKind());
516       unaryOperationExpr.expr().accept(this);
517     } else {
518       unaryOperationExpr.expr().accept(this);
519       operator(unaryOperationExpr.operatorKind());
520     }
521   }
522 
523   @Override
visit(RelationalOperationExpr relationalOperationExpr)524   public void visit(RelationalOperationExpr relationalOperationExpr) {
525     relationalOperationExpr.lhsExpr().accept(this);
526     space();
527     operator(relationalOperationExpr.operatorKind());
528     space();
529     relationalOperationExpr.rhsExpr().accept(this);
530   }
531 
532   @Override
visit(LogicalOperationExpr logicalOperationExpr)533   public void visit(LogicalOperationExpr logicalOperationExpr) {
534     logicalOperationExpr.lhsExpr().accept(this);
535     space();
536     operator(logicalOperationExpr.operatorKind());
537     space();
538     logicalOperationExpr.rhsExpr().accept(this);
539   }
540 
541   @Override
visit(AssignmentOperationExpr assignmentOperationExpr)542   public void visit(AssignmentOperationExpr assignmentOperationExpr) {
543     assignmentOperationExpr.variableExpr().accept(this);
544     space();
545     operator(assignmentOperationExpr.operatorKind());
546     space();
547     assignmentOperationExpr.valueExpr().accept(this);
548   }
549 
550   @Override
visit(LambdaExpr lambdaExpr)551   public void visit(LambdaExpr lambdaExpr) {
552     if (lambdaExpr.arguments().isEmpty()) {
553       leftParen();
554       rightParen();
555     } else if (lambdaExpr.arguments().size() == 1) {
556       // Print just the variable.
557       lambdaExpr.arguments().get(0).variable().identifier().accept(this);
558     } else {
559       // Stylistic choice - print the types and variable names for clarity.
560       leftParen();
561       int numArguments = lambdaExpr.arguments().size();
562       for (int i = 0; i < numArguments; i++) {
563         lambdaExpr.arguments().get(i).accept(this);
564         if (i < numArguments - 1) {
565           buffer.append(COMMA);
566           space();
567         }
568       }
569       rightParen();
570     }
571 
572     space();
573     buffer.append("->");
574     space();
575 
576     if (lambdaExpr.body().isEmpty()) {
577       // Just the return expression - don't render "return".
578       lambdaExpr.returnExpr().expr().accept(this);
579       return;
580     }
581 
582     leftBrace();
583     newline();
584     statements(lambdaExpr.body());
585     ExprStatement.withExpr(lambdaExpr.returnExpr()).accept(this);
586     rightBrace();
587   }
588 
589   /** =============================== STATEMENTS =============================== */
590   @Override
visit(ExprStatement exprStatement)591   public void visit(ExprStatement exprStatement) {
592     exprStatement.expression().accept(this);
593     semicolon();
594     newline();
595   }
596 
597   @Override
visit(BlockStatement blockStatement)598   public void visit(BlockStatement blockStatement) {
599     if (blockStatement.isStatic()) {
600       buffer.append(STATIC);
601       space();
602     }
603     leftBrace();
604     newline();
605     statements(blockStatement.body());
606     rightBrace();
607     newline();
608   }
609 
610   @Override
visit(IfStatement ifStatement)611   public void visit(IfStatement ifStatement) {
612     buffer.append(IF);
613     space();
614     leftParen();
615 
616     ifStatement.conditionExpr().accept(this);
617     rightParen();
618     space();
619     leftBrace();
620     newline();
621 
622     statements(ifStatement.body());
623     buffer.append(RIGHT_BRACE);
624     if (!ifStatement.elseIfs().isEmpty()) {
625       for (Map.Entry<Expr, List<Statement>> elseIfEntry : ifStatement.elseIfs().entrySet()) {
626         Expr elseIfConditionExpr = elseIfEntry.getKey();
627         List<Statement> elseIfBody = elseIfEntry.getValue();
628         space();
629         buffer.append(ELSE);
630         space();
631         buffer.append(IF);
632         space();
633         leftParen();
634         elseIfConditionExpr.accept(this);
635         rightParen();
636         space();
637         leftBrace();
638         newline();
639         statements(elseIfBody);
640         rightBrace();
641       }
642     }
643     if (!ifStatement.elseBody().isEmpty()) {
644       space();
645       buffer.append(ELSE);
646       space();
647       leftBrace();
648       newline();
649       statements(ifStatement.elseBody());
650       rightBrace();
651     }
652     newline();
653   }
654 
655   @Override
visit(ForStatement forStatement)656   public void visit(ForStatement forStatement) {
657     buffer.append(FOR);
658     space();
659     leftParen();
660     forStatement.localVariableExpr().accept(this);
661     space();
662     buffer.append(COLON);
663     space();
664     forStatement.collectionExpr().accept(this);
665     rightParen();
666     space();
667     leftBrace();
668     newline();
669     statements(forStatement.body());
670     rightBrace();
671     newline();
672   }
673 
674   @Override
visit(GeneralForStatement generalForStatement)675   public void visit(GeneralForStatement generalForStatement) {
676     buffer.append(FOR);
677     space();
678     leftParen();
679     generalForStatement.initializationExpr().accept(this);
680     semicolon();
681     space();
682 
683     generalForStatement.terminationExpr().accept(this);
684     semicolon();
685     space();
686 
687     generalForStatement.updateExpr().accept(this);
688     rightParen();
689     space();
690     leftBrace();
691     newline();
692 
693     statements(generalForStatement.body());
694     rightBrace();
695     newline();
696   }
697 
698   @Override
visit(WhileStatement whileStatement)699   public void visit(WhileStatement whileStatement) {
700     buffer.append(WHILE);
701     space();
702     leftParen();
703     whileStatement.conditionExpr().accept(this);
704     rightParen();
705     space();
706     leftBrace();
707     newline();
708     statements(whileStatement.body());
709     rightBrace();
710     newline();
711   }
712 
713   @Override
visit(TryCatchStatement tryCatchStatement)714   public void visit(TryCatchStatement tryCatchStatement) {
715     buffer.append(TRY);
716     space();
717     if (tryCatchStatement.tryResourceExpr() != null) {
718       leftParen();
719       tryCatchStatement.tryResourceExpr().accept(this);
720       rightParen();
721       space();
722     }
723     leftBrace();
724     newline();
725 
726     statements(tryCatchStatement.tryBody());
727     rightBrace();
728 
729     for (int i = 0; i < tryCatchStatement.catchVariableExprs().size(); i++) {
730       space();
731       buffer.append(CATCH);
732       space();
733       leftParen();
734       tryCatchStatement.catchVariableExprs().get(i).accept(this);
735       rightParen();
736       space();
737       leftBrace();
738       newline();
739       statements(tryCatchStatement.catchBlocks().get(i));
740       rightBrace();
741     }
742     newline();
743   }
744 
745   @Override
visit(SynchronizedStatement synchronizedStatement)746   public void visit(SynchronizedStatement synchronizedStatement) {
747     buffer.append(SYNCHRONIZED);
748     space();
749     leftParen();
750     synchronizedStatement.lock().accept(this);
751     rightParen();
752     space();
753     leftBrace();
754     newline();
755     statements(synchronizedStatement.body());
756     rightBrace();
757     newline();
758   }
759 
760   @Override
visit(CommentStatement commentStatement)761   public void visit(CommentStatement commentStatement) {
762     commentStatement.comment().accept(this);
763   }
764 
765   @Override
visit(EmptyLineStatement emptyLineStatement)766   public void visit(EmptyLineStatement emptyLineStatement) {
767     newline();
768   }
769 
770   @Override
visit(BreakStatement breakStatement)771   public void visit(BreakStatement breakStatement) {
772     buffer.append(BREAK);
773     semicolon();
774   }
775 
776   /** =============================== COMMENT =============================== */
777   @Override
visit(LineComment lineComment)778   public void visit(LineComment lineComment) {
779     // Split comments by new line and add `//` to each line.
780     String formattedSource =
781         JavaFormatter.format(
782             String.format("// %s", String.join("\n//", lineComment.comment().split("\\r?\\n"))));
783     buffer.append(formattedSource);
784   }
785 
786   @Override
visit(BlockComment blockComment)787   public void visit(BlockComment blockComment) {
788     // Split comments by new line and embrace the comment block with `/* */`.
789     StringBuilder sourceComment = new StringBuilder();
790     sourceComment.append(BLOCK_COMMENT_START).append(NEWLINE);
791     Arrays.stream(blockComment.comment().split("\\r?\\n"))
792         .forEach(
793             comment -> {
794               sourceComment.append(String.format("%s %s%s", ASTERISK, comment, NEWLINE));
795             });
796     sourceComment.append(BLOCK_COMMENT_END);
797     buffer.append(JavaFormatter.format(sourceComment.toString()));
798   }
799 
800   @Override
visit(JavaDocComment javaDocComment)801   public void visit(JavaDocComment javaDocComment) {
802     StringBuilder sourceComment = new StringBuilder();
803     sourceComment.append(JAVADOC_COMMENT_START).append(NEWLINE);
804     Arrays.stream(javaDocComment.comment().split("\\r?\\n"))
805         .forEach(
806             comment -> {
807               sourceComment.append(String.format("%s %s%s", ASTERISK, comment, NEWLINE));
808             });
809     sourceComment.append(BLOCK_COMMENT_END);
810     buffer.append(JavaFormatter.format(sourceComment.toString()));
811   }
812 
813   /** =============================== OTHER =============================== */
814   @Override
visit(MethodDefinition methodDefinition)815   public void visit(MethodDefinition methodDefinition) {
816     // Header comments, if any.
817     statements(methodDefinition.headerCommentStatements().stream().collect(Collectors.toList()));
818     // Annotations, if any.
819     annotations(methodDefinition.annotations());
820 
821     // Method scope.
822     methodDefinition.scope().accept(this);
823     space();
824 
825     // Templates, if any.
826     if (!methodDefinition.templateIdentifiers().isEmpty()) {
827       leftAngle();
828       IntStream.range(0, methodDefinition.templateIdentifiers().size())
829           .forEach(
830               i -> {
831                 methodDefinition.templateIdentifiers().get(i).accept(this);
832                 if (i < methodDefinition.templateIdentifiers().size() - 1) {
833                   buffer.append(COMMA);
834                   space();
835                 }
836               });
837       rightAngle();
838       space();
839     }
840 
841     // Modifiers.
842     if (methodDefinition.isAbstract()) {
843       buffer.append(ABSTRACT);
844       space();
845     }
846     if (methodDefinition.isStatic()) {
847       buffer.append(STATIC);
848       space();
849     }
850     if (methodDefinition.isFinal()) {
851       buffer.append(FINAL);
852       space();
853     }
854 
855     if (!methodDefinition.isConstructor()) {
856       methodDefinition.returnType().accept(this);
857       if (!methodDefinition.returnTemplateIdentifiers().isEmpty()) {
858         leftAngle();
859         IntStream.range(0, methodDefinition.returnTemplateIdentifiers().size())
860             .forEach(
861                 i -> {
862                   methodDefinition.returnTemplateIdentifiers().get(i).accept(this);
863                   if (i < methodDefinition.returnTemplateIdentifiers().size() - 1) {
864                     buffer.append(COMMA);
865                     space();
866                   }
867                 });
868         rightAngle();
869       }
870       space();
871     }
872 
873     // Method name.
874     methodDefinition.methodIdentifier().accept(this);
875     leftParen();
876 
877     // Arguments, if any.
878     int numArguments = methodDefinition.arguments().size();
879     for (int i = 0; i < numArguments; i++) {
880       methodDefinition.arguments().get(i).accept(this);
881       if (i < numArguments - 1) {
882         buffer.append(COMMA);
883         space();
884       }
885     }
886     rightParen();
887 
888     // Thrown exceptions.
889     if (!methodDefinition.throwsExceptions().isEmpty()) {
890       space();
891       buffer.append(THROWS);
892       space();
893 
894       Iterator<TypeNode> exceptionIter = methodDefinition.throwsExceptions().iterator();
895       while (exceptionIter.hasNext()) {
896         TypeNode exceptionType = exceptionIter.next();
897         exceptionType.accept(this);
898         if (exceptionIter.hasNext()) {
899           buffer.append(COMMA);
900           space();
901         }
902       }
903     }
904 
905     if (methodDefinition.isAbstract() && methodDefinition.body().isEmpty()) {
906       semicolon();
907       newline();
908       return;
909     }
910 
911     // Method body.
912     space();
913     leftBrace();
914     newline();
915     statements(methodDefinition.body());
916     if (methodDefinition.returnExpr() != null) {
917       ExprStatement.withExpr(methodDefinition.returnExpr()).accept(this);
918     }
919 
920     rightBrace();
921     newline();
922     newline();
923   }
924 
925   @Override
visit(ClassDefinition classDefinition)926   public void visit(ClassDefinition classDefinition) {
927     if (!classDefinition.isNested()) {
928       statements(classDefinition.fileHeader().stream().collect(Collectors.toList()));
929       newline();
930       importWriterVisitor.initialize(
931           classDefinition.packageString(), classDefinition.classIdentifier().name());
932       buffer.append(String.format("package %s;", classDefinition.packageString()));
933       newline();
934       newline();
935     }
936 
937     String regionTagReplace = "REPLACE_REGION_TAG";
938     if (classDefinition.regionTag() != null) {
939       statements(
940           Arrays.asList(
941               classDefinition
942                   .regionTag()
943                   .generateTag(RegionTag.RegionTagRegion.START, regionTagReplace)));
944     }
945 
946     // This must go first, so that we can check for type collisions.
947     classDefinition.accept(importWriterVisitor);
948     if (!classDefinition.isNested()) {
949       buffer.append(importWriterVisitor.write());
950     }
951     // Header comments, if any.
952     statements(classDefinition.headerCommentStatements().stream().collect(Collectors.toList()));
953     // Annotations, if any.
954     annotations(classDefinition.annotations());
955 
956     classDefinition.scope().accept(this);
957     space();
958 
959     // Modifiers.
960     if (classDefinition.isStatic()) {
961       buffer.append(STATIC);
962       space();
963     }
964     if (classDefinition.isFinal()) {
965       buffer.append(FINAL);
966       space();
967     }
968     if (classDefinition.isAbstract()) {
969       buffer.append(ABSTRACT);
970       space();
971     }
972 
973     // Name, extends, implements.
974     buffer.append(CLASS);
975     space();
976     classDefinition.classIdentifier().accept(this);
977     space();
978     if (classDefinition.extendsType() != null) {
979       buffer.append(EXTENDS);
980       space();
981       classDefinition.extendsType().accept(this);
982       space();
983     }
984 
985     if (!classDefinition.implementsTypes().isEmpty()) {
986       buffer.append(IMPLEMENTS);
987       space();
988 
989       int numImplementsTypes = classDefinition.implementsTypes().size();
990       for (int i = 0; i < numImplementsTypes; i++) {
991         classDefinition.implementsTypes().get(i).accept(this);
992         if (i < numImplementsTypes - 1) {
993           buffer.append(COMMA);
994         }
995         space();
996       }
997     }
998 
999     // Class body.
1000     leftBrace();
1001     newline();
1002 
1003     statements(classDefinition.statements());
1004     newline();
1005     methods(classDefinition.methods());
1006     newline();
1007     classes(classDefinition.nestedClasses());
1008 
1009     rightBrace();
1010     if (classDefinition.regionTag() != null) {
1011       statements(
1012           Arrays.asList(
1013               classDefinition
1014                   .regionTag()
1015                   .generateTag(RegionTag.RegionTagRegion.END, regionTagReplace)));
1016     }
1017 
1018     // We should have valid Java by now, so format it.
1019     if (!classDefinition.isNested()) {
1020       String formattedClazz = JavaFormatter.format(buffer.toString());
1021 
1022       // fixing region tag after formatting
1023       // formatter splits long region tags on multiple lines and moves the end tag up - doesn't meet
1024       // tag requirements. See https://github.com/google/google-java-format/issues/137
1025       if (classDefinition.regionTag() != null) {
1026         formattedClazz =
1027             formattedClazz.replaceAll(regionTagReplace, classDefinition.regionTag().generate());
1028         formattedClazz = formattedClazz.replaceAll("} // \\[END", "}\n// \\[END");
1029       }
1030       buffer.replace(0, buffer.length(), formattedClazz);
1031     }
1032   }
1033 
1034   @Override
visit(PackageInfoDefinition packageInfoDefinition)1035   public void visit(PackageInfoDefinition packageInfoDefinition) {
1036     statements(packageInfoDefinition.fileHeader().stream().collect(Collectors.toList()));
1037     newline();
1038     statements(
1039         packageInfoDefinition.headerCommentStatements().stream().collect(Collectors.toList()));
1040     newline();
1041 
1042     annotations(packageInfoDefinition.annotations());
1043     buffer.append(String.format("package %s;", packageInfoDefinition.pakkage()));
1044     newline();
1045 
1046     packageInfoDefinition.accept(importWriterVisitor);
1047     importWriterVisitor.initialize(packageInfoDefinition.pakkage());
1048     buffer.append(importWriterVisitor.write());
1049 
1050     // Format code.
1051     buffer.replace(0, buffer.length(), JavaFormatter.format(buffer.toString()));
1052   }
1053 
1054   /** =============================== PRIVATE HELPERS =============================== */
annotations(List<AnnotationNode> annotations)1055   private void annotations(List<AnnotationNode> annotations) {
1056     for (AnnotationNode annotation : annotations) {
1057       annotation.accept(this);
1058     }
1059   }
1060 
statements(List<Statement> statements)1061   private void statements(List<Statement> statements) {
1062     for (Statement statement : statements) {
1063       statement.accept(this);
1064     }
1065   }
1066 
methods(List<MethodDefinition> methods)1067   private void methods(List<MethodDefinition> methods) {
1068     for (MethodDefinition method : methods) {
1069       method.accept(this);
1070     }
1071   }
1072 
classes(List<ClassDefinition> classes)1073   private void classes(List<ClassDefinition> classes) {
1074     if (!classes.isEmpty()) {
1075       newline();
1076     }
1077     for (ClassDefinition classDef : classes) {
1078       classDef.accept(this);
1079       newline();
1080       newline();
1081     }
1082   }
1083 
space()1084   private void space() {
1085     buffer.append(SPACE);
1086   }
1087 
newline()1088   private void newline() {
1089     buffer.append(NEWLINE);
1090   }
1091 
leftParen()1092   private void leftParen() {
1093     buffer.append(LEFT_PAREN);
1094   }
1095 
rightParen()1096   private void rightParen() {
1097     buffer.append(RIGHT_PAREN);
1098   }
1099 
leftAngle()1100   private void leftAngle() {
1101     buffer.append(LEFT_ANGLE);
1102   }
1103 
rightAngle()1104   private void rightAngle() {
1105     buffer.append(RIGHT_ANGLE);
1106   }
1107 
leftBrace()1108   private void leftBrace() {
1109     buffer.append(LEFT_BRACE);
1110   }
1111 
rightBrace()1112   private void rightBrace() {
1113     buffer.append(RIGHT_BRACE);
1114   }
1115 
semicolon()1116   private void semicolon() {
1117     buffer.append(SEMICOLON);
1118   }
1119 
operator(OperatorKind kind)1120   private void operator(OperatorKind kind) {
1121     switch (kind) {
1122       case ARITHMETIC_ADDITION:
1123         buffer.append(OPERATOR_ADDITION);
1124         break;
1125       case ASSIGNMENT_XOR:
1126         buffer.append(OPERATOR_XOR);
1127         break;
1128       case ASSIGNMENT_MULTIPLY:
1129         buffer.append(OPERATOR_MULTIPLE_AND_ASSIGNMENT);
1130         break;
1131       case RELATIONAL_EQUAL_TO:
1132         buffer.append(OPERATOR_EQUAL_TO);
1133         break;
1134       case RELATIONAL_NOT_EQUAL_TO:
1135         buffer.append(OPERATOR_NOT_EQUAL_TO);
1136         break;
1137       case RELATIONAL_LESS_THAN:
1138         buffer.append(OPERATOR_LESS_THAN);
1139         break;
1140       case UNARY_POST_INCREMENT:
1141         buffer.append(OPERATOR_INCREMENT);
1142         break;
1143       case UNARY_LOGICAL_NOT:
1144         buffer.append(OPERATOR_LOGICAL_NOT);
1145         break;
1146       case LOGICAL_AND:
1147         buffer.append(OPERATOR_LOGICAL_AND);
1148         break;
1149       case LOGICAL_OR:
1150         buffer.append(OPERATOR_LOGICAL_OR);
1151         break;
1152     }
1153   }
1154 }
1155