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 * ATTENTION: Top-level members of compact source files are skipped from validation by this check.
076 * </p>
077 *
078 * <p>
079 * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
080 * </p>
081 *
082 * <p>Type declarations nested under interfaces by definition are public and static,
083 * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
084 * On the other hand, classes inside of interfaces can be abstract or non abstract.
085 * So, {@code abstract} modifier is allowed.
086 * </p>
087 *
088 * <p>Fields in interfaces and annotations are automatically
089 * public, static and final, so these modifiers are redundant as
090 * well.</p>
091 *
092 * <p>As annotations are a form of interface, their fields are also
093 * automatically public, static and final just as their
094 * annotation fields are automatically public and abstract.</p>
095 *
096 * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
097 * that the API of a record class is defined solely by its state description, and
098 * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
099 * 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
168public 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}