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