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