001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.imports;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.FullIdent;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
028
029/**
030 * <div>
031 * Checks that there are no static import statements.
032 * </div>
033 *
034 * <p>
035 * Rationale: Importing static members can lead to naming conflicts
036 * between class' members. It may lead to poor code readability since it
037 * may no longer be clear what class a member resides in (without looking
038 * at the import statement).
039 * </p>
040 *
041 * <p>
042 * Notes:
043 * If you exclude a starred import on a class this automatically excludes
044 * each member individually.
045 * </p>
046 *
047 * <p>
048 * For example: Excluding {@code java.lang.Math.*}. will allow the import
049 * of each static member in the Math class individually like
050 * {@code java.lang.Math.PI, java.lang.Math.cos, ...}.
051 * </p>
052 * <ul>
053 * <li>
054 * Property {@code excludes} - Control whether to allow for certain classes via
055 * a star notation to be excluded such as {@code java.lang.Math.*} or specific
056 * static members to be excluded like {@code java.lang.System.out} for a variable
057 * or {@code java.lang.Math.random} for a method. See notes section for details.
058 * Type is {@code java.lang.String[]}.
059 * Default value is {@code ""}.
060 * </li>
061 * </ul>
062 *
063 * <p>
064 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
065 * </p>
066 *
067 * <p>
068 * Violation Message Keys:
069 * </p>
070 * <ul>
071 * <li>
072 * {@code import.avoidStatic}
073 * </li>
074 * </ul>
075 *
076 * @since 5.0
077 */
078@StatelessCheck
079public class AvoidStaticImportCheck
080    extends AbstractCheck {
081
082    /**
083     * A key is pointing to the warning message text in "messages.properties"
084     * file.
085     */
086    public static final String MSG_KEY = "import.avoidStatic";
087
088    /**
089     * Control whether to allow for certain classes via a star notation to be
090     * excluded such as {@code java.lang.Math.*} or specific static members
091     * to be excluded like {@code java.lang.System.out} for a variable or
092     * {@code java.lang.Math.random} for a method. See notes section for details.
093     */
094    private String[] excludes = CommonUtil.EMPTY_STRING_ARRAY;
095
096    @Override
097    public int[] getDefaultTokens() {
098        return getRequiredTokens();
099    }
100
101    @Override
102    public int[] getAcceptableTokens() {
103        return getRequiredTokens();
104    }
105
106    @Override
107    public int[] getRequiredTokens() {
108        return new int[] {TokenTypes.STATIC_IMPORT};
109    }
110
111    /**
112     * Setter to control whether to allow for certain classes via a star notation
113     * to be excluded such as {@code java.lang.Math.*} or specific static members
114     * to be excluded like {@code java.lang.System.out} for a variable or
115     * {@code java.lang.Math.random} for a method. See notes section for details.
116     *
117     * @param excludes fully-qualified class names/specific
118     *     static members where static imports are ok
119     * @since 5.0
120     */
121    public void setExcludes(String... excludes) {
122        this.excludes = excludes.clone();
123    }
124
125    @Override
126    public void visitToken(final DetailAST ast) {
127        final DetailAST startingDot =
128            ast.getFirstChild().getNextSibling();
129        final FullIdent name = FullIdent.createFullIdent(startingDot);
130
131        final String nameText = name.getText();
132        if (!isExempt(nameText)) {
133            log(startingDot, MSG_KEY, nameText);
134        }
135    }
136
137    /**
138     * Checks if a class or static member is exempt from known excludes.
139     *
140     * @param classOrStaticMember
141     *                the class or static member
142     * @return true if except false if not
143     */
144    private boolean isExempt(String classOrStaticMember) {
145        boolean exempt = false;
146
147        for (String exclude : excludes) {
148            if (classOrStaticMember.equals(exclude)
149                    || isStarImportOfPackage(classOrStaticMember, exclude)) {
150                exempt = true;
151                break;
152            }
153        }
154        return exempt;
155    }
156
157    /**
158     * Returns true if classOrStaticMember is a starred name of package,
159     *  not just member name.
160     *
161     * @param classOrStaticMember - full name of member
162     * @param exclude - current exclusion
163     * @return true if member in exclusion list
164     */
165    private static boolean isStarImportOfPackage(String classOrStaticMember, String exclude) {
166        boolean result = false;
167        if (exclude.endsWith(".*")) {
168            // this section allows explicit imports
169            // to be exempt when configured using
170            // a starred import
171            final String excludeMinusDotStar =
172                exclude.substring(0, exclude.length() - 2);
173            if (classOrStaticMember.startsWith(excludeMinusDotStar)
174                    && !classOrStaticMember.equals(excludeMinusDotStar)) {
175                final String member = classOrStaticMember.substring(
176                        excludeMinusDotStar.length() + 1);
177                // if it contains a dot then it is not a member but a package
178                if (member.indexOf('.') == -1) {
179                    result = true;
180                }
181            }
182        }
183        return result;
184    }
185
186}