001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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                 TokenTypes.ANNOTATION_DEF ->
260                checkInterfaceModifiers(ast);
261            case TokenTypes.ENUM_DEF -> checkForRedundantModifier(ast, TokenTypes.LITERAL_STATIC);
262            case TokenTypes.CTOR_DEF -> checkConstructorModifiers(ast);
263            case TokenTypes.METHOD_DEF -> processMethods(ast);
264            case TokenTypes.RESOURCE -> processResources(ast);
265            case TokenTypes.RECORD_DEF ->
266                checkForRedundantModifier(ast, TokenTypes.FINAL, TokenTypes.LITERAL_STATIC);
267            case TokenTypes.VARIABLE_DEF,
268                 TokenTypes.PATTERN_VARIABLE_DEF ->
269                checkUnnamedVariables(ast);
270            case TokenTypes.LITERAL_CATCH ->
271                checkUnnamedVariables(ast.findFirstToken(TokenTypes.PARAMETER_DEF));
272            case TokenTypes.LAMBDA -> processLambdaParameters(ast);
273            case TokenTypes.CLASS_DEF,
274                 TokenTypes.ANNOTATION_FIELD_DEF -> {
275                // Nothing extra to do
276            }
277            default -> throw new IllegalStateException("Unexpected token type: " + ast.getType());
278        }
279
280        if (isInterfaceOrAnnotationMember(ast)) {
281            processInterfaceOrAnnotation(ast);
282        }
283
284        if (jdkVersion >= JDK_17) {
285            checkForRedundantModifier(ast, TokenTypes.STRICTFP);
286        }
287    }
288
289    /**
290     * Process lambda parameters.
291     *
292     * @param lambdaAst node of type {@link TokenTypes#LAMBDA}
293     */
294    private void processLambdaParameters(DetailAST lambdaAst) {
295        final DetailAST lambdaParameters = lambdaAst.findFirstToken(TokenTypes.PARAMETERS);
296        if (lambdaParameters != null) {
297            TokenUtil.forEachChild(lambdaParameters, TokenTypes.PARAMETER_DEF,
298                    this::checkUnnamedVariables);
299        }
300    }
301
302    /**
303     * Check if the variable is unnamed and has redundant final modifier.
304     *
305     * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
306     *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
307     *     or {@link TokenTypes#PARAMETER_DEF}
308     */
309    private void checkUnnamedVariables(DetailAST ast) {
310        if (jdkVersion >= JDK_22 && isUnnamedVariable(ast)) {
311            checkForRedundantModifier(ast, TokenTypes.FINAL);
312        }
313    }
314
315    /**
316     * Check if the variable is unnamed.
317     *
318     * @param ast node of type {@link TokenTypes#VARIABLE_DEF}
319     *     or {@link TokenTypes#PATTERN_VARIABLE_DEF}
320     *     or {@link TokenTypes#PARAMETER_DEF}
321     * @return true if the variable is unnamed
322     */
323    private static boolean isUnnamedVariable(DetailAST ast) {
324        return "_".equals(ast.findFirstToken(TokenTypes.IDENT).getText());
325    }
326
327    /**
328     * Check modifiers of constructor.
329     *
330     * @param ctorDefAst ast node of type {@link TokenTypes#CTOR_DEF}
331     */
332    private void checkConstructorModifiers(DetailAST ctorDefAst) {
333        if (isEnumMember(ctorDefAst)) {
334            checkEnumConstructorModifiers(ctorDefAst);
335        }
336        else {
337            checkClassConstructorModifiers(ctorDefAst);
338        }
339    }
340
341    /**
342     * Checks if interface has proper modifiers.
343     *
344     * @param ast interface to check
345     */
346    private void checkInterfaceModifiers(DetailAST ast) {
347        final DetailAST modifiers =
348            ast.findFirstToken(TokenTypes.MODIFIERS);
349
350        for (final int tokenType : TOKENS_FOR_INTERFACE_MODIFIERS) {
351            final DetailAST modifier =
352                    modifiers.findFirstToken(tokenType);
353            if (modifier != null) {
354                log(modifier, MSG_KEY, modifier.getText());
355            }
356        }
357    }
358
359    /**
360     * Check if enum constructor has proper modifiers.
361     *
362     * @param ast constructor of enum
363     */
364    private void checkEnumConstructorModifiers(DetailAST ast) {
365        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
366        TokenUtil.findFirstTokenByPredicate(
367            modifiers, mod -> mod.getType() != TokenTypes.ANNOTATION
368        ).ifPresent(modifier -> log(modifier, MSG_KEY, modifier.getText()));
369    }
370
371    /**
372     * Do validation of interface of annotation.
373     *
374     * @param ast token AST
375     */
376    private void processInterfaceOrAnnotation(DetailAST ast) {
377        final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
378        DetailAST modifier = modifiers.getFirstChild();
379        while (modifier != null) {
380            // javac does not allow final or static in interface methods
381            // order annotation fields hence no need to check that this
382            // is not a method or annotation field
383
384            final int type = modifier.getType();
385            if (type == TokenTypes.LITERAL_PUBLIC
386                || type == TokenTypes.LITERAL_STATIC
387                        && ast.getType() != TokenTypes.METHOD_DEF
388                || type == TokenTypes.ABSTRACT
389                        && ast.getType() != TokenTypes.CLASS_DEF
390                || type == TokenTypes.FINAL
391                        && ast.getType() != TokenTypes.CLASS_DEF) {
392                log(modifier, MSG_KEY, modifier.getText());
393            }
394
395            modifier = modifier.getNextSibling();
396        }
397    }
398
399    /**
400     * Process validation of Methods.
401     *
402     * @param ast method AST
403     */
404    private void processMethods(DetailAST ast) {
405        final DetailAST modifiers =
406                        ast.findFirstToken(TokenTypes.MODIFIERS);
407        // private method?
408        boolean checkFinal =
409            modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null;
410        // declared in a final class?
411        DetailAST parent = ast;
412        while (parent != null && !checkFinal) {
413            if (parent.getType() == TokenTypes.CLASS_DEF) {
414                final DetailAST classModifiers =
415                    parent.findFirstToken(TokenTypes.MODIFIERS);
416                checkFinal = classModifiers.findFirstToken(TokenTypes.FINAL) != null;
417                parent = null;
418            }
419            else if (parent.getType() == TokenTypes.LITERAL_NEW
420                    || parent.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
421                checkFinal = true;
422                parent = null;
423            }
424            else if (parent.getType() == TokenTypes.ENUM_DEF) {
425                checkFinal = modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
426                parent = null;
427            }
428            else {
429                parent = parent.getParent();
430            }
431        }
432        if (checkFinal && !isAnnotatedWithSafeVarargs(ast)) {
433            checkForRedundantModifier(ast, TokenTypes.FINAL);
434        }
435
436        if (ast.findFirstToken(TokenTypes.SLIST) == null) {
437            processAbstractMethodParameters(ast);
438        }
439    }
440
441    /**
442     * Process validation of parameters for Methods with no definition.
443     *
444     * @param ast method AST
445     */
446    private void processAbstractMethodParameters(DetailAST ast) {
447        final DetailAST parameters = ast.findFirstToken(TokenTypes.PARAMETERS);
448        TokenUtil.forEachChild(parameters, TokenTypes.PARAMETER_DEF, paramDef -> {
449            checkForRedundantModifier(paramDef, TokenTypes.FINAL);
450        });
451    }
452
453    /**
454     * Check if class constructor has proper modifiers.
455     *
456     * @param classCtorAst class constructor ast
457     */
458    private void checkClassConstructorModifiers(DetailAST classCtorAst) {
459        final DetailAST classDef = classCtorAst.getParent().getParent();
460        if (!isClassPublic(classDef) && !isClassProtected(classDef)) {
461            checkForRedundantModifier(classCtorAst, TokenTypes.LITERAL_PUBLIC);
462        }
463    }
464
465    /**
466     * Checks if given resource has redundant modifiers.
467     *
468     * @param ast ast
469     */
470    private void processResources(DetailAST ast) {
471        checkForRedundantModifier(ast, TokenTypes.FINAL);
472    }
473
474    /**
475     * Checks if given ast has a redundant modifier.
476     *
477     * @param ast ast
478     * @param modifierTypes The modifiers to check for.
479     */
480    private void checkForRedundantModifier(DetailAST ast, int... modifierTypes) {
481        Optional.ofNullable(ast.findFirstToken(TokenTypes.MODIFIERS))
482            .ifPresent(modifiers -> {
483                for (DetailAST childAst = modifiers.getFirstChild();
484                     childAst != null; childAst = childAst.getNextSibling()) {
485                    if (TokenUtil.isOfType(childAst, modifierTypes)) {
486                        log(childAst, MSG_KEY, childAst.getText());
487                    }
488                }
489            });
490    }
491
492    /**
493     * Checks if given class ast has protected modifier.
494     *
495     * @param classDef class ast
496     * @return true if class is protected, false otherwise
497     */
498    private static boolean isClassProtected(DetailAST classDef) {
499        final DetailAST classModifiers =
500                classDef.findFirstToken(TokenTypes.MODIFIERS);
501        return classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
502    }
503
504    /**
505     * Checks if given class is accessible from "public" scope.
506     *
507     * @param ast class def to check
508     * @return true if class is accessible from public scope,false otherwise
509     */
510    private static boolean isClassPublic(DetailAST ast) {
511        boolean isAccessibleFromPublic = false;
512        final DetailAST modifiersAst = ast.findFirstToken(TokenTypes.MODIFIERS);
513        final boolean hasPublicModifier =
514                modifiersAst.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
515
516        if (TokenUtil.isRootNode(ast.getParent())) {
517            isAccessibleFromPublic = hasPublicModifier;
518        }
519        else {
520            final DetailAST parentClassAst = ast.getParent().getParent();
521
522            if (hasPublicModifier || parentClassAst.getType() == TokenTypes.INTERFACE_DEF) {
523                isAccessibleFromPublic = isClassPublic(parentClassAst);
524            }
525        }
526
527        return isAccessibleFromPublic;
528    }
529
530    /**
531     * Checks if current AST node is member of Enum.
532     *
533     * @param ast AST node
534     * @return true if it is an enum member
535     */
536    private static boolean isEnumMember(DetailAST ast) {
537        final DetailAST parentTypeDef = ast.getParent().getParent();
538        return parentTypeDef.getType() == TokenTypes.ENUM_DEF;
539    }
540
541    /**
542     * Checks if current AST node is member of Interface or Annotation, not of their subnodes.
543     *
544     * @param ast AST node
545     * @return true or false
546     */
547    private static boolean isInterfaceOrAnnotationMember(DetailAST ast) {
548        DetailAST parentTypeDef = ast.getParent();
549        parentTypeDef = parentTypeDef.getParent();
550        return parentTypeDef != null
551                && (parentTypeDef.getType() == TokenTypes.INTERFACE_DEF
552                    || parentTypeDef.getType() == TokenTypes.ANNOTATION_DEF);
553    }
554
555    /**
556     * Checks if method definition is annotated with.
557     * <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/SafeVarargs.html">
558     * SafeVarargs</a> annotation
559     *
560     * @param methodDef method definition node
561     * @return true or false
562     */
563    private static boolean isAnnotatedWithSafeVarargs(DetailAST methodDef) {
564        boolean result = false;
565        final List<DetailAST> methodAnnotationsList = getMethodAnnotationsList(methodDef);
566        for (DetailAST annotationNode : methodAnnotationsList) {
567            if ("SafeVarargs".equals(annotationNode.getLastChild().getText())) {
568                result = true;
569                break;
570            }
571        }
572        return result;
573    }
574
575    /**
576     * Gets the list of annotations on method definition.
577     *
578     * @param methodDef method definition node
579     * @return List of annotations
580     */
581    private static List<DetailAST> getMethodAnnotationsList(DetailAST methodDef) {
582        final List<DetailAST> annotationsList = new ArrayList<>();
583        final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
584        TokenUtil.forEachChild(modifiers, TokenTypes.ANNOTATION, annotationsList::add);
585        return annotationsList;
586    }
587
588}