1 package com.github.sevntu.checkstyle.checks.design;
2
3 import java.util.HashSet;
4 import java.util.LinkedList;
5 import java.util.List;
6 import java.util.Set;
7 import java.util.regex.Pattern;
8
9 import antlr.collections.AST;
10
11 import com.puppycrawl.tools.checkstyle.api.Check;
12 import com.puppycrawl.tools.checkstyle.api.DetailAST;
13 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 public class ForbidWildcardAsReturnTypeCheck extends Check
32 {
33
34
35
36 public static final String MSG_KEY = "forbid.wildcard.as.return.type";
37
38
39
40 private static final int WILDCARD_EXTENDS_IDENT =
41 TokenTypes.TYPE_UPPER_BOUNDS;
42
43
44
45 private static final int WILDCARD_SUPER_IDENT =
46 TokenTypes.TYPE_LOWER_BOUNDS;
47
48
49
50 private static final DetailAST[] EMPTY_DETAILAST_ARRAY = new DetailAST[0];
51
52
53
54 private boolean checkPublicMethods = true;
55
56
57
58 private boolean checkProtectedMethods = true;
59
60
61
62 private boolean checkPackageMethods = true;
63
64
65
66 private boolean checkPrivateMethods;
67
68
69
70 private boolean checkOverrideMethods;
71
72
73
74 private boolean checkDeprecatedMethods;
75
76
77
78 private boolean allowReturnWildcardWithSuper;
79
80
81
82 private boolean allowReturnWildcardWithExtends;
83
84
85
86 private Pattern returnTypeClassNamesIgnoreRegex = Pattern.compile(
87 "^(Comparator|Comparable)$");
88
89 public void setCheckPublicMethods(boolean checkPublicMethods)
90 {
91 this.checkPublicMethods = checkPublicMethods;
92 }
93
94 public void setCheckProtectedMethods(boolean checkProtectedMethods)
95 {
96 this.checkProtectedMethods = checkProtectedMethods;
97 }
98
99 public void setCheckPackageMethods(boolean checkPackageMethods)
100 {
101 this.checkPackageMethods = checkPackageMethods;
102 }
103
104 public void setCheckPrivateMethods(boolean checkPrivateMethods)
105 {
106 this.checkPrivateMethods = checkPrivateMethods;
107 }
108
109 public void setCheckOverrideMethods(boolean checkOverrideMethods)
110 {
111 this.checkOverrideMethods = checkOverrideMethods;
112 }
113
114 public void setCheckDeprecatedMethods(boolean checkDeprecatedMethods)
115 {
116 this.checkDeprecatedMethods = checkDeprecatedMethods;
117 }
118
119 public void
120 setAllowReturnWildcardWithSuper(boolean allowReturnWildcardWithSuper)
121 {
122 this.allowReturnWildcardWithSuper = allowReturnWildcardWithSuper;
123 }
124
125 public void
126 setAllowReturnWildcardWithExtends(boolean allowReturnWildcardWithExtends)
127 {
128 this.allowReturnWildcardWithExtends = allowReturnWildcardWithExtends;
129 }
130
131 public void
132 setReturnTypeClassNamesIgnoreRegex(String returnTypeClassNamesIgnoreRegex)
133 {
134 this.returnTypeClassNamesIgnoreRegex = Pattern.compile(
135 returnTypeClassNamesIgnoreRegex);
136 }
137
138 @Override
139 public int[] getDefaultTokens()
140 {
141 return new int[] { TokenTypes.METHOD_DEF };
142 }
143
144 @Override
145 public void visitToken(DetailAST methodDefAst)
146 {
147 final String methodScope = getVisibilityScope(methodDefAst);
148 if (((checkPublicMethods && "public".equals(methodScope))
149 || (checkPrivateMethods && "private".equals(methodScope))
150 || (checkProtectedMethods && "protected".equals(methodScope))
151 || (checkPackageMethods && "package".equals(methodScope)))
152 && (checkOverrideMethods
153 || !hasAnnotation(methodDefAst, "Override"))
154 && (checkDeprecatedMethods
155 || !hasAnnotation(methodDefAst, "Deprecated")))
156 {
157 final List<DetailAST> wildcardTypeArguments =
158 getWildcardArgumentsAsMethodReturnType(methodDefAst);
159 if (!wildcardTypeArguments.isEmpty()
160 && !isIgnoreCase(methodDefAst, wildcardTypeArguments))
161 {
162 log(methodDefAst.getLineNo(), MSG_KEY);
163 }
164 }
165 }
166
167
168
169
170
171
172 private static String getVisibilityScope(DetailAST methodDefAst)
173 {
174 String result = "package";
175 if (isInsideInterfaceDefinition(methodDefAst)) {
176 result = "public";
177 }
178 else {
179 final String[] visibilityScopeModifiers = {"public", "private",
180 "protected", };
181 final Set<String> methodModifiers = getModifiers(methodDefAst);
182 for (final String modifier : visibilityScopeModifiers) {
183 if (methodModifiers.contains(modifier)) {
184 result = modifier;
185 break;
186 }
187 }
188 }
189 return result;
190 }
191
192
193
194
195
196
197 private static boolean isInsideInterfaceDefinition(DetailAST methodDefAst)
198 {
199 boolean result = false;
200 final DetailAST objBlock = methodDefAst.getParent();
201 final DetailAST interfaceDef = objBlock.getParent();
202 if (interfaceDef.getType() == TokenTypes.INTERFACE_DEF) {
203 result = true;
204 }
205 return result;
206 }
207
208
209
210
211
212
213 private static Set<String> getModifiers(DetailAST methodDefAst)
214 {
215 final AST modifiersAst = methodDefAst.getFirstChild();
216 final Set<String> modifiersSet = new HashSet<String>();
217 AST modifierAst = modifiersAst.getFirstChild();
218 while (modifierAst != null) {
219 modifiersSet.add(modifierAst.getText());
220 modifierAst = modifierAst.getNextSibling();
221 }
222 return modifiersSet;
223 }
224
225
226
227
228
229
230
231 private static String getIdentifier(final DetailAST ast)
232 {
233 String result = null;
234 final DetailAST identifier = ast.findFirstToken(TokenTypes.IDENT);
235 if (identifier != null) {
236 result = identifier.getText();
237 }
238 return result;
239 }
240
241
242
243
244
245
246
247 private static boolean hasAnnotation(DetailAST methodDefAst,
248 String annotationTitle)
249 {
250 boolean result = false;
251 final DetailAST modifiersAst = methodDefAst.getFirstChild();
252 if (hasChildToken(modifiersAst, TokenTypes.ANNOTATION)) {
253 DetailAST modifierAst = modifiersAst.getFirstChild();
254 while (modifierAst != null) {
255 if (modifierAst.getType() == TokenTypes.ANNOTATION
256 && annotationTitle.equals(getIdentifier(modifierAst))) {
257 result = true;
258 break;
259 }
260 modifierAst = modifierAst.getNextSibling();
261 }
262 }
263 return result;
264 }
265
266
267
268
269
270
271
272 private static List<DetailAST>
273 getWildcardArgumentsAsMethodReturnType(DetailAST methodDefAst)
274 {
275 final List<DetailAST> result = new LinkedList<DetailAST>();
276 final DetailAST methodTypeAst =
277 methodDefAst.findFirstToken(TokenTypes.TYPE);
278 final DetailAST[] methodTypeArgumentTokens =
279 getGenericTypeArguments(methodTypeAst);
280 for (DetailAST typeArgumentAst: methodTypeArgumentTokens) {
281 if (hasChildToken(typeArgumentAst, TokenTypes.WILDCARD_TYPE)) {
282 result.add(typeArgumentAst);
283 }
284 }
285 return result;
286 }
287
288
289
290
291
292
293
294 private static DetailAST[] getGenericTypeArguments(DetailAST typeAst)
295 {
296 DetailAST[] result = EMPTY_DETAILAST_ARRAY;
297 if (hasChildToken(typeAst, TokenTypes.TYPE_ARGUMENTS)) {
298 final DetailAST typeArguments = typeAst
299 .findFirstToken(TokenTypes.TYPE_ARGUMENTS);
300 final int argumentsCount = typeArguments
301 .getChildCount(TokenTypes.TYPE_ARGUMENT);
302 result = new DetailAST[argumentsCount];
303 DetailAST firstTypeArgument = typeArguments
304 .findFirstToken(TokenTypes.TYPE_ARGUMENT);
305 int counter = 0;
306 while (firstTypeArgument != null) {
307 if (firstTypeArgument.getType() == TokenTypes.TYPE_ARGUMENT) {
308 result[counter] = firstTypeArgument;
309 counter++;
310 }
311 firstTypeArgument = firstTypeArgument.getNextSibling();
312 }
313 }
314 return result;
315 }
316
317
318
319
320
321
322
323
324
325 private static boolean hasChildToken(DetailAST ast, int tokenType)
326 {
327 return ast.findFirstToken(tokenType) != null;
328 }
329
330
331
332
333
334
335
336
337 private boolean isIgnoreCase(DetailAST methodDefAst,
338 List<DetailAST> wildcardTypeArguments)
339 {
340 boolean result = false;
341 if (matchesIgnoreClassNames(methodDefAst)) {
342 result = true;
343 }
344 else {
345 final boolean hasExtendsWildcardAsReturnType =
346 hasBoundedWildcardAsReturnType(wildcardTypeArguments,
347 WILDCARD_EXTENDS_IDENT);
348 final boolean hasSuperWildcardAsReturnType =
349 hasBoundedWildcardAsReturnType(wildcardTypeArguments,
350 WILDCARD_SUPER_IDENT);
351 final boolean hasOnlyExtendsWildcardAsReturnType =
352 hasExtendsWildcardAsReturnType
353 && !hasSuperWildcardAsReturnType;
354 final boolean hasOnlySuperWildcardAsReturnType =
355 hasSuperWildcardAsReturnType
356 && !hasExtendsWildcardAsReturnType;
357 final boolean hasBoundedWildcardAsReturnType =
358 hasExtendsWildcardAsReturnType
359 || hasSuperWildcardAsReturnType;
360 final boolean isAllowedBoundedWildcards =
361 allowReturnWildcardWithExtends
362 && allowReturnWildcardWithSuper;
363 result = (isAllowedBoundedWildcards
364 && hasBoundedWildcardAsReturnType)
365 || (allowReturnWildcardWithExtends
366 && hasOnlyExtendsWildcardAsReturnType)
367 || (allowReturnWildcardWithSuper
368 && hasOnlySuperWildcardAsReturnType);
369 }
370 return result;
371 }
372
373
374
375
376
377
378
379 private boolean matchesIgnoreClassNames(DetailAST methodDefAst)
380 {
381 final DetailAST methodTypeAst =
382 methodDefAst.findFirstToken(TokenTypes.TYPE);
383 final String typeIdentifier = getIdentifier(methodTypeAst);
384 return returnTypeClassNamesIgnoreRegex
385 .matcher(typeIdentifier).matches();
386 }
387
388
389
390
391
392
393
394 private static boolean hasBoundedWildcardAsReturnType(
395 final List<DetailAST> typeArgumentsList, int boundedWildcardType)
396 {
397 boolean result = false;
398 for (DetailAST typeArgumentAst: typeArgumentsList) {
399 if (hasChildToken(typeArgumentAst, boundedWildcardType)) {
400 result = true;
401 break;
402 }
403 }
404 return result;
405 }
406 }