001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.imports;
020
021import java.io.File;
022import java.net.URI;
023
024import com.puppycrawl.tools.checkstyle.api.Check;
025import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
026import com.puppycrawl.tools.checkstyle.api.DetailAST;
027import com.puppycrawl.tools.checkstyle.api.FullIdent;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import org.apache.commons.beanutils.ConversionException;
030
031/**
032 * Check that controls what packages can be imported in each package. Useful
033 * for ensuring that application layering is not violated. Ideas on how the
034 * check can be improved include support for:
035 * <ul>
036 * <li>
037 * Change the default policy that if a package being checked does not
038 * match any guards, then it is allowed. Currently defaults to disallowed.
039 * </li>
040 * </ul>
041 *
042 * @author Oliver Burn
043 */
044public class ImportControlCheck extends Check
045{
046    /** The root package controller. */
047    private PkgControl root;
048    /** The package doing the import. */
049    private String inPkg;
050
051    /**
052     * The package controller for the current file. Used for performance
053     * optimisation.
054     */
055    private PkgControl currentLeaf;
056
057    @Override
058    public int[] getDefaultTokens()
059    {
060        return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
061                          TokenTypes.STATIC_IMPORT, };
062    }
063
064    @Override
065    public void beginTree(final DetailAST rootAST)
066    {
067        currentLeaf = null;
068    }
069
070    @Override
071    public void visitToken(final DetailAST ast)
072    {
073        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
074            final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
075            final FullIdent full = FullIdent.createFullIdent(nameAST);
076            if (root == null) {
077                log(nameAST, "import.control.missing.file");
078            }
079            else {
080                inPkg = full.getText();
081                currentLeaf = root.locateFinest(inPkg);
082                if (currentLeaf == null) {
083                    log(nameAST, "import.control.unknown.pkg");
084                }
085            }
086        }
087        else if (currentLeaf != null) {
088            final FullIdent imp;
089            if (ast.getType() == TokenTypes.IMPORT) {
090                imp = FullIdent.createFullIdentBelow(ast);
091            }
092            else {
093                // know it is a static import
094                imp = FullIdent.createFullIdent(ast
095                        .getFirstChild().getNextSibling());
096            }
097            final AccessResult access = currentLeaf.checkAccess(imp.getText(),
098                    inPkg);
099            if (!AccessResult.ALLOWED.equals(access)) {
100                log(ast, "import.control.disallowed", imp.getText());
101            }
102        }
103    }
104
105    /**
106     * Set the parameter for the url containing the import control
107     * configuration. It will cause the url to be loaded.
108     * @param url the url of the file to load.
109     * @throws ConversionException on error loading the file.
110     */
111    public void setUrl(final String url)
112    {
113        // Handle empty param
114        if ((url == null) || (url.trim().length() == 0)) {
115            return;
116        }
117        final URI uri;
118        try {
119            uri = URI.create(url);
120        }
121        catch (final IllegalArgumentException ex) {
122            throw new ConversionException("syntax error in url " + url, ex);
123        }
124        try {
125            root = ImportControlLoader.load(uri);
126        }
127        catch (final CheckstyleException ex) {
128            throw new ConversionException("Unable to load " + url, ex);
129        }
130    }
131
132    /**
133     * Set the pnameter for the file containing the import control
134     * configuration. It will cause the file to be loaded.
135     * @param name the name of the file to load.
136     * @throws ConversionException on error loading the file.
137     */
138    public void setFile(final String name)
139    {
140        // Handle empty param
141        if ((name == null) || (name.trim().length() == 0)) {
142            return;
143        }
144
145        try {
146            root = ImportControlLoader.load(new File(name).toURI());
147        }
148        catch (final CheckstyleException ex) {
149            throw new ConversionException("Unable to load " + name, ex);
150        }
151    }
152}