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  * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
110  * public enum EnumClass {
111  *   FIELD_1,
112  *   FIELD_2 {
113  *     &#64;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 }