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.indentation;
21
22 import java.util.Collection;
23 import java.util.Iterator;
24 import java.util.NavigableMap;
25 import java.util.TreeMap;
26
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 public class LineWrappingHandler {
39
40
41
42
43 public enum LineWrappingOptions {
44
45
46
47
48 IGNORE_FIRST_LINE,
49
50
51
52 NONE;
53
54
55
56
57
58
59
60
61
62
63 public static LineWrappingOptions ofBoolean(boolean val) {
64 LineWrappingOptions option = NONE;
65 if (val) {
66 option = IGNORE_FIRST_LINE;
67 }
68 return option;
69 }
70
71 }
72
73
74
75
76
77
78
79
80
81
82
83 private static final int[] IGNORED_LIST = {
84 TokenTypes.LCURLY,
85 TokenTypes.RCURLY,
86 TokenTypes.LITERAL_NEW,
87 TokenTypes.ARRAY_INIT,
88 TokenTypes.LITERAL_DEFAULT,
89 TokenTypes.LITERAL_CASE,
90 };
91
92
93
94
95
96
97 private final IndentationCheck indentCheck;
98
99
100
101
102
103
104
105 public LineWrappingHandler(IndentationCheck instance) {
106 indentCheck = instance;
107 }
108
109
110
111
112
113
114
115
116 public void checkIndentation(DetailAST firstNode, DetailAST lastNode) {
117 checkIndentation(firstNode, lastNode, indentCheck.getLineWrappingIndentation());
118 }
119
120
121
122
123
124
125
126
127 private void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel) {
128 checkIndentation(firstNode, lastNode, indentLevel,
129 -1, LineWrappingOptions.IGNORE_FIRST_LINE);
130 }
131
132
133
134
135
136
137
138
139
140
141 public void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel,
142 int startIndent, LineWrappingOptions ignoreFirstLine) {
143 final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes(firstNode,
144 lastNode);
145
146 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
147 if (firstLineNode.getType() == TokenTypes.AT) {
148 checkForAnnotationIndentation(firstNodesOnLines, indentLevel);
149 }
150
151 if (ignoreFirstLine == LineWrappingOptions.IGNORE_FIRST_LINE) {
152
153 firstNodesOnLines.remove(firstNodesOnLines.firstKey());
154 }
155
156 final int firstNodeIndent;
157 if (startIndent == -1) {
158 firstNodeIndent = getLineStart(firstLineNode);
159 }
160 else {
161 firstNodeIndent = startIndent;
162 }
163 final int currentIndent = firstNodeIndent + indentLevel;
164
165 for (DetailAST node : firstNodesOnLines.values()) {
166 final int currentType = node.getType();
167 if (checkForNullParameterChild(node) || checkForMethodLparenNewLine(node)
168 || !shouldProcessTextBlockLiteral(node)) {
169 continue;
170 }
171 if (currentType == TokenTypes.RPAREN) {
172 logWarningMessage(node, firstNodeIndent);
173 }
174 else if (!TokenUtil.isOfType(currentType, IGNORED_LIST)) {
175 logWarningMessage(node, currentIndent);
176 }
177 }
178 }
179
180
181
182
183
184
185
186 public void checkForAnnotationIndentation(
187 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
188 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
189 DetailAST node = firstLineNode.getParent();
190 while (node != null) {
191 if (node.getType() == TokenTypes.ANNOTATION) {
192 final DetailAST atNode = node.getFirstChild();
193 final NavigableMap<Integer, DetailAST> annotationLines =
194 firstNodesOnLines.subMap(
195 node.getLineNo(),
196 true,
197 getNextNodeLine(firstNodesOnLines, node),
198 true
199 );
200 checkAnnotationIndentation(atNode, annotationLines, indentLevel);
201 }
202 node = node.getNextSibling();
203 }
204 }
205
206
207
208
209
210
211
212 public static boolean checkForNullParameterChild(DetailAST node) {
213 return node.getFirstChild() == null && node.getType() == TokenTypes.PARAMETERS;
214 }
215
216
217
218
219
220
221
222 public static boolean checkForMethodLparenNewLine(DetailAST node) {
223 final int parentType = node.getParent().getType();
224 return parentType == TokenTypes.METHOD_DEF && node.getType() == TokenTypes.LPAREN;
225 }
226
227
228
229
230
231
232
233
234
235 private static Integer getNextNodeLine(
236 NavigableMap<Integer, DetailAST> firstNodesOnLines, DetailAST node) {
237 Integer nextNodeLine = firstNodesOnLines.higherKey(node.getLastChild().getLineNo());
238 if (nextNodeLine == null) {
239 nextNodeLine = firstNodesOnLines.lastKey();
240 }
241 return nextNodeLine;
242 }
243
244
245
246
247
248
249
250
251
252 private NavigableMap<Integer, DetailAST> collectFirstNodes(DetailAST firstNode,
253 DetailAST lastNode) {
254 final NavigableMap<Integer, DetailAST> result = new TreeMap<>();
255
256 result.put(firstNode.getLineNo(), firstNode);
257 DetailAST curNode = firstNode.getFirstChild();
258
259 while (curNode != lastNode) {
260 if (curNode.getType() == TokenTypes.OBJBLOCK
261 || curNode.getType() == TokenTypes.SLIST) {
262 curNode = curNode.getLastChild();
263 }
264
265 final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
266
267 if (firstTokenOnLine == null
268 || expandedTabsColumnNo(firstTokenOnLine) >= expandedTabsColumnNo(curNode)) {
269 result.put(curNode.getLineNo(), curNode);
270 }
271 curNode = getNextCurNode(curNode);
272 }
273 return result;
274 }
275
276
277
278
279
280
281
282
283 private boolean shouldProcessTextBlockLiteral(DetailAST node) {
284 return node.getType() != TokenTypes.TEXT_BLOCK_LITERAL_END
285 || expandedTabsColumnNo(node) == getLineStart(node);
286 }
287
288
289
290
291
292
293
294 private static DetailAST getNextCurNode(DetailAST curNode) {
295 DetailAST nodeToVisit = curNode.getFirstChild();
296 DetailAST currentNode = curNode;
297
298 while (nodeToVisit == null) {
299 nodeToVisit = currentNode.getNextSibling();
300 if (nodeToVisit == null) {
301 currentNode = currentNode.getParent();
302 }
303 }
304 return nodeToVisit;
305 }
306
307
308
309
310
311
312
313
314
315 private void checkAnnotationIndentation(DetailAST atNode,
316 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
317 final int firstNodeIndent = getLineStart(atNode);
318 final int currentIndent = firstNodeIndent + indentLevel;
319 final Collection<DetailAST> values = firstNodesOnLines.values();
320 final DetailAST lastAnnotationNode = atNode.getParent().getLastChild();
321 final int lastAnnotationLine = lastAnnotationNode.getLineNo();
322
323 final Iterator<DetailAST> itr = values.iterator();
324 while (firstNodesOnLines.size() > 1) {
325 final DetailAST node = itr.next();
326
327 final DetailAST parentNode = node.getParent();
328 final boolean isArrayInitPresentInAncestors =
329 isParentContainsTokenType(node, TokenTypes.ANNOTATION_ARRAY_INIT);
330 final boolean isCurrentNodeCloseAnnotationAloneInLine =
331 node.getLineNo() == lastAnnotationLine
332 && isEndOfScope(lastAnnotationNode, node);
333 if (!isArrayInitPresentInAncestors
334 && (isCurrentNodeCloseAnnotationAloneInLine
335 || node.getType() == TokenTypes.AT
336 && (parentNode.getParent().getType() == TokenTypes.MODIFIERS
337 || parentNode.getParent().getType() == TokenTypes.ANNOTATIONS)
338 || TokenUtil.areOnSameLine(node, atNode))) {
339 logWarningMessage(node, firstNodeIndent);
340 }
341 else if (!isArrayInitPresentInAncestors) {
342 logWarningMessage(node, currentIndent);
343 }
344 itr.remove();
345 }
346 }
347
348
349
350
351
352
353
354
355
356
357 private static boolean isEndOfScope(final DetailAST lastAnnotationNode, final DetailAST node) {
358 DetailAST checkNode = node;
359 boolean endOfScope = true;
360 while (endOfScope && !checkNode.equals(lastAnnotationNode)) {
361 switch (checkNode.getType()) {
362 case TokenTypes.RCURLY:
363 case TokenTypes.RBRACK:
364 while (checkNode.getNextSibling() == null) {
365 checkNode = checkNode.getParent();
366 }
367 checkNode = checkNode.getNextSibling();
368 break;
369 default:
370 endOfScope = false;
371 }
372 }
373 return endOfScope;
374 }
375
376
377
378
379
380
381
382
383 private static boolean isParentContainsTokenType(final DetailAST node, int type) {
384 boolean returnValue = false;
385 for (DetailAST ast = node.getParent(); ast != null; ast = ast.getParent()) {
386 if (ast.getType() == type) {
387 returnValue = true;
388 break;
389 }
390 }
391 return returnValue;
392 }
393
394
395
396
397
398
399
400
401
402 private int expandedTabsColumnNo(DetailAST ast) {
403 final String line =
404 indentCheck.getLine(ast.getLineNo() - 1);
405
406 return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(),
407 indentCheck.getIndentationTabWidth());
408 }
409
410
411
412
413
414
415
416
417 private int getLineStart(DetailAST ast) {
418 final String line = indentCheck.getLine(ast.getLineNo() - 1);
419 return getLineStart(line);
420 }
421
422
423
424
425
426
427
428 private int getLineStart(String line) {
429 int index = 0;
430 while (Character.isWhitespace(line.charAt(index))) {
431 index++;
432 }
433 return CommonUtil.lengthExpandedTabs(line, index, indentCheck.getIndentationTabWidth());
434 }
435
436
437
438
439
440
441
442
443
444 private void logWarningMessage(DetailAST currentNode, int currentIndent) {
445 if (indentCheck.isForceStrictCondition()) {
446 if (expandedTabsColumnNo(currentNode) != currentIndent) {
447 indentCheck.indentationLog(currentNode,
448 IndentationCheck.MSG_ERROR, currentNode.getText(),
449 expandedTabsColumnNo(currentNode), currentIndent);
450 }
451 }
452 else {
453 if (expandedTabsColumnNo(currentNode) < currentIndent) {
454 indentCheck.indentationLog(currentNode,
455 IndentationCheck.MSG_ERROR, currentNode.getText(),
456 expandedTabsColumnNo(currentNode), currentIndent);
457 }
458 }
459 }
460
461 }