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.javadoc;
21
22 import java.util.Optional;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import com.puppycrawl.tools.checkstyle.GlobalStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.DetailNode;
29 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 @GlobalStatefulCheck
53 public class JavadocLeadingAsteriskAlignCheck extends AbstractJavadocCheck {
54
55
56
57
58
59 public static final String MSG_KEY = "javadoc.asterisk.indentation";
60
61
62 private int javadocStartLineNumber;
63
64
65 private int expectedColumnNumberTabsExpanded;
66
67
68
69
70
71 private int expectedColumnNumberWithoutExpandedTabs;
72
73
74 private String[] fileLines;
75
76 @Override
77 public int[] getDefaultJavadocTokens() {
78 return new int[] {
79 JavadocCommentsTokenTypes.LEADING_ASTERISK,
80 };
81 }
82
83 @Override
84 public int[] getRequiredJavadocTokens() {
85 return getAcceptableJavadocTokens();
86 }
87
88 @Override
89 public void beginJavadocTree(DetailNode rootAst) {
90
91 fileLines = getLines();
92 final String startLine = fileLines[rootAst.getLineNumber() - 1];
93 javadocStartLineNumber = rootAst.getLineNumber();
94 expectedColumnNumberTabsExpanded = CommonUtil.lengthExpandedTabs(
95 startLine, rootAst.getColumnNumber() - 1, getTabWidth());
96 }
97
98 @Override
99 public void visitJavadocToken(DetailNode ast) {
100
101 final boolean isJavadocStartingLine = ast.getLineNumber() == javadocStartLineNumber;
102
103 if (!isJavadocStartingLine) {
104 final Optional<Integer> leadingAsteriskColumnNumber =
105 getAsteriskColumnNumber(ast.getText());
106
107 leadingAsteriskColumnNumber
108 .map(columnNumber -> expandedTabs(ast.getText(), columnNumber))
109 .filter(columnNumber -> {
110 return !hasValidAlignment(expectedColumnNumberTabsExpanded, columnNumber);
111 })
112 .ifPresent(columnNumber -> {
113 logViolation(ast.getLineNumber(),
114 columnNumber,
115 expectedColumnNumberTabsExpanded);
116 });
117 }
118 }
119
120 @Override
121 public void finishJavadocTree(DetailNode rootAst) {
122
123 final DetailAST javadocEndToken = getBlockCommentAst().getLastChild();
124 final String lastLine = fileLines[javadocEndToken.getLineNo() - 1];
125 final Optional<Integer> endingBlockColumnNumber = getAsteriskColumnNumber(lastLine);
126
127 endingBlockColumnNumber
128 .map(columnNumber -> expandedTabs(lastLine, columnNumber))
129 .filter(columnNumber -> {
130 return !hasValidAlignment(expectedColumnNumberTabsExpanded, columnNumber);
131 })
132 .ifPresent(columnNumber -> {
133 logViolation(javadocEndToken.getLineNo(),
134 columnNumber,
135 expectedColumnNumberTabsExpanded);
136 });
137 }
138
139
140
141
142
143
144
145
146
147
148 private int expandedTabs(String line, int columnNumber) {
149 expectedColumnNumberWithoutExpandedTabs = columnNumber - 1;
150 return CommonUtil.lengthExpandedTabs(
151 line, columnNumber, getTabWidth());
152 }
153
154
155
156
157
158
159
160
161 private static Optional<Integer> getAsteriskColumnNumber(String line) {
162 final Pattern pattern = Pattern.compile("^(\\s*)\\*");
163 final Matcher matcher = pattern.matcher(line);
164
165
166
167
168 return Optional.of(matcher)
169 .filter(Matcher::find)
170 .map(matcherInstance -> matcherInstance.group(1))
171 .map(groupLength -> groupLength.length() + 1);
172 }
173
174
175
176
177
178
179
180
181 private void logViolation(int lineNumber,
182 int asteriskColNumber,
183 int expectedColNumber) {
184
185 log(lineNumber,
186 expectedColumnNumberWithoutExpandedTabs,
187 MSG_KEY,
188 asteriskColNumber,
189 expectedColNumber);
190 }
191
192
193
194
195
196
197
198
199
200 private static boolean hasValidAlignment(int expectedColNumber,
201 int asteriskColNumber) {
202 return expectedColNumber - asteriskColNumber == 0;
203 }
204 }