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