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