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 continue;
169 }
170 if (currentType == TokenTypes.RPAREN) {
171 logWarningMessage(node, firstNodeIndent);
172 }
173 else if (!TokenUtil.isOfType(currentType, IGNORED_LIST)) {
174 logWarningMessage(node, currentIndent);
175 }
176 }
177 }
178
179
180
181
182
183
184
185 public void checkForAnnotationIndentation(
186 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
187 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
188 DetailAST node = firstLineNode.getParent();
189 while (node != null) {
190 if (node.getType() == TokenTypes.ANNOTATION) {
191 final DetailAST atNode = node.getFirstChild();
192 final NavigableMap<Integer, DetailAST> annotationLines =
193 firstNodesOnLines.subMap(
194 node.getLineNo(),
195 true,
196 getNextNodeLine(firstNodesOnLines, node),
197 true
198 );
199 checkAnnotationIndentation(atNode, annotationLines, indentLevel);
200 }
201 node = node.getNextSibling();
202 }
203 }
204
205
206
207
208
209
210
211 public static boolean checkForNullParameterChild(DetailAST node) {
212 return node.getFirstChild() == null && node.getType() == TokenTypes.PARAMETERS;
213 }
214
215
216
217
218
219
220
221 public static boolean checkForMethodLparenNewLine(DetailAST node) {
222 final int parentType = node.getParent().getType();
223 return parentType == TokenTypes.METHOD_DEF && node.getType() == TokenTypes.LPAREN;
224 }
225
226
227
228
229
230
231
232
233
234 private static Integer getNextNodeLine(
235 NavigableMap<Integer, DetailAST> firstNodesOnLines, DetailAST node) {
236 Integer nextNodeLine = firstNodesOnLines.higherKey(node.getLastChild().getLineNo());
237 if (nextNodeLine == null) {
238 nextNodeLine = firstNodesOnLines.lastKey();
239 }
240 return nextNodeLine;
241 }
242
243
244
245
246
247
248
249
250
251 private NavigableMap<Integer, DetailAST> collectFirstNodes(DetailAST firstNode,
252 DetailAST lastNode) {
253 final NavigableMap<Integer, DetailAST> result = new TreeMap<>();
254
255 result.put(firstNode.getLineNo(), firstNode);
256 DetailAST curNode = firstNode.getFirstChild();
257
258 while (curNode != lastNode) {
259 if (curNode.getType() == TokenTypes.OBJBLOCK
260 || curNode.getType() == TokenTypes.SLIST) {
261 curNode = curNode.getLastChild();
262 }
263
264 final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
265
266 if (firstTokenOnLine == null
267 || expandedTabsColumnNo(firstTokenOnLine) >= expandedTabsColumnNo(curNode)) {
268 result.put(curNode.getLineNo(), curNode);
269 }
270 curNode = getNextCurNode(curNode);
271 }
272 return result;
273 }
274
275
276
277
278
279
280
281 private static DetailAST getNextCurNode(DetailAST curNode) {
282 DetailAST nodeToVisit = curNode.getFirstChild();
283 DetailAST currentNode = curNode;
284
285 while (nodeToVisit == null) {
286 nodeToVisit = currentNode.getNextSibling();
287 if (nodeToVisit == null) {
288 currentNode = currentNode.getParent();
289 }
290 }
291 return nodeToVisit;
292 }
293
294
295
296
297
298
299
300
301
302 private void checkAnnotationIndentation(DetailAST atNode,
303 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
304 final int firstNodeIndent = getLineStart(atNode);
305 final int currentIndent = firstNodeIndent + indentLevel;
306 final Collection<DetailAST> values = firstNodesOnLines.values();
307 final DetailAST lastAnnotationNode = atNode.getParent().getLastChild();
308 final int lastAnnotationLine = lastAnnotationNode.getLineNo();
309
310 final Iterator<DetailAST> itr = values.iterator();
311 while (firstNodesOnLines.size() > 1) {
312 final DetailAST node = itr.next();
313
314 final DetailAST parentNode = node.getParent();
315 final boolean isArrayInitPresentInAncestors =
316 isParentContainsTokenType(node, TokenTypes.ANNOTATION_ARRAY_INIT);
317 final boolean isCurrentNodeCloseAnnotationAloneInLine =
318 node.getLineNo() == lastAnnotationLine
319 && isEndOfScope(lastAnnotationNode, node);
320 if (!isArrayInitPresentInAncestors
321 && (isCurrentNodeCloseAnnotationAloneInLine
322 || node.getType() == TokenTypes.AT
323 && (parentNode.getParent().getType() == TokenTypes.MODIFIERS
324 || parentNode.getParent().getType() == TokenTypes.ANNOTATIONS)
325 || TokenUtil.areOnSameLine(node, atNode))) {
326 logWarningMessage(node, firstNodeIndent);
327 }
328 else if (!isArrayInitPresentInAncestors) {
329 logWarningMessage(node, currentIndent);
330 }
331 itr.remove();
332 }
333 }
334
335
336
337
338
339
340
341
342
343
344 private static boolean isEndOfScope(final DetailAST lastAnnotationNode, final DetailAST node) {
345 DetailAST checkNode = node;
346 boolean endOfScope = true;
347 while (endOfScope && !checkNode.equals(lastAnnotationNode)) {
348 switch (checkNode.getType()) {
349 case TokenTypes.RCURLY:
350 case TokenTypes.RBRACK:
351 while (checkNode.getNextSibling() == null) {
352 checkNode = checkNode.getParent();
353 }
354 checkNode = checkNode.getNextSibling();
355 break;
356 default:
357 endOfScope = false;
358 }
359 }
360 return endOfScope;
361 }
362
363
364
365
366
367
368
369
370 private static boolean isParentContainsTokenType(final DetailAST node, int type) {
371 boolean returnValue = false;
372 for (DetailAST ast = node.getParent(); ast != null; ast = ast.getParent()) {
373 if (ast.getType() == type) {
374 returnValue = true;
375 break;
376 }
377 }
378 return returnValue;
379 }
380
381
382
383
384
385
386
387
388
389 private int expandedTabsColumnNo(DetailAST ast) {
390 final String line =
391 indentCheck.getLine(ast.getLineNo() - 1);
392
393 return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(),
394 indentCheck.getIndentationTabWidth());
395 }
396
397
398
399
400
401
402
403
404 private int getLineStart(DetailAST ast) {
405 final String line = indentCheck.getLine(ast.getLineNo() - 1);
406 return getLineStart(line);
407 }
408
409
410
411
412
413
414
415 private int getLineStart(String line) {
416 int index = 0;
417 while (Character.isWhitespace(line.charAt(index))) {
418 index++;
419 }
420 return CommonUtil.lengthExpandedTabs(line, index, indentCheck.getIndentationTabWidth());
421 }
422
423
424
425
426
427
428
429
430
431 private void logWarningMessage(DetailAST currentNode, int currentIndent) {
432 if (indentCheck.isForceStrictCondition()) {
433 if (expandedTabsColumnNo(currentNode) != currentIndent) {
434 indentCheck.indentationLog(currentNode,
435 IndentationCheck.MSG_ERROR, currentNode.getText(),
436 expandedTabsColumnNo(currentNode), currentIndent);
437 }
438 }
439 else {
440 if (expandedTabsColumnNo(currentNode) < currentIndent) {
441 indentCheck.indentationLog(currentNode,
442 IndentationCheck.MSG_ERROR, currentNode.getText(),
443 expandedTabsColumnNo(currentNode), currentIndent);
444 }
445 }
446 }
447
448 }