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