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.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   * @since 3.0
74   */
75  @StatelessCheck
76  public class ModifierOrderCheck
77      extends AbstractCheck {
78  
79      /**
80       * A key is pointing to the warning message text in "messages.properties"
81       * file.
82       */
83      public static final String MSG_ANNOTATION_ORDER = "annotation.order";
84  
85      /**
86       * A key is pointing to the warning message text in "messages.properties"
87       * file.
88       */
89      public static final String MSG_MODIFIER_ORDER = "mod.order";
90  
91      /**
92       * The order of modifiers as suggested in sections 8.1.1,
93       * 8.3.1 and 8.4.3 of the JLS.
94       */
95      private static final String[] JLS_ORDER = {
96          "public", "protected", "private", "abstract", "default", "static",
97          "sealed", "non-sealed", "final", "transient", "volatile",
98          "synchronized", "native", "strictfp",
99      };
100 
101     /**
102      * Creates a new {@code ModifierOrderCheck} instance.
103      */
104     public ModifierOrderCheck() {
105         // no code by default
106     }
107 
108     @Override
109     public int[] getDefaultTokens() {
110         return getRequiredTokens();
111     }
112 
113     @Override
114     public int[] getAcceptableTokens() {
115         return getRequiredTokens();
116     }
117 
118     @Override
119     public int[] getRequiredTokens() {
120         return new int[] {TokenTypes.MODIFIERS};
121     }
122 
123     @Override
124     public void visitToken(DetailAST ast) {
125         final List<DetailAST> mods = new ArrayList<>();
126         DetailAST modifier = ast.getFirstChild();
127         while (modifier != null) {
128             mods.add(modifier);
129             modifier = modifier.getNextSibling();
130         }
131 
132         if (!mods.isEmpty()) {
133             final DetailAST error = checkOrderSuggestedByJls(mods);
134             if (error != null) {
135                 if (error.getType() == TokenTypes.ANNOTATION) {
136                     log(error,
137                             MSG_ANNOTATION_ORDER,
138                              error.getFirstChild().getText()
139                              + error.getFirstChild().getNextSibling()
140                                 .getText());
141                 }
142                 else {
143                     log(error, MSG_MODIFIER_ORDER, error.getText());
144                 }
145             }
146         }
147     }
148 
149     /**
150      * Checks if the modifiers were added in the order suggested
151      * in the Java language specification.
152      *
153      * @param modifiers list of modifier AST tokens
154      * @return null if the order is correct, otherwise returns the offending
155      *     modifier AST.
156      */
157     private static DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) {
158         final Iterator<DetailAST> iterator = modifiers.iterator();
159 
160         // Speed past all initial annotations
161         DetailAST modifier = skipAnnotations(iterator);
162 
163         DetailAST offendingModifier = null;
164 
165         // All modifiers are annotations, no problem
166         if (modifier.getType() != TokenTypes.ANNOTATION) {
167             int index = 0;
168 
169             while (modifier != null
170                     && offendingModifier == null) {
171                 if (modifier.getType() == TokenTypes.ANNOTATION) {
172                     if (!isAnnotationOnType(modifier)) {
173                         // Annotation not at start of modifiers, bad
174                         offendingModifier = modifier;
175                     }
176                     break;
177                 }
178 
179                 while (index < JLS_ORDER.length
180                        && !JLS_ORDER[index].equals(modifier.getText())) {
181                     index++;
182                 }
183 
184                 if (index == JLS_ORDER.length) {
185                     // Current modifier is out of JLS order
186                     offendingModifier = modifier;
187                 }
188                 else if (iterator.hasNext()) {
189                     modifier = iterator.next();
190                 }
191                 else {
192                     // Reached end of modifiers without problem
193                     modifier = null;
194                 }
195             }
196         }
197         return offendingModifier;
198     }
199 
200     /**
201      * Skip all annotations in modifier block.
202      *
203      * @param modifierIterator iterator for collection of modifiers
204      * @return modifier next to last annotation
205      */
206     private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) {
207         DetailAST modifier;
208         do {
209             modifier = modifierIterator.next();
210         } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION);
211         return modifier;
212     }
213 
214     /**
215      * Checks whether annotation on type takes place.
216      *
217      * @param modifier modifier token.
218      * @return true if annotation on type takes place.
219      */
220     private static boolean isAnnotationOnType(DetailAST modifier) {
221         boolean annotationOnType = false;
222         final DetailAST modifiers = modifier.getParent();
223         final DetailAST definition = modifiers.getParent();
224         final int definitionType = definition.getType();
225         if (definitionType == TokenTypes.VARIABLE_DEF
226                 || definitionType == TokenTypes.PARAMETER_DEF
227                 || definitionType == TokenTypes.CTOR_DEF) {
228             annotationOnType = true;
229         }
230         else if (definitionType == TokenTypes.METHOD_DEF) {
231             final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE);
232             final int methodReturnType = typeToken.getLastChild().getType();
233             if (methodReturnType != TokenTypes.LITERAL_VOID) {
234                 annotationOnType = true;
235             }
236         }
237         return annotationOnType;
238     }
239 
240 }