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.whitespace;
21
22 import java.util.Locale;
23 import java.util.function.UnaryOperator;
24
25 import com.puppycrawl.tools.checkstyle.StatelessCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 @StatelessCheck
47 public class OperatorWrapCheck
48 extends AbstractCheck {
49
50
51
52
53
54 public static final String MSG_LINE_NEW = "line.new";
55
56
57
58
59
60 public static final String MSG_LINE_PREVIOUS = "line.previous";
61
62
63 private WrapOption option = WrapOption.NL;
64
65
66
67
68
69
70
71
72 public void setOption(String optionStr) {
73 option = WrapOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
74 }
75
76 @Override
77 public int[] getDefaultTokens() {
78 return new int[] {
79 TokenTypes.QUESTION,
80 TokenTypes.COLON,
81 TokenTypes.EQUAL,
82 TokenTypes.NOT_EQUAL,
83 TokenTypes.DIV,
84 TokenTypes.PLUS,
85 TokenTypes.MINUS,
86 TokenTypes.STAR,
87 TokenTypes.MOD,
88 TokenTypes.SR,
89 TokenTypes.BSR,
90 TokenTypes.GE,
91 TokenTypes.GT,
92 TokenTypes.SL,
93 TokenTypes.LE,
94 TokenTypes.LT,
95 TokenTypes.BXOR,
96 TokenTypes.BOR,
97 TokenTypes.LOR,
98 TokenTypes.BAND,
99 TokenTypes.LAND,
100 TokenTypes.TYPE_EXTENSION_AND,
101 TokenTypes.LITERAL_INSTANCEOF,
102 };
103 }
104
105 @Override
106 public int[] getAcceptableTokens() {
107 return new int[] {
108 TokenTypes.QUESTION,
109 TokenTypes.COLON,
110 TokenTypes.EQUAL,
111 TokenTypes.NOT_EQUAL,
112 TokenTypes.DIV,
113 TokenTypes.PLUS,
114 TokenTypes.MINUS,
115 TokenTypes.STAR,
116 TokenTypes.MOD,
117 TokenTypes.SR,
118 TokenTypes.BSR,
119 TokenTypes.GE,
120 TokenTypes.GT,
121 TokenTypes.SL,
122 TokenTypes.LE,
123 TokenTypes.LT,
124 TokenTypes.BXOR,
125 TokenTypes.BOR,
126 TokenTypes.LOR,
127 TokenTypes.BAND,
128 TokenTypes.LAND,
129 TokenTypes.LITERAL_INSTANCEOF,
130 TokenTypes.TYPE_EXTENSION_AND,
131 TokenTypes.ASSIGN,
132 TokenTypes.DIV_ASSIGN,
133 TokenTypes.PLUS_ASSIGN,
134 TokenTypes.MINUS_ASSIGN,
135 TokenTypes.STAR_ASSIGN,
136 TokenTypes.MOD_ASSIGN,
137 TokenTypes.SR_ASSIGN,
138 TokenTypes.BSR_ASSIGN,
139 TokenTypes.SL_ASSIGN,
140 TokenTypes.BXOR_ASSIGN,
141 TokenTypes.BOR_ASSIGN,
142 TokenTypes.BAND_ASSIGN,
143 TokenTypes.METHOD_REF,
144 };
145 }
146
147 @Override
148 public int[] getRequiredTokens() {
149 return CommonUtil.EMPTY_INT_ARRAY;
150 }
151
152 @Override
153 public void visitToken(DetailAST ast) {
154 if (isTargetNode(ast)) {
155 if (option == WrapOption.NL && isNewLineModeViolation(ast)) {
156 log(ast, MSG_LINE_NEW, ast.getText());
157 }
158 else if (option == WrapOption.EOL && isEndOfLineModeViolation(ast)) {
159 log(ast, MSG_LINE_PREVIOUS, ast.getText());
160 }
161 }
162 }
163
164
165
166
167
168
169
170 private static boolean isTargetNode(DetailAST node) {
171 final boolean result;
172 if (node.getType() == TokenTypes.COLON) {
173 result = !isColonFromLabel(node);
174 }
175 else if (node.getType() == TokenTypes.STAR) {
176
177 result = node.hasChildren();
178 }
179 else {
180 result = true;
181 }
182 return result;
183 }
184
185
186
187
188
189
190
191 private static boolean isNewLineModeViolation(DetailAST ast) {
192 return TokenUtil.areOnSameLine(ast, getLeftNode(ast))
193 && !TokenUtil.areOnSameLine(ast, getRightNode(ast));
194 }
195
196
197
198
199
200
201
202 private static boolean isEndOfLineModeViolation(DetailAST ast) {
203 return !TokenUtil.areOnSameLine(ast, getLeftNode(ast));
204 }
205
206
207
208
209
210
211
212 private static boolean isColonFromLabel(DetailAST node) {
213 return TokenUtil.isOfType(node.getParent(), TokenTypes.LABELED_STAT,
214 TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT);
215 }
216
217
218
219
220
221
222
223 private static boolean isAssignToVariable(DetailAST node) {
224 return TokenUtil.isOfType(node.getParent(), TokenTypes.VARIABLE_DEF, TokenTypes.RESOURCE);
225 }
226
227
228
229
230
231
232
233
234
235 private static DetailAST getLeftNode(DetailAST node) {
236 DetailAST result;
237 if (node.getFirstChild() == null || isAssignToVariable(node)) {
238 result = node.getPreviousSibling();
239 }
240 else if (isInPatternDefinition(node)) {
241 result = node.getFirstChild();
242 }
243 else {
244 result = adjustParens(node.getFirstChild(), DetailAST::getNextSibling);
245 }
246 while (result.getLastChild() != null) {
247 result = result.getLastChild();
248 }
249 return result;
250 }
251
252
253
254
255
256
257
258
259 private static boolean isInPatternDefinition(DetailAST node) {
260 DetailAST parent = node;
261 final int[] tokensToStopOn = {
262
263 TokenTypes.PATTERN_DEF,
264
265 TokenTypes.EXPR,
266 TokenTypes.RESOURCE,
267 TokenTypes.COMPILATION_UNIT,
268 };
269
270 do {
271 parent = parent.getParent();
272 } while (!TokenUtil.isOfType(parent, tokensToStopOn));
273 return parent.getType() == TokenTypes.PATTERN_DEF;
274 }
275
276
277
278
279
280
281
282
283
284 private static DetailAST getRightNode(DetailAST node) {
285 DetailAST result;
286 if (node.getLastChild() == null) {
287 result = node.getNextSibling();
288 }
289 else {
290 final DetailAST rightNode;
291 if (node.getType() == TokenTypes.QUESTION) {
292 rightNode = node.findFirstToken(TokenTypes.COLON).getPreviousSibling();
293 }
294 else {
295 rightNode = node.getLastChild();
296 }
297 result = adjustParens(rightNode, DetailAST::getPreviousSibling);
298 }
299
300 if (!TokenUtil.isOfType(result, TokenTypes.ARRAY_INIT, TokenTypes.ANNOTATION_ARRAY_INIT)) {
301 while (result.getFirstChild() != null) {
302 result = result.getFirstChild();
303 }
304 }
305 return result;
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 private static DetailAST adjustParens(DetailAST node, UnaryOperator<DetailAST> step) {
323 DetailAST result = node;
324 int accumulator = 0;
325 while (true) {
326 if (result.getType() == TokenTypes.LPAREN) {
327 accumulator--;
328 }
329 else if (result.getType() == TokenTypes.RPAREN) {
330 accumulator++;
331 }
332 if (accumulator == 0) {
333 break;
334 }
335 result = step.apply(result);
336 }
337 return result;
338 }
339
340 }