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.modifier;
21  
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  
31  /**
32   * <p>
33   * Checks that the order of modifiers conforms to the suggestions in the
34   * <a
35   * href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html">
36   * Java Language specification, sections 8.1.1, 8.3.1 and 8.4.3</a>.
37   * The correct order is:</p>
38  
39  <ol>
40    <li><span class="code">public</span></li>
41    <li><span class="code">protected</span></li>
42  
43    <li><span class="code">private</span></li>
44    <li><span class="code">abstract</span></li>
45    <li><span class="code">default</span></li>
46    <li><span class="code">static</span></li>
47    <li><span class="code">final</span></li>
48    <li><span class="code">transient</span></li>
49    <li><span class="code">volatile</span></li>
50  
51    <li><span class="code">synchronized</span></li>
52    <li><span class="code">native</span></li>
53    <li><span class="code">strictfp</span></li>
54  </ol>
55   * In additional, modifiers are checked to ensure all annotations
56   * are declared before all other modifiers.
57   * <p>
58   * Rationale: Code is easier to read if everybody follows
59   * a standard.
60   * </p>
61   * <p>
62   * An example of how to configure the check is:
63   * </p>
64   * <pre>
65   * &lt;module name="ModifierOrder"/&gt;
66   * </pre>
67   * @author Lars K├╝hne
68   */
69  @StatelessCheck
70  public class ModifierOrderCheck
71      extends AbstractCheck {
72  
73      /**
74       * A key is pointing to the warning message text in "messages.properties"
75       * file.
76       */
77      public static final String MSG_ANNOTATION_ORDER = "annotation.order";
78  
79      /**
80       * A key is pointing to the warning message text in "messages.properties"
81       * file.
82       */
83      public static final String MSG_MODIFIER_ORDER = "mod.order";
84  
85      /**
86       * The order of modifiers as suggested in sections 8.1.1,
87       * 8.3.1 and 8.4.3 of the JLS.
88       */
89      private static final String[] JLS_ORDER = {
90          "public", "protected", "private", "abstract", "default", "static",
91          "final", "transient", "volatile", "synchronized", "native", "strictfp",
92      };
93  
94      @Override
95      public int[] getDefaultTokens() {
96          return getRequiredTokens();
97      }
98  
99      @Override
100     public int[] getAcceptableTokens() {
101         return getRequiredTokens();
102     }
103 
104     @Override
105     public int[] getRequiredTokens() {
106         return new int[] {TokenTypes.MODIFIERS};
107     }
108 
109     @Override
110     public void visitToken(DetailAST ast) {
111         final List<DetailAST> mods = new ArrayList<>();
112         DetailAST modifier = ast.getFirstChild();
113         while (modifier != null) {
114             mods.add(modifier);
115             modifier = modifier.getNextSibling();
116         }
117 
118         if (!mods.isEmpty()) {
119             final DetailAST error = checkOrderSuggestedByJls(mods);
120             if (error != null) {
121                 if (error.getType() == TokenTypes.ANNOTATION) {
122                     log(error.getLineNo(), error.getColumnNo(),
123                             MSG_ANNOTATION_ORDER,
124                              error.getFirstChild().getText()
125                              + error.getFirstChild().getNextSibling()
126                                 .getText());
127                 }
128                 else {
129                     log(error.getLineNo(), error.getColumnNo(),
130                             MSG_MODIFIER_ORDER, error.getText());
131                 }
132             }
133         }
134     }
135 
136     /**
137      * Checks if the modifiers were added in the order suggested
138      * in the Java language specification.
139      *
140      * @param modifiers list of modifier AST tokens
141      * @return null if the order is correct, otherwise returns the offending
142      *     modifier AST.
143      */
144     private static DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) {
145         final Iterator<DetailAST> iterator = modifiers.iterator();
146 
147         //Speed past all initial annotations
148         DetailAST modifier = skipAnnotations(iterator);
149 
150         DetailAST offendingModifier = null;
151 
152         //All modifiers are annotations, no problem
153         if (modifier.getType() != TokenTypes.ANNOTATION) {
154             int index = 0;
155 
156             while (modifier != null
157                     && offendingModifier == null) {
158 
159                 if (modifier.getType() == TokenTypes.ANNOTATION) {
160                     if (!isAnnotationOnType(modifier)) {
161                         //Annotation not at start of modifiers, bad
162                         offendingModifier = modifier;
163                     }
164                     break;
165                 }
166 
167                 while (index < JLS_ORDER.length
168                        && !JLS_ORDER[index].equals(modifier.getText())) {
169                     index++;
170                 }
171 
172                 if (index == JLS_ORDER.length) {
173                     //Current modifier is out of JLS order
174                     offendingModifier = modifier;
175                 }
176                 else if (iterator.hasNext()) {
177                     modifier = iterator.next();
178                 }
179                 else {
180                     //Reached end of modifiers without problem
181                     modifier = null;
182                 }
183             }
184         }
185         return offendingModifier;
186     }
187 
188     /**
189      * Skip all annotations in modifier block.
190      * @param modifierIterator iterator for collection of modifiers
191      * @return modifier next to last annotation
192      */
193     private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) {
194         DetailAST modifier;
195         do {
196             modifier = modifierIterator.next();
197         } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION);
198         return modifier;
199     }
200 
201     /**
202      * Checks whether annotation on type takes place.
203      * @param modifier modifier token.
204      * @return true if annotation on type takes place.
205      */
206     private static boolean isAnnotationOnType(DetailAST modifier) {
207         boolean annotationOnType = false;
208         final DetailAST modifiers = modifier.getParent();
209         final DetailAST definition = modifiers.getParent();
210         final int definitionType = definition.getType();
211         if (definitionType == TokenTypes.VARIABLE_DEF
212                 || definitionType == TokenTypes.PARAMETER_DEF
213                 || definitionType == TokenTypes.CTOR_DEF) {
214             annotationOnType = true;
215         }
216         else if (definitionType == TokenTypes.METHOD_DEF) {
217             final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE);
218             final int methodReturnType = typeToken.getLastChild().getType();
219             if (methodReturnType != TokenTypes.LITERAL_VOID) {
220                 annotationOnType = true;
221             }
222         }
223         return annotationOnType;
224     }
225 }