View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <p>
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   * </p>
41   * <p>
42   * According to
43   * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852">
44   * Code Conventions for the Java Programming Language</a>, the parts of a class
45   * or interface declaration should appear in the following order:
46   * </p>
47   * <ol>
48   * <li>
49   * Class (static) variables. First the public class variables, then
50   * protected, then package level (no access modifier), and then private.
51   * </li>
52   * <li> Instance variables. First the public class variables, then
53   * protected, then package level (no access modifier), and then private.
54   * </li>
55   * <li> Constructors </li>
56   * <li> Methods </li>
57   * </ol>
58   * <p>
59   * Purpose of <b>ignore*</b> option is to ignore related violations,
60   * however it still impacts on other class members.
61   * </p>
62   * <p>ATTENTION: the check skips class fields which have
63   * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.3.3">
64   * forward references </a> from validation due to the fact that we have Checkstyle's limitations
65   * to clearly detect user intention of fields location and grouping. For example:
66   * </p>
67   * <pre>
68   * public class A {
69   *   private double x = 1.0;
70   *   private double y = 2.0;
71   *   public double slope = x / y; // will be skipped from validation due to forward reference
72   * }
73   * </pre>
74   * <ul>
75   * <li>
76   * Property {@code ignoreConstructors} - Control whether to ignore constructors.
77   * Type is {@code boolean}.
78   * Default value is {@code false}.
79   * </li>
80   * <li>
81   * Property {@code ignoreModifiers} - Control whether to ignore modifiers (fields, ...).
82   * Type is {@code boolean}.
83   * Default value is {@code false}.
84   * </li>
85   * </ul>
86   * <p>
87   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
88   * </p>
89   * <p>
90   * Violation Message Keys:
91   * </p>
92   * <ul>
93   * <li>
94   * {@code declaration.order.access}
95   * </li>
96   * <li>
97   * {@code declaration.order.constructor}
98   * </li>
99   * <li>
100  * {@code declaration.order.instance}
101  * </li>
102  * <li>
103  * {@code declaration.order.static}
104  * </li>
105  * </ul>
106  *
107  * @since 3.2
108  */
109 @FileStatefulCheck
110 public class DeclarationOrderCheck extends AbstractCheck {
111 
112     /**
113      * A key is pointing to the warning message text in "messages.properties"
114      * file.
115      */
116     public static final String MSG_CONSTRUCTOR = "declaration.order.constructor";
117 
118     /**
119      * A key is pointing to the warning message text in "messages.properties"
120      * file.
121      */
122     public static final String MSG_STATIC = "declaration.order.static";
123 
124     /**
125      * A key is pointing to the warning message text in "messages.properties"
126      * file.
127      */
128     public static final String MSG_INSTANCE = "declaration.order.instance";
129 
130     /**
131      * A key is pointing to the warning message text in "messages.properties"
132      * file.
133      */
134     public static final String MSG_ACCESS = "declaration.order.access";
135 
136     /** State for the VARIABLE_DEF. */
137     private static final int STATE_STATIC_VARIABLE_DEF = 1;
138 
139     /** State for the VARIABLE_DEF. */
140     private static final int STATE_INSTANCE_VARIABLE_DEF = 2;
141 
142     /** State for the CTOR_DEF. */
143     private static final int STATE_CTOR_DEF = 3;
144 
145     /** State for the METHOD_DEF. */
146     private static final int STATE_METHOD_DEF = 4;
147 
148     /**
149      * List of Declaration States. This is necessary due to
150      * inner classes that have their own state.
151      */
152     private Deque<ScopeState> scopeStates;
153 
154     /** Set of all class field names.*/
155     private Set<String> classFieldNames;
156 
157     /** Control whether to ignore constructors. */
158     private boolean ignoreConstructors;
159     /** Control whether to ignore modifiers (fields, ...). */
160     private boolean ignoreModifiers;
161 
162     @Override
163     public int[] getDefaultTokens() {
164         return getRequiredTokens();
165     }
166 
167     @Override
168     public int[] getAcceptableTokens() {
169         return getRequiredTokens();
170     }
171 
172     @Override
173     public int[] getRequiredTokens() {
174         return new int[] {
175             TokenTypes.CTOR_DEF,
176             TokenTypes.METHOD_DEF,
177             TokenTypes.MODIFIERS,
178             TokenTypes.OBJBLOCK,
179             TokenTypes.VARIABLE_DEF,
180             TokenTypes.COMPACT_CTOR_DEF,
181         };
182     }
183 
184     @Override
185     public void beginTree(DetailAST rootAST) {
186         scopeStates = new ArrayDeque<>();
187         classFieldNames = new HashSet<>();
188     }
189 
190     @Override
191     public void visitToken(DetailAST ast) {
192         final int parentType = ast.getParent().getType();
193 
194         switch (ast.getType()) {
195             case TokenTypes.OBJBLOCK:
196                 scopeStates.push(new ScopeState());
197                 break;
198             case TokenTypes.MODIFIERS:
199                 if (parentType == TokenTypes.VARIABLE_DEF
200                     && ast.getParent().getParent().getType() == TokenTypes.OBJBLOCK) {
201                     processModifiers(ast);
202                 }
203                 break;
204             case TokenTypes.CTOR_DEF:
205             case TokenTypes.COMPACT_CTOR_DEF:
206                 if (parentType == TokenTypes.OBJBLOCK) {
207                     processConstructor(ast);
208                 }
209                 break;
210             case TokenTypes.METHOD_DEF:
211                 if (parentType == TokenTypes.OBJBLOCK) {
212                     final ScopeState state = scopeStates.peek();
213                     // nothing can be bigger than method's state
214                     state.currentScopeState = STATE_METHOD_DEF;
215                 }
216                 break;
217             case TokenTypes.VARIABLE_DEF:
218                 if (ScopeUtil.isClassFieldDef(ast)) {
219                     final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT);
220                     classFieldNames.add(fieldDef.getText());
221                 }
222                 break;
223             default:
224                 break;
225         }
226     }
227 
228     /**
229      * Processes constructor.
230      *
231      * @param ast constructor AST.
232      */
233     private void processConstructor(DetailAST ast) {
234         final ScopeState state = scopeStates.peek();
235         if (state.currentScopeState > STATE_CTOR_DEF) {
236             if (!ignoreConstructors) {
237                 log(ast, MSG_CONSTRUCTOR);
238             }
239         }
240         else {
241             state.currentScopeState = STATE_CTOR_DEF;
242         }
243     }
244 
245     /**
246      * Processes modifiers.
247      *
248      * @param ast ast of Modifiers.
249      */
250     private void processModifiers(DetailAST ast) {
251         final ScopeState state = scopeStates.peek();
252         final boolean isStateValid = processModifiersState(ast, state);
253         processModifiersSubState(ast, state, isStateValid);
254     }
255 
256     /**
257      * Process if given modifiers are appropriate in given state
258      * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF},
259      * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is
260      * it updates states where appropriate or logs violation.
261      *
262      * @param modifierAst modifiers to process
263      * @param state current state
264      * @return true if modifierAst is valid in given state, false otherwise
265      */
266     private boolean processModifiersState(DetailAST modifierAst, ScopeState state) {
267         boolean isStateValid = true;
268         if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
269             if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) {
270                 isStateValid = false;
271                 log(modifierAst, MSG_INSTANCE);
272             }
273             else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) {
274                 state.declarationAccess = Scope.PUBLIC;
275                 state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF;
276             }
277         }
278         else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF
279                 || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) {
280             isStateValid = false;
281             log(modifierAst, MSG_STATIC);
282         }
283         return isStateValid;
284     }
285 
286     /**
287      * Checks if given modifiers are valid in substate of given
288      * state({@code Scope}), if it is it updates substate or else it
289      * logs violation.
290      *
291      * @param modifiersAst modifiers to process
292      * @param state current state
293      * @param isStateValid is main state for given modifiers is valid
294      */
295     private void processModifiersSubState(DetailAST modifiersAst, ScopeState state,
296                                           boolean isStateValid) {
297         final Scope access = ScopeUtil.getScopeFromMods(modifiersAst);
298         if (state.declarationAccess.compareTo(access) > 0) {
299             if (isStateValid
300                     && !ignoreModifiers
301                     && !isForwardReference(modifiersAst.getParent())) {
302                 log(modifiersAst, MSG_ACCESS);
303             }
304         }
305         else {
306             state.declarationAccess = access;
307         }
308     }
309 
310     /**
311      * Checks whether an identifier references a field which has been already defined in class.
312      *
313      * @param fieldDef a field definition.
314      * @return true if an identifier references a field which has been already defined in class.
315      */
316     private boolean isForwardReference(DetailAST fieldDef) {
317         final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT);
318         final Set<DetailAST> exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT);
319         boolean forwardReference = false;
320         for (DetailAST ident : exprIdents) {
321             if (classFieldNames.contains(ident.getText())) {
322                 forwardReference = true;
323                 break;
324             }
325         }
326         return forwardReference;
327     }
328 
329     /**
330      * Collects all tokens of specific type starting with the current ast node.
331      *
332      * @param ast ast node.
333      * @param tokenType token type.
334      * @return a set of all tokens of specific type starting with the current ast node.
335      */
336     private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) {
337         DetailAST vertex = ast;
338         final Set<DetailAST> result = new HashSet<>();
339         final Deque<DetailAST> stack = new ArrayDeque<>();
340         while (vertex != null || !stack.isEmpty()) {
341             if (!stack.isEmpty()) {
342                 vertex = stack.pop();
343             }
344             while (vertex != null) {
345                 if (vertex.getType() == tokenType && !vertex.equals(ast)) {
346                     result.add(vertex);
347                 }
348                 if (vertex.getNextSibling() != null) {
349                     stack.push(vertex.getNextSibling());
350                 }
351                 vertex = vertex.getFirstChild();
352             }
353         }
354         return result;
355     }
356 
357     @Override
358     public void leaveToken(DetailAST ast) {
359         if (ast.getType() == TokenTypes.OBJBLOCK) {
360             scopeStates.pop();
361         }
362     }
363 
364     /**
365      * Setter to control whether to ignore constructors.
366      *
367      * @param ignoreConstructors whether to ignore constructors.
368      * @since 5.2
369      */
370     public void setIgnoreConstructors(boolean ignoreConstructors) {
371         this.ignoreConstructors = ignoreConstructors;
372     }
373 
374     /**
375      * Setter to control whether to ignore modifiers (fields, ...).
376      *
377      * @param ignoreModifiers whether to ignore modifiers.
378      * @since 5.2
379      */
380     public void setIgnoreModifiers(boolean ignoreModifiers) {
381         this.ignoreModifiers = ignoreModifiers;
382     }
383 
384     /**
385      * Private class to encapsulate the state.
386      */
387     private static final class ScopeState {
388 
389         /** The state the check is in. */
390         private int currentScopeState = STATE_STATIC_VARIABLE_DEF;
391 
392         /** The sub-state the check is in. */
393         private Scope declarationAccess = Scope.PUBLIC;
394 
395     }
396 
397 }