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