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.List;
24 import java.util.Optional;
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 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32
33 /**
34 * <div>
35 * Checks for redundant modifiers.
36 * </div>
37 *
38 * <p>
39 * Rationale: The Java Language Specification strongly discourages the usage
40 * of {@code public} and {@code abstract} for method declarations in interface
41 * definitions as a matter of style.
42 * </p>
43 *
44 * <p>The check validates:</p>
45 * <ol>
46 * <li>
47 * Interface and annotation definitions.
48 * </li>
49 * <li>
50 * Final modifier on methods of final and anonymous classes.
51 * </li>
52 * <li>
53 * Type declarations nested under interfaces that are declared as {@code public} or {@code static}.
54 * </li>
55 * <li>
56 * Class constructors.
57 * </li>
58 * <li>
59 * Nested {@code enum} definitions that are declared as {@code static}.
60 * </li>
61 * <li>
62 * {@code record} definitions that are declared as {@code final} and nested
63 * {@code record} definitions that are declared as {@code static}.
64 * </li>
65 * <li>
66 * {@code strictfp} modifier when using JDK 17 or later. See reason at
67 * <a href="https://openjdk.org/jeps/306">JEP 306</a>
68 * </li>
69 * <li>
70 * {@code final} modifier on unnamed variables when using JDK 22 or later.
71 * </li>
72 * </ol>
73 *
74 * <p>
75 * ATTENTION: Top-level members of compact source files are skipped from validation by this check.
76 * </p>
77 *
78 * <p>
79 * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
80 * </p>
81 *
82 * <p>Type declarations nested under interfaces by definition are public and static,
83 * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
84 * On the other hand, classes inside of interfaces can be abstract or non abstract.
85 * So, {@code abstract} modifier is allowed.
86 * </p>
87 *
88 * <p>Fields in interfaces and annotations are automatically
89 * public, static and final, so these modifiers are redundant as
90 * well.</p>
91 *
92 * <p>As annotations are a form of interface, their fields are also
93 * automatically public, static and final just as their
94 * annotation fields are automatically public and abstract.</p>
95 *
96 * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
97 * that the API of a record class is defined solely by its state description, and
98 * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
99 * immediately enclosing instance which would silently add state to the record class.
100 * See <a href="https://openjdk.org/jeps/395">JEP 395</a> for more info.</p>
101 *
102 * <p>Enums by definition are static implicit subclasses of java.lang.Enum<E>.
103 * So, the {@code static} modifier on the enums is redundant. In addition,
104 * if enum is inside of interface, {@code public} modifier is also redundant.</p>
105 *
106 * <p>Enums can also contain abstract methods and methods which can be overridden by the declared
107 * enumeration fields.
108 * See the following example:</p>
109 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
110 * public enum EnumClass {
111 * FIELD_1,
112 * FIELD_2 {
113 * @Override
114 * public final void method1() {} // violation expected
115 * };
116 *
117 * public void method1() {}
118 * public final void method2() {} // no violation expected
119 * }
120 * </code></pre></div>
121 *
122 * <p>Since these methods can be overridden in these situations, the final methods are not
123 * marked as redundant even though they can't be extended by other classes/enums.</p>
124 *
125 * <p>
126 * Nested {@code enum} types are always static by default.
127 * </p>
128 *
129 * <p>Final classes by definition cannot be extended so the {@code final}
130 * modifier on the method of a final class is redundant.
131 * </p>
132 *
133 * <p>Public modifier for constructors in non-public non-protected classes
134 * is always obsolete: </p>
135 *
136 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
137 * public class PublicClass {
138 * public PublicClass() {} // OK
139 * }
140 *
141 * class PackagePrivateClass {
142 * public PackagePrivateClass() {} // violation expected
143 * }
144 * </code></pre></div>
145 *
146 * <p>There is no violation in the following example,
147 * because removing public modifier from ProtectedInnerClass
148 * constructor will make this code not compiling: </p>
149 *
150 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
151 * package a;
152 * public class ClassExample {
153 * protected class ProtectedInnerClass {
154 * public ProtectedInnerClass () {}
155 * }
156 * }
157 *
158 * package b;
159 * import a.ClassExample;
160 * public class ClassExtending extends ClassExample {
161 * ProtectedInnerClass pc = new ProtectedInnerClass();
162 * }
163 * </code></pre></div>
164 *
165 * @since 3.0
166 */
167 @StatelessCheck
168 public class RedundantModifierCheck
169 extends AbstractCheck {
170
171 /**
172 * A key is pointing to the warning message text in "messages.properties"
173 * file.
174 */
175 public static final String MSG_KEY = "redundantModifier";
176
177 /**
178 * An array of tokens for interface modifiers.
179 */
180 private static final int[] TOKENS_FOR_INTERFACE_MODIFIERS = {
181 TokenTypes.LITERAL_STATIC,
182 TokenTypes.ABSTRACT,
183 };
184
185 /**
186 * Constant for jdk 22 version number.
187 */
188 private static final int JDK_22 = 22;
189
190 /**
191 * Constant for jdk 17 version number.
192 *
193 */
194 private static final int JDK_17 = 17;
195
196 /**
197 * Set the JDK version that you are using.
198 * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
199 * as well as just the major JDK version alone (e.g. 8) is supported.
200 * This property only considers features from officially released
201 * Java versions as supported. Features introduced in preview releases are not considered
202 * supported until they are included in a non-preview release.
203 *
204 */
205 private int jdkVersion = JDK_22;
206
207 /**
208 * Setter to set the JDK version that you are using.
209 * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
210 * as well as just the major JDK version alone (e.g. 8) is supported.
211 * This property only considers features from officially released
212 * Java versions as supported. Features introduced in preview releases are not considered
213 * supported until they are included in a non-preview release.
214 *
215 * @param jdkVersion the Java version
216 * @since 10.18.0
217 */
218 public void setJdkVersion(String jdkVersion) {
219 final String singleVersionNumber;
220 if (jdkVersion.startsWith("1.")) {
221 singleVersionNumber = jdkVersion.substring(2);
222 }
223 else {
224 singleVersionNumber = jdkVersion;
225 }
226
227 this.jdkVersion = Integer.parseInt(singleVersionNumber);
228 }
229
230 @Override
231 public int[] getDefaultTokens() {
232 return getAcceptableTokens();
233 }
234
235 @Override
236 public int[] getRequiredTokens() {
237 return CommonUtil.EMPTY_INT_ARRAY;
238 }
239
240 @Override
241 public int[] getAcceptableTokens() {
242 return new int[] {
243 TokenTypes.METHOD_DEF,
244 TokenTypes.VARIABLE_DEF,
245 TokenTypes.ANNOTATION_FIELD_DEF,
246 TokenTypes.INTERFACE_DEF,
247 TokenTypes.CTOR_DEF,
248 TokenTypes.CLASS_DEF,
249 TokenTypes.ENUM_DEF,
250 TokenTypes.RESOURCE,
251 TokenTypes.ANNOTATION_DEF,
252 TokenTypes.RECORD_DEF,
253 TokenTypes.PATTERN_VARIABLE_DEF,
254 TokenTypes.LITERAL_CATCH,
255 TokenTypes.LAMBDA,
256 };
257 }
258
259 @Override
260 public void visitToken(DetailAST ast) {
261 switch (ast.getType()) {
262 case TokenTypes.INTERFACE_DEF,
263 TokenTypes.ANNOTATION_DEF ->
264 checkInterfaceModifiers(ast);
265 case TokenTypes.ENUM_DEF -> checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
266 case TokenTypes.CTOR_DEF -> checkConstructorModifiers(ast);
267 case TokenTypes.METHOD_DEF -> processMethods(ast);
268 case TokenTypes.RESOURCE -> processResources(ast);
269 case TokenTypes.RECORD_DEF ->
270 checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
271 case TokenTypes.VARIABLE_DEF,
272 TokenTypes.PATTERN_VARIABLE_DEF ->
273 checkUnnamedVariables(ast);
274 case TokenTypes.LITERAL_CATCH ->
275 checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
276 case TokenTypes.LAMBDA -> processLambdaParameters(ast);
277 case TokenTypes.CLASS_DEF,
278 TokenTypes.ANNOTATION_FIELD_DEF -> {
279 // Nothing extra to do
280 }
281 default -> throw new IllegalStateException("Unexpected token type: " + ast.getType());
282 }
283
284 if (isInterfaceOrAnnotationMember(ast)) {
285 processInterfaceOrAnnotation(ast);
286 }
287
288 if (jdkVersion >= JDK_17) {
289 checkForRedundantModifier(ast, TokenTypes.STRICTFP);
290 }
291 }
292
293 /**
294 * Process lambda parameters.
295 *
296 * @param lambdaAst node of type {@link TokenTypes#LAMBDA}
297 */
298 private void processLambdaParameters(DetailAST lambdaAst) {
299 final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
300 if (lambdaParameters != null) {
301 TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
302 this::checkUnnamedVariables);
303 }
304 }
305
306 /**
307 * Check if the variable is unnamed and has redundant final modifier.
308 *
309 * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
310 * or {@link TokenTypes#PATTERN_VARIABLE_DEF}
311 * or {@link TokenTypes#PARAMETER_DEF}
312 */
313 private void checkUnnamedVariables(DetailAST ast) {
314 if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
315 checkForRedundantModifier(ast, TokenTypes.FINAL);
316 }
317 }
318
319 /**
320 * Check if the variable is unnamed.
321 *
322 * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
323 * or {@link TokenTypes#PATTERN_VARIABLE_DEF}
324 * or {@link TokenTypes#PARAMETER_DEF}
325 * @return true if the variable is unnamed
326 */
327 private static boolean isUnnamedVariable(DetailAST ast) {
328 return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
329 }
330
331 /**
332 * Check modifiers of constructor.
333 *
334 * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF}
335 */
336 private void checkConstructorModifiers(DetailAST ctorDefAst) {
337 if (isEnumMember(ctorDefAst)) {
338 checkEnumConstructorModifiers(ctorDefAst);
339 }
340 else {
341 checkClassConstructorModifiers(ctorDefAst);
342 }
343 }
344
345 /**
346 * Checks if interface has proper modifiers.
347 *
348 * @param ast interface to check
349 */
350 private void checkInterfaceModifiers(DetailAST ast) {
351 final DetailAST modifiers =
352 ast.findFirstToken(TokenTypes.MODIFIERS);
353
354 for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
355 final DetailAST modifier =
356 modifiers.findFirstToken(tokenType);
357 if (modifier != null) {
358 log(modifier, MSG_KEY, modifier.getText());
359 }
360 }
361 }
362
363 /**
364 * Check if enum constructor has proper modifiers.
365 *
366 * @param ast constructor of enum
367 */
368 private void checkEnumConstructorModifiers(DetailAST ast) {
369 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
370 TokenUtil.findFirstTokenByPredicate(
371 modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
372 ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
373 }
374
375 /**
376 * Do validation of interface of annotation.
377 *
378 * @param ast token AST
379 */
380 private void processInterfaceOrAnnotation(DetailAST ast) {
381 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
382 DetailAST modifier = modifiers.getFirstChild();
383 while (modifier != null) {
384 // javac does not allow final or static in interface methods
385 // order annotation fields hence no need to check that this
386 // is not a method or annotation field
387
388 final int type = modifier.getType();
389 if (type == TokenTypes.LITERAL_PUBLIC
390 || type == TokenTypes.LITERAL_STATIC
391 && ast.getType() != TokenTypes.METHOD_DEF
392 || type == TokenTypes.ABSTRACT
393 && ast.getType() != TokenTypes.CLASS_DEF
394 || type == TokenTypes.FINAL
395 && ast.getType() != TokenTypes.CLASS_DEF) {
396 log(modifier, MSG_KEY, modifier.getText());
397 }
398
399 modifier = modifier.getNextSibling();
400 }
401 }
402
403 /**
404 * Process validation of Methods.
405 *
406 * @param ast method AST
407 */
408 private void processMethods(DetailAST ast) {
409 final DetailAST modifiers =
410 ast.findFirstToken(TokenTypes.MODIFIERS);
411 // private method?
412 boolean checkFinal =
413 modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
414 // declared in a final class?
415 DetailAST parent = ast;
416 while (parent != null && !checkFinal) {
417 if (parent.getType() == TokenTypes.CLASS_DEF) {
418 final DetailAST classModifiers =
419 parent.findFirstToken(TokenTypes.MODIFIERS);
420 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
421 parent = null;
422 }
423 else if (parent.getType() == TokenTypes.LITERAL_NEW
424 || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
425 checkFinal = true;
426 parent = null;
427 }
428 else if (parent.getType() == TokenTypes.ENUM_DEF) {
429 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
430 parent = null;
431 }
432 else {
433 parent = parent.getParent();
434 }
435 }
436 if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
437 checkForRedundantModifier(ast, TokenTypes.FINAL);
438 }
439
440 if (ast.findFirstToken(TokenTypes.SLIST) == null) {
441 processAbstractMethodParameters(ast);
442 }
443 }
444
445 /**
446 * Process validation of parameters for Methods with no definition.
447 *
448 * @param ast method AST
449 */
450 private void processAbstractMethodParameters(DetailAST ast) {
451 final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
452 TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
453 checkForRedundantModifier(paramDef, TokenTypes.FINAL);
454 });
455 }
456
457 /**
458 * Check if class constructor has proper modifiers.
459 *
460 * @param classCtorAst class constructor ast
461 */
462 private void checkClassConstructorModifiers(DetailAST classCtorAst) {
463 final DetailAST classDef = classCtorAst.getParent().getParent();
464 if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
465 checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
466 }
467 }
468
469 /**
470 * Checks if given resource has redundant modifiers.
471 *
472 * @param ast ast
473 */
474 private void processResources(DetailAST ast) {
475 checkForRedundantModifier(ast, TokenTypes.FINAL);
476 }
477
478 /**
479 * Checks if given ast has a redundant modifier.
480 *
481 * @param ast ast
482 * @param modifierTypes The modifiers to check for.
483 */
484 private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
485 Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
486 .ifPresent(modifiers -> {
487 for (DetailAST childAst = modifiers.getFirstChild();
488 childAst != null; childAst = childAst.getNextSibling()) {
489 if (TokenUtil.isOfType(childAst, modifierTypes)) {
490 log(childAst, MSG_KEY, childAst.getText());
491 }
492 }
493 });
494 }
495
496 /**
497 * Checks if given class ast has protected modifier.
498 *
499 * @param classDef class ast
500 * @return true if class is protected, false otherwise
501 */
502 private static boolean isClassProtected(DetailAST classDef) {
503 final DetailAST classModifiers =
504 classDef.findFirstToken(TokenTypes.MODIFIERS);
505 return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
506 }
507
508 /**
509 * Checks if given class is accessible from "public" scope.
510 *
511 * @param ast class def to check
512 * @return true if class is accessible from public scope,false otherwise
513 */
514 private static boolean isClassPublic(DetailAST ast) {
515 boolean isAccessibleFromPublic = false;
516 final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
517 final boolean hasPublicModifier =
518 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
519
520 if (TokenUtil.isRootNode(ast.getParent())) {
521 isAccessibleFromPublic = hasPublicModifier;
522 }
523 else {
524 final DetailAST parentClassAst = ast.getParent().getParent();
525
526 if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
527 isAccessibleFromPublic = isClassPublic(parentClassAst);
528 }
529 }
530
531 return isAccessibleFromPublic;
532 }
533
534 /**
535 * Checks if current AST node is member of Enum.
536 *
537 * @param ast AST node
538 * @return true if it is an enum member
539 */
540 private static boolean isEnumMember(DetailAST ast) {
541 final DetailAST parentTypeDef = ast.getParent().getParent();
542 return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
543 }
544
545 /**
546 * Checks if current AST node is member of Interface or Annotation, not of their subnodes.
547 *
548 * @param ast AST node
549 * @return true or false
550 */
551 private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
552 DetailAST parentTypeDef = ast.getParent();
553 parentTypeDef = parentTypeDef.getParent();
554 return parentTypeDef != null
555 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
556 || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
557 }
558
559 /**
560 * Checks if method definition is annotated with.
561 * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
562 * SafeVarargs</a> annotation
563 *
564 * @param methodDef method definition node
565 * @return true or false
566 */
567 private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
568 boolean result = false;
569 final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
570 for (DetailAST annotationNode : methodAnnotationsList) {
571 if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
572 result = true;
573 break;
574 }
575 }
576 return result;
577 }
578
579 /**
580 * Gets the list of annotations on method definition.
581 *
582 * @param methodDef method definition node
583 * @return List of annotations
584 */
585 private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
586 final List<DetailAST> annotationsList = new ArrayList<>();
587 final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
588 TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
589 return annotationsList;
590 }
591
592 }