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.checks.indentation;
021
022import com.puppycrawl.tools.checkstyle.api.DetailAST;
023import com.puppycrawl.tools.checkstyle.api.TokenTypes;
024
025/**
026 * Handler for class definitions.
027 *
028 */
029public class ClassDefHandler extends BlockParentHandler {
030
031    /** Token for modifier. */
032    private static final String MODIFIER = "modifier";
033
034    /**
035     * Construct an instance of this handler with the given indentation check,
036     * abstract syntax tree, and parent handler.
037     *
038     * @param indentCheck   the indentation check
039     * @param ast           the abstract syntax tree
040     * @param parent        the parent handler
041     */
042    public ClassDefHandler(IndentationCheck indentCheck,
043                           DetailAST ast,
044                           AbstractExpressionHandler parent) {
045        super(indentCheck, getHandlerName(ast), ast, parent);
046    }
047
048    @Override
049    protected DetailAST getLeftCurly() {
050        return getMainAst().findFirstToken(TokenTypes.OBJBLOCK)
051            .findFirstToken(TokenTypes.LCURLY);
052    }
053
054    @Override
055    protected DetailAST getRightCurly() {
056        return getMainAst().findFirstToken(TokenTypes.OBJBLOCK)
057            .findFirstToken(TokenTypes.RCURLY);
058    }
059
060    @Override
061    protected DetailAST getTopLevelAst() {
062        return null;
063        // note: ident checked by hand in check indentation;
064    }
065
066    @Override
067    protected DetailAST getListChild() {
068        return getMainAst().findFirstToken(TokenTypes.OBJBLOCK);
069    }
070
071    @Override
072    public void checkIndentation() {
073        final DetailAST modifiers = getMainAst().findFirstToken(TokenTypes.MODIFIERS);
074        if (modifiers.hasChildren()) {
075            checkModifiers();
076        }
077        else {
078            if (getMainAst().getType() != TokenTypes.ANNOTATION_DEF) {
079                final DetailAST ident = getMainAst().findFirstToken(TokenTypes.IDENT);
080                DetailAST tokenToCheck = getMainAst();
081                if (ident.getLineNo() == getMainAst().getLineNo()) {
082                    tokenToCheck = ident;
083                }
084                final int lineStart = getLineStart(tokenToCheck);
085                if (!getIndent().isAcceptable(lineStart)) {
086                    logError(tokenToCheck, "ident", lineStart);
087                }
088            }
089        }
090        if (getMainAst().getType() == TokenTypes.ANNOTATION_DEF) {
091            final DetailAST atAst = getMainAst().findFirstToken(TokenTypes.AT);
092            if (isOnStartOfLine(atAst)) {
093                checkWrappingIndentation(getMainAst(), getListChild(), 0,
094                        getIndent().getFirstIndentLevel(),
095                        LineWrappingHandler.LineWrappingOptions.NONE);
096            }
097        }
098        else {
099            checkWrappingIndentation(getMainAst(), getListChild());
100        }
101        super.checkIndentation();
102    }
103
104    @Override
105    protected int[] getCheckedChildren() {
106        return new int[] {
107            TokenTypes.EXPR,
108            TokenTypes.OBJBLOCK,
109            TokenTypes.LITERAL_BREAK,
110            TokenTypes.LITERAL_RETURN,
111            TokenTypes.LITERAL_THROW,
112            TokenTypes.LITERAL_CONTINUE,
113        };
114    }
115
116    /**
117     * Checks modifiers for class/annotation definitions.
118     */
119    private void checkModifiers() {
120        if (getMainAst().getType() == TokenTypes.ANNOTATION_DEF) {
121            checkAnnotationDefModifiers();
122        }
123        else {
124            checkClassDefModifiers();
125        }
126    }
127
128    /**
129     * Checks modifiers for annotation definitions, skipping modifiers that are
130     * not at the start of the line.
131     */
132    private void checkAnnotationDefModifiers() {
133        final DetailAST modifiers = getMainAst().findFirstToken(TokenTypes.MODIFIERS);
134        for (DetailAST modifier = modifiers.getFirstChild();
135             modifier != null;
136             modifier = modifier.getNextSibling()) {
137            if (isOnStartOfLine(modifier)
138                    && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) {
139                logError(modifier, MODIFIER, expandedTabsColumnNo(modifier));
140            }
141        }
142    }
143
144    /**
145     * Checks modifiers for class definitions, skipping wrapped modifiers
146     * that appear on lines after the first modifier line.
147     * Annotations that wrap before the class keyword are skipped only when
148     * they are correctly indented, since a correctly indented annotation
149     * before a wrongly indented class keyword is not itself a violation.
150     */
151    private void checkClassDefModifiers() {
152        final DetailAST modifiers = getMainAst().findFirstToken(TokenTypes.MODIFIERS);
153        final DetailAST firstModifier = modifiers.getFirstChild();
154        final DetailAST firstNonAnnotationMod = getFirstNonAnnotationModifier(firstModifier);
155        final int firstModifierLine = firstModifier.getLineNo();
156        final int firstNonAnnotationLine = firstNonAnnotationMod.getLineNo();
157
158        checkLineModifiers(firstModifier, firstModifierLine);
159
160        if (firstModifierLine != firstNonAnnotationLine) {
161            checkNonFirstLineModifiers(firstNonAnnotationMod);
162        }
163    }
164
165    /**
166     * Checks modifiers on the first modifier line.
167     *
168     * @param firstModifier the first modifier
169     * @param firstModifierLine line number of the first modifier
170     */
171    private void checkLineModifiers(DetailAST firstModifier,
172            int firstModifierLine) {
173        for (DetailAST modifier = firstModifier;
174             modifier != null
175                     && modifier.getLineNo() == firstModifierLine;
176             modifier = modifier.getNextSibling()) {
177            if (isOnStartOfLine(modifier)
178                    && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) {
179                logError(modifier, MODIFIER, expandedTabsColumnNo(modifier));
180            }
181        }
182    }
183
184    /**
185     * Checks modifiers on the line of the first non-annotation modifier,
186     * skipping correctly-indented annotations to avoid false positives
187     * when annotations wrap before the class keyword.
188     *
189     * @param firstNonAnnotationMod the first non-annotation modifier
190     */
191    private void checkNonFirstLineModifiers(DetailAST firstNonAnnotationMod) {
192        for (DetailAST modifier = firstNonAnnotationMod;
193             modifier != null;
194             modifier = modifier.getNextSibling()) {
195            if (isOnStartOfLine(modifier)
196                    && !getIndent().isAcceptable(
197                            expandedTabsColumnNo(modifier))) {
198                logError(modifier, MODIFIER, expandedTabsColumnNo(modifier));
199            }
200        }
201    }
202
203    /**
204     * Finds the first non-annotation modifier node.
205     *
206     * @param firstModifier the first modifier in the modifiers list
207     * @return the first non-annotation modifier node
208     */
209    private static DetailAST getFirstNonAnnotationModifier(DetailAST firstModifier) {
210        DetailAST result = firstModifier;
211        for (DetailAST modifier = firstModifier;
212             modifier != null;
213             modifier = modifier.getNextSibling()) {
214            if (modifier.getType() != TokenTypes.ANNOTATION) {
215                result = modifier;
216                break;
217            }
218        }
219        return result;
220    }
221
222    /**
223     * Creates a handler name for this class according to ast type.
224     *
225     * @param ast the abstract syntax tree.
226     * @return handler name for this class.
227     */
228    private static String getHandlerName(DetailAST ast) {
229        final int tokenType = ast.getType();
230
231        return switch (tokenType) {
232            case TokenTypes.CLASS_DEF -> "class def";
233            case TokenTypes.ENUM_DEF -> "enum def";
234            case TokenTypes.ANNOTATION_DEF -> "annotation def";
235            case TokenTypes.RECORD_DEF -> "record def";
236            default -> "interface def";
237        };
238    }
239
240}