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.design;
21
22 import java.util.ArrayDeque;
23 import java.util.Deque;
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.TokenTypes;
30
31
32
33
34
35
36
37
38
39
40
41
42
43 @FileStatefulCheck
44 public final class MutableExceptionCheck extends AbstractCheck {
45
46
47
48
49
50 public static final String MSG_KEY = "mutable.exception";
51
52
53 private static final String DEFAULT_FORMAT = "^.*Exception$|^.*Error$|^.*Throwable$";
54
55 private final Deque<Boolean> checkingStack = new ArrayDeque<>();
56
57 private Pattern extendedClassNameFormat = Pattern.compile(DEFAULT_FORMAT);
58
59 private boolean checking;
60
61 private Pattern format = Pattern.compile(DEFAULT_FORMAT);
62
63
64
65
66
67 public void setExtendedClassNameFormat(Pattern extendedClassNameFormat) {
68 this.extendedClassNameFormat = extendedClassNameFormat;
69 }
70
71
72
73
74
75 public void setFormat(Pattern pattern) {
76 format = pattern;
77 }
78
79 @Override
80 public int[] getDefaultTokens() {
81 return getRequiredTokens();
82 }
83
84 @Override
85 public int[] getRequiredTokens() {
86 return new int[] {TokenTypes.CLASS_DEF, TokenTypes.VARIABLE_DEF};
87 }
88
89 @Override
90 public int[] getAcceptableTokens() {
91 return getRequiredTokens();
92 }
93
94 @Override
95 public void visitToken(DetailAST ast) {
96 switch (ast.getType()) {
97 case TokenTypes.CLASS_DEF:
98 visitClassDef(ast);
99 break;
100 case TokenTypes.VARIABLE_DEF:
101 visitVariableDef(ast);
102 break;
103 default:
104 throw new IllegalStateException(ast.toString());
105 }
106 }
107
108 @Override
109 public void leaveToken(DetailAST ast) {
110 if (ast.getType() == TokenTypes.CLASS_DEF) {
111 leaveClassDef();
112 }
113 }
114
115
116
117
118
119 private void visitClassDef(DetailAST ast) {
120 checkingStack.push(checking);
121 checking = isNamedAsException(ast) && isExtendedClassNamedAsException(ast);
122 }
123
124
125 private void leaveClassDef() {
126 checking = checkingStack.pop();
127 }
128
129
130
131
132
133 private void visitVariableDef(DetailAST ast) {
134 if (checking && ast.getParent().getType() == TokenTypes.OBJBLOCK) {
135 final DetailAST modifiersAST =
136 ast.findFirstToken(TokenTypes.MODIFIERS);
137
138 if (modifiersAST.findFirstToken(TokenTypes.FINAL) == null) {
139 log(ast, MSG_KEY, ast.findFirstToken(TokenTypes.IDENT).getText());
140 }
141 }
142 }
143
144
145
146
147
148
149 private boolean isNamedAsException(DetailAST ast) {
150 final String className = ast.findFirstToken(TokenTypes.IDENT).getText();
151 return format.matcher(className).find();
152 }
153
154
155
156
157
158
159 private boolean isExtendedClassNamedAsException(DetailAST ast) {
160 boolean result = false;
161 final DetailAST extendsClause = ast.findFirstToken(TokenTypes.EXTENDS_CLAUSE);
162 if (extendsClause != null) {
163 DetailAST currentNode = extendsClause;
164 while (currentNode.getLastChild() != null) {
165 currentNode = currentNode.getLastChild();
166 }
167 final String extendedClassName = currentNode.getText();
168 result = extendedClassNameFormat.matcher(extendedClassName).matches();
169 }
170 return result;
171 }
172
173 }