001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.ArrayList;
023import java.util.List;
024import java.util.regex.Pattern;
025
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;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
031
032/**
033 * <p>
034 * Checks for imports from a set of illegal packages.
035 * By default, the check rejects all {@code sun.*} packages
036 * since programs that contain direct calls to the {@code sun.*} packages
037 * are <a href="http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html">
038 * not 100% Pure Java</a>.
039 * </p>
040 * <p>
041 * To reject other packages, set property illegalPkgs to a comma-separated
042 * list of the illegal packages.
043 * </p>
044 * <p>
045 * An example of how to configure the check is:
046 * </p>
047 * <pre>
048 * &lt;module name="IllegalImport"/&gt;
049 * </pre>
050 * <p>
051 * An example of how to configure the check so that it rejects packages
052 * {@code java.io.*} and {@code java.sql.*} is
053 * </p>
054 * <pre>
055 * &lt;module name="IllegalImport"&gt;
056 *    &lt;property name="illegalPkgs" value="java.io, java.sql"/&gt;
057 * &lt;/module&gt;
058 *
059 * Compatible with Java 1.5 source.
060 *
061 * </pre>
062 * @author Oliver Burn
063 * @author Lars Kühne
064 */
065public class IllegalImportCheck
066    extends AbstractCheck {
067
068    /**
069     * A key is pointing to the warning message text in "messages.properties"
070     * file.
071     */
072    public static final String MSG_KEY = "import.illegal";
073
074    /** The compiled regular expressions for packages. */
075    private final List<Pattern> illegalPkgsRegexps = new ArrayList<>();
076
077    /** The compiled regular expressions for classes. */
078    private final List<Pattern> illegalClassesRegexps = new ArrayList<>();
079
080    /** List of illegal packages. */
081    private String[] illegalPkgs;
082
083    /** List of illegal classes. */
084    private String[] illegalClasses;
085
086    /**
087     * Whether the packages or class names
088     * should be interpreted as regular expressions.
089     */
090    private boolean regexp;
091
092    /**
093     * Creates a new {@code IllegalImportCheck} instance.
094     */
095    public IllegalImportCheck() {
096        setIllegalPkgs("sun");
097    }
098
099    /**
100     * Set the list of illegal packages.
101     * @param from array of illegal packages
102     */
103    public final void setIllegalPkgs(String... from) {
104        illegalPkgs = from.clone();
105        illegalPkgsRegexps.clear();
106        for (String illegalPkg : illegalPkgs) {
107            illegalPkgsRegexps.add(CommonUtils.createPattern("^" + illegalPkg + "\\..*"));
108        }
109    }
110
111    /**
112     * Set the list of illegal classes.
113     * @param from array of illegal classes
114     */
115    public void setIllegalClasses(String... from) {
116        illegalClasses = from.clone();
117        illegalClassesRegexps.clear();
118        for (String illegalClass : illegalClasses) {
119            illegalClassesRegexps.add(CommonUtils.createPattern(illegalClass));
120        }
121    }
122
123    /**
124     * Controls whether the packages or class names
125     * should be interpreted as regular expressions.
126     * @param regexp a {@code Boolean} value
127     */
128    public void setRegexp(boolean regexp) {
129        this.regexp = regexp;
130    }
131
132    @Override
133    public int[] getDefaultTokens() {
134        return getAcceptableTokens();
135    }
136
137    @Override
138    public int[] getAcceptableTokens() {
139        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
140    }
141
142    @Override
143    public int[] getRequiredTokens() {
144        return getAcceptableTokens();
145    }
146
147    @Override
148    public void visitToken(DetailAST ast) {
149        final FullIdent imp;
150        if (ast.getType() == TokenTypes.IMPORT) {
151            imp = FullIdent.createFullIdentBelow(ast);
152        }
153        else {
154            imp = FullIdent.createFullIdent(
155                ast.getFirstChild().getNextSibling());
156        }
157        if (isIllegalImport(imp.getText())) {
158            log(ast.getLineNo(),
159                ast.getColumnNo(),
160                MSG_KEY,
161                imp.getText());
162        }
163    }
164
165    /**
166     * Checks if an import matches one of the regular expressions
167     * for illegal packages or illegal class names.
168     * @param importText the argument of the import keyword
169     * @return if {@code importText} matches one of the regular expressions
170     *         for illegal packages or illegal class names
171     */
172    private boolean isIllegalImportByRegularExpressions(String importText) {
173        boolean result = false;
174        for (Pattern pattern : illegalPkgsRegexps) {
175            if (pattern.matcher(importText).matches()) {
176                result = true;
177                break;
178            }
179        }
180        if (!result) {
181            for (Pattern pattern : illegalClassesRegexps) {
182                if (pattern.matcher(importText).matches()) {
183                    result = true;
184                    break;
185                }
186            }
187        }
188        return result;
189    }
190
191    /**
192     * Checks if an import is from a package or class name that must not be used.
193     * @param importText the argument of the import keyword
194     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
195     */
196    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
197        boolean result = false;
198        for (String element : illegalPkgs) {
199            if (importText.startsWith(element + ".")) {
200                result = true;
201                break;
202            }
203        }
204        if (!result && illegalClasses != null) {
205            for (String element : illegalClasses) {
206                if (importText.equals(element)) {
207                    result = true;
208                    break;
209                }
210            }
211        }
212        return result;
213    }
214
215    /**
216     * Checks if an import is from a package or class name that must not be used.
217     * @param importText the argument of the import keyword
218     * @return if {@code importText} is illegal import
219     */
220    private boolean isIllegalImport(String importText) {
221        final boolean result;
222        if (regexp) {
223            result = isIllegalImportByRegularExpressions(importText);
224        }
225        else {
226            result = isIllegalImportByPackagesAndClassNames(importText);
227        }
228        return result;
229    }
230}