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.ArrayList;
023import java.util.List;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.FullIdent;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
032
033/**
034 * <p>
035 * Checks for imports from a set of illegal packages.
036 * By default, the check rejects all {@code sun.*} packages
037 * since programs that contain direct calls to the {@code sun.*} packages
038 * are <a href="http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html">
039 * not 100% Pure Java</a>.
040 * </p>
041 * <p>
042 * To reject other packages, set property illegalPkgs to a comma-separated
043 * list of the illegal packages.
044 * </p>
045 * <p>
046 * An example of how to configure the check is:
047 * </p>
048 * <pre>
049 * &lt;module name="IllegalImport"/&gt;
050 * </pre>
051 * <p>
052 * An example of how to configure the check so that it rejects packages
053 * {@code java.io.*} and {@code java.sql.*} is
054 * </p>
055 * <pre>
056 * &lt;module name="IllegalImport"&gt;
057 *    &lt;property name="illegalPkgs" value="java.io, java.sql"/&gt;
058 * &lt;/module&gt;
059 *
060 * Compatible with Java 1.5 source.
061 *
062 * </pre>
063 */
064@StatelessCheck
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     * @noinspection WeakerAccess
103     */
104    public final void setIllegalPkgs(String... from) {
105        illegalPkgs = from.clone();
106        illegalPkgsRegexps.clear();
107        for (String illegalPkg : illegalPkgs) {
108            illegalPkgsRegexps.add(CommonUtil.createPattern("^" + illegalPkg + "\\..*"));
109        }
110    }
111
112    /**
113     * Set the list of illegal classes.
114     * @param from array of illegal classes
115     */
116    public void setIllegalClasses(String... from) {
117        illegalClasses = from.clone();
118        for (String illegalClass : illegalClasses) {
119            illegalClassesRegexps.add(CommonUtil.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 getRequiredTokens();
135    }
136
137    @Override
138    public int[] getAcceptableTokens() {
139        return getRequiredTokens();
140    }
141
142    @Override
143    public int[] getRequiredTokens() {
144        return new int[] {TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT};
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,
159                MSG_KEY,
160                imp.getText());
161        }
162    }
163
164    /**
165     * Checks if an import matches one of the regular expressions
166     * for illegal packages or illegal class names.
167     * @param importText the argument of the import keyword
168     * @return if {@code importText} matches one of the regular expressions
169     *         for illegal packages or illegal class names
170     */
171    private boolean isIllegalImportByRegularExpressions(String importText) {
172        boolean result = false;
173        for (Pattern pattern : illegalPkgsRegexps) {
174            if (pattern.matcher(importText).matches()) {
175                result = true;
176                break;
177            }
178        }
179        if (!result) {
180            for (Pattern pattern : illegalClassesRegexps) {
181                if (pattern.matcher(importText).matches()) {
182                    result = true;
183                    break;
184                }
185            }
186        }
187        return result;
188    }
189
190    /**
191     * Checks if an import is from a package or class name that must not be used.
192     * @param importText the argument of the import keyword
193     * @return if {@code importText} contains an illegal package prefix or equals illegal class name
194     */
195    private boolean isIllegalImportByPackagesAndClassNames(String importText) {
196        boolean result = false;
197        for (String element : illegalPkgs) {
198            if (importText.startsWith(element + ".")) {
199                result = true;
200                break;
201            }
202        }
203        if (!result && illegalClasses != null) {
204            for (String element : illegalClasses) {
205                if (importText.equals(element)) {
206                    result = true;
207                    break;
208                }
209            }
210        }
211        return result;
212    }
213
214    /**
215     * Checks if an import is from a package or class name that must not be used.
216     * @param importText the argument of the import keyword
217     * @return if {@code importText} is illegal import
218     */
219    private boolean isIllegalImport(String importText) {
220        final boolean result;
221        if (regexp) {
222            result = isIllegalImportByRegularExpressions(importText);
223        }
224        else {
225            result = isIllegalImportByPackagesAndClassNames(importText);
226        }
227        return result;
228    }
229
230}