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