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