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.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 * <div>
033 * Checks for redundant import statements. An import statement is
034 * considered redundant if:
035 * </div>
036 * <ul>
037 *   <li>It is a duplicate of another import. This is, when a class or a module
038 *   is imported 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 *
045 * @since 3.0
046 */
047@FileStatefulCheck
048public class RedundantImportCheck
049    extends AbstractCheck {
050
051    /**
052     * A key is pointing to the warning message text in "messages.properties"
053     * file.
054     */
055    public static final String MSG_LANG = "import.lang";
056
057    /**
058     * A key is pointing to the warning message text in "messages.properties"
059     * file.
060     */
061    public static final String MSG_SAME = "import.same";
062
063    /**
064     * A key is pointing to the warning message text in "messages.properties"
065     * file.
066     */
067    public static final String MSG_DUPLICATE = "import.duplicate";
068
069    /** Set of the imports. */
070    private final Set<FullIdent> imports = new HashSet<>();
071    /** Set of static and module imports. */
072    private final Set<FullIdent> staticAndModuleImports = new HashSet<>();
073
074    /** Name of package in file. */
075    private String pkgName;
076
077    /**
078     * Creates a new {@code RedundantImportCheck} instance.
079     */
080    public RedundantImportCheck() {
081        // no code by default
082    }
083
084    @Override
085    public void beginTree(DetailAST aRootAST) {
086        pkgName = null;
087        imports.clear();
088        staticAndModuleImports.clear();
089    }
090
091    @Override
092    public int[] getDefaultTokens() {
093        return getRequiredTokens();
094    }
095
096    @Override
097    public int[] getAcceptableTokens() {
098        return getRequiredTokens();
099    }
100
101    @Override
102    public int[] getRequiredTokens() {
103        return new int[] {
104            TokenTypes.IMPORT,
105            TokenTypes.STATIC_IMPORT,
106            TokenTypes.PACKAGE_DEF,
107            TokenTypes.MODULE_IMPORT,
108        };
109    }
110
111    @Override
112    public void visitToken(DetailAST ast) {
113        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
114            pkgName = FullIdent.createFullIdent(
115                    ast.getLastChild().getPreviousSibling()).getText();
116        }
117        else if (ast.getType() == TokenTypes.IMPORT) {
118            final FullIdent imp = FullIdent.createFullIdentBelow(ast);
119            final String importText = imp.getText();
120            if (isFromPackage(importText, "java.lang")) {
121                log(ast, MSG_LANG, importText);
122            }
123            // imports from unnamed package are not allowed,
124            // so we are checking SAME rule only for named packages
125            else if (pkgName != null && isFromPackage(importText, pkgName)) {
126                log(ast, MSG_SAME, importText);
127            }
128            // Check for a duplicate import
129            imports.stream().filter(full -> importText.equals(full.getText()))
130                .forEach(full -> log(ast, MSG_DUPLICATE, full.getLineNo(), importText));
131
132            imports.add(imp);
133        }
134        else {
135            // Check for a duplicate static or module import
136            final DetailAST identNode = ast.getLastChild().getPreviousSibling();
137            final FullIdent importFullIdent = FullIdent.createFullIdent(identNode);
138            final String importText = importFullIdent.getText();
139
140            staticAndModuleImports
141                    .stream()
142                    .filter(existingImport -> importText.equals(existingImport.getText()))
143                    .forEach(existingImport -> {
144                        log(ast, MSG_DUPLICATE, existingImport.getLineNo(), importText);
145                    });
146
147            staticAndModuleImports.add(importFullIdent);
148        }
149    }
150
151    /**
152     * Determines if an import statement is for types from a specified package.
153     *
154     * @param importName the import name
155     * @param pkg the package name
156     * @return whether from the package
157     */
158    private static boolean isFromPackage(String importName, String pkg) {
159        // imports from unnamed package are not allowed:
160        // https://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html#jls-7.5
161        // So '.' must be present in member name and we are not checking for it
162        final int index = importName.lastIndexOf('.');
163        final String front = importName.substring(0, index);
164        return pkg.equals(front);
165    }
166
167}