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