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