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, § 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 }