View Javadoc
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&#60;E&#62;.
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  *     &#64;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 }