View Javadoc
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&#60;E&#62;.
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  * {@snippet lang="text" :
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  * }
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  * {@snippet lang="text" :
137  * public class PublicClass {
138  *   public PublicClass() {} // OK
139  * }
140  *
141  * class PackagePrivateClass {
142  *   public PackagePrivateClass() {} // violation expected
143  * }
144  * }
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  * {@snippet lang="text" :
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  * }
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      * Creates a new {@code RedundantModifierCheck} instance.
209      */
210     public RedundantModifierCheck() {
211         // no code by default
212     }
213 
214     /**
215      * Setter to set the JDK version that you are using.
216      * Old JDK version numbering is supported (e.g. 1.8 for Java 8)
217      * as well as just the major JDK version alone (e.g. 8) is supported.
218      * This property only considers features from officially released
219      * Java versions as supported. Features introduced in preview releases are not considered
220      * supported until they are included in a non-preview release.
221      *
222      * @param jdkVersion the Java version
223      * @since 10.18.0
224      */
225     public void setJdkVersion(String jdkVersion) {
226         final String singleVersionNumber;
227         if (jdkVersion.startsWith("1.")) {
228             singleVersionNumber = jdkVersion.substring(2);
229         }
230         else {
231             singleVersionNumber = jdkVersion;
232         }
233 
234         this.jdkVersion = Integer.parseInt(singleVersionNumber);
235     }
236 
237     @Override
238     public int[] getDefaultTokens() {
239         return getAcceptableTokens();
240     }
241 
242     @Override
243     public int[] getRequiredTokens() {
244         return CommonUtil.EMPTY_INT_ARRAY;
245     }
246 
247     @Override
248     public int[] getAcceptableTokens() {
249         return new int[] {
250             TokenTypes.METHOD_DEF,
251             TokenTypes.VARIABLE_DEF,
252             TokenTypes.ANNOTATION_FIELD_DEF,
253             TokenTypes.INTERFACE_DEF,
254             TokenTypes.CTOR_DEF,
255             TokenTypes.CLASS_DEF,
256             TokenTypes.ENUM_DEF,
257             TokenTypes.RESOURCE,
258             TokenTypes.ANNOTATION_DEF,
259             TokenTypes.RECORD_DEF,
260             TokenTypes.PATTERN_VARIABLE_DEF,
261             TokenTypes.LITERAL_CATCH,
262             TokenTypes.LAMBDA,
263         };
264     }
265 
266     @Override
267     public void visitToken(DetailAST ast) {
268         switch (ast.getType()) {
269             case TokenTypes.INTERFACE_DEF,
270                  TokenTypes.ANNOTATION_DEF ->
271                 checkInterfaceModifiers(ast);
272             case TokenTypes.ENUM_DEF -> checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
273             case TokenTypes.CTOR_DEF -> checkConstructorModifiers(ast);
274             case TokenTypes.METHOD_DEF -> processMethods(ast);
275             case TokenTypes.RESOURCE -> processResources(ast);
276             case TokenTypes.RECORD_DEF ->
277                 checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
278             case TokenTypes.VARIABLE_DEF,
279                  TokenTypes.PATTERN_VARIABLE_DEF ->
280                 checkUnnamedVariables(ast);
281             case TokenTypes.LITERAL_CATCH ->
282                 checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
283             case TokenTypes.LAMBDA -> processLambdaParameters(ast);
284             case TokenTypes.CLASS_DEF,
285                  TokenTypes.ANNOTATION_FIELD_DEF -> {
286                 // Nothing extra to do
287             }
288             default -> throw new IllegalStateException("Unexpected token type: " + ast.getType());
289         }
290 
291         if (isInterfaceOrAnnotationMember(ast)) {
292             processInterfaceOrAnnotation(ast);
293         }
294 
295         if (jdkVersion >= JDK_17) {
296             checkForRedundantModifier(ast, TokenTypes.STRICTFP);
297         }
298     }
299 
300     /**
301      * Process lambda parameters.
302      *
303      * @param lambdaAst node of type {@link TokenTypes#LAMBDA}
304      */
305     private void processLambdaParameters(DetailAST lambdaAst) {
306         final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
307         if (lambdaParameters != null) {
308             TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
309                     this::checkUnnamedVariables);
310         }
311     }
312 
313     /**
314      * Check if the variable is unnamed and has redundant final modifier.
315      *
316      * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
317      *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
318      *     or {@link TokenTypes#PARAMETER_DEF}
319      */
320     private void checkUnnamedVariables(DetailAST ast) {
321         if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
322             checkForRedundantModifier(ast, TokenTypes.FINAL);
323         }
324     }
325 
326     /**
327      * Check if the variable is unnamed.
328      *
329      * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
330      *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
331      *     or {@link TokenTypes#PARAMETER_DEF}
332      * @return true if the variable is unnamed
333      */
334     private static boolean isUnnamedVariable(DetailAST ast) {
335         return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
336     }
337 
338     /**
339      * Check modifiers of constructor.
340      *
341      * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF}
342      */
343     private void checkConstructorModifiers(DetailAST ctorDefAst) {
344         if (isEnumMember(ctorDefAst)) {
345             checkEnumConstructorModifiers(ctorDefAst);
346         }
347         else {
348             checkClassConstructorModifiers(ctorDefAst);
349         }
350     }
351 
352     /**
353      * Checks if interface has proper modifiers.
354      *
355      * @param ast interface to check
356      */
357     private void checkInterfaceModifiers(DetailAST ast) {
358         final DetailAST modifiers =
359             ast.findFirstToken(TokenTypes.MODIFIERS);
360 
361         for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
362             final DetailAST modifier =
363                     modifiers.findFirstToken(tokenType);
364             if (modifier != null) {
365                 log(modifier, MSG_KEY, modifier.getText());
366             }
367         }
368     }
369 
370     /**
371      * Check if enum constructor has proper modifiers.
372      *
373      * @param ast constructor of enum
374      */
375     private void checkEnumConstructorModifiers(DetailAST ast) {
376         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
377         TokenUtil.findFirstTokenByPredicate(
378             modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
379         ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
380     }
381 
382     /**
383      * Do validation of interface of annotation.
384      *
385      * @param ast token AST
386      */
387     private void processInterfaceOrAnnotation(DetailAST ast) {
388         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
389         DetailAST modifier = modifiers.getFirstChild();
390         while (modifier != null) {
391             // javac does not allow final or static in interface methods
392             // order annotation fields hence no need to check that this
393             // is not a method or annotation field
394 
395             final int type = modifier.getType();
396             if (type == TokenTypes.LITERAL_PUBLIC
397                 || type == TokenTypes.LITERAL_STATIC
398                         && ast.getType() != TokenTypes.METHOD_DEF
399                 || type == TokenTypes.ABSTRACT
400                         && ast.getType() != TokenTypes.CLASS_DEF
401                 || type == TokenTypes.FINAL
402                         && ast.getType() != TokenTypes.CLASS_DEF) {
403                 log(modifier, MSG_KEY, modifier.getText());
404             }
405 
406             modifier = modifier.getNextSibling();
407         }
408     }
409 
410     /**
411      * Process validation of Methods.
412      *
413      * @param ast method AST
414      */
415     private void processMethods(DetailAST ast) {
416         final DetailAST modifiers =
417                         ast.findFirstToken(TokenTypes.MODIFIERS);
418         // private method?
419         boolean checkFinal =
420             modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
421         // declared in a final class?
422         DetailAST parent = ast;
423         while (parent != null && !checkFinal) {
424             if (parent.getType() == TokenTypes.CLASS_DEF) {
425                 final DetailAST classModifiers =
426                     parent.findFirstToken(TokenTypes.MODIFIERS);
427                 checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
428                 parent = null;
429             }
430             else if (parent.getType() == TokenTypes.LITERAL_NEW
431                     || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
432                 checkFinal = true;
433                 parent = null;
434             }
435             else if (parent.getType() == TokenTypes.ENUM_DEF) {
436                 checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
437                 parent = null;
438             }
439             else {
440                 parent = parent.getParent();
441             }
442         }
443         if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
444             checkForRedundantModifier(ast, TokenTypes.FINAL);
445         }
446 
447         if (ast.findFirstToken(TokenTypes.SLIST) == null) {
448             processAbstractMethodParameters(ast);
449         }
450     }
451 
452     /**
453      * Process validation of parameters for Methods with no definition.
454      *
455      * @param ast method AST
456      */
457     private void processAbstractMethodParameters(DetailAST ast) {
458         final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
459         TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
460             checkForRedundantModifier(paramDef, TokenTypes.FINAL);
461         });
462     }
463 
464     /**
465      * Check if class constructor has proper modifiers.
466      *
467      * @param classCtorAst class constructor ast
468      */
469     private void checkClassConstructorModifiers(DetailAST classCtorAst) {
470         final DetailAST classDef = classCtorAst.getParent().getParent();
471         if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
472             checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
473         }
474     }
475 
476     /**
477      * Checks if given resource has redundant modifiers.
478      *
479      * @param ast ast
480      */
481     private void processResources(DetailAST ast) {
482         checkForRedundantModifier(ast, TokenTypes.FINAL);
483     }
484 
485     /**
486      * Checks if given ast has a redundant modifier.
487      *
488      * @param ast ast
489      * @param modifierTypes The modifiers to check for.
490      */
491     private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
492         Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
493             .ifPresent(modifiers -> {
494                 for (DetailAST childAst = modifiers.getFirstChild();
495                      childAst != null; childAst = childAst.getNextSibling()) {
496                     if (TokenUtil.isOfType(childAst, modifierTypes)) {
497                         log(childAst, MSG_KEY, childAst.getText());
498                     }
499                 }
500             });
501     }
502 
503     /**
504      * Checks if given class ast has protected modifier.
505      *
506      * @param classDef class ast
507      * @return true if class is protected, false otherwise
508      */
509     private static boolean isClassProtected(DetailAST classDef) {
510         final DetailAST classModifiers =
511                 classDef.findFirstToken(TokenTypes.MODIFIERS);
512         return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
513     }
514 
515     /**
516      * Checks if given class is accessible from "public" scope.
517      *
518      * @param ast class def to check
519      * @return true if class is accessible from public scope,false otherwise
520      */
521     private static boolean isClassPublic(DetailAST ast) {
522         boolean isAccessibleFromPublic = false;
523         final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
524         final boolean hasPublicModifier =
525                 modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
526 
527         if (TokenUtil.isRootNode(ast.getParent())) {
528             isAccessibleFromPublic = hasPublicModifier;
529         }
530         else {
531             final DetailAST parentClassAst = ast.getParent().getParent();
532 
533             if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
534                 isAccessibleFromPublic = isClassPublic(parentClassAst);
535             }
536         }
537 
538         return isAccessibleFromPublic;
539     }
540 
541     /**
542      * Checks if current AST node is member of Enum.
543      *
544      * @param ast AST node
545      * @return true if it is an enum member
546      */
547     private static boolean isEnumMember(DetailAST ast) {
548         final DetailAST parentTypeDef = ast.getParent().getParent();
549         return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
550     }
551 
552     /**
553      * Checks if current AST node is member of Interface or Annotation, not of their subnodes.
554      *
555      * @param ast AST node
556      * @return true or false
557      */
558     private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
559         DetailAST parentTypeDef = ast.getParent();
560         parentTypeDef = parentTypeDef.getParent();
561         return parentTypeDef != null
562                 && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
563                     || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
564     }
565 
566     /**
567      * Checks if method definition is annotated with.
568      * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
569      * SafeVarargs</a> annotation
570      *
571      * @param methodDef method definition node
572      * @return true or false
573      */
574     private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
575         boolean result = false;
576         final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
577         for (DetailAST annotationNode : methodAnnotationsList) {
578             if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
579                 result = true;
580                 break;
581             }
582         }
583         return result;
584     }
585 
586     /**
587      * Gets the list of annotations on method definition.
588      *
589      * @param methodDef method definition node
590      * @return List of annotations
591      */
592     private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
593         final List<DetailAST> annotationsList = new ArrayList<>();
594         final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
595         TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
596         return annotationsList;
597     }
598 
599 }