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