View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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     @Override
102     public int[] getDefaultTokens() {
103         return getRequiredTokens();
104     }
105 
106     @Override
107     public int[] getAcceptableTokens() {
108         return getRequiredTokens();
109     }
110 
111     @Override
112     public int[] getRequiredTokens() {
113         return new int[] {TokenTypes.MODIFIERS};
114     }
115 
116     @Override
117     public void visitToken(DetailAST ast) {
118         final List<DetailAST> mods = new ArrayList<>();
119         DetailAST modifier = ast.getFirstChild();
120         while (modifier != null) {
121             mods.add(modifier);
122             modifier = modifier.getNextSibling();
123         }
124 
125         if (!mods.isEmpty()) {
126             final DetailAST error = checkOrderSuggestedByJls(mods);
127             if (error != null) {
128                 if (error.getType() == TokenTypes.ANNOTATION) {
129                     log(error,
130                             MSG_ANNOTATION_ORDER,
131                              error.getFirstChild().getText()
132                              + error.getFirstChild().getNextSibling()
133                                 .getText());
134                 }
135                 else {
136                     log(error, MSG_MODIFIER_ORDER, error.getText());
137                 }
138             }
139         }
140     }
141 
142     /**
143      * Checks if the modifiers were added in the order suggested
144      * in the Java language specification.
145      *
146      * @param modifiers list of modifier AST tokens
147      * @return null if the order is correct, otherwise returns the offending
148      *     modifier AST.
149      */
150     private static DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) {
151         final Iterator<DetailAST> iterator = modifiers.iterator();
152 
153         // Speed past all initial annotations
154         DetailAST modifier = skipAnnotations(iterator);
155 
156         DetailAST offendingModifier = null;
157 
158         // All modifiers are annotations, no problem
159         if (modifier.getType() != TokenTypes.ANNOTATION) {
160             int index = 0;
161 
162             while (modifier != null
163                     && offendingModifier == null) {
164                 if (modifier.getType() == TokenTypes.ANNOTATION) {
165                     if (!isAnnotationOnType(modifier)) {
166                         // Annotation not at start of modifiers, bad
167                         offendingModifier = modifier;
168                     }
169                     break;
170                 }
171 
172                 while (index < JLS_ORDER.length
173                        && !JLS_ORDER[index].equals(modifier.getText())) {
174                     index++;
175                 }
176 
177                 if (index == JLS_ORDER.length) {
178                     // Current modifier is out of JLS order
179                     offendingModifier = modifier;
180                 }
181                 else if (iterator.hasNext()) {
182                     modifier = iterator.next();
183                 }
184                 else {
185                     // Reached end of modifiers without problem
186                     modifier = null;
187                 }
188             }
189         }
190         return offendingModifier;
191     }
192 
193     /**
194      * Skip all annotations in modifier block.
195      *
196      * @param modifierIterator iterator for collection of modifiers
197      * @return modifier next to last annotation
198      */
199     private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) {
200         DetailAST modifier;
201         do {
202             modifier = modifierIterator.next();
203         } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION);
204         return modifier;
205     }
206 
207     /**
208      * Checks whether annotation on type takes place.
209      *
210      * @param modifier modifier token.
211      * @return true if annotation on type takes place.
212      */
213     private static boolean isAnnotationOnType(DetailAST modifier) {
214         boolean annotationOnType = false;
215         final DetailAST modifiers = modifier.getParent();
216         final DetailAST definition = modifiers.getParent();
217         final int definitionType = definition.getType();
218         if (definitionType == TokenTypes.VARIABLE_DEF
219                 || definitionType == TokenTypes.PARAMETER_DEF
220                 || definitionType == TokenTypes.CTOR_DEF) {
221             annotationOnType = true;
222         }
223         else if (definitionType == TokenTypes.METHOD_DEF) {
224             final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE);
225             final int methodReturnType = typeToken.getLastChild().getType();
226             if (methodReturnType != TokenTypes.LITERAL_VOID) {
227                 annotationOnType = true;
228             }
229         }
230         return annotationOnType;
231     }
232 
233 }