blob: ea24b8e7408ff0cbc1accf67ba0f9d75e8486ad3 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.imports;
import java.net.URI;
import java.util.Collections;
import java.util.Set;
import java.util.regex.Pattern;
import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
import com.puppycrawl.tools.checkstyle.api.FullIdent;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* Check that controls what packages can be imported in each package. Useful
* for ensuring that application layering is not violated. Ideas on how the
* check can be improved include support for:
* <ul>
* <li>
* Change the default policy that if a package being checked does not
* match any guards, then it is allowed. Currently defaults to disallowed.
* </li>
* </ul>
*
* @author Oliver Burn
*/
@FileStatefulCheck
public class ImportControlCheck extends AbstractCheck implements ExternalResourceHolder {
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_MISSING_FILE = "import.control.missing.file";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_UNKNOWN_PKG = "import.control.unknown.pkg";
/**
* A key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_DISALLOWED = "import.control.disallowed";
/**
* A part of message for exception.
*/
private static final String UNABLE_TO_LOAD = "Unable to load ";
/** Location of import control file. */
private String fileLocation;
/** The filepath pattern this check applies to. */
private Pattern path = Pattern.compile(".*");
/** Whether to process the current file. */
private boolean processCurrentFile;
/** The root package controller. */
private ImportControl root;
/** The package doing the import. */
private String packageName;
/**
* The package controller for the current file. Used for performance
* optimisation.
*/
private ImportControl currentImportControl;
@Override
public int[] getDefaultTokens() {
return getAcceptableTokens();
}
@Override
public int[] getAcceptableTokens() {
return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT,
TokenTypes.STATIC_IMPORT, };
}
@Override
public int[] getRequiredTokens() {
return getAcceptableTokens();
}
@Override
public void beginTree(DetailAST rootAST) {
currentImportControl = null;
processCurrentFile = path.matcher(getFileContents().getFileName()).find();
}
@Override
public void visitToken(DetailAST ast) {
if (processCurrentFile) {
if (ast.getType() == TokenTypes.PACKAGE_DEF) {
if (root == null) {
log(ast, MSG_MISSING_FILE);
}
else {
packageName = getPackageText(ast);
currentImportControl = root.locateFinest(packageName);
if (currentImportControl == null) {
log(ast, MSG_UNKNOWN_PKG);
}
}
}
else if (currentImportControl != null) {
final String importText = getImportText(ast);
final AccessResult access =
currentImportControl.checkAccess(packageName, importText);
if (access != AccessResult.ALLOWED) {
log(ast, MSG_DISALLOWED, importText);
}
}
}
}
@Override
public Set<String> getExternalResourceLocations() {
return Collections.singleton(fileLocation);
}
/**
* Returns package text.
* @param ast PACKAGE_DEF ast node
* @return String that represents full package name
*/
private static String getPackageText(DetailAST ast) {
final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
return FullIdent.createFullIdent(nameAST).getText();
}
/**
* Returns import text.
* @param ast ast node that represents import
* @return String that represents importing class
*/
private static String getImportText(DetailAST ast) {
final FullIdent imp;
if (ast.getType() == TokenTypes.IMPORT) {
imp = FullIdent.createFullIdentBelow(ast);
}
else {
// know it is a static import
imp = FullIdent.createFullIdent(ast
.getFirstChild().getNextSibling());
}
return imp.getText();
}
/**
* Set the name for the file containing the import control
* configuration. It can also be a URL or resource in the classpath.
* It will cause the file to be loaded.
* @param uri the uri of the file to load.
* @throws IllegalArgumentException on error loading the file.
*/
public void setFile(URI uri) {
// Handle empty param
if (uri != null) {
try {
root = ImportControlLoader.load(uri);
fileLocation = uri.toString();
}
catch (CheckstyleException ex) {
throw new IllegalArgumentException(UNABLE_TO_LOAD + uri, ex);
}
}
}
/**
* Set the file path pattern that this check applies to.
* @param pattern the file path regex this check should apply to.
*/
public void setPath(Pattern pattern) {
path = pattern;
}
}