001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.doclets;
021
022import java.io.FileNotFoundException;
023import java.io.FileOutputStream;
024import java.io.OutputStreamWriter;
025import java.io.PrintWriter;
026import java.io.Writer;
027import java.nio.charset.StandardCharsets;
028import java.util.Arrays;
029import java.util.List;
030import java.util.Locale;
031import java.util.stream.Collectors;
032
033import com.sun.javadoc.ClassDoc;
034import com.sun.javadoc.DocErrorReporter;
035import com.sun.javadoc.FieldDoc;
036import com.sun.javadoc.RootDoc;
037import com.sun.javadoc.Tag;
038
039/**
040 * Doclet which is used to write property file with short descriptions
041 * (first sentences) of TokenTypes' constants.
042 * Request: 724871
043 * For ide plugins (like the eclipse plugin) it would be useful to have
044 * programmatic access to the first sentence of the TokenType constants,
045 * so they can use them in their configuration gui.
046 * @author o_sukhodolsky
047 */
048public final class TokenTypesDoclet {
049    /** Command line option to specify file to write output of the doclet. */
050    private static final String DEST_FILE_OPT = "-destfile";
051
052    /** Stop instances being created. */
053    private TokenTypesDoclet() {
054    }
055
056    /**
057     * The doclet's starter method.
058     * @param root {@code RootDoc} given to the doclet
059     * @return true if the given {@code RootDoc} is processed.
060     * @exception FileNotFoundException will be thrown if the doclet
061     *            will be unable to write to the specified file.
062     */
063    public static boolean start(RootDoc root)
064            throws FileNotFoundException {
065        final String fileName = getDestFileName(root.options());
066        final FileOutputStream fos = new FileOutputStream(fileName);
067        final Writer osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
068        final PrintWriter writer = new PrintWriter(osw, false);
069
070        try {
071            final ClassDoc[] classes = root.classes();
072            final FieldDoc[] fields = classes[0].fields();
073            for (final FieldDoc field : fields) {
074                if (field.isStatic() && field.isPublic() && field.isFinal()
075                        && "int".equals(field.type().qualifiedTypeName())) {
076
077                    final String firstSentence;
078
079                    if (field.firstSentenceTags().length == 1) {
080                        firstSentence = field.firstSentenceTags()[0].text();
081                    }
082                    else if (Arrays.stream(field.firstSentenceTags())
083                            .filter(tag -> !"Text".equals(tag.name())).count() == 1) {
084                        // We have to filter "Text" tags because of jdk parsing bug
085                        // till JDK-8186270
086                        firstSentence = field.firstSentenceTags()[0].text()
087                                + "<code>"
088                                + field.firstSentenceTags()[1].text()
089                                + "</code>"
090                                + field.firstSentenceTags()[2].text();
091                    }
092                    else {
093                        final List<Tag> tags = Arrays.asList(field.firstSentenceTags());
094                        final String joinedTags = tags
095                            .stream()
096                            .map(Tag::toString)
097                            .collect(Collectors.joining("\", \"", "[\"", "\"]"));
098                        final String message = String.format(Locale.ROOT,
099                                "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}