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 @Override 134 public int[] getDefaultTokens() { 135 return getRequiredTokens(); 136 } 137 138 @Override 139 public int[] getAcceptableTokens() { 140 return getRequiredTokens(); 141 } 142 143 @Override 144 public int[] getRequiredTokens() { 145 return new int[] { 146 TokenTypes.CTOR_DEF, 147 TokenTypes.METHOD_DEF, 148 TokenTypes.MODIFIERS, 149 TokenTypes.OBJBLOCK, 150 TokenTypes.VARIABLE_DEF, 151 TokenTypes.COMPACT_CTOR_DEF, 152 }; 153 } 154 155 @Override 156 public void beginTree(DetailAST rootAST) { 157 scopeStates = new ArrayDeque<>(); 158 classFieldNames = new HashSet<>(); 159 scopeStates.push(new ScopeState()); 160 } 161 162 @Override 163 public void visitToken(DetailAST ast) { 164 final int parentType = ast.getParent().getType(); 165 166 switch (ast.getType()) { 167 case TokenTypes.OBJBLOCK -> scopeStates.push(new ScopeState()); 168 169 case TokenTypes.MODIFIERS -> { 170 if (parentType == TokenTypes.VARIABLE_DEF 171 && isTypeMemberContainer(ast.getParent().getParent().getType())) { 172 processModifiers(ast); 173 } 174 } 175 176 case TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF -> { 177 if (parentType == TokenTypes.OBJBLOCK) { 178 processConstructor(ast); 179 } 180 } 181 182 case TokenTypes.METHOD_DEF -> { 183 if (isTypeMemberContainer(parentType)) { 184 final ScopeState state = scopeStates.peek(); 185 // nothing can be bigger than method's state 186 state.currentScopeState = STATE_METHOD_DEF; 187 } 188 } 189 190 case TokenTypes.VARIABLE_DEF -> { 191 if (ScopeUtil.isClassFieldDef(ast)) { 192 final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT); 193 classFieldNames.add(fieldDef.getText()); 194 } 195 } 196 197 default -> { 198 // do nothing 199 } 200 } 201 } 202 203 /** 204 * Checks whether the given token type is a container of class-level 205 * members: an object block, or the implicit class of a JEP 512 compact 206 * source file. 207 * 208 * @param type the token type to check 209 * @return true if the type holds class-level members 210 */ 211 private static boolean isTypeMemberContainer(int type) { 212 return type == TokenTypes.OBJBLOCK 213 || type == TokenTypes.COMPACT_COMPILATION_UNIT; 214 } 215 216 /** 217 * Processes constructor. 218 * 219 * @param ast constructor AST. 220 */ 221 private void processConstructor(DetailAST ast) { 222 final ScopeState state = scopeStates.peek(); 223 if (state.currentScopeState > STATE_CTOR_DEF) { 224 if (!ignoreConstructors) { 225 log(ast, MSG_CONSTRUCTOR); 226 } 227 } 228 else { 229 state.currentScopeState = STATE_CTOR_DEF; 230 } 231 } 232 233 /** 234 * Processes modifiers. 235 * 236 * @param ast ast of Modifiers. 237 */ 238 private void processModifiers(DetailAST ast) { 239 final ScopeState state = scopeStates.peek(); 240 final boolean isStateValid = processModifiersState(ast, state); 241 processModifiersSubState(ast, state, isStateValid); 242 } 243 244 /** 245 * Process if given modifiers are appropriate in given state 246 * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF}, 247 * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is 248 * it updates states where appropriate or logs violation. 249 * 250 * @param modifierAst modifiers to process 251 * @param state current state 252 * @return true if modifierAst is valid in given state, false otherwise 253 */ 254 private boolean processModifiersState(DetailAST modifierAst, ScopeState state) { 255 boolean isStateValid = true; 256 if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 257 if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) { 258 isStateValid = false; 259 log(modifierAst, MSG_INSTANCE); 260 } 261 else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) { 262 state.declarationAccess = Scope.PUBLIC; 263 state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF; 264 } 265 } 266 else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF 267 || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) { 268 isStateValid = false; 269 log(modifierAst, MSG_STATIC); 270 } 271 return isStateValid; 272 } 273 274 /** 275 * Checks if given modifiers are valid in substate of given 276 * state({@code Scope}), if it is it updates substate or else it 277 * 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 389}