1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks;
21
22 import java.util.Optional;
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.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.FullIdent;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @FileStatefulCheck
69 public class UncommentedMainCheck
70 extends AbstractCheck {
71
72
73
74
75
76 public static final String MSG_KEY = "uncommented.main";
77
78
79 private static final Set<String> STRING_PARAMETER_NAMES = Set.of(
80 String[].class.getCanonicalName(),
81 String.class.getCanonicalName(),
82 String[].class.getSimpleName(),
83 String.class.getSimpleName()
84 );
85
86
87
88
89
90 private Pattern excludedClasses = Pattern.compile("^$");
91
92 private String currentClass;
93
94 private FullIdent packageName;
95
96 private int classDepth;
97
98
99
100
101
102
103
104
105 public void setExcludedClasses(Pattern excludedClasses) {
106 this.excludedClasses = excludedClasses;
107 }
108
109 @Override
110 public int[] getAcceptableTokens() {
111 return getRequiredTokens();
112 }
113
114 @Override
115 public int[] getDefaultTokens() {
116 return getRequiredTokens();
117 }
118
119 @Override
120 public int[] getRequiredTokens() {
121 return new int[] {
122 TokenTypes.METHOD_DEF,
123 TokenTypes.CLASS_DEF,
124 TokenTypes.PACKAGE_DEF,
125 TokenTypes.RECORD_DEF,
126 };
127 }
128
129 @Override
130 public void beginTree(DetailAST rootAST) {
131 packageName = FullIdent.createFullIdent(null);
132 classDepth = 0;
133 }
134
135 @Override
136 public void leaveToken(DetailAST ast) {
137 if (ast.getType() == TokenTypes.CLASS_DEF) {
138 classDepth--;
139 }
140 }
141
142 @Override
143 public void visitToken(DetailAST ast) {
144 switch (ast.getType()) {
145 case TokenTypes.PACKAGE_DEF:
146 visitPackageDef(ast);
147 break;
148 case TokenTypes.RECORD_DEF:
149 case TokenTypes.CLASS_DEF:
150 visitClassOrRecordDef(ast);
151 break;
152 case TokenTypes.METHOD_DEF:
153 visitMethodDef(ast);
154 break;
155 default:
156 throw new IllegalStateException(ast.toString());
157 }
158 }
159
160
161
162
163
164
165 private void visitPackageDef(DetailAST packageDef) {
166 packageName = FullIdent.createFullIdent(packageDef.getLastChild()
167 .getPreviousSibling());
168 }
169
170
171
172
173
174
175 private void visitClassOrRecordDef(DetailAST classOrRecordDef) {
176
177
178 if (classDepth == 0) {
179 final DetailAST ident = classOrRecordDef.findFirstToken(TokenTypes.IDENT);
180 currentClass = packageName.getText() + "." + ident.getText();
181 classDepth++;
182 }
183 }
184
185
186
187
188
189
190
191 private void visitMethodDef(DetailAST method) {
192 if (classDepth == 1
193
194 && checkClassName()
195 && checkName(method)
196 && checkModifiers(method)
197 && checkType(method)
198 && checkParams(method)) {
199 log(method, MSG_KEY);
200 }
201 }
202
203
204
205
206
207
208 private boolean checkClassName() {
209 return !excludedClasses.matcher(currentClass).find();
210 }
211
212
213
214
215
216
217
218 private static boolean checkName(DetailAST method) {
219 final DetailAST ident = method.findFirstToken(TokenTypes.IDENT);
220 return "main".equals(ident.getText());
221 }
222
223
224
225
226
227
228
229 private static boolean checkModifiers(DetailAST method) {
230 final DetailAST modifiers =
231 method.findFirstToken(TokenTypes.MODIFIERS);
232
233 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
234 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
235 }
236
237
238
239
240
241
242
243 private static boolean checkType(DetailAST method) {
244 final DetailAST type =
245 method.findFirstToken(TokenTypes.TYPE).getFirstChild();
246 return type.getType() == TokenTypes.LITERAL_VOID;
247 }
248
249
250
251
252
253
254
255 private static boolean checkParams(DetailAST method) {
256 boolean checkPassed = false;
257 final DetailAST params = method.findFirstToken(TokenTypes.PARAMETERS);
258
259 if (params.getChildCount() == 1) {
260 final DetailAST parameterType = params.getFirstChild().findFirstToken(TokenTypes.TYPE);
261 final boolean isArrayDeclaration =
262 parameterType.findFirstToken(TokenTypes.ARRAY_DECLARATOR) != null;
263 final Optional<DetailAST> varargs = Optional.ofNullable(
264 params.getFirstChild().findFirstToken(TokenTypes.ELLIPSIS));
265
266 if (isArrayDeclaration || varargs.isPresent()) {
267 checkPassed = isStringType(parameterType.getFirstChild());
268 }
269 }
270 return checkPassed;
271 }
272
273
274
275
276
277
278
279 private static boolean isStringType(DetailAST typeAst) {
280 final FullIdent type = FullIdent.createFullIdent(typeAst);
281 return STRING_PARAMETER_NAMES.contains(type.getText());
282 }
283
284 }