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;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.PrintWriter;
25 import java.nio.charset.StandardCharsets;
26 import java.util.function.Consumer;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
29
30 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
31 import com.puppycrawl.tools.checkstyle.api.DetailAST;
32 import com.puppycrawl.tools.checkstyle.api.DetailNode;
33 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
36 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
37 import picocli.CommandLine;
38 import picocli.CommandLine.Command;
39 import picocli.CommandLine.Option;
40 import picocli.CommandLine.ParameterException;
41 import picocli.CommandLine.Parameters;
42 import picocli.CommandLine.ParseResult;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 public final class JavadocPropertiesGenerator {
58
59
60
61
62
63
64 private static final Pattern END_OF_SENTENCE_PATTERN = Pattern.compile(
65 "(([^.?!]|[.?!](?!\\s|$))*+[.?!])(\\s|$)");
66
67
68
69
70 private JavadocPropertiesGenerator() {
71 }
72
73
74
75
76
77
78
79 public static void main(String... args) throws CheckstyleException {
80 final CliOptions cliOptions = new CliOptions();
81 final CommandLine cmd = new CommandLine(cliOptions);
82 try {
83 final ParseResult parseResult = cmd.parseArgs(args);
84 if (parseResult.isUsageHelpRequested()) {
85 cmd.usage(System.out);
86 }
87 else {
88 writePropertiesFile(cliOptions);
89 }
90 }
91 catch (ParameterException exc) {
92 System.err.println(exc.getMessage());
93 exc.getCommandLine().usage(System.err);
94 }
95 }
96
97
98
99
100
101
102
103 private static void writePropertiesFile(CliOptions options) throws CheckstyleException {
104 try (PrintWriter writer = new PrintWriter(options.outputFile, StandardCharsets.UTF_8)) {
105 final DetailAST top = JavaParser.parseFile(options.inputFile,
106 JavaParser.Options.WITH_COMMENTS).getFirstChild();
107 final DetailAST objBlock = getClassBody(top);
108 if (objBlock != null) {
109 iteratePublicStaticIntFields(objBlock, writer::println);
110 }
111 }
112 catch (IOException exc) {
113 throw new CheckstyleException("Failed to write javadoc properties of '"
114 + options.inputFile + "' to '" + options.outputFile + "'", exc);
115 }
116 }
117
118
119
120
121
122
123
124
125
126 private static void iteratePublicStaticIntFields(DetailAST objBlock, Consumer<String> consumer)
127 throws CheckstyleException {
128 for (DetailAST member = objBlock.getFirstChild(); member != null;
129 member = member.getNextSibling()) {
130 if (isPublicStaticFinalIntField(member)) {
131 final DetailAST modifiers = member.findFirstToken(TokenTypes.MODIFIERS);
132 final String firstJavadocSentence = getFirstJavadocSentence(modifiers);
133 if (firstJavadocSentence != null) {
134 consumer.accept(getName(member) + "=" + firstJavadocSentence.trim());
135 }
136 }
137 }
138 }
139
140
141
142
143
144
145
146 private static DetailAST getClassBody(DetailAST top) {
147 DetailAST ast = top;
148 while (ast != null && ast.getType() != TokenTypes.CLASS_DEF) {
149 ast = ast.getNextSibling();
150 }
151 DetailAST objBlock = null;
152 if (ast != null) {
153 objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
154 }
155 return objBlock;
156 }
157
158
159
160
161
162
163
164 private static boolean isPublicStaticFinalIntField(DetailAST ast) {
165 boolean result = ast.getType() == TokenTypes.VARIABLE_DEF;
166 if (result) {
167 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
168 final DetailAST arrayDeclarator = type.getFirstChild().getNextSibling();
169 result = arrayDeclarator == null
170 && type.getFirstChild().getType() == TokenTypes.LITERAL_INT;
171 if (result) {
172 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
173 result = modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
174 && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) != null
175 && modifiers.findFirstToken(TokenTypes.FINAL) != null;
176 }
177 }
178 return result;
179 }
180
181
182
183
184
185
186
187 private static String getName(DetailAST ast) {
188 return NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT)).getText();
189 }
190
191
192
193
194
195
196
197
198
199
200
201
202
203 private static String getFirstJavadocSentence(DetailAST ast) throws CheckstyleException {
204 String firstSentence = null;
205 for (DetailAST child = ast.getFirstChild(); child != null && firstSentence == null;
206 child = child.getNextSibling()) {
207
208 if (child.getType() == TokenTypes.ANNOTATION) {
209 firstSentence = getFirstJavadocSentence(child);
210 }
211
212 else if (child.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
213 && JavadocUtil.isJavadocComment(child)) {
214 final DetailNode tree = DetailNodeTreeStringPrinter.parseJavadocAsDetailNode(child);
215 firstSentence = getFirstJavadocSentence(tree);
216 }
217 }
218 return firstSentence;
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232 private static String getFirstJavadocSentence(DetailNode tree) throws CheckstyleException {
233 String firstSentence = null;
234 final StringBuilder builder = new StringBuilder(128);
235
236 for (DetailNode node = tree.getFirstChild(); node != null;
237 node = node.getNextSibling()) {
238 if (node.getType() == JavadocCommentsTokenTypes.TEXT) {
239 final Matcher matcher = END_OF_SENTENCE_PATTERN.matcher(node.getText());
240 if (matcher.find()) {
241
242 firstSentence = builder.append(matcher.group(1)).toString();
243 break;
244 }
245
246
247 builder.append(node.getText());
248 }
249 else if (node.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG) {
250 formatInlineCodeTag(builder, node);
251 }
252 else {
253 formatHtmlElement(builder, node);
254 }
255 }
256 return firstSentence;
257 }
258
259
260
261
262
263
264
265
266 private static void formatInlineCodeTag(StringBuilder builder, DetailNode inlineTag)
267 throws CheckstyleException {
268 final int tagType = inlineTag.getFirstChild().getType();
269
270 if (tagType != JavadocCommentsTokenTypes.LITERAL_INLINE_TAG
271 && tagType != JavadocCommentsTokenTypes.CODE_INLINE_TAG) {
272 throw new CheckstyleException("Unsupported inline tag "
273 + JavadocUtil.getTokenName(tagType));
274 }
275
276 final boolean wrapWithCodeTag = tagType == JavadocCommentsTokenTypes.CODE_INLINE_TAG;
277
278 for (DetailNode node = inlineTag.getFirstChild().getFirstChild(); node != null;
279 node = node.getNextSibling()) {
280 switch (node.getType()) {
281
282 case JavadocCommentsTokenTypes.TEXT -> {
283 if (wrapWithCodeTag) {
284 builder.append("<code>").append(node.getText().trim()).append("</code>");
285 }
286 else {
287 builder.append(node.getText().trim());
288 }
289 }
290 case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_START,
291 JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_END,
292 JavadocCommentsTokenTypes.TAG_NAME -> {
293
294 }
295 default -> throw new CheckstyleException("Unsupported child in the inline tag "
296 + JavadocUtil.getTokenName(node.getType()));
297 }
298 }
299 }
300
301
302
303
304
305
306
307 private static void formatHtmlElement(StringBuilder builder, DetailNode node) {
308 switch (node.getType()) {
309 case JavadocCommentsTokenTypes.TAG_OPEN,
310 JavadocCommentsTokenTypes.TAG_CLOSE,
311 JavadocCommentsTokenTypes.TAG_SLASH,
312 JavadocCommentsTokenTypes.TAG_SLASH_CLOSE,
313 JavadocCommentsTokenTypes.TAG_NAME,
314 JavadocCommentsTokenTypes.TEXT ->
315 builder.append(node.getText());
316
317 default -> {
318 for (DetailNode child = node.getFirstChild(); child != null;
319 child = child.getNextSibling()) {
320 formatHtmlElement(builder, child);
321 }
322 }
323 }
324
325 }
326
327
328
329
330 @Command(name = "java com.puppycrawl.tools.checkstyle.JavadocPropertiesGenerator",
331 mixinStandardHelpOptions = true)
332 private static final class CliOptions {
333
334
335
336
337 @Option(names = "--destfile", required = true, description = "The output file.")
338 private File outputFile;
339
340
341
342
343 @Parameters(index = "0", description = "The input file.")
344 private File inputFile;
345 }
346 }