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