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.Set;
023
024import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
025import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.TokenTypes;
028import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
029
030/**
031 * <div>Checks for redundant modifiers on fields and methods declared directly
032 * in a compact source file. Ordinary and nested declarations are checked by
033 * {@code RedundantModifier}.</div>
034 *
035 * @since 14.4.0
036 */
037@FileStatefulCheck
038public class RedundantModifierCompactSourceCheck extends AbstractCheck {
039
040    /** A key is pointing to the warning message text in "messages.properties" file. */
041    public static final String MSG_KEY = "redundantModifierCompactSource";
042
043    /** Modifiers that are redundant on a method declared directly in a compact source file. */
044    private static final Set<Integer> METHOD_MODIFIERS = Set.of(
045        TokenTypes.LITERAL_PUBLIC, TokenTypes.LITERAL_PROTECTED,
046        TokenTypes.LITERAL_PRIVATE, TokenTypes.LITERAL_STATIC,
047        TokenTypes.FINAL, TokenTypes.STRICTFP);
048
049    /** Modifiers that are redundant on a field declared directly in a compact source file. */
050    private static final Set<Integer> FIELD_MODIFIERS = Set.of(
051        TokenTypes.LITERAL_PUBLIC, TokenTypes.LITERAL_PROTECTED,
052        TokenTypes.LITERAL_PRIVATE, TokenTypes.LITERAL_STATIC);
053
054    /** One of these is mandatory on a valid @SafeVarargs method. */
055    private static final Set<Integer> SAFE_VARARGS_REQUIRED = Set.of(
056        TokenTypes.LITERAL_STATIC, TokenTypes.FINAL, TokenTypes.LITERAL_PRIVATE);
057
058    /** Simple and qualified names of the {@code SafeVarargs} annotation. */
059    private static final Set<String> SAFE_VARARGS_NAMES =
060        Set.of("SafeVarargs", "java.lang.SafeVarargs");
061
062    /**
063     * Root of the file being processed. Only a compact source file can have a
064     * field or method as a direct child of its root.
065     */
066    private DetailAST root;
067
068    /**
069     * Creates a new {@code RedundantModifierCompactSourceCheck} instance.
070     */
071    public RedundantModifierCompactSourceCheck() {
072        // no code by default
073    }
074
075    @Override
076    public void beginTree(DetailAST rootAST) {
077        root = rootAST;
078    }
079
080    @Override
081    public int[] getDefaultTokens() {
082        return getRequiredTokens();
083    }
084
085    @Override
086    public int[] getAcceptableTokens() {
087        return getRequiredTokens();
088    }
089
090    @Override
091    public int[] getRequiredTokens() {
092        return new int[] {TokenTypes.METHOD_DEF, TokenTypes.VARIABLE_DEF};
093    }
094
095    @Override
096    public void visitToken(DetailAST ast) {
097        if (ast.getParent() == root) {
098            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
099            if (ast.getType() == TokenTypes.METHOD_DEF) {
100                checkModifiers(modifiers, METHOD_MODIFIERS,
101                        isRequiredModifierKept(ast, modifiers));
102            }
103            else {
104                checkModifiers(modifiers, FIELD_MODIFIERS, false);
105            }
106        }
107    }
108
109    /**
110     * Logs every modifier that is redundant for the declaration.
111     *
112     * @param modifiers the MODIFIERS node of the declaration
113     * @param redundant token types of modifiers that are redundant
114     * @param keepRequired whether the modifier making {@code @SafeVarargs} valid must be kept
115     */
116    private void checkModifiers(DetailAST modifiers, Set<Integer> redundant,
117                                boolean keepRequired) {
118        for (DetailAST modifier = modifiers.getFirstChild();
119                modifier != null; modifier = modifier.getNextSibling()) {
120            final int type = modifier.getType();
121            if (redundant.contains(type)
122                    && !(keepRequired && SAFE_VARARGS_REQUIRED.contains(type))) {
123                log(modifier, MSG_KEY, modifier.getText());
124            }
125        }
126    }
127
128    /**
129     * Checks whether a modifier is the single one making {@code @SafeVarargs} valid.
130     *
131     * @param methodDef the METHOD_DEF node
132     * @param modifiers the MODIFIERS node of the method
133     * @return true if the required modifier must not be reported
134     */
135    private static boolean isRequiredModifierKept(DetailAST methodDef, DetailAST modifiers) {
136        return AnnotationUtil.containsAnnotation(methodDef, SAFE_VARARGS_NAMES)
137                && countRequiredModifiers(modifiers) == 1;
138    }
139
140    /**
141     * Counts the modifiers that can make a {@code @SafeVarargs} method valid.
142     *
143     * @param modifiers the MODIFIERS node
144     * @return number of such modifiers
145     */
146    private static int countRequiredModifiers(DetailAST modifiers) {
147        int count = 0;
148        for (DetailAST modifier = modifiers.getFirstChild();
149                modifier != null; modifier = modifier.getNextSibling()) {
150            if (SAFE_VARARGS_REQUIRED.contains(modifier.getType())) {
151                count++;
152            }
153        }
154        return count;
155    }
156
157}