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