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 packages can be imported in each package. 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 * @author Oliver Burn 047 */ 048@FileStatefulCheck 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 URI file; 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 getRequiredTokens(); 096 } 097 098 @Override 099 public int[] getAcceptableTokens() { 100 return getRequiredTokens(); 101 } 102 103 @Override 104 public int[] getRequiredTokens() { 105 return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT, }; 106 } 107 108 @Override 109 public void beginTree(DetailAST rootAST) { 110 currentImportControl = null; 111 processCurrentFile = path.matcher(getFileContents().getFileName()).find(); 112 } 113 114 @Override 115 public void visitToken(DetailAST ast) { 116 if (processCurrentFile) { 117 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 118 if (root == null) { 119 log(ast, MSG_MISSING_FILE); 120 } 121 else { 122 packageName = getPackageText(ast); 123 currentImportControl = root.locateFinest(packageName); 124 if (currentImportControl == null) { 125 log(ast, MSG_UNKNOWN_PKG); 126 } 127 } 128 } 129 else if (currentImportControl != null) { 130 final String importText = getImportText(ast); 131 final AccessResult access = 132 currentImportControl.checkAccess(packageName, importText); 133 if (access != AccessResult.ALLOWED) { 134 log(ast, MSG_DISALLOWED, importText); 135 } 136 } 137 } 138 } 139 140 @Override 141 public Set<String> getExternalResourceLocations() { 142 return Collections.singleton(file.toString()); 143 } 144 145 /** 146 * Returns package text. 147 * @param ast PACKAGE_DEF ast node 148 * @return String that represents full package name 149 */ 150 private static String getPackageText(DetailAST ast) { 151 final DetailAST nameAST = ast.getLastChild().getPreviousSibling(); 152 return FullIdent.createFullIdent(nameAST).getText(); 153 } 154 155 /** 156 * Returns import text. 157 * @param ast ast node that represents import 158 * @return String that represents importing class 159 */ 160 private static String getImportText(DetailAST ast) { 161 final FullIdent imp; 162 if (ast.getType() == TokenTypes.IMPORT) { 163 imp = FullIdent.createFullIdentBelow(ast); 164 } 165 else { 166 // know it is a static import 167 imp = FullIdent.createFullIdent(ast 168 .getFirstChild().getNextSibling()); 169 } 170 return imp.getText(); 171 } 172 173 /** 174 * Set the name for the file containing the import control 175 * configuration. It can also be a URL or resource in the classpath. 176 * It will cause the file to be loaded. 177 * @param uri the uri of the file to load. 178 * @throws IllegalArgumentException on error loading the file. 179 */ 180 public void setFile(URI uri) { 181 // Handle empty param 182 if (uri != null) { 183 try { 184 root = ImportControlLoader.load(uri); 185 file = uri; 186 } 187 catch (CheckstyleException ex) { 188 throw new IllegalArgumentException(UNABLE_TO_LOAD + uri, ex); 189 } 190 } 191 } 192 193 /** 194 * Set the file path pattern that this check applies to. 195 * @param pattern the file path regex this check should apply to. 196 */ 197 public void setPath(Pattern pattern) { 198 path = pattern; 199 } 200 201}