001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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.io.File;
023import java.net.URI;
024import java.util.Set;
025
026import org.apache.commons.beanutils.ConversionException;
027
028import com.google.common.collect.ImmutableSet;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
031import com.puppycrawl.tools.checkstyle.api.DetailAST;
032import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
033import com.puppycrawl.tools.checkstyle.api.FullIdent;
034import com.puppycrawl.tools.checkstyle.api.TokenTypes;
035import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
036
037/**
038 * Check that controls what packages can be imported in each package. Useful
039 * for ensuring that application layering is not violated. Ideas on how the
040 * check can be improved include support for:
041 * <ul>
042 * <li>
043 * Change the default policy that if a package being checked does not
044 * match any guards, then it is allowed. Currently defaults to disallowed.
045 * </li>
046 * </ul>
047 *
048 * @author Oliver Burn
049 */
050public class ImportControlCheck extends AbstractCheck implements ExternalResourceHolder {
051
052    /**
053     * A key is pointing to the warning message text in "messages.properties"
054     * file.
055     */
056    public static final String MSG_MISSING_FILE = "import.control.missing.file";
057
058    /**
059     * A key is pointing to the warning message text in "messages.properties"
060     * file.
061     */
062    public static final String MSG_UNKNOWN_PKG = "import.control.unknown.pkg";
063
064    /**
065     * A key is pointing to the warning message text in "messages.properties"
066     * file.
067     */
068    public static final String MSG_DISALLOWED = "import.control.disallowed";
069
070    /**
071     * A part of message for exception.
072     */
073    private static final String UNABLE_TO_LOAD = "Unable to load ";
074
075    /** Location of import control file. */
076    private String fileLocation;
077
078    /** The root package controller. */
079    private PkgControl root;
080    /** The package doing the import. */
081    private String inPkg;
082
083    /**
084     * The package controller for the current file. Used for performance
085     * optimisation.
086     */
087    private PkgControl currentLeaf;
088
089    @Override
090    public int[] getDefaultTokens() {
091        return getAcceptableTokens();
092    }
093
094    @Override
095    public int[] getAcceptableTokens() {
096        return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
097                          TokenTypes.STATIC_IMPORT, };
098    }
099
100    @Override
101    public int[] getRequiredTokens() {
102        return getAcceptableTokens();
103    }
104
105    @Override
106    public void beginTree(final DetailAST rootAST) {
107        currentLeaf = null;
108    }
109
110    @Override
111    public void visitToken(final DetailAST ast) {
112        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
113            final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
114            final FullIdent full = FullIdent.createFullIdent(nameAST);
115            if (root == null) {
116                log(nameAST, MSG_MISSING_FILE);
117            }
118            else {
119                inPkg = full.getText();
120                currentLeaf = root.locateFinest(inPkg);
121                if (currentLeaf == null) {
122                    log(nameAST, MSG_UNKNOWN_PKG);
123                }
124            }
125        }
126        else if (currentLeaf != null) {
127            final FullIdent imp;
128            if (ast.getType() == TokenTypes.IMPORT) {
129                imp = FullIdent.createFullIdentBelow(ast);
130            }
131            else {
132                // know it is a static import
133                imp = FullIdent.createFullIdent(ast
134                        .getFirstChild().getNextSibling());
135            }
136            final AccessResult access = currentLeaf.checkAccess(imp.getText(),
137                    inPkg);
138            if (access != AccessResult.ALLOWED) {
139                log(ast, MSG_DISALLOWED, imp.getText());
140            }
141        }
142    }
143
144    @Override
145    public Set<String> getExternalResourceLocations() {
146        return ImmutableSet.of(fileLocation);
147    }
148
149    /**
150     * Set the name for the file containing the import control
151     * configuration. It will cause the file to be loaded.
152     * @param name the name of the file to load.
153     * @throws ConversionException on error loading the file.
154     */
155    public void setFile(final String name) {
156        // Handle empty param
157        if (!CommonUtils.isBlank(name)) {
158            try {
159                root = ImportControlLoader.load(new File(name).toURI());
160                fileLocation = name;
161            }
162            catch (final CheckstyleException ex) {
163                throw new ConversionException(UNABLE_TO_LOAD + name, ex);
164            }
165        }
166    }
167
168    /**
169     * Set the parameter for the url containing the import control
170     * configuration. It will cause the url to be loaded.
171     * @param url the url of the file to load.
172     * @throws ConversionException on error loading the file.
173     */
174    public void setUrl(final String url) {
175        // Handle empty param
176        if (!CommonUtils.isBlank(url)) {
177            final URI uri;
178            try {
179                uri = URI.create(url);
180            }
181            catch (final IllegalArgumentException ex) {
182                throw new ConversionException("Syntax error in url " + url, ex);
183            }
184            try {
185                root = ImportControlLoader.load(uri);
186                fileLocation = url;
187            }
188            catch (final CheckstyleException ex) {
189                throw new ConversionException(UNABLE_TO_LOAD + url, ex);
190            }
191        }
192    }
193}