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
31
32
33
34
35
36
37 public class LineWrappingHandler {
38
39
40
41
42 public enum LineWrappingOptions {
43
44
45
46
47 IGNORE_FIRST_LINE,
48
49
50
51 NONE;
52
53
54
55
56
57
58
59
60 public static LineWrappingOptions ofBoolean(boolean val) {
61 LineWrappingOptions option = NONE;
62 if (val) {
63 option = IGNORE_FIRST_LINE;
64 }
65 return option;
66 }
67
68 }
69
70
71
72
73
74
75 private final IndentationCheck indentCheck;
76
77
78
79
80
81
82
83 public LineWrappingHandler(IndentationCheck instance) {
84 indentCheck = instance;
85 }
86
87
88
89
90
91
92
93
94 public void checkIndentation(DetailAST firstNode, DetailAST lastNode) {
95 checkIndentation(firstNode, lastNode, indentCheck.getLineWrappingIndentation());
96 }
97
98
99
100
101
102
103
104
105 private void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel) {
106 checkIndentation(firstNode, lastNode, indentLevel,
107 -1, LineWrappingOptions.IGNORE_FIRST_LINE);
108 }
109
110
111
112
113
114
115
116
117
118
119 public void checkIndentation(DetailAST firstNode, DetailAST lastNode, int indentLevel,
120 int startIndent, LineWrappingOptions ignoreFirstLine) {
121 final NavigableMap<Integer, DetailAST> firstNodesOnLines = collectFirstNodes(firstNode,
122 lastNode);
123
124 final DetailAST firstLineNode = firstNodesOnLines.get(firstNodesOnLines.firstKey());
125 if (firstLineNode.getType() == TokenTypes.AT) {
126 DetailAST node = firstLineNode.getParent();
127 while (node != null) {
128 if (node.getType() == TokenTypes.ANNOTATION) {
129 final DetailAST atNode = node.getFirstChild();
130 final NavigableMap<Integer, DetailAST> annotationLines =
131 firstNodesOnLines.subMap(
132 node.getLineNo(),
133 true,
134 getNextNodeLine(firstNodesOnLines, node),
135 true
136 );
137 checkAnnotationIndentation(atNode, annotationLines, indentLevel);
138 }
139 node = node.getNextSibling();
140 }
141 }
142
143 if (ignoreFirstLine == LineWrappingOptions.IGNORE_FIRST_LINE) {
144
145 firstNodesOnLines.remove(firstNodesOnLines.firstKey());
146 }
147
148 final int firstNodeIndent;
149 if (startIndent == -1) {
150 firstNodeIndent = getLineStart(firstLineNode);
151 }
152 else {
153 firstNodeIndent = startIndent;
154 }
155 final int currentIndent = firstNodeIndent + indentLevel;
156
157 for (DetailAST node : firstNodesOnLines.values()) {
158 final int currentType = node.getType();
159
160 if (currentType == TokenTypes.RPAREN) {
161 logWarningMessage(node, firstNodeIndent);
162 }
163 else if (currentType != TokenTypes.RCURLY && currentType != TokenTypes.ARRAY_INIT) {
164 logWarningMessage(node, currentIndent);
165 }
166 }
167 }
168
169
170
171
172
173
174
175
176
177 private static Integer getNextNodeLine(
178 NavigableMap<Integer, DetailAST> firstNodesOnLines, DetailAST node) {
179 Integer nextNodeLine = firstNodesOnLines.higherKey(node.getLastChild().getLineNo());
180 if (nextNodeLine == null) {
181 nextNodeLine = firstNodesOnLines.lastKey();
182 }
183 return nextNodeLine;
184 }
185
186
187
188
189
190
191
192
193
194 private NavigableMap<Integer, DetailAST> collectFirstNodes(DetailAST firstNode,
195 DetailAST lastNode) {
196 final NavigableMap<Integer, DetailAST> result = new TreeMap<>();
197
198 result.put(firstNode.getLineNo(), firstNode);
199 DetailAST curNode = firstNode.getFirstChild();
200
201 while (curNode != lastNode) {
202 if (curNode.getType() == TokenTypes.OBJBLOCK
203 || curNode.getType() == TokenTypes.SLIST) {
204 curNode = curNode.getLastChild();
205 }
206
207 final DetailAST firstTokenOnLine = result.get(curNode.getLineNo());
208
209 if (firstTokenOnLine == null
210 || expandedTabsColumnNo(firstTokenOnLine) >= expandedTabsColumnNo(curNode)) {
211 result.put(curNode.getLineNo(), curNode);
212 }
213 curNode = getNextCurNode(curNode);
214 }
215 return result;
216 }
217
218
219
220
221
222
223
224 private static DetailAST getNextCurNode(DetailAST curNode) {
225 DetailAST nodeToVisit = curNode.getFirstChild();
226 DetailAST currentNode = curNode;
227
228 while (nodeToVisit == null) {
229 nodeToVisit = currentNode.getNextSibling();
230 if (nodeToVisit == null) {
231 currentNode = currentNode.getParent();
232 }
233 }
234 return nodeToVisit;
235 }
236
237
238
239
240
241
242
243
244
245 private void checkAnnotationIndentation(DetailAST atNode,
246 NavigableMap<Integer, DetailAST> firstNodesOnLines, int indentLevel) {
247 final int firstNodeIndent = getLineStart(atNode);
248 final int currentIndent = firstNodeIndent + indentLevel;
249 final Collection<DetailAST> values = firstNodesOnLines.values();
250 final DetailAST lastAnnotationNode = atNode.getParent().getLastChild();
251 final int lastAnnotationLine = lastAnnotationNode.getLineNo();
252
253 final Iterator<DetailAST> itr = values.iterator();
254 while (firstNodesOnLines.size() > 1) {
255 final DetailAST node = itr.next();
256
257 final DetailAST parentNode = node.getParent();
258 final boolean isCurrentNodeCloseAnnotationAloneInLine =
259 node.getLineNo() == lastAnnotationLine
260 && isEndOfScope(lastAnnotationNode, node);
261 if (isCurrentNodeCloseAnnotationAloneInLine
262 || node.getType() == TokenTypes.AT
263 && (parentNode.getParent().getType() == TokenTypes.MODIFIERS
264 || parentNode.getParent().getType() == TokenTypes.ANNOTATIONS)
265 || node.getLineNo() == atNode.getLineNo()) {
266 logWarningMessage(node, firstNodeIndent);
267 }
268 else {
269 logWarningMessage(node, currentIndent);
270 }
271 itr.remove();
272 }
273 }
274
275
276
277
278
279
280
281
282
283
284 private static boolean isEndOfScope(final DetailAST lastAnnotationNode, final DetailAST node) {
285 DetailAST checkNode = node;
286 boolean endOfScope = true;
287 while (endOfScope && !checkNode.equals(lastAnnotationNode)) {
288 switch (checkNode.getType()) {
289 case TokenTypes.RCURLY:
290 case TokenTypes.RBRACK:
291 while (checkNode.getNextSibling() == null) {
292 checkNode = checkNode.getParent();
293 }
294 checkNode = checkNode.getNextSibling();
295 break;
296 default:
297 endOfScope = false;
298 }
299 }
300 return endOfScope;
301 }
302
303
304
305
306
307
308
309
310
311 private int expandedTabsColumnNo(DetailAST ast) {
312 final String line =
313 indentCheck.getLine(ast.getLineNo() - 1);
314
315 return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(),
316 indentCheck.getIndentationTabWidth());
317 }
318
319
320
321
322
323
324
325
326 private int getLineStart(DetailAST ast) {
327 final String line = indentCheck.getLine(ast.getLineNo() - 1);
328 return getLineStart(line);
329 }
330
331
332
333
334
335
336
337 private int getLineStart(String line) {
338 int index = 0;
339 while (Character.isWhitespace(line.charAt(index))) {
340 index++;
341 }
342 return CommonUtil.lengthExpandedTabs(line, index, indentCheck.getIndentationTabWidth());
343 }
344
345
346
347
348
349
350
351
352
353 private void logWarningMessage(DetailAST currentNode, int currentIndent) {
354 if (indentCheck.isForceStrictCondition()) {
355 if (expandedTabsColumnNo(currentNode) != currentIndent) {
356 indentCheck.indentationLog(currentNode.getLineNo(),
357 IndentationCheck.MSG_ERROR, currentNode.getText(),
358 expandedTabsColumnNo(currentNode), currentIndent);
359 }
360 }
361 else {
362 if (expandedTabsColumnNo(currentNode) < currentIndent) {
363 indentCheck.indentationLog(currentNode.getLineNo(),
364 IndentationCheck.MSG_ERROR, currentNode.getText(),
365 expandedTabsColumnNo(currentNode), currentIndent);
366 }
367 }
368 }
369
370 }