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   * <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 }