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