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.util.Arrays;
23 import java.util.HashSet;
24 import java.util.Set;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.FullIdent;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
32
33 /**
34 * <div>
35 * Checks that there are no module imports.
36 * </div>
37 *
38 * <p>
39 * Rationale: Module import declarations ({@code import module M;}) import, on
40 * demand, every public top level type exported by a module and by any
41 * modules it transitively reads. This is a much broader, less explicit
42 * surface than single type or on demand package imports, making it harder to
43 * tell where a type comes from, and it increases the risk of ambiguous
44 * references between same named types in different exported packages.
45 * Disallowing module imports keeps imports explicit and predictable.
46 * </p>
47 *
48 * @since 14.1.0
49 */
50 @FileStatefulCheck
51 public class AvoidModuleImportCheck extends AbstractCheck {
52
53 /**
54 * A key is pointing to the warning message text in "messages.properties"
55 * file.
56 */
57 public static final String MSG_KEY = "import.avoidModule";
58
59 /**
60 * A key is pointing to the warning message text in "messages.properties"
61 * file.
62 */
63 public static final String MSG_COUNT = "import.avoidModuleCount";
64
65 /**
66 * Specify module names for which {@code import module} declarations are allowed.
67 */
68 private final Set<String> excludes = new HashSet<>();
69
70 /**
71 * Maximum number of allowed module imports.
72 */
73 private int maxAllowedModuleImports;
74
75 /**
76 * Counter for used module imports.
77 */
78 private int currentModuleImportsCount;
79
80 /**
81 * Creates a new {@code AvoidModuleImportCheck} instance.
82 */
83 public AvoidModuleImportCheck() {
84 // no code by default
85 }
86
87 @Override
88 public int[] getDefaultTokens() {
89 return getRequiredTokens();
90 }
91
92 @Override
93 public int[] getAcceptableTokens() {
94 return new int[] {
95 TokenTypes.MODULE_IMPORT,
96 };
97 }
98
99 @Override
100 public int[] getRequiredTokens() {
101 return getAcceptableTokens();
102 }
103
104 /**
105 * Setter to specify modules allowed to import.
106 *
107 * @param excludesParam module names
108 * @since 14.1.0
109 */
110 public void setExcludes(String... excludesParam) {
111 excludes.addAll(Arrays.asList(excludesParam));
112 }
113
114 /**
115 * Setter to control number of module imports allowed.
116 *
117 * @param count the number of module imports allowed
118 * @since 14.1.0
119 */
120 public void setMaxAllowedModuleImports(int count) {
121 maxAllowedModuleImports = count;
122 }
123
124 @Override
125 public void beginTree(DetailAST rootAST) {
126 currentModuleImportsCount = 0;
127 }
128
129 @Override
130 public void visitToken(DetailAST ast) {
131 currentModuleImportsCount++;
132 final DetailAST module = NullUtil.notNull(ast.getFirstChild());
133 final DetailAST startingDot = NullUtil.notNull(module.getNextSibling());
134 final String name = FullIdent.createFullIdent(startingDot).getText();
135 if (currentModuleImportsCount > maxAllowedModuleImports
136 && !excludes.contains(name)) {
137 if (maxAllowedModuleImports > 0) {
138 log(ast, MSG_COUNT, maxAllowedModuleImports);
139 }
140 else {
141 log(ast, MSG_KEY, name);
142 }
143 }
144 }
145
146 }