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