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}