View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle;
21  
22  import java.util.ArrayDeque;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Optional;
28  import java.util.Queue;
29  import java.util.stream.Collectors;
30  
31  import org.antlr.v4.runtime.BufferedTokenStream;
32  import org.antlr.v4.runtime.CommonTokenStream;
33  import org.antlr.v4.runtime.ParserRuleContext;
34  import org.antlr.v4.runtime.Token;
35  import org.antlr.v4.runtime.tree.ParseTree;
36  import org.antlr.v4.runtime.tree.TerminalNode;
37  
38  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
39  import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageLexer;
40  import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParser;
41  import com.puppycrawl.tools.checkstyle.grammar.java.JavaLanguageParserBaseVisitor;
42  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
43  
44  /**
45   * Visitor class used to build Checkstyle's Java AST from the parse tree produced by
46   * {@link JavaLanguageParser}. In each {@code visit...} method, we visit the children of a node
47   * (which correspond to subrules) or create terminal nodes (tokens), and return a subtree as a
48   * result.
49   *
50   * <p>Example:</p>
51   *
52   * <p>The following package declaration:</p>
53   * <pre>
54   * package com.puppycrawl.tools.checkstyle;
55   * </pre>
56   *
57   * <p>
58   * Will be parsed by the {@code packageDeclaration} rule from {@code JavaLanguageParser.g4}:
59   * </p>
60   * <pre>
61   * packageDeclaration
62   *     : annotations[true] LITERAL_PACKAGE qualifiedName SEMI
63   *     ;
64   * </pre>
65   *
66   * <p>
67   * We override the {@code visitPackageDeclaration} method generated by ANTLR in
68   * {@link JavaLanguageParser} at
69   * {@link JavaAstVisitor#visitPackageDeclaration(JavaLanguageParser.PackageDeclarationContext)}
70   * to create a subtree based on the subrules and tokens found in the {@code packageDeclaration}
71   * subrule accordingly, thus producing the following AST:
72   * </p>
73   * <pre>
74   * PACKAGE_DEF -&gt; package
75   * |--ANNOTATIONS -&gt; ANNOTATIONS
76   * |--DOT -&gt; .
77   * |   |--DOT -&gt; .
78   * |   |   |--DOT -&gt; .
79   * |   |   |   |--IDENT -&gt; com
80   * |   |   |   `--IDENT -&gt; puppycrawl
81   * |   |   `--IDENT -&gt; tools
82   * |   `--IDENT -&gt; checkstyle
83   * `--SEMI -&gt; ;
84   * </pre>
85   *
86   * <p>
87   * See <a href="https://github.com/checkstyle/checkstyle/pull/10434">#10434</a>
88   * for a good example of how
89   * to make changes to Checkstyle's grammar and AST.
90   * </p>
91   *
92   * <p>
93   * The order of {@code visit...} methods in {@code JavaAstVisitor.java} and production rules in
94   * {@code JavaLanguageParser.g4} should be consistent to ease maintenance.
95   * </p>
96   */
97  public final class JavaAstVisitor extends JavaLanguageParserBaseVisitor<DetailAstImpl> {
98  
99      /** String representation of the left shift operator. */
100     private static final String LEFT_SHIFT = "<<";
101 
102     /** String representation of the unsigned right shift operator. */
103     private static final String UNSIGNED_RIGHT_SHIFT = ">>>";
104 
105     /** String representation of the right shift operator. */
106     private static final String RIGHT_SHIFT = ">>";
107 
108     /**
109      * The tokens here are technically expressions, but should
110      * not return an EXPR token as their root.
111      */
112     private static final int[] EXPRESSIONS_WITH_NO_EXPR_ROOT = {
113         TokenTypes.CTOR_CALL,
114         TokenTypes.SUPER_CTOR_CALL,
115         TokenTypes.LAMBDA,
116     };
117 
118     /** Token stream to check for hidden tokens. */
119     private final BufferedTokenStream tokens;
120 
121     /**
122      * Constructs a JavaAstVisitor with given token stream.
123      *
124      * @param tokenStream the token stream to check for hidden tokens
125      */
126     public JavaAstVisitor(CommonTokenStream tokenStream) {
127         tokens = tokenStream;
128     }
129 
130     @Override
131     public DetailAstImpl visitCompilationUnit(JavaLanguageParser.CompilationUnitContext ctx) {
132         final DetailAstImpl compilationUnit;
133         // 'EOF' token is always present; therefore if we only have one child, we have an empty file
134         final boolean isEmptyFile = ctx.children.size() == 1;
135         if (isEmptyFile) {
136             compilationUnit = null;
137         }
138         else {
139             compilationUnit = createImaginary(TokenTypes.COMPILATION_UNIT);
140             // last child is 'EOF', we do not include this token in AST
141             processChildren(compilationUnit, ctx.children.subList(0, ctx.children.size() - 1));
142         }
143         return compilationUnit;
144     }
145 
146     @Override
147     public DetailAstImpl visitPackageDeclaration(
148             JavaLanguageParser.PackageDeclarationContext ctx) {
149         final DetailAstImpl packageDeclaration =
150                 create(TokenTypes.PACKAGE_DEF, (Token) ctx.LITERAL_PACKAGE().getPayload());
151         packageDeclaration.addChild(visit(ctx.annotations()));
152         packageDeclaration.addChild(visit(ctx.qualifiedName()));
153         packageDeclaration.addChild(create(ctx.SEMI()));
154         return packageDeclaration;
155     }
156 
157     @Override
158     public DetailAstImpl visitImportDec(JavaLanguageParser.ImportDecContext ctx) {
159         final DetailAstImpl importRoot = create(ctx.start);
160 
161         // Static import
162         final TerminalNode literalStaticNode = ctx.LITERAL_STATIC();
163         if (literalStaticNode != null) {
164             importRoot.setType(TokenTypes.STATIC_IMPORT);
165             importRoot.addChild(create(literalStaticNode));
166         }
167 
168         // Handle star imports
169         final boolean isStarImport = ctx.STAR() != null;
170         if (isStarImport) {
171             final DetailAstImpl dot = create(ctx.DOT());
172             dot.addChild(visit(ctx.qualifiedName()));
173             dot.addChild(create(ctx.STAR()));
174             importRoot.addChild(dot);
175         }
176         else {
177             importRoot.addChild(visit(ctx.qualifiedName()));
178         }
179 
180         importRoot.addChild(create(ctx.SEMI()));
181         return importRoot;
182     }
183 
184     @Override
185     public DetailAstImpl visitSingleSemiImport(JavaLanguageParser.SingleSemiImportContext ctx) {
186         return create(ctx.SEMI());
187     }
188 
189     @Override
190     public DetailAstImpl visitTypeDeclaration(JavaLanguageParser.TypeDeclarationContext ctx) {
191         final DetailAstImpl typeDeclaration;
192         if (ctx.type == null) {
193             typeDeclaration = create(ctx.semi.get(0));
194             ctx.semi.subList(1, ctx.semi.size())
195                     .forEach(semi -> addLastSibling(typeDeclaration, create(semi)));
196         }
197         else {
198             typeDeclaration = visit(ctx.type);
199         }
200         return typeDeclaration;
201     }
202 
203     @Override
204     public DetailAstImpl visitModifier(JavaLanguageParser.ModifierContext ctx) {
205         return flattenedTree(ctx);
206     }
207 
208     @Override
209     public DetailAstImpl visitVariableModifier(JavaLanguageParser.VariableModifierContext ctx) {
210         return flattenedTree(ctx);
211     }
212 
213     @Override
214     public DetailAstImpl visitClassDeclaration(JavaLanguageParser.ClassDeclarationContext ctx) {
215         return createTypeDeclaration(ctx, TokenTypes.CLASS_DEF, ctx.mods);
216     }
217 
218     @Override
219     public DetailAstImpl visitRecordDeclaration(JavaLanguageParser.RecordDeclarationContext ctx) {
220         return createTypeDeclaration(ctx, TokenTypes.RECORD_DEF, ctx.mods);
221     }
222 
223     @Override
224     public DetailAstImpl visitRecordComponentsList(
225             JavaLanguageParser.RecordComponentsListContext ctx) {
226         final DetailAstImpl lparen = create(ctx.LPAREN());
227 
228         // We make a "RECORD_COMPONENTS" node whether components exist or not
229         if (ctx.recordComponents() == null) {
230             addLastSibling(lparen, createImaginary(TokenTypes.RECORD_COMPONENTS));
231         }
232         else {
233             addLastSibling(lparen, visit(ctx.recordComponents()));
234         }
235         addLastSibling(lparen, create(ctx.RPAREN()));
236         return lparen;
237     }
238 
239     @Override
240     public DetailAstImpl visitRecordComponents(JavaLanguageParser.RecordComponentsContext ctx) {
241         final DetailAstImpl recordComponents = createImaginary(TokenTypes.RECORD_COMPONENTS);
242         processChildren(recordComponents, ctx.children);
243         return recordComponents;
244     }
245 
246     @Override
247     public DetailAstImpl visitRecordComponent(JavaLanguageParser.RecordComponentContext ctx) {
248         final DetailAstImpl recordComponent = createImaginary(TokenTypes.RECORD_COMPONENT_DEF);
249         processChildren(recordComponent, ctx.children);
250         return recordComponent;
251     }
252 
253     @Override
254     public DetailAstImpl visitLastRecordComponent(
255             JavaLanguageParser.LastRecordComponentContext ctx) {
256         final DetailAstImpl recordComponent = createImaginary(TokenTypes.RECORD_COMPONENT_DEF);
257         processChildren(recordComponent, ctx.children);
258         return recordComponent;
259     }
260 
261     @Override
262     public DetailAstImpl visitRecordBody(JavaLanguageParser.RecordBodyContext ctx) {
263         final DetailAstImpl objBlock = createImaginary(TokenTypes.OBJBLOCK);
264         processChildren(objBlock, ctx.children);
265         return objBlock;
266     }
267 
268     @Override
269     public DetailAstImpl visitCompactConstructorDeclaration(
270             JavaLanguageParser.CompactConstructorDeclarationContext ctx) {
271         final DetailAstImpl compactConstructor = createImaginary(TokenTypes.COMPACT_CTOR_DEF);
272         compactConstructor.addChild(createModifiers(ctx.mods));
273         compactConstructor.addChild(visit(ctx.id()));
274         compactConstructor.addChild(visit(ctx.constructorBlock()));
275         return compactConstructor;
276     }
277 
278     @Override
279     public DetailAstImpl visitClassExtends(JavaLanguageParser.ClassExtendsContext ctx) {
280         final DetailAstImpl classExtends = create(ctx.EXTENDS_CLAUSE());
281         classExtends.addChild(visit(ctx.type));
282         return classExtends;
283     }
284 
285     @Override
286     public DetailAstImpl visitImplementsClause(JavaLanguageParser.ImplementsClauseContext ctx) {
287         final DetailAstImpl classImplements = create(TokenTypes.IMPLEMENTS_CLAUSE,
288                 (Token) ctx.LITERAL_IMPLEMENTS().getPayload());
289         classImplements.addChild(visit(ctx.typeList()));
290         return classImplements;
291     }
292 
293     @Override
294     public DetailAstImpl visitTypeParameters(JavaLanguageParser.TypeParametersContext ctx) {
295         final DetailAstImpl typeParameters = createImaginary(TokenTypes.TYPE_PARAMETERS);
296         typeParameters.addChild(create(TokenTypes.GENERIC_START, (Token) ctx.LT().getPayload()));
297         // Exclude '<' and '>'
298         processChildren(typeParameters, ctx.children.subList(1, ctx.children.size() - 1));
299         typeParameters.addChild(create(TokenTypes.GENERIC_END, (Token) ctx.GT().getPayload()));
300         return typeParameters;
301     }
302 
303     @Override
304     public DetailAstImpl visitTypeParameter(JavaLanguageParser.TypeParameterContext ctx) {
305         final DetailAstImpl typeParameter = createImaginary(TokenTypes.TYPE_PARAMETER);
306         processChildren(typeParameter, ctx.children);
307         return typeParameter;
308     }
309 
310     @Override
311     public DetailAstImpl visitTypeUpperBounds(JavaLanguageParser.TypeUpperBoundsContext ctx) {
312         // In this case, we call 'extends` TYPE_UPPER_BOUNDS
313         final DetailAstImpl typeUpperBounds = create(TokenTypes.TYPE_UPPER_BOUNDS,
314                 (Token) ctx.EXTENDS_CLAUSE().getPayload());
315         // 'extends' is child[0]
316         processChildren(typeUpperBounds, ctx.children.subList(1, ctx.children.size()));
317         return typeUpperBounds;
318     }
319 
320     @Override
321     public DetailAstImpl visitTypeBound(JavaLanguageParser.TypeBoundContext ctx) {
322         final DetailAstImpl typeBoundType = visit(ctx.typeBoundType(0));
323         final Iterator<JavaLanguageParser.TypeBoundTypeContext> typeBoundTypeIterator =
324                 ctx.typeBoundType().listIterator(1);
325         ctx.BAND().forEach(band -> {
326             addLastSibling(typeBoundType, create(TokenTypes.TYPE_EXTENSION_AND,
327                                 (Token) band.getPayload()));
328             addLastSibling(typeBoundType, visit(typeBoundTypeIterator.next()));
329         });
330         return typeBoundType;
331     }
332 
333     @Override
334     public DetailAstImpl visitTypeBoundType(JavaLanguageParser.TypeBoundTypeContext ctx) {
335         return flattenedTree(ctx);
336     }
337 
338     @Override
339     public DetailAstImpl visitEnumDeclaration(JavaLanguageParser.EnumDeclarationContext ctx) {
340         return createTypeDeclaration(ctx, TokenTypes.ENUM_DEF, ctx.mods);
341     }
342 
343     @Override
344     public DetailAstImpl visitEnumBody(JavaLanguageParser.EnumBodyContext ctx) {
345         final DetailAstImpl objBlock = createImaginary(TokenTypes.OBJBLOCK);
346         processChildren(objBlock, ctx.children);
347         return objBlock;
348     }
349 
350     @Override
351     public DetailAstImpl visitEnumConstants(JavaLanguageParser.EnumConstantsContext ctx) {
352         return flattenedTree(ctx);
353     }
354 
355     @Override
356     public DetailAstImpl visitEnumConstant(JavaLanguageParser.EnumConstantContext ctx) {
357         final DetailAstImpl enumConstant =
358                 createImaginary(TokenTypes.ENUM_CONSTANT_DEF);
359         processChildren(enumConstant, ctx.children);
360         return enumConstant;
361     }
362 
363     @Override
364     public DetailAstImpl visitEnumBodyDeclarations(
365             JavaLanguageParser.EnumBodyDeclarationsContext ctx) {
366         return flattenedTree(ctx);
367     }
368 
369     @Override
370     public DetailAstImpl visitInterfaceDeclaration(
371             JavaLanguageParser.InterfaceDeclarationContext ctx) {
372         return createTypeDeclaration(ctx, TokenTypes.INTERFACE_DEF, ctx.mods);
373     }
374 
375     @Override
376     public DetailAstImpl visitInterfaceExtends(JavaLanguageParser.InterfaceExtendsContext ctx) {
377         final DetailAstImpl interfaceExtends = create(ctx.EXTENDS_CLAUSE());
378         interfaceExtends.addChild(visit(ctx.typeList()));
379         return interfaceExtends;
380     }
381 
382     @Override
383     public DetailAstImpl visitClassBody(JavaLanguageParser.ClassBodyContext ctx) {
384         final DetailAstImpl objBlock = createImaginary(TokenTypes.OBJBLOCK);
385         processChildren(objBlock, ctx.children);
386         return objBlock;
387     }
388 
389     @Override
390     public DetailAstImpl visitInterfaceBody(JavaLanguageParser.InterfaceBodyContext ctx) {
391         final DetailAstImpl objBlock = createImaginary(TokenTypes.OBJBLOCK);
392         processChildren(objBlock, ctx.children);
393         return objBlock;
394     }
395 
396     @Override
397     public DetailAstImpl visitEmptyClass(JavaLanguageParser.EmptyClassContext ctx) {
398         return flattenedTree(ctx);
399     }
400 
401     @Override
402     public DetailAstImpl visitClassBlock(JavaLanguageParser.ClassBlockContext ctx) {
403         final DetailAstImpl classBlock;
404         if (ctx.LITERAL_STATIC() == null) {
405             // We call it an INSTANCE_INIT
406             classBlock = createImaginary(TokenTypes.INSTANCE_INIT);
407         }
408         else {
409             classBlock = create(TokenTypes.STATIC_INIT, (Token) ctx.LITERAL_STATIC().getPayload());
410             classBlock.setText(TokenUtil.getTokenName(TokenTypes.STATIC_INIT));
411         }
412         classBlock.addChild(visit(ctx.block()));
413         return classBlock;
414     }
415 
416     @Override
417     public DetailAstImpl visitMethodDeclaration(JavaLanguageParser.MethodDeclarationContext ctx) {
418         final DetailAstImpl methodDef = createImaginary(TokenTypes.METHOD_DEF);
419         methodDef.addChild(createModifiers(ctx.mods));
420 
421         // Process all children except C style array declarators
422         processChildren(methodDef, ctx.children.stream()
423                 .filter(child -> !(child instanceof JavaLanguageParser.ArrayDeclaratorContext))
424                 .toList());
425 
426         // We add C style array declarator brackets to TYPE ast
427         final DetailAstImpl typeAst = (DetailAstImpl) methodDef.findFirstToken(TokenTypes.TYPE);
428         ctx.cStyleArrDec.forEach(child -> typeAst.addChild(visit(child)));
429 
430         return methodDef;
431     }
432 
433     @Override
434     public DetailAstImpl visitMethodBody(JavaLanguageParser.MethodBodyContext ctx) {
435         return flattenedTree(ctx);
436     }
437 
438     @Override
439     public DetailAstImpl visitThrowsList(JavaLanguageParser.ThrowsListContext ctx) {
440         final DetailAstImpl throwsRoot = create(ctx.LITERAL_THROWS());
441         throwsRoot.addChild(visit(ctx.qualifiedNameList()));
442         return throwsRoot;
443     }
444 
445     @Override
446     public DetailAstImpl visitConstructorDeclaration(
447             JavaLanguageParser.ConstructorDeclarationContext ctx) {
448         final DetailAstImpl constructorDeclaration = createImaginary(TokenTypes.CTOR_DEF);
449         constructorDeclaration.addChild(createModifiers(ctx.mods));
450         processChildren(constructorDeclaration, ctx.children);
451         return constructorDeclaration;
452     }
453 
454     @Override
455     public DetailAstImpl visitFieldDeclaration(JavaLanguageParser.FieldDeclarationContext ctx) {
456         final DetailAstImpl dummyNode = new DetailAstImpl();
457         // Since the TYPE AST is built by visitVariableDeclarator(), we skip it here (child [0])
458         // We also append the SEMI token to the first child [size() - 1],
459         // until https://github.com/checkstyle/checkstyle/issues/3151
460         processChildren(dummyNode, ctx.children.subList(1, ctx.children.size() - 1));
461         dummyNode.getFirstChild().addChild(create(ctx.SEMI()));
462         return dummyNode.getFirstChild();
463     }
464 
465     @Override
466     public DetailAstImpl visitInterfaceBodyDeclaration(
467             JavaLanguageParser.InterfaceBodyDeclarationContext ctx) {
468         final DetailAstImpl returnTree;
469         if (ctx.SEMI() == null) {
470             returnTree = visit(ctx.interfaceMemberDeclaration());
471         }
472         else {
473             returnTree = create(ctx.SEMI());
474         }
475         return returnTree;
476     }
477 
478     @Override
479     public DetailAstImpl visitInterfaceMethodDeclaration(
480             JavaLanguageParser.InterfaceMethodDeclarationContext ctx) {
481         final DetailAstImpl methodDef = createImaginary(TokenTypes.METHOD_DEF);
482         methodDef.addChild(createModifiers(ctx.mods));
483 
484         // Process all children except C style array declarators and modifiers
485         final List<ParseTree> children = ctx.children
486                 .stream()
487                 .filter(child -> !(child instanceof JavaLanguageParser.ArrayDeclaratorContext))
488                 .toList();
489         processChildren(methodDef, children);
490 
491         // We add C style array declarator brackets to TYPE ast
492         final DetailAstImpl typeAst = (DetailAstImpl) methodDef.findFirstToken(TokenTypes.TYPE);
493         ctx.cStyleArrDec.forEach(child -> typeAst.addChild(visit(child)));
494 
495         return methodDef;
496     }
497 
498     @Override
499     public DetailAstImpl visitVariableDeclarators(
500             JavaLanguageParser.VariableDeclaratorsContext ctx) {
501         return flattenedTree(ctx);
502     }
503 
504     @Override
505     public DetailAstImpl visitVariableDeclarator(
506             JavaLanguageParser.VariableDeclaratorContext ctx) {
507         final DetailAstImpl variableDef = createImaginary(TokenTypes.VARIABLE_DEF);
508         variableDef.addChild(createModifiers(ctx.mods));
509 
510         final DetailAstImpl type = visit(ctx.type);
511         variableDef.addChild(type);
512         variableDef.addChild(visit(ctx.id()));
513 
514         // Add C style array declarator brackets to TYPE ast
515         ctx.arrayDeclarator().forEach(child -> type.addChild(visit(child)));
516 
517         // If this is an assignment statement, ASSIGN becomes the parent of EXPR
518         final TerminalNode assignNode = ctx.ASSIGN();
519         if (assignNode != null) {
520             final DetailAstImpl assign = create(assignNode);
521             variableDef.addChild(assign);
522             assign.addChild(visit(ctx.variableInitializer()));
523         }
524         return variableDef;
525     }
526 
527     @Override
528     public DetailAstImpl visitVariableDeclaratorId(
529             JavaLanguageParser.VariableDeclaratorIdContext ctx) {
530         final DetailAstImpl root = new DetailAstImpl();
531         root.addChild(createModifiers(ctx.mods));
532         final DetailAstImpl type = visit(ctx.type);
533         root.addChild(type);
534 
535         final DetailAstImpl declaratorId;
536         if (ctx.LITERAL_THIS() == null) {
537             declaratorId = visit(ctx.qualifiedName());
538         }
539         else if (ctx.DOT() == null) {
540             declaratorId = create(ctx.LITERAL_THIS());
541         }
542         else {
543             declaratorId = create(ctx.DOT());
544             declaratorId.addChild(visit(ctx.qualifiedName()));
545             declaratorId.addChild(create(ctx.LITERAL_THIS()));
546         }
547 
548         root.addChild(declaratorId);
549         ctx.arrayDeclarator().forEach(child -> type.addChild(visit(child)));
550 
551         return root.getFirstChild();
552     }
553 
554     @Override
555     public DetailAstImpl visitArrayInitializer(JavaLanguageParser.ArrayInitializerContext ctx) {
556         final DetailAstImpl arrayInitializer = create(TokenTypes.ARRAY_INIT, ctx.start);
557         // ARRAY_INIT was child[0]
558         processChildren(arrayInitializer, ctx.children.subList(1, ctx.children.size()));
559         return arrayInitializer;
560     }
561 
562     @Override
563     public DetailAstImpl visitClassOrInterfaceType(
564             JavaLanguageParser.ClassOrInterfaceTypeContext ctx) {
565         final DetailAstPair currentAST = new DetailAstPair();
566         DetailAstPair.addAstChild(currentAST, visit(ctx.id()));
567         DetailAstPair.addAstChild(currentAST, visit(ctx.typeArguments()));
568 
569         // This is how we build the annotations/ qualified name/ type parameters tree
570         for (ParserRuleContext extendedContext : ctx.extended) {
571             final DetailAstImpl dot = create(extendedContext.start);
572             DetailAstPair.makeAstRoot(currentAST, dot);
573             extendedContext.children
574                 .forEach(child -> DetailAstPair.addAstChild(currentAST, visit(child)));
575         }
576 
577         // Create imaginary 'TYPE' parent if specified
578         final DetailAstImpl returnTree;
579         if (ctx.createImaginaryNode) {
580             returnTree = createImaginary(TokenTypes.TYPE);
581             returnTree.addChild(currentAST.root);
582         }
583         else {
584             returnTree = currentAST.root;
585         }
586         return returnTree;
587     }
588 
589     @Override
590     public DetailAstImpl visitSimpleTypeArgument(
591             JavaLanguageParser.SimpleTypeArgumentContext ctx) {
592         final DetailAstImpl typeArgument =
593                 createImaginary(TokenTypes.TYPE_ARGUMENT);
594         typeArgument.addChild(visit(ctx.typeType()));
595         return typeArgument;
596     }
597 
598     @Override
599     public DetailAstImpl visitWildCardTypeArgument(
600             JavaLanguageParser.WildCardTypeArgumentContext ctx) {
601         final DetailAstImpl typeArgument = createImaginary(TokenTypes.TYPE_ARGUMENT);
602         typeArgument.addChild(visit(ctx.annotations()));
603         typeArgument.addChild(create(TokenTypes.WILDCARD_TYPE,
604                 (Token) ctx.QUESTION().getPayload()));
605 
606         if (ctx.upperBound != null) {
607             final DetailAstImpl upperBound = create(TokenTypes.TYPE_UPPER_BOUNDS, ctx.upperBound);
608             upperBound.addChild(visit(ctx.typeType()));
609             typeArgument.addChild(upperBound);
610         }
611         else if (ctx.lowerBound != null) {
612             final DetailAstImpl lowerBound = create(TokenTypes.TYPE_LOWER_BOUNDS, ctx.lowerBound);
613             lowerBound.addChild(visit(ctx.typeType()));
614             typeArgument.addChild(lowerBound);
615         }
616 
617         return typeArgument;
618     }
619 
620     @Override
621     public DetailAstImpl visitQualifiedNameList(JavaLanguageParser.QualifiedNameListContext ctx) {
622         return flattenedTree(ctx);
623     }
624 
625     @Override
626     public DetailAstImpl visitFormalParameters(JavaLanguageParser.FormalParametersContext ctx) {
627         final DetailAstImpl lparen = create(ctx.LPAREN());
628 
629         // We make a "PARAMETERS" node whether parameters exist or not
630         if (ctx.formalParameterList() == null) {
631             addLastSibling(lparen, createImaginary(TokenTypes.PARAMETERS));
632         }
633         else {
634             addLastSibling(lparen, visit(ctx.formalParameterList()));
635         }
636         addLastSibling(lparen, create(ctx.RPAREN()));
637         return lparen;
638     }
639 
640     @Override
641     public DetailAstImpl visitFormalParameterList(
642             JavaLanguageParser.FormalParameterListContext ctx) {
643         final DetailAstImpl parameters = createImaginary(TokenTypes.PARAMETERS);
644         processChildren(parameters, ctx.children);
645         return parameters;
646     }
647 
648     @Override
649     public DetailAstImpl visitFormalParameter(JavaLanguageParser.FormalParameterContext ctx) {
650         final DetailAstImpl variableDeclaratorId =
651                 visitVariableDeclaratorId(ctx.variableDeclaratorId());
652         final DetailAstImpl parameterDef = createImaginary(TokenTypes.PARAMETER_DEF);
653         parameterDef.addChild(variableDeclaratorId);
654         return parameterDef;
655     }
656 
657     @Override
658     public DetailAstImpl visitLastFormalParameter(
659             JavaLanguageParser.LastFormalParameterContext ctx) {
660         final DetailAstImpl parameterDef =
661                 createImaginary(TokenTypes.PARAMETER_DEF);
662         parameterDef.addChild(visit(ctx.variableDeclaratorId()));
663         final DetailAstImpl ident = (DetailAstImpl) parameterDef.findFirstToken(TokenTypes.IDENT);
664         ident.addPreviousSibling(create(ctx.ELLIPSIS()));
665         // We attach annotations on ellipses in varargs to the 'TYPE' ast
666         final DetailAstImpl type = (DetailAstImpl) parameterDef.findFirstToken(TokenTypes.TYPE);
667         type.addChild(visit(ctx.annotations()));
668         return parameterDef;
669     }
670 
671     @Override
672     public DetailAstImpl visitQualifiedName(JavaLanguageParser.QualifiedNameContext ctx) {
673         final DetailAstImpl ast = visit(ctx.id());
674         final DetailAstPair currentAst = new DetailAstPair();
675         DetailAstPair.addAstChild(currentAst, ast);
676 
677         for (ParserRuleContext extendedContext : ctx.extended) {
678             final DetailAstImpl dot = create(extendedContext.start);
679             DetailAstPair.makeAstRoot(currentAst, dot);
680             final List<ParseTree> childList = extendedContext
681                     .children.subList(1, extendedContext.children.size());
682             processChildren(dot, childList);
683         }
684         return currentAst.getRoot();
685     }
686 
687     @Override
688     public DetailAstImpl visitLiteral(JavaLanguageParser.LiteralContext ctx) {
689         return flattenedTree(ctx);
690     }
691 
692     @Override
693     public DetailAstImpl visitIntegerLiteral(JavaLanguageParser.IntegerLiteralContext ctx) {
694         final int[] longTypes = {
695             JavaLanguageLexer.DECIMAL_LITERAL_LONG,
696             JavaLanguageLexer.HEX_LITERAL_LONG,
697             JavaLanguageLexer.OCT_LITERAL_LONG,
698             JavaLanguageLexer.BINARY_LITERAL_LONG,
699         };
700 
701         final int tokenType;
702         if (TokenUtil.isOfType(ctx.start.getType(), longTypes)) {
703             tokenType = TokenTypes.NUM_LONG;
704         }
705         else {
706             tokenType = TokenTypes.NUM_INT;
707         }
708 
709         return create(tokenType, ctx.start);
710     }
711 
712     @Override
713     public DetailAstImpl visitFloatLiteral(JavaLanguageParser.FloatLiteralContext ctx) {
714         final DetailAstImpl floatLiteral;
715         if (TokenUtil.isOfType(ctx.start.getType(),
716                 JavaLanguageLexer.DOUBLE_LITERAL, JavaLanguageLexer.HEX_DOUBLE_LITERAL)) {
717             floatLiteral = create(TokenTypes.NUM_DOUBLE, ctx.start);
718         }
719         else {
720             floatLiteral = create(TokenTypes.NUM_FLOAT, ctx.start);
721         }
722         return floatLiteral;
723     }
724 
725     @Override
726     public DetailAstImpl visitTextBlockLiteral(JavaLanguageParser.TextBlockLiteralContext ctx) {
727         final DetailAstImpl textBlockLiteralBegin = create(ctx.TEXT_BLOCK_LITERAL_BEGIN());
728         textBlockLiteralBegin.addChild(create(ctx.TEXT_BLOCK_CONTENT()));
729         textBlockLiteralBegin.addChild(create(ctx.TEXT_BLOCK_LITERAL_END()));
730         return textBlockLiteralBegin;
731     }
732 
733     @Override
734     public DetailAstImpl visitAnnotations(JavaLanguageParser.AnnotationsContext ctx) {
735         final DetailAstImpl annotations;
736 
737         if (!ctx.createImaginaryNode && ctx.anno.isEmpty()) {
738             // There are no annotations, and we don't want to create the empty node
739             annotations = null;
740         }
741         else {
742             // There are annotations, or we just want the empty node
743             annotations = createImaginary(TokenTypes.ANNOTATIONS);
744             processChildren(annotations, ctx.anno);
745         }
746 
747         return annotations;
748     }
749 
750     @Override
751     public DetailAstImpl visitAnnotation(JavaLanguageParser.AnnotationContext ctx) {
752         final DetailAstImpl annotation = createImaginary(TokenTypes.ANNOTATION);
753         processChildren(annotation, ctx.children);
754         return annotation;
755     }
756 
757     @Override
758     public DetailAstImpl visitElementValuePairs(JavaLanguageParser.ElementValuePairsContext ctx) {
759         return flattenedTree(ctx);
760     }
761 
762     @Override
763     public DetailAstImpl visitElementValuePair(JavaLanguageParser.ElementValuePairContext ctx) {
764         final DetailAstImpl elementValuePair =
765                 createImaginary(TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR);
766         processChildren(elementValuePair, ctx.children);
767         return elementValuePair;
768     }
769 
770     @Override
771     public DetailAstImpl visitElementValue(JavaLanguageParser.ElementValueContext ctx) {
772         return flattenedTree(ctx);
773     }
774 
775     @Override
776     public DetailAstImpl visitElementValueArrayInitializer(
777             JavaLanguageParser.ElementValueArrayInitializerContext ctx) {
778         final DetailAstImpl arrayInit =
779                 create(TokenTypes.ANNOTATION_ARRAY_INIT, (Token) ctx.LCURLY().getPayload());
780         processChildren(arrayInit, ctx.children.subList(1, ctx.children.size()));
781         return arrayInit;
782     }
783 
784     @Override
785     public DetailAstImpl visitAnnotationTypeDeclaration(
786             JavaLanguageParser.AnnotationTypeDeclarationContext ctx) {
787         return createTypeDeclaration(ctx, TokenTypes.ANNOTATION_DEF, ctx.mods);
788     }
789 
790     @Override
791     public DetailAstImpl visitAnnotationTypeBody(
792             JavaLanguageParser.AnnotationTypeBodyContext ctx) {
793         final DetailAstImpl objBlock = createImaginary(TokenTypes.OBJBLOCK);
794         processChildren(objBlock, ctx.children);
795         return objBlock;
796     }
797 
798     @Override
799     public DetailAstImpl visitAnnotationTypeElementDeclaration(
800             JavaLanguageParser.AnnotationTypeElementDeclarationContext ctx) {
801         final DetailAstImpl returnTree;
802         if (ctx.SEMI() == null) {
803             returnTree = visit(ctx.annotationTypeElementRest());
804         }
805         else {
806             returnTree = create(ctx.SEMI());
807         }
808         return returnTree;
809     }
810 
811     @Override
812     public DetailAstImpl visitAnnotationField(JavaLanguageParser.AnnotationFieldContext ctx) {
813         final DetailAstImpl dummyNode = new DetailAstImpl();
814         // Since the TYPE AST is built by visitAnnotationMethodOrConstantRest(), we skip it
815         // here (child [0])
816         processChildren(dummyNode, Collections.singletonList(ctx.children.get(1)));
817         // We also append the SEMI token to the first child [size() - 1],
818         // until https://github.com/checkstyle/checkstyle/issues/3151
819         dummyNode.getFirstChild().addChild(create(ctx.SEMI()));
820         return dummyNode.getFirstChild();
821     }
822 
823     @Override
824     public DetailAstImpl visitAnnotationType(JavaLanguageParser.AnnotationTypeContext ctx) {
825         return flattenedTree(ctx);
826     }
827 
828     @Override
829     public DetailAstImpl visitAnnotationMethodRest(
830             JavaLanguageParser.AnnotationMethodRestContext ctx) {
831         final DetailAstImpl annotationFieldDef =
832                 createImaginary(TokenTypes.ANNOTATION_FIELD_DEF);
833         annotationFieldDef.addChild(createModifiers(ctx.mods));
834         annotationFieldDef.addChild(visit(ctx.type));
835 
836         // Process all children except C style array declarators
837         processChildren(annotationFieldDef, ctx.children.stream()
838                 .filter(child -> !(child instanceof JavaLanguageParser.ArrayDeclaratorContext))
839                 .toList());
840 
841         // We add C style array declarator brackets to TYPE ast
842         final DetailAstImpl typeAst =
843                 (DetailAstImpl) annotationFieldDef.findFirstToken(TokenTypes.TYPE);
844         ctx.cStyleArrDec.forEach(child -> typeAst.addChild(visit(child)));
845 
846         return annotationFieldDef;
847     }
848 
849     @Override
850     public DetailAstImpl visitDefaultValue(JavaLanguageParser.DefaultValueContext ctx) {
851         final DetailAstImpl defaultValue = create(ctx.LITERAL_DEFAULT());
852         defaultValue.addChild(visit(ctx.elementValue()));
853         return defaultValue;
854     }
855 
856     @Override
857     public DetailAstImpl visitConstructorBlock(JavaLanguageParser.ConstructorBlockContext ctx) {
858         final DetailAstImpl slist = create(TokenTypes.SLIST, ctx.start);
859         // SLIST was child [0]
860         processChildren(slist, ctx.children.subList(1, ctx.children.size()));
861         return slist;
862     }
863 
864     @Override
865     public DetailAstImpl visitExplicitCtorCall(JavaLanguageParser.ExplicitCtorCallContext ctx) {
866         final DetailAstImpl root;
867         if (ctx.LITERAL_THIS() == null) {
868             root = create(TokenTypes.SUPER_CTOR_CALL, (Token) ctx.LITERAL_SUPER().getPayload());
869         }
870         else {
871             root = create(TokenTypes.CTOR_CALL, (Token) ctx.LITERAL_THIS().getPayload());
872         }
873         root.addChild(visit(ctx.typeArguments()));
874         root.addChild(visit(ctx.arguments()));
875         root.addChild(create(ctx.SEMI()));
876         return root;
877     }
878 
879     @Override
880     public DetailAstImpl visitPrimaryCtorCall(JavaLanguageParser.PrimaryCtorCallContext ctx) {
881         final DetailAstImpl primaryCtorCall = create(TokenTypes.SUPER_CTOR_CALL,
882                 (Token) ctx.LITERAL_SUPER().getPayload());
883         // filter 'LITERAL_SUPER'
884         processChildren(primaryCtorCall, ctx.children.stream()
885                    .filter(child -> !child.equals(ctx.LITERAL_SUPER()))
886                    .toList());
887         return primaryCtorCall;
888     }
889 
890     @Override
891     public DetailAstImpl visitBlock(JavaLanguageParser.BlockContext ctx) {
892         final DetailAstImpl slist = create(TokenTypes.SLIST, ctx.start);
893         // SLIST was child [0]
894         processChildren(slist, ctx.children.subList(1, ctx.children.size()));
895         return slist;
896     }
897 
898     @Override
899     public DetailAstImpl visitLocalVar(JavaLanguageParser.LocalVarContext ctx) {
900         return flattenedTree(ctx);
901     }
902 
903     @Override
904     public DetailAstImpl visitBlockStat(JavaLanguageParser.BlockStatContext ctx) {
905         return flattenedTree(ctx);
906     }
907 
908     @Override
909     public DetailAstImpl visitAssertExp(JavaLanguageParser.AssertExpContext ctx) {
910         final DetailAstImpl assertExp = create(ctx.ASSERT());
911         // child[0] is 'ASSERT'
912         processChildren(assertExp, ctx.children.subList(1, ctx.children.size()));
913         return assertExp;
914     }
915 
916     @Override
917     public DetailAstImpl visitIfStat(JavaLanguageParser.IfStatContext ctx) {
918         final DetailAstImpl ifStat = create(ctx.LITERAL_IF());
919         // child[0] is 'LITERAL_IF'
920         processChildren(ifStat, ctx.children.subList(1, ctx.children.size()));
921         return ifStat;
922     }
923 
924     @Override
925     public DetailAstImpl visitForStat(JavaLanguageParser.ForStatContext ctx) {
926         final DetailAstImpl forInit = create(ctx.start);
927         // child[0] is LITERAL_FOR
928         processChildren(forInit, ctx.children.subList(1, ctx.children.size()));
929         return forInit;
930     }
931 
932     @Override
933     public DetailAstImpl visitWhileStat(JavaLanguageParser.WhileStatContext ctx) {
934         final DetailAstImpl whileStatement = create(ctx.start);
935         // 'LITERAL_WHILE' is child[0]
936         processChildren(whileStatement, ctx.children.subList(1, ctx.children.size()));
937         return whileStatement;
938     }
939 
940     @Override
941     public DetailAstImpl visitDoStat(JavaLanguageParser.DoStatContext ctx) {
942         final DetailAstImpl doStatement = create(ctx.start);
943         // 'LITERAL_DO' is child[0]
944         doStatement.addChild(visit(ctx.statement()));
945         // We make 'LITERAL_WHILE' into 'DO_WHILE'
946         doStatement.addChild(create(TokenTypes.DO_WHILE, (Token) ctx.LITERAL_WHILE().getPayload()));
947         doStatement.addChild(visit(ctx.parExpression()));
948         doStatement.addChild(create(ctx.SEMI()));
949         return doStatement;
950     }
951 
952     @Override
953     public DetailAstImpl visitTryStat(JavaLanguageParser.TryStatContext ctx) {
954         final DetailAstImpl tryStat = create(ctx.start);
955         // child[0] is 'LITERAL_TRY'
956         processChildren(tryStat, ctx.children.subList(1, ctx.children.size()));
957         return tryStat;
958     }
959 
960     @Override
961     public DetailAstImpl visitTryWithResourceStat(
962             JavaLanguageParser.TryWithResourceStatContext ctx) {
963         final DetailAstImpl tryWithResources = create(ctx.LITERAL_TRY());
964         // child[0] is 'LITERAL_TRY'
965         processChildren(tryWithResources, ctx.children.subList(1, ctx.children.size()));
966         return tryWithResources;
967     }
968 
969     @Override
970     public DetailAstImpl visitYieldStat(JavaLanguageParser.YieldStatContext ctx) {
971         final DetailAstImpl yieldParent = create(ctx.LITERAL_YIELD());
972         // LITERAL_YIELD is child[0]
973         processChildren(yieldParent, ctx.children.subList(1, ctx.children.size()));
974         return yieldParent;
975     }
976 
977     @Override
978     public DetailAstImpl visitSyncStat(JavaLanguageParser.SyncStatContext ctx) {
979         final DetailAstImpl syncStatement = create(ctx.start);
980         // child[0] is 'LITERAL_SYNCHRONIZED'
981         processChildren(syncStatement, ctx.children.subList(1, ctx.children.size()));
982         return syncStatement;
983     }
984 
985     @Override
986     public DetailAstImpl visitReturnStat(JavaLanguageParser.ReturnStatContext ctx) {
987         final DetailAstImpl returnStat = create(ctx.LITERAL_RETURN());
988         // child[0] is 'LITERAL_RETURN'
989         processChildren(returnStat, ctx.children.subList(1, ctx.children.size()));
990         return returnStat;
991     }
992 
993     @Override
994     public DetailAstImpl visitThrowStat(JavaLanguageParser.ThrowStatContext ctx) {
995         final DetailAstImpl throwStat = create(ctx.LITERAL_THROW());
996         // child[0] is 'LITERAL_THROW'
997         processChildren(throwStat, ctx.children.subList(1, ctx.children.size()));
998         return throwStat;
999     }
1000 
1001     @Override
1002     public DetailAstImpl visitBreakStat(JavaLanguageParser.BreakStatContext ctx) {
1003         final DetailAstImpl literalBreak = create(ctx.LITERAL_BREAK());
1004         // child[0] is 'LITERAL_BREAK'
1005         processChildren(literalBreak, ctx.children.subList(1, ctx.children.size()));
1006         return literalBreak;
1007     }
1008 
1009     @Override
1010     public DetailAstImpl visitContinueStat(JavaLanguageParser.ContinueStatContext ctx) {
1011         final DetailAstImpl continueStat = create(ctx.LITERAL_CONTINUE());
1012         // child[0] is 'LITERAL_CONTINUE'
1013         processChildren(continueStat, ctx.children.subList(1, ctx.children.size()));
1014         return continueStat;
1015     }
1016 
1017     @Override
1018     public DetailAstImpl visitEmptyStat(JavaLanguageParser.EmptyStatContext ctx) {
1019         return create(TokenTypes.EMPTY_STAT, ctx.start);
1020     }
1021 
1022     @Override
1023     public DetailAstImpl visitExpStat(JavaLanguageParser.ExpStatContext ctx) {
1024         final DetailAstImpl expStatRoot = visit(ctx.statementExpression);
1025         addLastSibling(expStatRoot, create(ctx.SEMI()));
1026         return expStatRoot;
1027     }
1028 
1029     @Override
1030     public DetailAstImpl visitLabelStat(JavaLanguageParser.LabelStatContext ctx) {
1031         final DetailAstImpl labelStat = create(TokenTypes.LABELED_STAT,
1032                 (Token) ctx.COLON().getPayload());
1033         labelStat.addChild(visit(ctx.id()));
1034         labelStat.addChild(visit(ctx.statement()));
1035         return labelStat;
1036     }
1037 
1038     @Override
1039     public DetailAstImpl visitSwitchExpressionOrStatement(
1040             JavaLanguageParser.SwitchExpressionOrStatementContext ctx) {
1041         final DetailAstImpl switchStat = create(ctx.LITERAL_SWITCH());
1042         switchStat.addChild(visit(ctx.parExpression()));
1043         switchStat.addChild(create(ctx.LCURLY()));
1044         switchStat.addChild(visit(ctx.switchBlock()));
1045         switchStat.addChild(create(ctx.RCURLY()));
1046         return switchStat;
1047     }
1048 
1049     @Override
1050     public DetailAstImpl visitSwitchRules(JavaLanguageParser.SwitchRulesContext ctx) {
1051         final DetailAstImpl dummyRoot = new DetailAstImpl();
1052         ctx.switchLabeledRule().forEach(switchLabeledRuleContext -> {
1053             final DetailAstImpl switchRule = visit(switchLabeledRuleContext);
1054             final DetailAstImpl switchRuleParent = createImaginary(TokenTypes.SWITCH_RULE);
1055             switchRuleParent.addChild(switchRule);
1056             dummyRoot.addChild(switchRuleParent);
1057         });
1058         return dummyRoot.getFirstChild();
1059     }
1060 
1061     @Override
1062     public DetailAstImpl visitSwitchBlocks(JavaLanguageParser.SwitchBlocksContext ctx) {
1063         final DetailAstImpl dummyRoot = new DetailAstImpl();
1064         ctx.groups.forEach(group -> dummyRoot.addChild(visit(group)));
1065 
1066         // Add any empty switch labels to end of statement in one 'CASE_GROUP'
1067         if (!ctx.emptyLabels.isEmpty()) {
1068             final DetailAstImpl emptyLabelParent =
1069                     createImaginary(TokenTypes.CASE_GROUP);
1070             ctx.emptyLabels.forEach(label -> emptyLabelParent.addChild(visit(label)));
1071             dummyRoot.addChild(emptyLabelParent);
1072         }
1073         return dummyRoot.getFirstChild();
1074     }
1075 
1076     @Override
1077     public DetailAstImpl visitSwitchLabeledExpression(
1078             JavaLanguageParser.SwitchLabeledExpressionContext ctx) {
1079         return flattenedTree(ctx);
1080     }
1081 
1082     @Override
1083     public DetailAstImpl visitSwitchLabeledBlock(
1084             JavaLanguageParser.SwitchLabeledBlockContext ctx) {
1085         return flattenedTree(ctx);
1086     }
1087 
1088     @Override
1089     public DetailAstImpl visitSwitchLabeledThrow(
1090             JavaLanguageParser.SwitchLabeledThrowContext ctx) {
1091         final DetailAstImpl switchLabel = visit(ctx.switchLabel());
1092         addLastSibling(switchLabel, create(ctx.LAMBDA()));
1093         final DetailAstImpl literalThrow = create(ctx.LITERAL_THROW());
1094         literalThrow.addChild(visit(ctx.expression()));
1095         literalThrow.addChild(create(ctx.SEMI()));
1096         addLastSibling(switchLabel, literalThrow);
1097         return switchLabel;
1098     }
1099 
1100     @Override
1101     public DetailAstImpl visitElseStat(JavaLanguageParser.ElseStatContext ctx) {
1102         final DetailAstImpl elseStat = create(ctx.LITERAL_ELSE());
1103         // child[0] is 'LITERAL_ELSE'
1104         processChildren(elseStat, ctx.children.subList(1, ctx.children.size()));
1105         return elseStat;
1106     }
1107 
1108     @Override
1109     public DetailAstImpl visitCatchClause(JavaLanguageParser.CatchClauseContext ctx) {
1110         final DetailAstImpl catchClause = create(TokenTypes.LITERAL_CATCH,
1111                 (Token) ctx.LITERAL_CATCH().getPayload());
1112         // 'LITERAL_CATCH' is child[0]
1113         processChildren(catchClause, ctx.children.subList(1, ctx.children.size()));
1114         return catchClause;
1115     }
1116 
1117     @Override
1118     public DetailAstImpl visitCatchParameter(JavaLanguageParser.CatchParameterContext ctx) {
1119         final DetailAstImpl catchParameterDef = createImaginary(TokenTypes.PARAMETER_DEF);
1120         catchParameterDef.addChild(createModifiers(ctx.mods));
1121         // filter mods
1122         processChildren(catchParameterDef, ctx.children.stream()
1123                 .filter(child -> !(child instanceof JavaLanguageParser.VariableModifierContext))
1124                 .toList());
1125         return catchParameterDef;
1126     }
1127 
1128     @Override
1129     public DetailAstImpl visitCatchType(JavaLanguageParser.CatchTypeContext ctx) {
1130         final DetailAstImpl type = createImaginary(TokenTypes.TYPE);
1131         processChildren(type, ctx.children);
1132         return type;
1133     }
1134 
1135     @Override
1136     public DetailAstImpl visitFinallyBlock(JavaLanguageParser.FinallyBlockContext ctx) {
1137         final DetailAstImpl finallyBlock = create(ctx.LITERAL_FINALLY());
1138         // child[0] is 'LITERAL_FINALLY'
1139         processChildren(finallyBlock, ctx.children.subList(1, ctx.children.size()));
1140         return finallyBlock;
1141     }
1142 
1143     @Override
1144     public DetailAstImpl visitResourceSpecification(
1145             JavaLanguageParser.ResourceSpecificationContext ctx) {
1146         final DetailAstImpl resourceSpecification =
1147                 createImaginary(TokenTypes.RESOURCE_SPECIFICATION);
1148         processChildren(resourceSpecification, ctx.children);
1149         return resourceSpecification;
1150     }
1151 
1152     @Override
1153     public DetailAstImpl visitResources(JavaLanguageParser.ResourcesContext ctx) {
1154         final DetailAstImpl firstResource = visit(ctx.resource(0));
1155         final DetailAstImpl resources = createImaginary(TokenTypes.RESOURCES);
1156         resources.addChild(firstResource);
1157         processChildren(resources, ctx.children.subList(1, ctx.children.size()));
1158         return resources;
1159     }
1160 
1161     @Override
1162     public DetailAstImpl visitResourceDeclaration(
1163             JavaLanguageParser.ResourceDeclarationContext ctx) {
1164         final DetailAstImpl resource = createImaginary(TokenTypes.RESOURCE);
1165         resource.addChild(visit(ctx.variableDeclaratorId()));
1166 
1167         final DetailAstImpl assign = create(ctx.ASSIGN());
1168         resource.addChild(assign);
1169         assign.addChild(visit(ctx.expression()));
1170         return resource;
1171     }
1172 
1173     @Override
1174     public DetailAstImpl visitVariableAccess(JavaLanguageParser.VariableAccessContext ctx) {
1175         final DetailAstImpl resource = createImaginary(TokenTypes.RESOURCE);
1176 
1177         final DetailAstImpl childNode;
1178         if (ctx.LITERAL_THIS() == null) {
1179             childNode = visit(ctx.id());
1180         }
1181         else {
1182             childNode = create(ctx.LITERAL_THIS());
1183         }
1184 
1185         if (ctx.accessList.isEmpty()) {
1186             resource.addChild(childNode);
1187         }
1188         else {
1189             final DetailAstPair currentAst = new DetailAstPair();
1190             ctx.accessList.forEach(fieldAccess -> {
1191                 DetailAstPair.addAstChild(currentAst, visit(fieldAccess.expr()));
1192                 DetailAstPair.makeAstRoot(currentAst, create(fieldAccess.DOT()));
1193             });
1194             resource.addChild(currentAst.getRoot());
1195             resource.getFirstChild().addChild(childNode);
1196         }
1197         return resource;
1198     }
1199 
1200     @Override
1201     public DetailAstImpl visitSwitchBlockStatementGroup(
1202             JavaLanguageParser.SwitchBlockStatementGroupContext ctx) {
1203         final DetailAstImpl caseGroup = createImaginary(TokenTypes.CASE_GROUP);
1204         processChildren(caseGroup, ctx.switchLabel());
1205         final DetailAstImpl sList = createImaginary(TokenTypes.SLIST);
1206         processChildren(sList, ctx.slists);
1207         caseGroup.addChild(sList);
1208         return caseGroup;
1209     }
1210 
1211     @Override
1212     public DetailAstImpl visitCaseLabel(JavaLanguageParser.CaseLabelContext ctx) {
1213         final DetailAstImpl caseLabel = create(ctx.LITERAL_CASE());
1214         // child [0] is 'LITERAL_CASE'
1215         processChildren(caseLabel, ctx.children.subList(1, ctx.children.size()));
1216         return caseLabel;
1217     }
1218 
1219     @Override
1220     public DetailAstImpl visitDefaultLabel(JavaLanguageParser.DefaultLabelContext ctx) {
1221         final DetailAstImpl defaultLabel = create(ctx.LITERAL_DEFAULT());
1222         if (ctx.COLON() != null) {
1223             defaultLabel.addChild(create(ctx.COLON()));
1224         }
1225         return defaultLabel;
1226     }
1227 
1228     @Override
1229     public DetailAstImpl visitCaseConstants(JavaLanguageParser.CaseConstantsContext ctx) {
1230         return flattenedTree(ctx);
1231     }
1232 
1233     @Override
1234     public DetailAstImpl visitCaseConstant(JavaLanguageParser.CaseConstantContext ctx) {
1235         return flattenedTree(ctx);
1236     }
1237 
1238     @Override
1239     public DetailAstImpl visitEnhancedFor(JavaLanguageParser.EnhancedForContext ctx) {
1240         final DetailAstImpl leftParen = create(ctx.LPAREN());
1241         final DetailAstImpl enhancedForControl =
1242                  visit(ctx.getChild(1));
1243         final DetailAstImpl forEachClause = createImaginary(TokenTypes.FOR_EACH_CLAUSE);
1244         forEachClause.addChild(enhancedForControl);
1245         addLastSibling(leftParen, forEachClause);
1246         addLastSibling(leftParen, create(ctx.RPAREN()));
1247         return leftParen;
1248     }
1249 
1250     @Override
1251     public DetailAstImpl visitForFor(JavaLanguageParser.ForForContext ctx) {
1252         final DetailAstImpl dummyRoot = new DetailAstImpl();
1253         dummyRoot.addChild(create(ctx.LPAREN()));
1254 
1255         if (ctx.forInit() == null) {
1256             final DetailAstImpl imaginaryForInitParent =
1257                     createImaginary(TokenTypes.FOR_INIT);
1258             dummyRoot.addChild(imaginaryForInitParent);
1259         }
1260         else {
1261             dummyRoot.addChild(visit(ctx.forInit()));
1262         }
1263 
1264         dummyRoot.addChild(create(ctx.SEMI(0)));
1265 
1266         final DetailAstImpl forCondParent = createImaginary(TokenTypes.FOR_CONDITION);
1267         forCondParent.addChild(visit(ctx.forCond));
1268         dummyRoot.addChild(forCondParent);
1269         dummyRoot.addChild(create(ctx.SEMI(1)));
1270 
1271         final DetailAstImpl forItParent = createImaginary(TokenTypes.FOR_ITERATOR);
1272         forItParent.addChild(visit(ctx.forUpdate));
1273         dummyRoot.addChild(forItParent);
1274 
1275         dummyRoot.addChild(create(ctx.RPAREN()));
1276 
1277         return dummyRoot.getFirstChild();
1278     }
1279 
1280     @Override
1281     public DetailAstImpl visitForInit(JavaLanguageParser.ForInitContext ctx) {
1282         final DetailAstImpl forInit = createImaginary(TokenTypes.FOR_INIT);
1283         processChildren(forInit, ctx.children);
1284         return forInit;
1285     }
1286 
1287     @Override
1288     public DetailAstImpl visitEnhancedForControl(
1289             JavaLanguageParser.EnhancedForControlContext ctx) {
1290         final DetailAstImpl variableDeclaratorId =
1291                  visit(ctx.variableDeclaratorId());
1292         final DetailAstImpl variableDef = createImaginary(TokenTypes.VARIABLE_DEF);
1293         variableDef.addChild(variableDeclaratorId);
1294 
1295         addLastSibling(variableDef, create(ctx.COLON()));
1296         addLastSibling(variableDef, visit(ctx.expression()));
1297         return variableDef;
1298     }
1299 
1300     @Override
1301     public DetailAstImpl visitEnhancedForControlWithRecordPattern(
1302             JavaLanguageParser.EnhancedForControlWithRecordPatternContext ctx) {
1303         final DetailAstImpl recordPattern =
1304                  visit(ctx.pattern());
1305         addLastSibling(recordPattern, create(ctx.COLON()));
1306         addLastSibling(recordPattern, visit(ctx.expression()));
1307         return recordPattern;
1308     }
1309 
1310     @Override
1311     public DetailAstImpl visitParExpression(JavaLanguageParser.ParExpressionContext ctx) {
1312         return flattenedTree(ctx);
1313     }
1314 
1315     @Override
1316     public DetailAstImpl visitExpressionList(JavaLanguageParser.ExpressionListContext ctx) {
1317         final DetailAstImpl elist = createImaginary(TokenTypes.ELIST);
1318         processChildren(elist, ctx.children);
1319         return elist;
1320     }
1321 
1322     @Override
1323     public DetailAstImpl visitExpression(JavaLanguageParser.ExpressionContext ctx) {
1324         return buildExpressionNode(ctx.expr());
1325     }
1326 
1327     @Override
1328     public DetailAstImpl visitRefOp(JavaLanguageParser.RefOpContext ctx) {
1329         final DetailAstImpl bop = create(ctx.bop);
1330         final DetailAstImpl leftChild = visit(ctx.expr());
1331         final DetailAstImpl rightChild = create(TokenTypes.IDENT, ctx.stop);
1332         bop.addChild(leftChild);
1333         bop.addChild(rightChild);
1334         return bop;
1335     }
1336 
1337     @Override
1338     public DetailAstImpl visitSuperExp(JavaLanguageParser.SuperExpContext ctx) {
1339         final DetailAstImpl bop = create(ctx.bop);
1340         bop.addChild(visit(ctx.expr()));
1341         bop.addChild(create(ctx.LITERAL_SUPER()));
1342         DetailAstImpl superSuffixParent = visit(ctx.superSuffix());
1343 
1344         if (superSuffixParent == null) {
1345             superSuffixParent = bop;
1346         }
1347         else {
1348             DetailAstImpl firstChild = superSuffixParent;
1349             while (firstChild.getFirstChild() != null) {
1350                 firstChild = firstChild.getFirstChild();
1351             }
1352             firstChild.addPreviousSibling(bop);
1353         }
1354 
1355         return superSuffixParent;
1356     }
1357 
1358     @Override
1359     public DetailAstImpl visitInstanceOfExp(JavaLanguageParser.InstanceOfExpContext ctx) {
1360         final DetailAstImpl literalInstanceOf = create(ctx.LITERAL_INSTANCEOF());
1361         literalInstanceOf.addChild(visit(ctx.expr()));
1362         final ParseTree patternOrType = ctx.getChild(2);
1363 
1364         final DetailAstImpl patternDef;
1365         if (patternOrType instanceof JavaLanguageParser.ParenPatternContext) {
1366             // Parenthesized pattern has a `PATTERN_DEF` parent
1367             patternDef = createImaginary(TokenTypes.PATTERN_DEF);
1368             patternDef.addChild(visit(patternOrType));
1369         }
1370         else {
1371             patternDef = visit(patternOrType);
1372         }
1373         literalInstanceOf.addChild(patternDef);
1374         return literalInstanceOf;
1375     }
1376 
1377     @Override
1378     public DetailAstImpl visitBitShift(JavaLanguageParser.BitShiftContext ctx) {
1379         final DetailAstImpl shiftOperation;
1380 
1381         // We determine the type of shift operation in the parser, instead of the
1382         // lexer as in older grammars. This makes it easier to parse type parameters
1383         // and less than/ greater than operators in general.
1384         if (ctx.LT().size() == LEFT_SHIFT.length()) {
1385             shiftOperation = create(TokenTypes.SL, (Token) ctx.LT(0).getPayload());
1386             shiftOperation.setText(LEFT_SHIFT);
1387         }
1388         else if (ctx.GT().size() == UNSIGNED_RIGHT_SHIFT.length()) {
1389             shiftOperation = create(TokenTypes.BSR, (Token) ctx.GT(0).getPayload());
1390             shiftOperation.setText(UNSIGNED_RIGHT_SHIFT);
1391         }
1392         else {
1393             shiftOperation = create(TokenTypes.SR, (Token) ctx.GT(0).getPayload());
1394             shiftOperation.setText(RIGHT_SHIFT);
1395         }
1396 
1397         shiftOperation.addChild(visit(ctx.expr(0)));
1398         shiftOperation.addChild(visit(ctx.expr(1)));
1399         return shiftOperation;
1400     }
1401 
1402     @Override
1403     public DetailAstImpl visitNewExp(JavaLanguageParser.NewExpContext ctx) {
1404         final DetailAstImpl newExp = create(ctx.LITERAL_NEW());
1405         // child [0] is LITERAL_NEW
1406         processChildren(newExp, ctx.children.subList(1, ctx.children.size()));
1407         return newExp;
1408     }
1409 
1410     @Override
1411     public DetailAstImpl visitPrefix(JavaLanguageParser.PrefixContext ctx) {
1412         final int tokenType = switch (ctx.prefix.getType()) {
1413             case JavaLanguageLexer.PLUS -> TokenTypes.UNARY_PLUS;
1414             case JavaLanguageLexer.MINUS -> TokenTypes.UNARY_MINUS;
1415             default -> ctx.prefix.getType();
1416         };
1417         final DetailAstImpl prefix = create(tokenType, ctx.prefix);
1418         prefix.addChild(visit(ctx.expr()));
1419         return prefix;
1420     }
1421 
1422     @Override
1423     public DetailAstImpl visitCastExp(JavaLanguageParser.CastExpContext ctx) {
1424         final DetailAstImpl cast = create(TokenTypes.TYPECAST, (Token) ctx.LPAREN().getPayload());
1425         // child [0] is LPAREN
1426         processChildren(cast, ctx.children.subList(1, ctx.children.size()));
1427         return cast;
1428     }
1429 
1430     @Override
1431     public DetailAstImpl visitIndexOp(JavaLanguageParser.IndexOpContext ctx) {
1432         // LBRACK -> INDEX_OP is root of this AST
1433         final DetailAstImpl indexOp = create(TokenTypes.INDEX_OP,
1434                 (Token) ctx.LBRACK().getPayload());
1435 
1436         // add expression(IDENT) on LHS
1437         indexOp.addChild(visit(ctx.expr(0)));
1438 
1439         // create imaginary node for expression on RHS
1440         final DetailAstImpl expr = visit(ctx.expr(1));
1441         final DetailAstImpl imaginaryExpr = createImaginary(TokenTypes.EXPR);
1442         imaginaryExpr.addChild(expr);
1443         indexOp.addChild(imaginaryExpr);
1444 
1445         // complete AST by adding RBRACK
1446         indexOp.addChild(create(ctx.RBRACK()));
1447         return indexOp;
1448     }
1449 
1450     @Override
1451     public DetailAstImpl visitInvOp(JavaLanguageParser.InvOpContext ctx) {
1452         final DetailAstPair currentAst = new DetailAstPair();
1453 
1454         final DetailAstImpl returnAst = visit(ctx.expr());
1455         DetailAstPair.addAstChild(currentAst, returnAst);
1456         DetailAstPair.makeAstRoot(currentAst, create(ctx.bop));
1457 
1458         DetailAstPair.addAstChild(currentAst,
1459                  visit(ctx.nonWildcardTypeArguments()));
1460         DetailAstPair.addAstChild(currentAst, visit(ctx.id()));
1461         final DetailAstImpl lparen = create(TokenTypes.METHOD_CALL,
1462                 (Token) ctx.LPAREN().getPayload());
1463         DetailAstPair.makeAstRoot(currentAst, lparen);
1464 
1465         // We always add an 'ELIST' node
1466         final DetailAstImpl expressionList = Optional.ofNullable(visit(ctx.expressionList()))
1467                 .orElseGet(() -> createImaginary(TokenTypes.ELIST));
1468 
1469         DetailAstPair.addAstChild(currentAst, expressionList);
1470         DetailAstPair.addAstChild(currentAst, create(ctx.RPAREN()));
1471 
1472         return currentAst.root;
1473     }
1474 
1475     @Override
1476     public DetailAstImpl visitInitExp(JavaLanguageParser.InitExpContext ctx) {
1477         final DetailAstImpl dot = create(ctx.bop);
1478         dot.addChild(visit(ctx.expr()));
1479         final DetailAstImpl literalNew = create(ctx.LITERAL_NEW());
1480         literalNew.addChild(visit(ctx.nonWildcardTypeArguments()));
1481         literalNew.addChild(visit(ctx.innerCreator()));
1482         dot.addChild(literalNew);
1483         return dot;
1484     }
1485 
1486     @Override
1487     public DetailAstImpl visitSimpleMethodCall(JavaLanguageParser.SimpleMethodCallContext ctx) {
1488         final DetailAstImpl methodCall = create(TokenTypes.METHOD_CALL,
1489                 (Token) ctx.LPAREN().getPayload());
1490         methodCall.addChild(visit(ctx.id()));
1491         // We always add an 'ELIST' node
1492         final DetailAstImpl expressionList = Optional.ofNullable(visit(ctx.expressionList()))
1493                 .orElseGet(() -> createImaginary(TokenTypes.ELIST));
1494 
1495         methodCall.addChild(expressionList);
1496         methodCall.addChild(create((Token) ctx.RPAREN().getPayload()));
1497         return methodCall;
1498     }
1499 
1500     @Override
1501     public DetailAstImpl visitLambdaExp(JavaLanguageParser.LambdaExpContext ctx) {
1502         final DetailAstImpl lambda = create(ctx.LAMBDA());
1503         lambda.addChild(visit(ctx.lambdaParameters()));
1504 
1505         final JavaLanguageParser.BlockContext blockContext = ctx.block();
1506         final DetailAstImpl rightHandLambdaChild;
1507         if (blockContext != null) {
1508             rightHandLambdaChild = visit(blockContext);
1509         }
1510         else {
1511             // Lambda expression child is built the same way that we build
1512             // the initial expression node in visitExpression, i.e. with
1513             // an imaginary EXPR node. This results in nested EXPR nodes
1514             // in the AST.
1515             rightHandLambdaChild = buildExpressionNode(ctx.expr());
1516         }
1517         lambda.addChild(rightHandLambdaChild);
1518         return lambda;
1519     }
1520 
1521     @Override
1522     public DetailAstImpl visitThisExp(JavaLanguageParser.ThisExpContext ctx) {
1523         final DetailAstImpl bop = create(ctx.bop);
1524         bop.addChild(visit(ctx.expr()));
1525         bop.addChild(create(ctx.LITERAL_THIS()));
1526         return bop;
1527     }
1528 
1529     @Override
1530     public DetailAstImpl visitPrimaryExp(JavaLanguageParser.PrimaryExpContext ctx) {
1531         return flattenedTree(ctx);
1532     }
1533 
1534     @Override
1535     public DetailAstImpl visitPostfix(JavaLanguageParser.PostfixContext ctx) {
1536         final DetailAstImpl postfix;
1537         if (ctx.postfix.getType() == JavaLanguageLexer.INC) {
1538             postfix = create(TokenTypes.POST_INC, ctx.postfix);
1539         }
1540         else {
1541             postfix = create(TokenTypes.POST_DEC, ctx.postfix);
1542         }
1543         postfix.addChild(visit(ctx.expr()));
1544         return postfix;
1545     }
1546 
1547     @Override
1548     public DetailAstImpl visitMethodRef(JavaLanguageParser.MethodRefContext ctx) {
1549         final DetailAstImpl doubleColon = create(TokenTypes.METHOD_REF,
1550                 (Token) ctx.DOUBLE_COLON().getPayload());
1551         final List<ParseTree> children = ctx.children.stream()
1552                 .filter(child -> !child.equals(ctx.DOUBLE_COLON()))
1553                 .toList();
1554         processChildren(doubleColon, children);
1555         return doubleColon;
1556     }
1557 
1558     @Override
1559     public DetailAstImpl visitTernaryOp(JavaLanguageParser.TernaryOpContext ctx) {
1560         final DetailAstImpl root = create(ctx.QUESTION());
1561         processChildren(root, ctx.children.stream()
1562                 .filter(child -> !child.equals(ctx.QUESTION()))
1563                 .toList());
1564         return root;
1565     }
1566 
1567     @Override
1568     public DetailAstImpl visitBinOp(JavaLanguageParser.BinOpContext ctx) {
1569         final DetailAstImpl bop = create(ctx.bop);
1570 
1571         // To improve performance, we iterate through binary operations
1572         // since they are frequently deeply nested.
1573         final List<JavaLanguageParser.BinOpContext> binOpList = new ArrayList<>();
1574         ParseTree firstExpression = ctx.expr(0);
1575         while (firstExpression instanceof JavaLanguageParser.BinOpContext) {
1576             // Get all nested binOps
1577             binOpList.add((JavaLanguageParser.BinOpContext) firstExpression);
1578             firstExpression = ((JavaLanguageParser.BinOpContext) firstExpression).expr(0);
1579         }
1580 
1581         if (binOpList.isEmpty()) {
1582             final DetailAstImpl leftChild = visit(ctx.children.get(0));
1583             bop.addChild(leftChild);
1584         }
1585         else {
1586             // Map all descendants to individual AST's since we can parallelize this
1587             // operation
1588             final Queue<DetailAstImpl> descendantList = binOpList.parallelStream()
1589                     .map(this::getInnerBopAst)
1590                     .collect(Collectors.toCollection(ArrayDeque::new));
1591 
1592             bop.addChild(descendantList.poll());
1593             DetailAstImpl pointer = bop.getFirstChild();
1594             // Build tree
1595             for (DetailAstImpl descendant : descendantList) {
1596                 pointer.getFirstChild().addPreviousSibling(descendant);
1597                 pointer = descendant;
1598             }
1599         }
1600 
1601         bop.addChild(visit(ctx.children.get(2)));
1602         return bop;
1603     }
1604 
1605     /**
1606      * Builds the binary operation (binOp) AST.
1607      *
1608      * @param descendant the BinOpContext to build AST from
1609      * @return binOp AST
1610      */
1611     private DetailAstImpl getInnerBopAst(JavaLanguageParser.BinOpContext descendant) {
1612         final DetailAstImpl innerBop = create(descendant.bop);
1613         final JavaLanguageParser.ExprContext expr = descendant.expr(0);
1614         if (!(expr instanceof JavaLanguageParser.BinOpContext)) {
1615             innerBop.addChild(visit(expr));
1616         }
1617         innerBop.addChild(visit(descendant.expr(1)));
1618         return innerBop;
1619     }
1620 
1621     @Override
1622     public DetailAstImpl visitMethodCall(JavaLanguageParser.MethodCallContext ctx) {
1623         final DetailAstImpl methodCall = create(TokenTypes.METHOD_CALL,
1624                 (Token) ctx.LPAREN().getPayload());
1625         // We always add an 'ELIST' node
1626         final DetailAstImpl expressionList = Optional.ofNullable(visit(ctx.expressionList()))
1627                 .orElseGet(() -> createImaginary(TokenTypes.ELIST));
1628 
1629         final DetailAstImpl dot = create(ctx.DOT());
1630         dot.addChild(visit(ctx.expr()));
1631         dot.addChild(visit(ctx.id()));
1632         methodCall.addChild(dot);
1633         methodCall.addChild(expressionList);
1634         methodCall.addChild(create((Token) ctx.RPAREN().getPayload()));
1635         return methodCall;
1636     }
1637 
1638     @Override
1639     public DetailAstImpl visitTypeCastParameters(
1640             JavaLanguageParser.TypeCastParametersContext ctx) {
1641         final DetailAstImpl typeType = visit(ctx.typeType(0));
1642         for (int i = 0; i < ctx.BAND().size(); i++) {
1643             addLastSibling(typeType, create(TokenTypes.TYPE_EXTENSION_AND,
1644                                 (Token) ctx.BAND(i).getPayload()));
1645             addLastSibling(typeType, visit(ctx.typeType(i + 1)));
1646         }
1647         return typeType;
1648     }
1649 
1650     @Override
1651     public DetailAstImpl visitSingleLambdaParam(JavaLanguageParser.SingleLambdaParamContext ctx) {
1652         return flattenedTree(ctx);
1653     }
1654 
1655     @Override
1656     public DetailAstImpl visitFormalLambdaParam(JavaLanguageParser.FormalLambdaParamContext ctx) {
1657         final DetailAstImpl lparen = create(ctx.LPAREN());
1658 
1659         // We add an 'PARAMETERS' node here whether it exists or not
1660         final DetailAstImpl parameters = Optional.ofNullable(visit(ctx.formalParameterList()))
1661                 .orElseGet(() -> createImaginary(TokenTypes.PARAMETERS));
1662         addLastSibling(lparen, parameters);
1663         addLastSibling(lparen, create(ctx.RPAREN()));
1664         return lparen;
1665     }
1666 
1667     @Override
1668     public DetailAstImpl visitMultiLambdaParam(JavaLanguageParser.MultiLambdaParamContext ctx) {
1669         final DetailAstImpl lparen = create(ctx.LPAREN());
1670         addLastSibling(lparen, visit(ctx.multiLambdaParams()));
1671         addLastSibling(lparen, create(ctx.RPAREN()));
1672         return lparen;
1673     }
1674 
1675     @Override
1676     public DetailAstImpl visitMultiLambdaParams(JavaLanguageParser.MultiLambdaParamsContext ctx) {
1677         final DetailAstImpl parameters = createImaginary(TokenTypes.PARAMETERS);
1678         parameters.addChild(createLambdaParameter(ctx.id(0)));
1679 
1680         for (int i = 0; i < ctx.COMMA().size(); i++) {
1681             parameters.addChild(create(ctx.COMMA(i)));
1682             parameters.addChild(createLambdaParameter(ctx.id(i + 1)));
1683         }
1684         return parameters;
1685     }
1686 
1687     /**
1688      * Creates a 'PARAMETER_DEF' node for a lambda expression, with
1689      * imaginary modifier and type nodes.
1690      *
1691      * @param ctx the IdContext to create imaginary nodes for
1692      * @return DetailAstImpl of lambda parameter
1693      */
1694     private DetailAstImpl createLambdaParameter(JavaLanguageParser.IdContext ctx) {
1695         final DetailAstImpl ident = visitId(ctx);
1696         final DetailAstImpl parameter = createImaginary(TokenTypes.PARAMETER_DEF);
1697         final DetailAstImpl modifiers = createImaginary(TokenTypes.MODIFIERS);
1698         final DetailAstImpl type = createImaginary(TokenTypes.TYPE);
1699         parameter.addChild(modifiers);
1700         parameter.addChild(type);
1701         parameter.addChild(ident);
1702         return parameter;
1703     }
1704 
1705     @Override
1706     public DetailAstImpl visitParenPrimary(JavaLanguageParser.ParenPrimaryContext ctx) {
1707         return flattenedTree(ctx);
1708     }
1709 
1710     @Override
1711     public DetailAstImpl visitTokenPrimary(JavaLanguageParser.TokenPrimaryContext ctx) {
1712         return flattenedTree(ctx);
1713     }
1714 
1715     @Override
1716     public DetailAstImpl visitClassRefPrimary(JavaLanguageParser.ClassRefPrimaryContext ctx) {
1717         final DetailAstImpl dot = create(ctx.DOT());
1718         final DetailAstImpl primaryTypeNoArray = visit(ctx.type);
1719         dot.addChild(primaryTypeNoArray);
1720         if (TokenUtil.isOfType(primaryTypeNoArray, TokenTypes.DOT)) {
1721             // We append '[]' to the qualified name 'TYPE' `ast
1722             ctx.arrayDeclarator()
1723                     .forEach(child -> primaryTypeNoArray.addChild(visit(child)));
1724         }
1725         else {
1726             ctx.arrayDeclarator()
1727                     .forEach(child -> addLastSibling(primaryTypeNoArray, visit(child)));
1728         }
1729         dot.addChild(create(ctx.LITERAL_CLASS()));
1730         return dot;
1731     }
1732 
1733     @Override
1734     public DetailAstImpl visitPrimitivePrimary(JavaLanguageParser.PrimitivePrimaryContext ctx) {
1735         final DetailAstImpl dot = create(ctx.DOT());
1736         final DetailAstImpl primaryTypeNoArray = visit(ctx.type);
1737         dot.addChild(primaryTypeNoArray);
1738         ctx.arrayDeclarator().forEach(child -> dot.addChild(visit(child)));
1739         dot.addChild(create(ctx.LITERAL_CLASS()));
1740         return dot;
1741     }
1742 
1743     @Override
1744     public DetailAstImpl visitCreator(JavaLanguageParser.CreatorContext ctx) {
1745         return flattenedTree(ctx);
1746     }
1747 
1748     @Override
1749     public DetailAstImpl visitCreatedNameObject(JavaLanguageParser.CreatedNameObjectContext ctx) {
1750         final DetailAstPair currentAST = new DetailAstPair();
1751         DetailAstPair.addAstChild(currentAST, visit(ctx.annotations()));
1752         DetailAstPair.addAstChild(currentAST, visit(ctx.id()));
1753         DetailAstPair.addAstChild(currentAST, visit(ctx.typeArgumentsOrDiamond()));
1754 
1755         // This is how we build the type arguments/ qualified name tree
1756         for (ParserRuleContext extendedContext : ctx.extended) {
1757             final DetailAstImpl dot = create(extendedContext.start);
1758             DetailAstPair.makeAstRoot(currentAST, dot);
1759             final List<ParseTree> childList = extendedContext
1760                     .children.subList(1, extendedContext.children.size());
1761             processChildren(dot, childList);
1762         }
1763 
1764         return currentAST.root;
1765     }
1766 
1767     @Override
1768     public DetailAstImpl visitCreatedNamePrimitive(
1769             JavaLanguageParser.CreatedNamePrimitiveContext ctx) {
1770         return flattenedTree(ctx);
1771     }
1772 
1773     @Override
1774     public DetailAstImpl visitInnerCreator(JavaLanguageParser.InnerCreatorContext ctx) {
1775         return flattenedTree(ctx);
1776     }
1777 
1778     @Override
1779     public DetailAstImpl visitArrayCreatorRest(JavaLanguageParser.ArrayCreatorRestContext ctx) {
1780         final DetailAstImpl arrayDeclarator = create(TokenTypes.ARRAY_DECLARATOR,
1781                 (Token) ctx.LBRACK().getPayload());
1782         final JavaLanguageParser.ExpressionContext expression = ctx.expression();
1783         final TerminalNode rbrack = ctx.RBRACK();
1784         // child[0] is LBRACK
1785         for (int i = 1; i < ctx.children.size(); i++) {
1786             if (ctx.children.get(i) == rbrack) {
1787                 arrayDeclarator.addChild(create(rbrack));
1788             }
1789             else if (ctx.children.get(i) == expression) {
1790                 // Handle '[8]', etc.
1791                 arrayDeclarator.addChild(visit(expression));
1792             }
1793             else {
1794                 addLastSibling(arrayDeclarator, visit(ctx.children.get(i)));
1795             }
1796         }
1797         return arrayDeclarator;
1798     }
1799 
1800     @Override
1801     public DetailAstImpl visitBracketsWithExp(JavaLanguageParser.BracketsWithExpContext ctx) {
1802         final DetailAstImpl dummyRoot = new DetailAstImpl();
1803         dummyRoot.addChild(visit(ctx.annotations()));
1804         final DetailAstImpl arrayDeclarator =
1805                 create(TokenTypes.ARRAY_DECLARATOR, (Token) ctx.LBRACK().getPayload());
1806         arrayDeclarator.addChild(visit(ctx.expression()));
1807         arrayDeclarator.addChild(create(ctx.stop));
1808         dummyRoot.addChild(arrayDeclarator);
1809         return dummyRoot.getFirstChild();
1810     }
1811 
1812     @Override
1813     public DetailAstImpl visitClassCreatorRest(JavaLanguageParser.ClassCreatorRestContext ctx) {
1814         return flattenedTree(ctx);
1815     }
1816 
1817     @Override
1818     public DetailAstImpl visitDiamond(JavaLanguageParser.DiamondContext ctx) {
1819         final DetailAstImpl typeArguments =
1820                 createImaginary(TokenTypes.TYPE_ARGUMENTS);
1821         typeArguments.addChild(create(TokenTypes.GENERIC_START,
1822                 (Token) ctx.LT().getPayload()));
1823         typeArguments.addChild(create(TokenTypes.GENERIC_END,
1824                 (Token) ctx.GT().getPayload()));
1825         return typeArguments;
1826     }
1827 
1828     @Override
1829     public DetailAstImpl visitTypeArgs(JavaLanguageParser.TypeArgsContext ctx) {
1830         return flattenedTree(ctx);
1831     }
1832 
1833     @Override
1834     public DetailAstImpl visitNonWildcardDiamond(
1835             JavaLanguageParser.NonWildcardDiamondContext ctx) {
1836         final DetailAstImpl typeArguments =
1837                 createImaginary(TokenTypes.TYPE_ARGUMENTS);
1838         typeArguments.addChild(create(TokenTypes.GENERIC_START,
1839                 (Token) ctx.LT().getPayload()));
1840         typeArguments.addChild(create(TokenTypes.GENERIC_END,
1841                 (Token) ctx.GT().getPayload()));
1842         return typeArguments;
1843     }
1844 
1845     @Override
1846     public DetailAstImpl visitNonWildcardTypeArguments(
1847             JavaLanguageParser.NonWildcardTypeArgumentsContext ctx) {
1848         final DetailAstImpl typeArguments = createImaginary(TokenTypes.TYPE_ARGUMENTS);
1849         typeArguments.addChild(create(TokenTypes.GENERIC_START, (Token) ctx.LT().getPayload()));
1850         typeArguments.addChild(visit(ctx.typeArgumentsTypeList()));
1851         typeArguments.addChild(create(TokenTypes.GENERIC_END, (Token) ctx.GT().getPayload()));
1852         return typeArguments;
1853     }
1854 
1855     @Override
1856     public DetailAstImpl visitTypeArgumentsTypeList(
1857             JavaLanguageParser.TypeArgumentsTypeListContext ctx) {
1858         final DetailAstImpl firstIdent = visit(ctx.typeType(0));
1859         final DetailAstImpl firstTypeArgument = createImaginary(TokenTypes.TYPE_ARGUMENT);
1860         firstTypeArgument.addChild(firstIdent);
1861 
1862         for (int i = 0; i < ctx.COMMA().size(); i++) {
1863             addLastSibling(firstTypeArgument, create(ctx.COMMA(i)));
1864             final DetailAstImpl ident = visit(ctx.typeType(i + 1));
1865             final DetailAstImpl typeArgument = createImaginary(TokenTypes.TYPE_ARGUMENT);
1866             typeArgument.addChild(ident);
1867             addLastSibling(firstTypeArgument, typeArgument);
1868         }
1869         return firstTypeArgument;
1870     }
1871 
1872     @Override
1873     public DetailAstImpl visitTypeList(JavaLanguageParser.TypeListContext ctx) {
1874         return flattenedTree(ctx);
1875     }
1876 
1877     @Override
1878     public DetailAstImpl visitTypeType(JavaLanguageParser.TypeTypeContext ctx) {
1879         final DetailAstImpl type = createImaginary(TokenTypes.TYPE);
1880         processChildren(type, ctx.children);
1881 
1882         final DetailAstImpl returnTree;
1883         if (ctx.createImaginaryNode) {
1884             returnTree = type;
1885         }
1886         else {
1887             returnTree = type.getFirstChild();
1888         }
1889         return returnTree;
1890     }
1891 
1892     @Override
1893     public DetailAstImpl visitArrayDeclarator(JavaLanguageParser.ArrayDeclaratorContext ctx) {
1894         final DetailAstImpl arrayDeclarator = create(TokenTypes.ARRAY_DECLARATOR,
1895                 (Token) ctx.LBRACK().getPayload());
1896         arrayDeclarator.addChild(create(ctx.RBRACK()));
1897 
1898         final DetailAstImpl returnTree;
1899         final DetailAstImpl annotations = visit(ctx.anno);
1900         if (annotations == null) {
1901             returnTree = arrayDeclarator;
1902         }
1903         else {
1904             returnTree = annotations;
1905             addLastSibling(returnTree, arrayDeclarator);
1906         }
1907         return returnTree;
1908     }
1909 
1910     @Override
1911     public DetailAstImpl visitPrimitiveType(JavaLanguageParser.PrimitiveTypeContext ctx) {
1912         return create(ctx.start);
1913     }
1914 
1915     @Override
1916     public DetailAstImpl visitTypeArguments(JavaLanguageParser.TypeArgumentsContext ctx) {
1917         final DetailAstImpl typeArguments = createImaginary(TokenTypes.TYPE_ARGUMENTS);
1918         typeArguments.addChild(create(TokenTypes.GENERIC_START, (Token) ctx.LT().getPayload()));
1919         // Exclude '<' and '>'
1920         processChildren(typeArguments, ctx.children.subList(1, ctx.children.size() - 1));
1921         typeArguments.addChild(create(TokenTypes.GENERIC_END, (Token) ctx.GT().getPayload()));
1922         return typeArguments;
1923     }
1924 
1925     @Override
1926     public DetailAstImpl visitSuperSuffixDot(JavaLanguageParser.SuperSuffixDotContext ctx) {
1927         final DetailAstImpl root;
1928         if (ctx.LPAREN() == null) {
1929             root = create(ctx.DOT());
1930             root.addChild(visit(ctx.id()));
1931         }
1932         else {
1933             root = create(TokenTypes.METHOD_CALL, (Token) ctx.LPAREN().getPayload());
1934 
1935             final DetailAstImpl dot = create(ctx.DOT());
1936             dot.addChild(visit(ctx.id()));
1937             root.addChild(dot);
1938 
1939             final DetailAstImpl expressionList = Optional.ofNullable(visit(ctx.expressionList()))
1940                     .orElseGet(() -> createImaginary(TokenTypes.ELIST));
1941             root.addChild(expressionList);
1942 
1943             root.addChild(create(ctx.RPAREN()));
1944         }
1945 
1946         return root;
1947     }
1948 
1949     @Override
1950     public DetailAstImpl visitArguments(JavaLanguageParser.ArgumentsContext ctx) {
1951         final DetailAstImpl lparen = create(ctx.LPAREN());
1952 
1953         // We always add an 'ELIST' node
1954         final DetailAstImpl expressionList = Optional.ofNullable(visit(ctx.expressionList()))
1955                 .orElseGet(() -> createImaginary(TokenTypes.ELIST));
1956         addLastSibling(lparen, expressionList);
1957         addLastSibling(lparen, create(ctx.RPAREN()));
1958         return lparen;
1959     }
1960 
1961     @Override
1962     public DetailAstImpl visitPattern(JavaLanguageParser.PatternContext ctx) {
1963         final JavaLanguageParser.InnerPatternContext innerPattern = ctx.innerPattern();
1964         final ParserRuleContext primaryPattern = innerPattern.primaryPattern();
1965         final ParserRuleContext recordPattern = innerPattern.recordPattern();
1966         final boolean isSimpleTypePattern = primaryPattern != null
1967                 && primaryPattern.getChild(0) instanceof JavaLanguageParser.TypePatternContext;
1968 
1969         final DetailAstImpl pattern;
1970 
1971         if (recordPattern != null) {
1972             pattern = visit(recordPattern);
1973         }
1974         else if (isSimpleTypePattern) {
1975             // For simple type pattern like 'Integer i`, we do not add `PATTERN_DEF` parent
1976             pattern = visit(primaryPattern);
1977         }
1978         else {
1979             pattern = createImaginary(TokenTypes.PATTERN_DEF);
1980             pattern.addChild(visit(ctx.getChild(0)));
1981         }
1982         return pattern;
1983     }
1984 
1985     @Override
1986     public DetailAstImpl visitInnerPattern(JavaLanguageParser.InnerPatternContext ctx) {
1987         return flattenedTree(ctx);
1988     }
1989 
1990     @Override
1991     public DetailAstImpl visitGuardedPattern(JavaLanguageParser.GuardedPatternContext ctx) {
1992         final DetailAstImpl guardAstNode = flattenedTree(ctx.guard());
1993         guardAstNode.addChild(visit(ctx.primaryPattern()));
1994         guardAstNode.addChild(visit(ctx.expression()));
1995         return guardAstNode;
1996     }
1997 
1998     @Override
1999     public DetailAstImpl visitParenPattern(JavaLanguageParser.ParenPatternContext ctx) {
2000         final DetailAstImpl lparen = create(ctx.LPAREN());
2001         final ParseTree innerPattern = ctx.getChild(1);
2002         lparen.addChild(visit(innerPattern));
2003         lparen.addChild(create(ctx.RPAREN()));
2004         return lparen;
2005     }
2006 
2007     @Override
2008     public DetailAstImpl visitRecordPatternDef(JavaLanguageParser.RecordPatternDefContext ctx) {
2009         return flattenedTree(ctx);
2010     }
2011 
2012     @Override
2013     public DetailAstImpl visitTypePatternDef(
2014             JavaLanguageParser.TypePatternDefContext ctx) {
2015         final DetailAstImpl type = visit(ctx.type);
2016         final DetailAstImpl patternVariableDef = createImaginary(TokenTypes.PATTERN_VARIABLE_DEF);
2017         patternVariableDef.addChild(createModifiers(ctx.mods));
2018         patternVariableDef.addChild(type);
2019         patternVariableDef.addChild(visit(ctx.id()));
2020         return patternVariableDef;
2021     }
2022 
2023     @Override
2024     public DetailAstImpl visitUnnamedPatternDef(JavaLanguageParser.UnnamedPatternDefContext ctx) {
2025         return create(TokenTypes.UNNAMED_PATTERN_DEF, ctx.start);
2026     }
2027 
2028     @Override
2029     public DetailAstImpl visitRecordPattern(JavaLanguageParser.RecordPatternContext ctx) {
2030         final DetailAstImpl recordPattern = createImaginary(TokenTypes.RECORD_PATTERN_DEF);
2031         recordPattern.addChild(createModifiers(ctx.mods));
2032         processChildren(recordPattern,
2033                 ctx.children.subList(ctx.mods.size(), ctx.children.size()));
2034         return recordPattern;
2035     }
2036 
2037     @Override
2038     public DetailAstImpl visitRecordComponentPatternList(
2039             JavaLanguageParser.RecordComponentPatternListContext ctx) {
2040         final DetailAstImpl recordComponents =
2041                 createImaginary(TokenTypes.RECORD_PATTERN_COMPONENTS);
2042         processChildren(recordComponents, ctx.children);
2043         return recordComponents;
2044     }
2045 
2046     @Override
2047     public DetailAstImpl visitPermittedSubclassesAndInterfaces(
2048             JavaLanguageParser.PermittedSubclassesAndInterfacesContext ctx) {
2049         final DetailAstImpl literalPermits =
2050                 create(TokenTypes.PERMITS_CLAUSE, (Token) ctx.LITERAL_PERMITS().getPayload());
2051         // 'LITERAL_PERMITS' is child[0]
2052         processChildren(literalPermits, ctx.children.subList(1, ctx.children.size()));
2053         return literalPermits;
2054     }
2055 
2056     @Override
2057     public DetailAstImpl visitId(JavaLanguageParser.IdContext ctx) {
2058         return create(TokenTypes.IDENT, ctx.start);
2059     }
2060 
2061     /**
2062      * Builds the AST for a particular node, then returns a "flattened" tree
2063      * of siblings. This method should be used in rule contexts such as
2064      * {@code variableDeclarators}, where we have both terminals and non-terminals.
2065      *
2066      * @param ctx the ParserRuleContext to base tree on
2067      * @return flattened DetailAstImpl
2068      */
2069     private DetailAstImpl flattenedTree(ParserRuleContext ctx) {
2070         final DetailAstImpl dummyNode = new DetailAstImpl();
2071         processChildren(dummyNode, ctx.children);
2072         return dummyNode.getFirstChild();
2073     }
2074 
2075     /**
2076      * Adds all the children from the given ParseTree or JavaParserContext
2077      * list to the parent DetailAstImpl.
2078      *
2079      * @param parent the DetailAstImpl to add children to
2080      * @param children the list of children to add
2081      */
2082     private void processChildren(DetailAstImpl parent, List<? extends ParseTree> children) {
2083         children.forEach(child -> {
2084             if (child instanceof TerminalNode) {
2085                 // Child is a token, create a new DetailAstImpl and add it to parent
2086                 parent.addChild(create((TerminalNode) child));
2087             }
2088             else {
2089                 // Child is another rule context; visit it, create token, and add to parent
2090                 parent.addChild(visit(child));
2091             }
2092         });
2093     }
2094 
2095     /**
2096      * Create a DetailAstImpl from a given token and token type. This method
2097      * should be used for imaginary nodes only, i.e. 'OBJBLOCK -&gt; OBJBLOCK',
2098      * where the text on the RHS matches the text on the LHS.
2099      *
2100      * @param tokenType the token type of this DetailAstImpl
2101      * @return new DetailAstImpl of given type
2102      */
2103     private static DetailAstImpl createImaginary(int tokenType) {
2104         final DetailAstImpl detailAst = new DetailAstImpl();
2105         detailAst.setType(tokenType);
2106         detailAst.setText(TokenUtil.getTokenName(tokenType));
2107         return detailAst;
2108     }
2109 
2110     /**
2111      * Create a DetailAstImpl from a given token and token type. This method
2112      * should be used for literal nodes only, i.e. 'PACKAGE_DEF -&gt; package'.
2113      *
2114      * @param tokenType the token type of this DetailAstImpl
2115      * @param startToken the first token that appears in this DetailAstImpl.
2116      * @return new DetailAstImpl of given type
2117      */
2118     private DetailAstImpl create(int tokenType, Token startToken) {
2119         final DetailAstImpl ast = create(startToken);
2120         ast.setType(tokenType);
2121         return ast;
2122     }
2123 
2124     /**
2125      * Create a DetailAstImpl from a given token. This method should be
2126      * used for terminal nodes, i.e. {@code LCURLY}, when we are building
2127      * an AST for a specific token, regardless of position.
2128      *
2129      * @param token the token to build the DetailAstImpl from
2130      * @return new DetailAstImpl of given type
2131      */
2132     private DetailAstImpl create(Token token) {
2133         final int tokenIndex = token.getTokenIndex();
2134         final List<Token> tokensToLeft =
2135                 tokens.getHiddenTokensToLeft(tokenIndex, JavaLanguageLexer.COMMENTS);
2136         final List<Token> tokensToRight =
2137                 tokens.getHiddenTokensToRight(tokenIndex, JavaLanguageLexer.COMMENTS);
2138 
2139         final DetailAstImpl detailAst = new DetailAstImpl();
2140         detailAst.initialize(token);
2141         if (tokensToLeft != null) {
2142             detailAst.setHiddenBefore(tokensToLeft);
2143         }
2144         if (tokensToRight != null) {
2145             detailAst.setHiddenAfter(tokensToRight);
2146         }
2147         return detailAst;
2148     }
2149 
2150     /**
2151      * Create a DetailAstImpl from a given TerminalNode. This method should be
2152      * used for terminal nodes, i.e. {@code @}.
2153      *
2154      * @param node the TerminalNode to build the DetailAstImpl from
2155      * @return new DetailAstImpl of given type
2156      */
2157     private DetailAstImpl create(TerminalNode node) {
2158         return create((Token) node.getPayload());
2159     }
2160 
2161     /**
2162      * Creates a type declaration DetailAstImpl from a given rule context.
2163      *
2164      * @param ctx ParserRuleContext we are in
2165      * @param type the type declaration to create
2166      * @param modifierList respective modifiers
2167      * @return type declaration DetailAstImpl
2168      */
2169     private DetailAstImpl createTypeDeclaration(ParserRuleContext ctx, int type,
2170                                                 List<? extends ParseTree> modifierList) {
2171         final DetailAstImpl typeDeclaration = createImaginary(type);
2172         typeDeclaration.addChild(createModifiers(modifierList));
2173         processChildren(typeDeclaration, ctx.children);
2174         return typeDeclaration;
2175     }
2176 
2177     /**
2178      * Builds the modifiers AST.
2179      *
2180      * @param modifierList the list of modifier contexts
2181      * @return "MODIFIERS" ast
2182      */
2183     private DetailAstImpl createModifiers(List<? extends ParseTree> modifierList) {
2184         final DetailAstImpl mods = createImaginary(TokenTypes.MODIFIERS);
2185         processChildren(mods, modifierList);
2186         return mods;
2187     }
2188 
2189     /**
2190      * Add new sibling to the end of existing siblings.
2191      *
2192      * @param self DetailAstImpl to add last sibling to
2193      * @param sibling DetailAstImpl sibling to add
2194      */
2195     private static void addLastSibling(DetailAstImpl self, DetailAstImpl sibling) {
2196         DetailAstImpl nextSibling = self;
2197         if (nextSibling != null) {
2198             while (nextSibling.getNextSibling() != null) {
2199                 nextSibling = nextSibling.getNextSibling();
2200             }
2201             nextSibling.setNextSibling(sibling);
2202         }
2203     }
2204 
2205     @Override
2206     public DetailAstImpl visit(ParseTree tree) {
2207         DetailAstImpl ast = null;
2208         if (tree != null) {
2209             ast = tree.accept(this);
2210         }
2211         return ast;
2212     }
2213 
2214     /**
2215      * Builds an expression node. This is used to build the root of an expression with
2216      * an imaginary {@code EXPR} node.
2217      *
2218      * @param exprNode expression to build node for
2219      * @return expression DetailAstImpl node
2220      */
2221     private DetailAstImpl buildExpressionNode(ParseTree exprNode) {
2222         final DetailAstImpl expression = visit(exprNode);
2223 
2224         final DetailAstImpl exprRoot;
2225         if (TokenUtil.isOfType(expression, EXPRESSIONS_WITH_NO_EXPR_ROOT)) {
2226             exprRoot = expression;
2227         }
2228         else {
2229             // create imaginary 'EXPR' node as root of expression
2230             exprRoot = createImaginary(TokenTypes.EXPR);
2231             exprRoot.addChild(expression);
2232         }
2233         return exprRoot;
2234     }
2235 
2236     /**
2237      * Used to swap and organize DetailAstImpl subtrees.
2238      */
2239     private static final class DetailAstPair {
2240 
2241         /** The root DetailAstImpl of this pair. */
2242         private DetailAstImpl root;
2243 
2244         /** The child (potentially with siblings) of this pair. */
2245         private DetailAstImpl child;
2246 
2247         /**
2248          * Moves child reference to the last child.
2249          */
2250         private void advanceChildToEnd() {
2251             while (child.getNextSibling() != null) {
2252                 child = child.getNextSibling();
2253             }
2254         }
2255 
2256         /**
2257          * Returns the root node.
2258          *
2259          * @return the root node
2260          */
2261         private DetailAstImpl getRoot() {
2262             return root;
2263         }
2264 
2265         /**
2266          * This method is used to replace the {@code ^} (set as root node) ANTLR2
2267          * operator.
2268          *
2269          * @param pair the DetailAstPair to use for swapping nodes
2270          * @param ast the new root
2271          */
2272         private static void makeAstRoot(DetailAstPair pair, DetailAstImpl ast) {
2273             ast.addChild(pair.root);
2274             pair.child = pair.root;
2275             pair.advanceChildToEnd();
2276             pair.root = ast;
2277         }
2278 
2279         /**
2280          * Adds a child (or new root) to the given DetailAstPair.
2281          *
2282          * @param pair the DetailAstPair to add child to
2283          * @param ast the child to add
2284          */
2285         private static void addAstChild(DetailAstPair pair, DetailAstImpl ast) {
2286             if (ast != null) {
2287                 if (pair.root == null) {
2288                     pair.root = ast;
2289                 }
2290                 else {
2291                     pair.child.setNextSibling(ast);
2292                 }
2293                 pair.child = ast;
2294             }
2295         }
2296     }
2297 }