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