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 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 exc) {
91              System.err.println(exc.getMessage());
92              exc.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 exc) {
112             throw new CheckstyleException("Failed to write javadoc properties of '"
113                     + options.inputFile + "' to '" + options.outputFile + "'", exc);
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 
235         for (DetailNode node = tree.getFirstChild(); node != null;
236                 node = node.getNextSibling()) {
237             if (node.getType() == JavadocCommentsTokenTypes.TEXT) {
238                 final Matcher matcher = END_OF_SENTENCE_PATTERN.matcher(node.getText());
239                 if (matcher.find()) {
240                     
241                     firstSentence = builder.append(matcher.group(1)).toString();
242                     break;
243                 }
244                 
245                 
246                 builder.append(node.getText());
247             }
248             else if (node.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG) {
249                 formatInlineCodeTag(builder, node);
250             }
251             else {
252                 formatHtmlElement(builder, node);
253             }
254         }
255         return firstSentence;
256     }
257 
258     
259 
260 
261 
262 
263 
264 
265     private static void formatInlineCodeTag(StringBuilder builder, DetailNode inlineTag)
266             throws CheckstyleException {
267         final int tagType = inlineTag.getFirstChild().getType();
268 
269         if (tagType != JavadocCommentsTokenTypes.LITERAL_INLINE_TAG
270                 && tagType != JavadocCommentsTokenTypes.CODE_INLINE_TAG) {
271             throw new CheckstyleException("Unsupported inline tag "
272                 + JavadocUtil.getTokenName(tagType));
273         }
274 
275         final boolean wrapWithCodeTag = tagType == JavadocCommentsTokenTypes.CODE_INLINE_TAG;
276 
277         for (DetailNode node = inlineTag.getFirstChild().getFirstChild(); node != null;
278                 node = node.getNextSibling()) {
279             switch (node.getType()) {
280                 
281                 case JavadocCommentsTokenTypes.TEXT:
282                     if (wrapWithCodeTag) {
283                         builder.append("<code>").append(node.getText().trim()).append("</code>");
284                     }
285                     else {
286                         builder.append(node.getText().trim());
287                     }
288                     break;
289                 
290                 case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_START:
291                 case JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG_END:
292                 case JavadocCommentsTokenTypes.TAG_NAME:
293                     break;
294                 default:
295                     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 }