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 @FileStatefulCheck
51 public class MultipleStringLiteralsCheck extends AbstractCheck {
52
53
54
55
56
57 public static final String MSG_KEY = "multiple.string.literal";
58
59
60
61
62 private static final Pattern ALL_NEW_LINES = Pattern.compile("\\R");
63
64
65
66
67 private static final String QUOTE = "\"";
68
69
70
71
72 private final Map<String, List<DetailAST>> stringMap = new HashMap<>();
73
74
75
76
77
78
79 @XdocsPropertyType(PropertyType.TOKEN_ARRAY)
80 private final BitSet ignoreOccurrenceContext = new BitSet();
81
82
83
84
85 private int allowedDuplicates = 1;
86
87
88
89
90 private Pattern ignoreStringsRegexp;
91
92
93
94
95 public MultipleStringLiteralsCheck() {
96 setIgnoreStringsRegexp(Pattern.compile("^\"\"$"));
97 ignoreOccurrenceContext.set(TokenTypes.ANNOTATION);
98 }
99
100
101
102
103
104
105
106 public void setAllowedDuplicates(int allowedDuplicates) {
107 this.allowedDuplicates = allowedDuplicates;
108 }
109
110
111
112
113
114
115
116
117
118
119 public final void setIgnoreStringsRegexp(Pattern ignoreStringsRegexp) {
120 if (ignoreStringsRegexp == null || ignoreStringsRegexp.pattern().isEmpty()) {
121 this.ignoreStringsRegexp = null;
122 }
123 else {
124 this.ignoreStringsRegexp = ignoreStringsRegexp;
125 }
126 }
127
128
129
130
131
132
133
134
135
136 public final void setIgnoreOccurrenceContext(String... strRep) {
137 ignoreOccurrenceContext.clear();
138 for (final String s : strRep) {
139 final int type = TokenUtil.getTokenId(s);
140 ignoreOccurrenceContext.set(type);
141 }
142 }
143
144 @Override
145 public int[] getDefaultTokens() {
146 return getRequiredTokens();
147 }
148
149 @Override
150 public int[] getAcceptableTokens() {
151 return getRequiredTokens();
152 }
153
154 @Override
155 public int[] getRequiredTokens() {
156 return new int[] {
157 TokenTypes.STRING_LITERAL,
158 TokenTypes.TEXT_BLOCK_CONTENT,
159 };
160 }
161
162 @Override
163 public void visitToken(DetailAST ast) {
164 if (!isInIgnoreOccurrenceContext(ast)) {
165 final String currentString;
166 if (ast.getType() == TokenTypes.TEXT_BLOCK_CONTENT) {
167 final String strippedString =
168 CheckUtil.stripIndentAndInitialNewLineFromTextBlock(ast.getText());
169
170 currentString = QUOTE + strippedString + QUOTE;
171 }
172 else {
173 currentString = ast.getText();
174 }
175 if (ignoreStringsRegexp == null
176 || !ignoreStringsRegexp.matcher(currentString).find()) {
177 stringMap.computeIfAbsent(currentString, key -> new ArrayList<>()).add(ast);
178 }
179 }
180 }
181
182
183
184
185
186
187
188
189
190 private boolean isInIgnoreOccurrenceContext(DetailAST ast) {
191 boolean isInIgnoreOccurrenceContext = false;
192 for (DetailAST token = ast; token != null; token = token.getParent()) {
193 final int type = token.getType();
194 if (ignoreOccurrenceContext.get(type)) {
195 isInIgnoreOccurrenceContext = true;
196 break;
197 }
198 }
199 return isInIgnoreOccurrenceContext;
200 }
201
202 @Override
203 public void beginTree(DetailAST rootAST) {
204 stringMap.clear();
205 }
206
207 @Override
208 public void finishTree(DetailAST rootAST) {
209 for (Map.Entry<String, List<DetailAST>> stringListEntry : stringMap.entrySet()) {
210 final List<DetailAST> hits = stringListEntry.getValue();
211 if (hits.size() > allowedDuplicates) {
212 final DetailAST firstFinding = hits.get(0);
213 final String recurringString =
214 ALL_NEW_LINES.matcher(
215 stringListEntry.getKey()).replaceAll("\\\\n");
216 log(firstFinding, MSG_KEY, recurringString, hits.size());
217 }
218 }
219 }
220 }
221