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