1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.imports;
21
22 import java.net.URI;
23 import java.util.Set;
24 import java.util.regex.Pattern;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
31 import com.puppycrawl.tools.checkstyle.api.FullIdent;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
34
35 /**
36 * <div>
37 * Controls what can be imported in each package and file. Useful for ensuring
38 * that application layering rules are not violated, especially on large projects.
39 * </div>
40 *
41 * <p>
42 * You can control imports based on the package name or based on the file name.
43 * When controlling packages, all files and sub-packages in the declared package
44 * will be controlled by this check. To specify differences between a main package
45 * and a sub-package, you must define the sub-package inside the main package.
46 * When controlling file, only the file name is considered and only files processed by
47 * <a href="https://checkstyle.org/config.html#TreeWalker">TreeWalker</a>.
48 * The file's extension is ignored.
49 * </p>
50 *
51 * <p>
52 * Short description of the behaviour:
53 * </p>
54 * <ul>
55 * <li>
56 * Check starts checking from the longest matching subpackage (later 'current subpackage') or
57 * the first file name match described inside import control file to package defined in class file.
58 * <ul>
59 * <li>
60 * The longest matching subpackage is found by starting with the root package and
61 * examining if any of the sub-packages or file definitions match the current
62 * class' package or file name.
63 * </li>
64 * <li>
65 * If a file name is matched first, that is considered the longest match and becomes
66 * the current file/subpackage.
67 * </li>
68 * <li>
69 * If another subpackage is matched, then it's subpackages and file names are examined
70 * for the next longest match and the process repeats recursively.
71 * </li>
72 * <li>
73 * If no subpackages or file names are matched, the current subpackage is then used.
74 * </li>
75 * </ul>
76 * </li>
77 * <li>
78 * Order of rules in the same subpackage/root are defined by the order of declaration
79 * in the XML file, which is from top (first) to bottom (last).
80 * </li>
81 * <li>
82 * If there is matching allow/disallow rule inside the current file/subpackage
83 * then the Check returns the first "allowed" or "disallowed" message.
84 * </li>
85 * <li>
86 * If there is no matching allow/disallow rule inside the current file/subpackage
87 * then it continues checking in the parent subpackage.
88 * </li>
89 * <li>
90 * If there is no matching allow/disallow rule in any of the files/subpackages,
91 * including the root level (import-control), then the import is disallowed by default.
92 * </li>
93 * </ul>
94 *
95 * <p>
96 * The DTD for an import control XML document is at
97 * <a href="https://checkstyle.org/dtds/import_control_1_5.dtd">
98 * https://checkstyle.org/dtds/import_control_1_5.dtd</a>.
99 * It contains documentation on each of the elements and attributes.
100 * </p>
101 *
102 * <p>
103 * The check validates a XML document when it loads the document. To validate against
104 * the above DTD, include the following document type declaration in your XML document:
105 * </p>
106 * <div class="wrapper"><pre class="prettyprint"><code class="language-xml">
107 * <!DOCTYPE import-control PUBLIC
108 * "-//Checkstyle//DTD ImportControl Configuration 1.5//EN"
109 * "https://checkstyle.org/dtds/import_control_1_5.dtd">
110 * </code></pre></div>
111 *
112 * @since 4.0
113 */
114 @FileStatefulCheck
115 public class ImportControlCheck extends AbstractCheck implements ExternalResourceHolder {
116
117 /**
118 * A key is pointing to the warning message text in "messages.properties"
119 * file.
120 */
121 public static final String MSG_MISSING_FILE = "import.control.missing.file";
122
123 /**
124 * A key is pointing to the warning message text in "messages.properties"
125 * file.
126 */
127 public static final String MSG_UNKNOWN_PKG = "import.control.unknown.pkg";
128
129 /**
130 * A key is pointing to the warning message text in "messages.properties"
131 * file.
132 */
133 public static final String MSG_DISALLOWED = "import.control.disallowed";
134
135 /**
136 * A part of message for exception.
137 */
138 private static final String UNABLE_TO_LOAD = "Unable to load ";
139
140 /**
141 * Specify the location of the file containing the import control configuration.
142 * It can be a regular file, URL or resource path. It will try loading the path
143 * as a URL first, then as a file, and finally as a resource.
144 */
145 private URI file;
146
147 /**
148 * Specify the regular expression of file paths to which this check should apply.
149 * Files that don't match the pattern will not be checked. The pattern will
150 * be matched against the full absolute file path.
151 */
152 private Pattern path = Pattern.compile(".*");
153 /** Whether to process the current file. */
154 private boolean processCurrentFile;
155
156 /** The root package controller. */
157 private PkgImportControl root;
158 /** The package doing the import. */
159 private String packageName;
160 /** The file name doing the import. */
161 private String fileName;
162
163 /**
164 * The package controller for the current file. Used for performance
165 * optimisation.
166 */
167 private AbstractImportControl currentImportControl;
168
169 @Override
170 public int[] getDefaultTokens() {
171 return getRequiredTokens();
172 }
173
174 @Override
175 public int[] getAcceptableTokens() {
176 return getRequiredTokens();
177 }
178
179 @Override
180 public int[] getRequiredTokens() {
181 return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT,
182 TokenTypes.MODULE_IMPORT, };
183 }
184
185 @Override
186 public void beginTree(DetailAST rootAST) {
187 currentImportControl = null;
188 final String fullFileName = getFilePath();
189 processCurrentFile = path.matcher(fullFileName).find();
190 fileName = CommonUtil.getFileNameWithoutExtension(fullFileName);
191 }
192
193 @Override
194 public void visitToken(DetailAST ast) {
195 if (processCurrentFile) {
196 if (ast.getType() == TokenTypes.PACKAGE_DEF) {
197 if (root == null) {
198 log(ast, MSG_MISSING_FILE);
199 }
200 else {
201 packageName = getPackageText(ast);
202 currentImportControl = root.locateFinest(packageName, fileName);
203 if (currentImportControl == null) {
204 log(ast, MSG_UNKNOWN_PKG);
205 }
206 }
207 }
208 else if (currentImportControl != null) {
209 final String importText = getImportText(ast);
210 final AccessResult access = currentImportControl.checkAccess(packageName, fileName,
211 importText);
212 if (access != AccessResult.ALLOWED) {
213 log(ast, MSG_DISALLOWED, importText);
214 }
215 }
216 }
217 }
218
219 @Override
220 public Set<String> getExternalResourceLocations() {
221 return Set.of(file.toASCIIString());
222 }
223
224 /**
225 * Returns package text.
226 *
227 * @param ast PACKAGE_DEF ast node
228 * @return String that represents full package name
229 */
230 private static String getPackageText(DetailAST ast) {
231 final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
232 return FullIdent.createFullIdent(nameAST).getText();
233 }
234
235 /**
236 * Returns import text.
237 *
238 * @param ast ast node that represents import
239 * @return String that represents importing class
240 */
241 private static String getImportText(DetailAST ast) {
242 final FullIdent imp;
243 if (ast.getType() == TokenTypes.IMPORT) {
244 imp = FullIdent.createFullIdentBelow(ast);
245 }
246 else {
247 // static import or module import
248 imp = FullIdent.createFullIdent(ast
249 .getFirstChild().getNextSibling());
250 }
251 return imp.getText();
252 }
253
254 /**
255 * Setter to specify the location of the file containing the import control configuration.
256 * It can be a regular file, URL or resource path. It will try loading the path
257 * as a URL first, then as a file, and finally as a resource.
258 *
259 * @param uri the uri of the file to load.
260 * @throws IllegalArgumentException on error loading the file.
261 * @since 4.0
262 */
263 public void setFile(URI uri) {
264 // Handle empty param
265 if (uri != null) {
266 try {
267 root = ImportControlLoader.load(uri);
268 file = uri;
269 }
270 catch (CheckstyleException exc) {
271 throw new IllegalArgumentException(UNABLE_TO_LOAD + uri, exc);
272 }
273 }
274 }
275
276 /**
277 * Setter to specify the regular expression of file paths to which this check should apply.
278 * Files that don't match the pattern will not be checked. The pattern will be matched
279 * against the full absolute file path.
280 *
281 * @param pattern the file path regex this check should apply to.
282 * @since 7.5
283 */
284 public void setPath(Pattern pattern) {
285 path = pattern;
286 }
287 }