View Javadoc
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 }