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