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.coding; 021 022import java.util.ArrayDeque; 023import java.util.Deque; 024import java.util.HashSet; 025import java.util.Set; 026 027import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.Scope; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 033 034/** 035 * <div> 036 * Checks that the parts of a class, record, or interface declaration appear in the order 037 * suggested by the 038 * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852"> 039 * Code Conventions for the Java Programming Language</a>. 040 * </div> 041 * 042 * <p> 043 * According to 044 * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852"> 045 * Code Conventions for the Java Programming Language</a>, the parts of a class 046 * or interface declaration should appear in the following order: 047 * </p> 048 * <ol> 049 * <li> 050 * Class (static) variables. First the public class variables, then 051 * protected, then package level (no access modifier), and then private. 052 * </li> 053 * <li> Instance variables. First the public class variables, then 054 * protected, then package level (no access modifier), and then private. 055 * </li> 056 * <li> Constructors </li> 057 * <li> Methods </li> 058 * </ol> 059 * 060 * <p> 061 * Purpose of <b>ignore*</b> option is to ignore related violations, 062 * however it still impacts on other class members. 063 * </p> 064 * 065 * <p>ATTENTION: the check skips class fields which have 066 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.3.3"> 067 * forward references </a> from validation due to the fact that we have Checkstyle's limitations 068 * to clearly detect user intention of fields location and grouping. For example: 069 * </p> 070 * {@snippet lang="text" : 071 * public class A { 072 * private double x = 1.0; 073 * private double y = 2.0; 074 * public double slope = x / y; // will be skipped from validation due to forward reference 075 * } 076 * } 077 * 078 * @since 3.2 079 */ 080@FileStatefulCheck 081public class DeclarationOrderCheck extends AbstractCheck { 082 083 /** 084 * A key is pointing to the warning message text in "messages.properties" file. 085 */ 086 public static final String MSG_CONSTRUCTOR = "declaration.order.constructor"; 087 088 /** 089 * A key is pointing to the warning message text in "messages.properties" file. 090 */ 091 public static final String MSG_STATIC = "declaration.order.static"; 092 093 /** 094 * A key is pointing to the warning message text in "messages.properties" file. 095 */ 096 public static final String MSG_INSTANCE = "declaration.order.instance"; 097 098 /** 099 * A key is pointing to the warning message text in "messages.properties" file. 100 */ 101 public static final String MSG_ACCESS = "declaration.order.access"; 102 103 /** State for the VARIABLE_DEF. */ 104 private static final int STATE_STATIC_VARIABLE_DEF = 1; 105 106 /** State for the VARIABLE_DEF. */ 107 private static final int STATE_INSTANCE_VARIABLE_DEF = 2; 108 109 /** State for the CTOR_DEF. */ 110 private static final int STATE_CTOR_DEF = 3; 111 112 /** State for the METHOD_DEF. */ 113 private static final int STATE_METHOD_DEF = 4; 114 115 /** 116 * List of Declaration States. This is necessary due to inner classes that have their own state. 117 */ 118 private Deque<ScopeState> scopeStates; 119 120 /** Set of all class field names.*/ 121 private Set<String> classFieldNames; 122 123 /** Control whether to ignore constructors. */ 124 private boolean ignoreConstructors; 125 /** Control whether to ignore modifiers (fields, ...). */ 126 private boolean ignoreModifiers; 127 128 /** 129 * Creates a new {@code DeclarationOrderCheck} instance. 130 */ 131 public DeclarationOrderCheck() { 132 // no code by default 133 } 134 135 @Override 136 public int[] getDefaultTokens() { 137 return getRequiredTokens(); 138 } 139 140 @Override 141 public int[] getAcceptableTokens() { 142 return getRequiredTokens(); 143 } 144 145 @Override 146 public int[] getRequiredTokens() { 147 return new int[] { 148 TokenTypes.CTOR_DEF, 149 TokenTypes.METHOD_DEF, 150 TokenTypes.MODIFIERS, 151 TokenTypes.OBJBLOCK, 152 TokenTypes.VARIABLE_DEF, 153 TokenTypes.COMPACT_CTOR_DEF, 154 }; 155 } 156 157 @Override 158 public void beginTree(DetailAST rootAST) { 159 scopeStates = new ArrayDeque<>(); 160 classFieldNames = new HashSet<>(); 161 scopeStates.push(new ScopeState()); 162 } 163 164 @Override 165 public void visitToken(DetailAST ast) { 166 final int parentType = ast.getParent().getType(); 167 168 switch (ast.getType()) { 169 case TokenTypes.OBJBLOCK -> scopeStates.push(new ScopeState()); 170 171 case TokenTypes.MODIFIERS -> { 172 if (parentType == TokenTypes.VARIABLE_DEF 173 && isTypeMemberContainer(ast.getParent().getParent().getType())) { 174 processModifiers(ast); 175 } 176 } 177 178 case TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF -> { 179 if (parentType == TokenTypes.OBJBLOCK) { 180 processConstructor(ast); 181 } 182 } 183 184 case TokenTypes.METHOD_DEF -> { 185 if (isTypeMemberContainer(parentType)) { 186 final ScopeState state = scopeStates.peek(); 187 // nothing can be bigger than method's state 188 state.currentScopeState = STATE_METHOD_DEF; 189 } 190 } 191 192 case TokenTypes.VARIABLE_DEF -> { 193 if (ScopeUtil.isClassFieldDef(ast)) { 194 final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT); 195 classFieldNames.add(fieldDef.getText()); 196 } 197 } 198 199 default -> { 200 // do nothing 201 } 202 } 203 } 204 205 /** 206 * Checks whether the given token type is a container of class-level 207 * members: an object block, or the implicit class of a JEP 512 compact source file. 208 * 209 * @param type the token type to check 210 * @return true if the type holds class-level members 211 */ 212 private static boolean isTypeMemberContainer(int type) { 213 return type == TokenTypes.OBJBLOCK 214 || type == TokenTypes.COMPACT_COMPILATION_UNIT; 215 } 216 217 /** 218 * Processes constructor. 219 * 220 * @param ast constructor AST. 221 */ 222 private void processConstructor(DetailAST ast) { 223 final ScopeState state = scopeStates.peek(); 224 if (state.currentScopeState > STATE_CTOR_DEF) { 225 if (!ignoreConstructors) { 226 log(ast, MSG_CONSTRUCTOR); 227 } 228 } 229 else { 230 state.currentScopeState = STATE_CTOR_DEF; 231 } 232 } 233 234 /** 235 * Processes modifiers. 236 * 237 * @param ast ast of Modifiers. 238 */ 239 private void processModifiers(DetailAST ast) { 240 final ScopeState state = scopeStates.peek(); 241 final boolean isStateValid = processModifiersState(ast, state); 242 processModifiersSubState(ast, state, isStateValid); 243 } 244 245 /** 246 * Process if given modifiers are appropriate in given state 247 * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF}, 248 * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is 249 * it updates states where appropriate or logs violation. 250 * 251 * @param modifierAst modifiers to process 252 * @param state current state 253 * @return true if modifierAst is valid in given state, false otherwise 254 */ 255 private boolean processModifiersState(DetailAST modifierAst, ScopeState state) { 256 boolean isStateValid = true; 257 if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 258 if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) { 259 isStateValid = false; 260 log(modifierAst, MSG_INSTANCE); 261 } 262 else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) { 263 state.declarationAccess = Scope.PUBLIC; 264 state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF; 265 } 266 } 267 else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF 268 || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) { 269 isStateValid = false; 270 log(modifierAst, MSG_STATIC); 271 } 272 return isStateValid; 273 } 274 275 /** 276 * Checks if given modifiers are valid in substate of given 277 * state({@code Scope}), if it is it updates substate or else it logs violation. 278 * 279 * @param modifiersAst modifiers to process 280 * @param state current state 281 * @param isStateValid is main state for given modifiers is valid 282 */ 283 private void processModifiersSubState(DetailAST modifiersAst, ScopeState state, 284 boolean isStateValid) { 285 final Scope access = ScopeUtil.getScopeFromMods(modifiersAst); 286 if (state.declarationAccess.compareTo(access) > 0) { 287 if (isStateValid 288 && !ignoreModifiers 289 && !isForwardReference(modifiersAst.getParent())) { 290 log(modifiersAst, MSG_ACCESS); 291 } 292 } 293 else { 294 state.declarationAccess = access; 295 } 296 } 297 298 /** 299 * Checks whether an identifier references a field which has been already defined in class. 300 * 301 * @param fieldDef a field definition. 302 * @return true if an identifier references a field which has been already defined in class. 303 */ 304 private boolean isForwardReference(DetailAST fieldDef) { 305 final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT); 306 final Set<DetailAST> exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT); 307 boolean forwardReference = false; 308 for (DetailAST ident : exprIdents) { 309 if (classFieldNames.contains(ident.getText())) { 310 forwardReference = true; 311 break; 312 } 313 } 314 return forwardReference; 315 } 316 317 /** 318 * Collects all tokens of specific type starting with the current ast node. 319 * 320 * @param ast ast node. 321 * @param tokenType token type. 322 * @return a set of all tokens of specific type starting with the current ast node. 323 */ 324 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) { 325 final Deque<DetailAST> stack = new ArrayDeque<>(); 326 stack.push(ast); 327 328 final Set<DetailAST> result = new HashSet<>(); 329 330 while (!stack.isEmpty()) { 331 final DetailAST current = stack.pop(); 332 if (current.getType() == tokenType && !current.equals(ast)) { 333 result.add(current); 334 } 335 336 final DetailAST sibling = current.getNextSibling(); 337 if (sibling != null) { 338 stack.push(sibling); 339 } 340 341 final DetailAST child = current.getFirstChild(); 342 if (child != null) { 343 stack.push(child); 344 } 345 } 346 return result; 347 } 348 349 @Override 350 public void leaveToken(DetailAST ast) { 351 if (ast.getType() == TokenTypes.OBJBLOCK) { 352 scopeStates.pop(); 353 } 354 } 355 356 /** 357 * Setter to control whether to ignore constructors. 358 * 359 * @param ignoreConstructors whether to ignore constructors. 360 * @since 5.2 361 */ 362 public void setIgnoreConstructors(boolean ignoreConstructors) { 363 this.ignoreConstructors = ignoreConstructors; 364 } 365 366 /** 367 * Setter to control whether to ignore modifiers (fields, ...). 368 * 369 * @param ignoreModifiers whether to ignore modifiers. 370 * @since 5.2 371 */ 372 public void setIgnoreModifiers(boolean ignoreModifiers) { 373 this.ignoreModifiers = ignoreModifiers; 374 } 375 376 /** 377 * Private class to encapsulate the state. 378 */ 379 private static final class ScopeState { 380 381 /** The state the check is in. */ 382 private int currentScopeState = STATE_STATIC_VARIABLE_DEF; 383 384 /** The sub-state the check is in. */ 385 private Scope declarationAccess = Scope.PUBLIC; 386 387 /** 388 * Creates a new {@code ScopeState} instance. 389 */ 390 private ScopeState() { 391 // no code by default 392 } 393 394 } 395 396}