001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 java.util.HashSet;
023import java.util.Set;
024
025import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.FullIdent;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030
031/**
032 * <p>
033 * Checks for redundant import statements. An import statement is
034 * considered redundant if:
035 * </p>
036 * <ul>
037 *   <li>It is a duplicate of another import. This is, when a class is imported
038 *   more than once.</li>
039 *   <li>The class non-statically imported is from the {@code java.lang}
040 *   package, e.g. importing {@code java.lang.String}.</li>
041 *   <li>The class non-statically imported is from the same package as the
042 *   current package.</li>
043 * </ul>
044 * <p>
045 * To configure the check:
046 * </p>
047 * <pre>
048 * &lt;module name="RedundantImport"/&gt;
049 * </pre>
050 * <p>
051 * Example:
052 * </p>
053 * <pre>
054 * package Test;
055 * import static Test.MyClass.*; // OK, static import
056 * import static java.lang.Integer.MAX_VALUE; // OK, static import
057 *
058 * import Test.MyClass; // violation, imported from the same package as the current package
059 * import java.lang.String; // violation, the class imported is from the 'java.lang' package
060 * import java.util.Scanner; // OK
061 * import java.util.Scanner; // violation, it is a duplicate of another import
062 * public class MyClass{ };
063 * </pre>
064 * <p>
065 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
066 * </p>
067 * <p>
068 * Violation Message Keys:
069 * </p>
070 * <ul>
071 * <li>
072 * {@code import.duplicate}
073 * </li>
074 * <li>
075 * {@code import.lang}
076 * </li>
077 * <li>
078 * {@code import.same}
079 * </li>
080 * </ul>
081 *
082 * @since 3.0
083 */
084@FileStatefulCheck
085public class RedundantImportCheck
086    extends AbstractCheck {
087
088    /**
089     * A key is pointing to the warning message text in "messages.properties"
090     * file.
091     */
092    public static final String MSG_LANG = "import.lang";
093
094    /**
095     * A key is pointing to the warning message text in "messages.properties"
096     * file.
097     */
098    public static final String MSG_SAME = "import.same";
099
100    /**
101     * A key is pointing to the warning message text in "messages.properties"
102     * file.
103     */
104    public static final String MSG_DUPLICATE = "import.duplicate";
105
106    /** Set of the imports. */
107    private final Set<FullIdent> imports = new HashSet<>();
108    /** Set of static imports. */
109    private final Set<FullIdent> staticImports = new HashSet<>();
110
111    /** Name of package in file. */
112    private String pkgName;
113
114    @Override
115    public void beginTree(DetailAST aRootAST) {
116        pkgName = null;
117        imports.clear();
118        staticImports.clear();
119    }
120
121    @Override
122    public int[] getDefaultTokens() {
123        return getRequiredTokens();
124    }
125
126    @Override
127    public int[] getAcceptableTokens() {
128        return getRequiredTokens();
129    }
130
131    @Override
132    public int[] getRequiredTokens() {
133        return new int[] {
134            TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT, TokenTypes.PACKAGE_DEF,
135        };
136    }
137
138    @Override
139    public void visitToken(DetailAST ast) {
140        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
141            pkgName = FullIdent.createFullIdent(
142                    ast.getLastChild().getPreviousSibling()).getText();
143        }
144        else if (ast.getType() == TokenTypes.IMPORT) {
145            final FullIdent imp = FullIdent.createFullIdentBelow(ast);
146            if (isFromPackage(imp.getText(), "java.lang")) {
147                log(ast, MSG_LANG, imp.getText());
148            }
149            // imports from unnamed package are not allowed,
150            // so we are checking SAME rule only for named packages
151            else if (pkgName != null && isFromPackage(imp.getText(), pkgName)) {
152                log(ast, MSG_SAME, imp.getText());
153            }
154            // Check for a duplicate import
155            imports.stream().filter(full -> imp.getText().equals(full.getText()))
156                .forEach(full -> log(ast, MSG_DUPLICATE, full.getLineNo(), imp.getText()));
157
158            imports.add(imp);
159        }
160        else {
161            // Check for a duplicate static import
162            final FullIdent imp =
163                FullIdent.createFullIdent(
164                    ast.getLastChild().getPreviousSibling());
165            staticImports.stream().filter(full -> imp.getText().equals(full.getText()))
166                .forEach(full -> log(ast, MSG_DUPLICATE, full.getLineNo(), imp.getText()));
167
168            staticImports.add(imp);
169        }
170    }
171
172    /**
173     * Determines if an import statement is for types from a specified package.
174     *
175     * @param importName the import name
176     * @param pkg the package name
177     * @return whether from the package
178     */
179    private static boolean isFromPackage(String importName, String pkg) {
180        // imports from unnamed package are not allowed:
181        // https://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html#jls-7.5
182        // So '.' must be present in member name and we are not checking for it
183        final int index = importName.lastIndexOf('.');
184        final String front = importName.substring(0, index);
185        return pkg.equals(front);
186    }
187
188}