View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.doclets;
21  
22  import java.io.FileNotFoundException;
23  import java.io.FileOutputStream;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.io.Writer;
27  import java.nio.charset.StandardCharsets;
28  import java.util.Arrays;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.stream.Collectors;
32  
33  import com.sun.javadoc.ClassDoc;
34  import com.sun.javadoc.DocErrorReporter;
35  import com.sun.javadoc.FieldDoc;
36  import com.sun.javadoc.RootDoc;
37  import com.sun.javadoc.Tag;
38  
39  /**
40   * Doclet which is used to write property file with short descriptions
41   * (first sentences) of TokenTypes' constants.
42   * Request: 724871
43   * For ide plugins (like the eclipse plugin) it would be useful to have
44   * programmatic access to the first sentence of the TokenType constants,
45   * so they can use them in their configuration gui.
46   * @author o_sukhodolsky
47   */
48  public final class TokenTypesDoclet {
49      /** Command line option to specify file to write output of the doclet. */
50      private static final String DEST_FILE_OPT = "-destfile";
51  
52      /** Stop instances being created. */
53      private TokenTypesDoclet() {
54      }
55  
56      /**
57       * The doclet's starter method.
58       * @param root {@code RootDoc} given to the doclet
59       * @return true if the given {@code RootDoc} is processed.
60       * @exception FileNotFoundException will be thrown if the doclet
61       *            will be unable to write to the specified file.
62       */
63      public static boolean start(RootDoc root)
64              throws FileNotFoundException {
65          final String fileName = getDestFileName(root.options());
66          final FileOutputStream fos = new FileOutputStream(fileName);
67          final Writer osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
68          final PrintWriter writer = new PrintWriter(osw, false);
69  
70          try {
71              final ClassDoc[] classes = root.classes();
72              final FieldDoc[] fields = classes[0].fields();
73              for (final FieldDoc field : fields) {
74                  if (field.isStatic() && field.isPublic() && field.isFinal()
75                          && "int".equals(field.type().qualifiedTypeName())) {
76  
77                      final String firstSentence;
78  
79                      if (field.firstSentenceTags().length == 1) {
80                          firstSentence = field.firstSentenceTags()[0].text();
81                      }
82                      else if (Arrays.stream(field.firstSentenceTags())
83                              .filter(tag -> !"Text".equals(tag.name())).count() == 1) {
84                          // We have to filter "Text" tags because of jdk parsing bug
85                          // till JDK-8186270
86                          firstSentence = field.firstSentenceTags()[0].text()
87                                  + "<code>"
88                                  + field.firstSentenceTags()[1].text()
89                                  + "</code>"
90                                  + field.firstSentenceTags()[2].text();
91                      }
92                      else {
93                          final List<Tag> tags = Arrays.asList(field.firstSentenceTags());
94                          final String joinedTags = tags
95                              .stream()
96                              .map(Tag::toString)
97                              .collect(Collectors.joining("\", \"", "[\"", "\"]"));
98                          final String message = String.format(Locale.ROOT,
99                                  "Should be only one tag for %s. Tags %s.",
100                                 field.toString(), joinedTags);
101                         throw new IllegalArgumentException(message);
102                     }
103                     writer.println(field.name() + "=" + firstSentence);
104                 }
105             }
106         }
107         finally {
108             writer.close();
109         }
110 
111         return true;
112     }
113 
114     /**
115      * Returns option length (how many parts are in option).
116      * @param option option name to process
117      * @return option length (how many parts are in option).
118      */
119     public static int optionLength(String option) {
120         int length = 0;
121         if (DEST_FILE_OPT.equals(option)) {
122             length = 2;
123         }
124         return length;
125     }
126 
127     /**
128      * Checks that only valid options was specified.
129      * @param options all parsed options
130      * @param reporter the reporter to report errors.
131      * @return true if only valid options was specified
132      */
133     public static boolean checkOptions(String[][] options, DocErrorReporter reporter) {
134         boolean foundDestFileOption = false;
135         boolean onlyOneDestFileOption = true;
136         for (final String[] opt : options) {
137             if (DEST_FILE_OPT.equals(opt[0])) {
138                 if (foundDestFileOption) {
139                     reporter.printError("Only one -destfile option allowed.");
140                     onlyOneDestFileOption = false;
141                     break;
142                 }
143                 foundDestFileOption = true;
144             }
145         }
146         if (!foundDestFileOption) {
147             reporter.printError("Usage: javadoc -destfile file -doclet TokenTypesDoclet ...");
148         }
149         return onlyOneDestFileOption && foundDestFileOption;
150     }
151 
152     /**
153      * Reads destination file name.
154      * @param options all specified options.
155      * @return destination file name
156      */
157     private static String getDestFileName(String[]... options) {
158         String fileName = null;
159         for (final String[] opt : options) {
160             if (DEST_FILE_OPT.equals(opt[0])) {
161                 fileName = opt[1];
162             }
163         }
164         return fileName;
165     }
166 }