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