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