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