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