001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.modifier;
021
022import java.util.ArrayList;
023import java.util.List;
024import java.util.Optional;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
032
033/**
034 * <div>
035 * Checks for redundant modifiers.
036 * </div>
037 *
038 * <p>
039 * Rationale: The Java Language Specification strongly discourages the usage
040 * of {@code public} and {@code abstract} for method declarations in interface
041 * definitions as a matter of style.
042 * </p>
043 *
044 * <p>The check validates:</p>
045 * <ol>
046 * <li>
047 * Interface and annotation definitions.
048 * </li>
049 * <li>
050 * Final modifier on methods of final and anonymous classes.
051 * </li>
052 * <li>
053 * Type declarations nested under interfaces that are declared as {@code public} or {@code static}.
054 * </li>
055 * <li>
056 * Class constructors.
057 * </li>
058 * <li>
059 * Nested {@code enum} definitions that are declared as {@code static}.
060 * </li>
061 * <li>
062 * {@code record} definitions that are declared as {@code final} and nested
063 * {@code record} definitions that are declared as {@code static}.
064 * </li>
065 * <li>
066 * {@code strictfp} modifier when using JDK 17 or later. See reason at
067 * <a href="https://openjdk.org/jeps/306">JEP 306</a>
068 * </li>
069 * <li>
070 * {@code final} modifier on unnamed variables when using JDK 22 or later.
071 * </li>
072 * </ol>
073 *
074 * <p>
075 * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
076 * </p>
077 *
078 * <p>Type declarations nested under interfaces by definition are public and static,
079 * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
080 * On the other hand, classes inside of interfaces can be abstract or non abstract.
081 * So, {@code abstract} modifier is allowed.
082 * </p>
083 *
084 * <p>Fields in interfaces and annotations are automatically
085 * public, static and final, so these modifiers are redundant as
086 * well.</p>
087 *
088 * <p>As annotations are a form of interface, their fields are also
089 * automatically public, static and final just as their
090 * annotation fields are automatically public and abstract.</p>
091 *
092 * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
093 * that the API of a record class is defined solely by its state description, and
094 * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
095 * immediately enclosing instance which would silently add state to the record class.
096 * See <a href="https://openjdk.org/jeps/395">JEP 395</a> for more info.</p>
097 *
098 * <p>Enums by definition are static implicit subclasses of java.lang.Enum&#60;E&#62;.
099 * 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
164public 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}