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