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