001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 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 imports that are redundant. 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. For example 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 * An example of how to configure the check is:
046 * </p>
047 * <pre>
048 * &lt;module name="RedundantImport"/&gt;
049 * </pre>
050 * Compatible with Java 1.5 source.
051 *
052 * @author Oliver Burn
053 */
054@FileStatefulCheck
055public class RedundantImportCheck
056    extends AbstractCheck {
057
058    /**
059     * A key is pointing to the warning message text in "messages.properties"
060     * file.
061     */
062    public static final String MSG_LANG = "import.lang";
063
064    /**
065     * A key is pointing to the warning message text in "messages.properties"
066     * file.
067     */
068    public static final String MSG_SAME = "import.same";
069
070    /**
071     * A key is pointing to the warning message text in "messages.properties"
072     * file.
073     */
074    public static final String MSG_DUPLICATE = "import.duplicate";
075
076    /** Set of the imports. */
077    private final Set<FullIdent> imports = new HashSet<>();
078    /** Set of static imports. */
079    private final Set<FullIdent> staticImports = new HashSet<>();
080
081    /** Name of package in file. */
082    private String pkgName;
083
084    @Override
085    public void beginTree(DetailAST aRootAST) {
086        pkgName = null;
087        imports.clear();
088        staticImports.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, TokenTypes.STATIC_IMPORT, TokenTypes.PACKAGE_DEF,
105        };
106    }
107
108    @Override
109    public void visitToken(DetailAST ast) {
110        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
111            pkgName = FullIdent.createFullIdent(
112                    ast.getLastChild().getPreviousSibling()).getText();
113        }
114        else if (ast.getType() == TokenTypes.IMPORT) {
115            final FullIdent imp = FullIdent.createFullIdentBelow(ast);
116            if (isFromPackage(imp.getText(), "java.lang")) {
117                log(ast.getLineNo(), ast.getColumnNo(), MSG_LANG,
118                    imp.getText());
119            }
120            // imports from unnamed package are not allowed,
121            // so we are checking SAME rule only for named packages
122            else if (pkgName != null && isFromPackage(imp.getText(), pkgName)) {
123                log(ast.getLineNo(), ast.getColumnNo(), MSG_SAME,
124                    imp.getText());
125            }
126            // Check for a duplicate import
127            imports.stream().filter(full -> imp.getText().equals(full.getText()))
128                .forEach(full -> log(ast.getLineNo(), ast.getColumnNo(),
129                    MSG_DUPLICATE, full.getLineNo(),
130                    imp.getText()));
131
132            imports.add(imp);
133        }
134        else {
135            // Check for a duplicate static import
136            final FullIdent imp =
137                FullIdent.createFullIdent(
138                    ast.getLastChild().getPreviousSibling());
139            staticImports.stream().filter(full -> imp.getText().equals(full.getText()))
140                .forEach(full -> log(ast.getLineNo(), ast.getColumnNo(),
141                    MSG_DUPLICATE, full.getLineNo(), imp.getText()));
142
143            staticImports.add(imp);
144        }
145    }
146
147    /**
148     * Determines if an import statement is for types from a specified package.
149     * @param importName the import name
150     * @param pkg the package name
151     * @return whether from the package
152     */
153    private static boolean isFromPackage(String importName, String pkg) {
154        // imports from unnamed package are not allowed:
155        // https://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html#jls-7.5
156        // So '.' must be present in member name and we are not checking for it
157        final int index = importName.lastIndexOf('.');
158        final String front = importName.substring(0, index);
159        return front.equals(pkg);
160    }
161
162}