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