001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 } 160 161 @Override 162 public void visitToken(DetailAST ast) { 163 final int parentType = ast.getParent().getType(); 164 165 switch (ast.getType()) { 166 case TokenTypes.OBJBLOCK -> scopeStates.push(new ScopeState()); 167 168 case TokenTypes.MODIFIERS -> { 169 if (parentType == TokenTypes.VARIABLE_DEF 170 && ast.getParent().getParent().getType() == TokenTypes.OBJBLOCK) { 171 processModifiers(ast); 172 } 173 } 174 175 case TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF -> { 176 if (parentType == TokenTypes.OBJBLOCK) { 177 processConstructor(ast); 178 } 179 } 180 181 case TokenTypes.METHOD_DEF -> { 182 if (parentType == TokenTypes.OBJBLOCK) { 183 final ScopeState state = scopeStates.peek(); 184 // nothing can be bigger than method's state 185 state.currentScopeState = STATE_METHOD_DEF; 186 } 187 } 188 189 case TokenTypes.VARIABLE_DEF -> { 190 if (ScopeUtil.isClassFieldDef(ast)) { 191 final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT); 192 classFieldNames.add(fieldDef.getText()); 193 } 194 } 195 196 default -> { 197 // do nothing 198 } 199 } 200 } 201 202 /** 203 * Processes constructor. 204 * 205 * @param ast constructor AST. 206 */ 207 private void processConstructor(DetailAST ast) { 208 final ScopeState state = scopeStates.peek(); 209 if (state.currentScopeState > STATE_CTOR_DEF) { 210 if (!ignoreConstructors) { 211 log(ast, MSG_CONSTRUCTOR); 212 } 213 } 214 else { 215 state.currentScopeState = STATE_CTOR_DEF; 216 } 217 } 218 219 /** 220 * Processes modifiers. 221 * 222 * @param ast ast of Modifiers. 223 */ 224 private void processModifiers(DetailAST ast) { 225 final ScopeState state = scopeStates.peek(); 226 final boolean isStateValid = processModifiersState(ast, state); 227 processModifiersSubState(ast, state, isStateValid); 228 } 229 230 /** 231 * Process if given modifiers are appropriate in given state 232 * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF}, 233 * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is 234 * it updates states where appropriate or logs violation. 235 * 236 * @param modifierAst modifiers to process 237 * @param state current state 238 * @return true if modifierAst is valid in given state, false otherwise 239 */ 240 private boolean processModifiersState(DetailAST modifierAst, ScopeState state) { 241 boolean isStateValid = true; 242 if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) { 243 if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) { 244 isStateValid = false; 245 log(modifierAst, MSG_INSTANCE); 246 } 247 else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) { 248 state.declarationAccess = Scope.PUBLIC; 249 state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF; 250 } 251 } 252 else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF 253 || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) { 254 isStateValid = false; 255 log(modifierAst, MSG_STATIC); 256 } 257 return isStateValid; 258 } 259 260 /** 261 * Checks if given modifiers are valid in substate of given 262 * state({@code Scope}), if it is it updates substate or else it 263 * logs violation. 264 * 265 * @param modifiersAst modifiers to process 266 * @param state current state 267 * @param isStateValid is main state for given modifiers is valid 268 */ 269 private void processModifiersSubState(DetailAST modifiersAst, ScopeState state, 270 boolean isStateValid) { 271 final Scope access = ScopeUtil.getScopeFromMods(modifiersAst); 272 if (state.declarationAccess.compareTo(access) > 0) { 273 if (isStateValid 274 && !ignoreModifiers 275 && !isForwardReference(modifiersAst.getParent())) { 276 log(modifiersAst, MSG_ACCESS); 277 } 278 } 279 else { 280 state.declarationAccess = access; 281 } 282 } 283 284 /** 285 * Checks whether an identifier references a field which has been already defined in class. 286 * 287 * @param fieldDef a field definition. 288 * @return true if an identifier references a field which has been already defined in class. 289 */ 290 private boolean isForwardReference(DetailAST fieldDef) { 291 final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT); 292 final Set<DetailAST> exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT); 293 boolean forwardReference = false; 294 for (DetailAST ident : exprIdents) { 295 if (classFieldNames.contains(ident.getText())) { 296 forwardReference = true; 297 break; 298 } 299 } 300 return forwardReference; 301 } 302 303 /** 304 * Collects all tokens of specific type starting with the current ast node. 305 * 306 * @param ast ast node. 307 * @param tokenType token type. 308 * @return a set of all tokens of specific type starting with the current ast node. 309 */ 310 private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) { 311 DetailAST vertex = ast; 312 final Set<DetailAST> result = new HashSet<>(); 313 final Deque<DetailAST> stack = new ArrayDeque<>(); 314 while (vertex != null || !stack.isEmpty()) { 315 if (!stack.isEmpty()) { 316 vertex = stack.pop(); 317 } 318 while (vertex != null) { 319 if (vertex.getType() == tokenType && !vertex.equals(ast)) { 320 result.add(vertex); 321 } 322 if (vertex.getNextSibling() != null) { 323 stack.push(vertex.getNextSibling()); 324 } 325 vertex = vertex.getFirstChild(); 326 } 327 } 328 return result; 329 } 330 331 @Override 332 public void leaveToken(DetailAST ast) { 333 if (ast.getType() == TokenTypes.OBJBLOCK) { 334 scopeStates.pop(); 335 } 336 } 337 338 /** 339 * Setter to control whether to ignore constructors. 340 * 341 * @param ignoreConstructors whether to ignore constructors. 342 * @since 5.2 343 */ 344 public void setIgnoreConstructors(boolean ignoreConstructors) { 345 this.ignoreConstructors = ignoreConstructors; 346 } 347 348 /** 349 * Setter to control whether to ignore modifiers (fields, ...). 350 * 351 * @param ignoreModifiers whether to ignore modifiers. 352 * @since 5.2 353 */ 354 public void setIgnoreModifiers(boolean ignoreModifiers) { 355 this.ignoreModifiers = ignoreModifiers; 356 } 357 358 /** 359 * Private class to encapsulate the state. 360 */ 361 private static final class ScopeState { 362 363 /** The state the check is in. */ 364 private int currentScopeState = STATE_STATIC_VARIABLE_DEF; 365 366 /** The sub-state the check is in. */ 367 private Scope declarationAccess = Scope.PUBLIC; 368 369 } 370 371}