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.coding;
21
22 import java.util.ArrayList;
23 import java.util.BitSet;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.regex.Pattern;
28
29 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
30 import com.puppycrawl.tools.checkstyle.PropertyType;
31 import com.puppycrawl.tools.checkstyle.XdocsPropertyType;
32 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
33 import com.puppycrawl.tools.checkstyle.api.DetailAST;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
36 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 @FileStatefulCheck
84 public class MultipleStringLiteralsCheck extends AbstractCheck {
85
86
87
88
89
90 public static final String MSG_KEY = "multiple.string.literal";
91
92
93
94
95 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
96
97
98
99
100 private static final String QUOTE = "\"";
101
102
103
104
105 private final Map<String, List<DetailAST>> stringMap = new HashMap<>();
106
107
108
109
110
111
112 @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
113 private final BitSet ignoreOccurrenceContext = new BitSet();
114
115
116
117
118 private int allowedDuplicates = 1;
119
120
121
122
123 private Pattern ignoreStringsRegexp;
124
125
126
127
128 public MultipleStringLiteralsCheck() {
129 setIgnoreStringsRegexp(Pattern.compile("^\"\"$"));
130 ignoreOccurrenceContext.set(TokenTypes.ANNOTATION);
131 }
132
133
134
135
136
137
138
139 public void setAllowedDuplicates(int allowedDuplicates) {
140 this.allowedDuplicates = allowedDuplicates;
141 }
142
143
144
145
146
147
148
149
150
151
152 public final void setIgnoreStringsRegexp(Pattern ignoreStringsRegexp) {
153 if (ignoreStringsRegexp == null || ignoreStringsRegexp.pattern().isEmpty()) {
154 this.ignoreStringsRegexp = null;
155 }
156 else {
157 this.ignoreStringsRegexp = ignoreStringsRegexp;
158 }
159 }
160
161
162
163
164
165
166
167
168
169 public final void setIgnoreOccurrenceContext(String... strRep) {
170 ignoreOccurrenceContext.clear();
171 for (final String s : strRep) {
172 final int type = TokenUtil.getTokenId(s);
173 ignoreOccurrenceContext.set(type);
174 }
175 }
176
177 @Override
178 public int[] getDefaultTokens() {
179 return getRequiredTokens();
180 }
181
182 @Override
183 public int[] getAcceptableTokens() {
184 return getRequiredTokens();
185 }
186
187 @Override
188 public int[] getRequiredTokens() {
189 return new int[] {
190 TokenTypes.STRING_LITERAL,
191 TokenTypes.TEXT_BLOCK_CONTENT,
192 };
193 }
194
195 @Override
196 public void visitToken(DetailAST ast) {
197 if (!isInIgnoreOccurrenceContext(ast)) {
198 final String currentString;
199 if (ast.getType() == TokenTypes.TEXT_BLOCK_CONTENT) {
200 final String strippedString =
201 CheckUtil.stripIndentAndInitialNewLineFromTextBlock(ast.getText());
202
203 currentString = QUOTE + strippedString + QUOTE;
204 }
205 else {
206 currentString = ast.getText();
207 }
208 if (ignoreStringsRegexp == null
209 || !ignoreStringsRegexp.matcher(currentString).find()) {
210 stringMap.computeIfAbsent(currentString, key -> new ArrayList<>()).add(ast);
211 }
212 }
213 }
214
215
216
217
218
219
220
221
222
223 private boolean isInIgnoreOccurrenceContext(DetailAST ast) {
224 boolean isInIgnoreOccurrenceContext = false;
225 for (DetailAST token = ast; token != null; token = token.getParent()) {
226 final int type = token.getType();
227 if (ignoreOccurrenceContext.get(type)) {
228 isInIgnoreOccurrenceContext = true;
229 break;
230 }
231 }
232 return isInIgnoreOccurrenceContext;
233 }
234
235 @Override
236 public void beginTree(DetailAST rootAST) {
237 stringMap.clear();
238 }
239
240 @Override
241 public void finishTree(DetailAST rootAST) {
242 for (Map.Entry<String, List<DetailAST>> stringListEntry : stringMap.entrySet()) {
243 final List<DetailAST> hits = stringListEntry.getValue();
244 if (hits.size() > allowedDuplicates) {
245 final DetailAST firstFinding = hits.get(0);
246 final String recurringString =
247 ALL_NEW_LINES.matcher(
248 stringListEntry.getKey()).replaceAll("\\\\n");
249 log(firstFinding, MSG_KEY, recurringString, hits.size());
250 }
251 }
252 }
253 }
254