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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 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 * </code></pre></div> 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" 085 * file. 086 */ 087 public static final String MSG_CONSTRUCTOR = "declaration.order.constructor"; 088 089 /** 090 * A key is pointing to the warning message text in "messages.properties" 091 * file. 092 */ 093 public static final String MSG_STATIC = "declaration.order.static"; 094 095 /** 096 * A key is pointing to the warning message text in "messages.properties" 097 * file. 098 */ 099 public static final String MSG_INSTANCE = "declaration.order.instance"; 100 101 /** 102 * A key is pointing to the warning message text in "messages.properties" 103 * file. 104 */ 105 public static final String MSG_ACCESS = "declaration.order.access"; 106 107 /** State for the VARIABLE_DEF. */ 108 private static final int STATE_STATIC_VARIABLE_DEF = 1; 109 110 /** State for the VARIABLE_DEF. */ 111 private static final int STATE_INSTANCE_VARIABLE_DEF = 2; 112 113 /** State for the CTOR_DEF. */ 114 private static final int STATE_CTOR_DEF = 3; 115 116 /** State for the METHOD_DEF. */ 117 private static final int STATE_METHOD_DEF = 4; 118 119 /** 120 * List of Declaration States. This is necessary due to 121 * inner classes that have their own state. 122 */ 123 private Deque<ScopeState> scopeStates; 124 125 /** Set of all class field names.*/ 126 private Set<String> classFieldNames; 127 128 /** Control whether to ignore constructors. */ 129 private boolean ignoreConstructors; 130 /** Control whether to ignore modifiers (fields, ...). */ 131 private boolean ignoreModifiers; 132 133 /** 134 * Creates a new {@code DeclarationOrderCheck} instance. 135 */ 136 public DeclarationOrderCheck() { 137 // no code by default 138 } 139 140 @Override 141 public int[] getDefaultTokens() { 142 return getRequiredTokens(); 143 } 144 145 @Override 146 public int[] getAcceptableTokens() { 147 return getRequiredTokens(); 148 } 149 150 @Override 151 public int[] getRequiredTokens() { 152 return new int[] { 153 TokenTypes.CTOR_DEF, 154 TokenTypes.METHOD_DEF, 155 TokenTypes.MODIFIERS, 156 TokenTypes.OBJBLOCK, 157 TokenTypes.VARIABLE_DEF, 158 TokenTypes.COMPACT_CTOR_DEF, 159 }; 160 } 161 162 @Override 163 public void beginTree(DetailAST rootAST) { 164 scopeStates = new ArrayDeque<>(); 165 classFieldNames = new HashSet<>(); 166 scopeStates.push(new ScopeState()); 167 } 168 169 @Override 170 public void visitToken(DetailAST ast) { 171 final int parentType = ast.getParent().getType(); 172 173 switch (ast.getType()) { 174 case TokenTypes.OBJBLOCK -> scopeStates.push(new ScopeState()); 175 176 case TokenTypes.MODIFIERS -> { 177 if (parentType == TokenTypes.VARIABLE_DEF 178 && isTypeMemberContainer(ast.getParent().getParent().getType())) { 179 processModifiers(ast); 180 } 181 } 182 183 case TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF -> { 184 if (parentType == TokenTypes.OBJBLOCK) { 185 processConstructor(ast); 186 } 187 } 188 189 case TokenTypes.METHOD_DEF -> { 190 if (isTypeMemberContainer(parentType)) { 191 final ScopeState state = scopeStates.peek(); 192 // nothing can be bigger than method's state 193 state.currentScopeState = STATE_METHOD_DEF; 194 } 195 } 196 197 case TokenTypes.VARIABLE_DEF -> { 198 if (ScopeUtil.isClassFieldDef(ast)) { 199 final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT); 200 classFieldNames.add(fieldDef.getText()); 201 } 202 } 203 204 default -> { 205 // do nothing 206 } 207 } 208 } 209 210 /** 211 * Checks whether the given token type is a container of class-level 212 * members: an object block, or the implicit class of a JEP 512 compact 213 * source file. 214 * 215 * @param type the token type to check 216 * @return true if the type holds class-level members 217 */ 218 private static boolean isTypeMemberContainer(int type) { 219 return type == TokenTypes.OBJBLOCK 220 || type == TokenTypes.COMPACT_COMPILATION_UNIT; 221 } 222 223 /** 224 * Processes constructor. 225 * 226 * @param ast constructor AST. 227 */ 228 private void processConstructor(DetailAST ast) { 229 final ScopeState state = scopeStates.peek(); 230 if (state.currentScopeState > STATE_CTOR_DEF) { 231 if (!ignoreConstructors) { 232 log(ast, MSG_CONSTRUCTOR); 233 } 234 } 235 else { 236 state.currentScopeState = STATE_CTOR_DEF; 237 } 238 } 239 240 /** 241 * Processes modifiers. 242 * 243 * @param ast ast of Modifiers. 244 */ 245 private void processModifiers(DetailAST ast) { 246 final ScopeState state = scopeStates.peek(); 247 final boolean isStateValid = processModifiersState(ast, state); 248 processModifiersSubState(ast, state, isStateValid); 249 } 250 251 /** 252 * Process if given modifiers are appropriate in given state 253 * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF}, 254 * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is 255 * it updates states where appropriate or logs violation. 256 * 257 * @param modifierAst modifiers to process 258 * @param state current state 259 * @return true if modifierAst is valid in given state, false otherwise 260 */ 261 private boolean processModifiersState(DetailAST modifierAst, ScopeState state) { 262 boolean isStateValid = true; 263 if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 264 if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) { 265 isStateValid = false; 266 log(modifierAst, MSG_INSTANCE); 267 } 268 else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) { 269 state.declarationAccess = Scope.PUBLIC; 270 state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF; 271 } 272 } 273 else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF 274 || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) { 275 isStateValid = false; 276 log(modifierAst, MSG_STATIC); 277 } 278 return isStateValid; 279 } 280 281 /** 282 * Checks if given modifiers are valid in substate of given 283 * state({@code Scope}), if it is it updates substate or else it 284 * logs violation. 285 * 286 * @param modifiersAst modifiers to process 287 * @param state current state 288 * @param isStateValid is main state for given modifiers is valid 289 */ 290 private void processModifiersSubState(DetailAST modifiersAst, ScopeState state, 291 boolean isStateValid) { 292 final Scope access = ScopeUtil.getScopeFromMods(modifiersAst); 293 if (state.declarationAccess.compareTo(access) > 0) { 294 if (isStateValid 295 && !ignoreModifiers 296 && !isForwardReference(modifiersAst.getParent())) { 297 log(modifiersAst, MSG_ACCESS); 298 } 299 } 300 else { 301 state.declarationAccess = access; 302 } 303 } 304 305 /** 306 * Checks whether an identifier references a field which has been already defined in class. 307 * 308 * @param fieldDef a field definition. 309 * @return true if an identifier references a field which has been already defined in class. 310 */ 311 private boolean isForwardReference(DetailAST fieldDef) { 312 final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT); 313 final Set<DetailAST> exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT); 314 boolean forwardReference = false; 315 for (DetailAST ident : exprIdents) { 316 if (classFieldNames.contains(ident.getText())) { 317 forwardReference = true; 318 break; 319 } 320 } 321 return forwardReference; 322 } 323 324 /** 325 * Collects all tokens of specific type starting with the current ast node. 326 * 327 * @param ast ast node. 328 * @param tokenType token type. 329 * @return a set of all tokens of specific type starting with the current ast node. 330 */ 331 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) { 332 final Deque<DetailAST> stack = new ArrayDeque<>(); 333 stack.push(ast); 334 335 final Set<DetailAST> result = new HashSet<>(); 336 337 while (!stack.isEmpty()) { 338 final DetailAST current = stack.pop(); 339 if (current.getType() == tokenType && !current.equals(ast)) { 340 result.add(current); 341 } 342 343 final DetailAST sibling = current.getNextSibling(); 344 if (sibling != null) { 345 stack.push(sibling); 346 } 347 348 final DetailAST child = current.getFirstChild(); 349 if (child != null) { 350 stack.push(child); 351 } 352 } 353 return result; 354 } 355 356 @Override 357 public void leaveToken(DetailAST ast) { 358 if (ast.getType() == TokenTypes.OBJBLOCK) { 359 scopeStates.pop(); 360 } 361 } 362 363 /** 364 * Setter to control whether to ignore constructors. 365 * 366 * @param ignoreConstructors whether to ignore constructors. 367 * @since 5.2 368 */ 369 public void setIgnoreConstructors(boolean ignoreConstructors) { 370 this.ignoreConstructors = ignoreConstructors; 371 } 372 373 /** 374 * Setter to control whether to ignore modifiers (fields, ...). 375 * 376 * @param ignoreModifiers whether to ignore modifiers. 377 * @since 5.2 378 */ 379 public void setIgnoreModifiers(boolean ignoreModifiers) { 380 this.ignoreModifiers = ignoreModifiers; 381 } 382 383 /** 384 * Private class to encapsulate the state. 385 */ 386 private static final class ScopeState { 387 388 /** The state the check is in. */ 389 private int currentScopeState = STATE_STATIC_VARIABLE_DEF; 390 391 /** The sub-state the check is in. */ 392 private Scope declarationAccess = Scope.PUBLIC; 393 394 /** 395 * Creates a new {@code ScopeState} instance. 396 */ 397 private ScopeState() { 398 // no code by default 399 } 400 } 401 402}