1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.coding;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
24 import java.util.HashSet;
25 import java.util.Set;
26
27 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.Scope;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
33
34 /**
35 * <div>
36 * Checks that the parts of a class, record, or interface declaration appear in the order
37 * suggested by the
38 * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852">
39 * Code Conventions for the Java Programming Language</a>.
40 * </div>
41 *
42 * <p>
43 * According to
44 * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852">
45 * Code Conventions for the Java Programming Language</a>, the parts of a class
46 * or interface declaration should appear in the following order:
47 * </p>
48 * <ol>
49 * <li>
50 * Class (static) variables. First the public class variables, then
51 * protected, then package level (no access modifier), and then private.
52 * </li>
53 * <li> Instance variables. First the public class variables, then
54 * protected, then package level (no access modifier), and then private.
55 * </li>
56 * <li> Constructors </li>
57 * <li> Methods </li>
58 * </ol>
59 *
60 * <p>
61 * Purpose of <b>ignore*</b> option is to ignore related violations,
62 * however it still impacts on other class members.
63 * </p>
64 *
65 * <p>ATTENTION: the check skips class fields which have
66 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.3.3">
67 * forward references </a> from validation due to the fact that we have Checkstyle's limitations
68 * to clearly detect user intention of fields location and grouping. For example:
69 * </p>
70 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
71 * public class A {
72 * private double x = 1.0;
73 * private double y = 2.0;
74 * public double slope = x / y; // will be skipped from validation due to forward reference
75 * }
76 * </code></pre></div>
77 *
78 * @since 3.2
79 */
80 @FileStatefulCheck
81 public class DeclarationOrderCheck extends AbstractCheck {
82
83 /**
84 * A key is pointing to the warning message text in "messages.properties"
85 * file.
86 */
87 public static final String MSG_CONSTRUCTOR = "declaration.order.constructor";
88
89 /**
90 * A key is pointing to the warning message text in "messages.properties"
91 * file.
92 */
93 public static final String MSG_STATIC = "declaration.order.static";
94
95 /**
96 * A key is pointing to the warning message text in "messages.properties"
97 * file.
98 */
99 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 }