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