001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 * <p>
035 * Checks for redundant modifiers.
036 * </p>
037 * <p>
038 * Rationale: The Java Language Specification strongly discourages the usage
039 * of {@code public} and {@code abstract} for method declarations in interface
040 * definitions as a matter of style.
041 * </p>
042 * <p>The check validates:</p>
043 * <ol>
044 * <li>
045 * Interface and annotation definitions.
046 * </li>
047 * <li>
048 * Final modifier on methods of final and anonymous classes.
049 * </li>
050 * <li>
051 * Type declarations nested under interfaces that are declared as {@code public} or {@code static}.
052 * </li>
053 * <li>
054 * Class constructors.
055 * </li>
056 * <li>
057 * Nested {@code enum} definitions that are declared as {@code static}.
058 * </li>
059 * <li>
060 * {@code record} definitions that are declared as {@code final} and nested
061 * {@code record} definitions that are declared as {@code static}.
062 * </li>
063 * </ol>
064 * <p>
065 * interfaces by definition are abstract so the {@code abstract} modifier is redundant on them.
066 * </p>
067 * <p>Type declarations nested under interfaces by definition are public and static,
068 * so the {@code public} and {@code static} modifiers on nested type declarations are redundant.
069 * On the other hand, classes inside of interfaces can be abstract or non abstract.
070 * So, {@code abstract} modifier is allowed.
071 * </p>
072 * <p>Fields in interfaces and annotations are automatically
073 * public, static and final, so these modifiers are redundant as
074 * well.</p>
075 *
076 * <p>As annotations are a form of interface, their fields are also
077 * automatically public, static and final just as their
078 * annotation fields are automatically public and abstract.</p>
079 *
080 * <p>A record class is implicitly final and cannot be abstract, these restrictions emphasize
081 * that the API of a record class is defined solely by its state description, and
082 * cannot be enhanced later by another class. Nested records are implicitly static. This avoids an
083 * immediately enclosing instance which would silently add state to the record class.
084 * See <a href="https://openjdk.org/jeps/395">JEP 395</a> for more info.</p>
085 *
086 * <p>Enums by definition are static implicit subclasses of java.lang.Enum&#60;E&#62;.
087 * So, the {@code static} modifier on the enums is redundant. In addition,
088 * if enum is inside of interface, {@code public} modifier is also redundant.</p>
089 *
090 * <p>Enums can also contain abstract methods and methods which can be overridden by the declared
091 * enumeration fields.
092 * See the following example:</p>
093 * <pre>
094 * public enum EnumClass {
095 *   FIELD_1,
096 *   FIELD_2 {
097 *     &#64;Override
098 *     public final void method1() {} // violation expected
099 *   };
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
188public 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}