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