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