001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.doclets;
020
021import java.io.File;
022import java.io.FileWriter;
023import java.io.IOException;
024import java.io.PrintWriter;
025import java.util.Arrays;
026import java.util.Comparator;
027import com.sun.javadoc.ClassDoc;
028import com.sun.javadoc.RootDoc;
029import com.sun.javadoc.Tag;
030
031/**
032 * Doclet which is used to extract Anakia input files from the
033 * Javadoc of Check implementations, so the Check's docs are
034 * autogenerated.
035 * Attention: this is incomplete autogenerator of Check's documentation
036 * from the Check's javadoc. It is not used now, and should be removed from
037 * master branch till completed.
038 * @author lkuehne
039 */
040public final class CheckDocsDoclet
041{
042    /** javadoc command line option for dest dir. */
043    private static final String DEST_DIR_OPT = "-d";
044
045    /** Stop instances being created. */
046    private CheckDocsDoclet()
047    {
048    }
049
050    /**
051     * Comparator that compares the {@link ClassDoc ClassDocs} of two checks
052     * by their check name.
053     */
054    private static class ClassDocByCheckNameComparator implements
055        Comparator<ClassDoc>
056    {
057        /** {@inheritDoc} */
058        public int compare(ClassDoc object1, ClassDoc object2)
059        {
060            final String checkName1 = getCheckName(object1);
061            final String checkName2 = getCheckName(object2);
062            return checkName1.compareTo(checkName2);
063        }
064    }
065
066    /**
067     * The first sentence of the check description.
068     *
069     * @param classDoc class doc of the check, e.g. EmptyStatement
070     * @return The first sentence of the check description.
071     */
072    private static String getDescription(final ClassDoc classDoc)
073    {
074        final Tag[] tags = classDoc.firstSentenceTags();
075        final StringBuffer buf = new StringBuffer();
076        if (tags.length > 0) {
077            buf.append(tags[0].text());
078        }
079        removeOpeningParagraphTag(buf);
080        return buf.toString();
081    }
082
083    /**
084     * Removes an opening p tag from a StringBuffer.
085     * @param text the text to process
086     */
087    private static void removeOpeningParagraphTag(final StringBuffer text)
088    {
089        final String openTag = "<p>";
090        final int tagLen = openTag.length();
091        if ((text.length() > tagLen)
092                && text.substring(0, tagLen).equals(openTag))
093        {
094            text.delete(0, tagLen);
095        }
096    }
097
098    /**
099     * Returns the official name of a check.
100     *
101     * @param classDoc the the check's documentation as extracted by javadoc
102     * @return the check name, e.g. "IllegalImport" for
103     * the "c.p.t.c.c.i.IllegalImportCheck" class.
104     */
105    private static String getCheckName(final ClassDoc classDoc)
106    {
107        final String strippedClassName = classDoc.typeName();
108        final String checkName;
109        if (strippedClassName.endsWith("Check")) {
110            checkName = strippedClassName.substring(
111                    0, strippedClassName.length() - "Check".length());
112        }
113        else {
114            checkName = strippedClassName;
115        }
116        return checkName;
117    }
118
119    /**
120     * Writes the opening tags of an xdoc.
121     * @param printWriter you guessed it ... the target to print to :)
122     * @param title the title to use for the document.
123     */
124    private static void writeXdocsHeader(
125            final PrintWriter printWriter,
126            final String title)
127    {
128        printWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
129        printWriter.println("<document>");
130        printWriter.println("<properties>");
131        printWriter.println("<title>" + title + "</title>");
132        printWriter.println("<author "
133                + "email=\"checkstyle-devel@lists.sourceforge.net"
134                + "\">Checkstyle Development Team</author>");
135        printWriter.println("</properties>");
136        printWriter.println("<body>");
137        printWriter.flush();
138    }
139
140    /**
141     * Writes the closing tags of an xdoc document.
142     * @param printWriter you guessed it ... the target to print to :)
143     */
144    private static void writeXdocsFooter(final PrintWriter printWriter)
145    {
146        printWriter.println("</body>");
147        printWriter.println("</document>");
148        printWriter.flush();
149    }
150
151    /**
152     * Doclet entry point.
153     * @param root parsed javadoc of all java files passed to the javadoc task
154     * @return true (TODO: semantics of the return value is not clear to me)
155     * @throws IOException if there are problems writing output
156     */
157    public static boolean start(RootDoc root) throws IOException
158    {
159        final ClassDoc[] classDocs = root.classes();
160
161        final File destDir = new File(getDestDir(root.options()));
162
163        final File checksIndexFile = new File(destDir, "availablechecks.xml");
164        final PrintWriter fileWriter = new PrintWriter(
165                new FileWriter(checksIndexFile));
166        writeXdocsHeader(fileWriter, "Available Checks");
167
168        fileWriter.println("<p>Checkstyle provides many checks that you can"
169                + " apply to your source code. Below is an alphabetical"
170                + " reference, the site navigation menu provides a reference"
171                + " organized by functionality.</p>");
172        fileWriter.println("<table>");
173
174        Arrays.sort(classDocs, new ClassDocByCheckNameComparator());
175
176        for (final ClassDoc classDoc : classDocs) {
177
178            // TODO: introduce a "CheckstyleModule" interface
179            // so we can do better in the next line...
180            if (classDoc.typeName().endsWith("Check")
181                    && !classDoc.isAbstract())
182            {
183                String pageName = getPageName(classDoc);
184
185                // allow checks to override pageName when
186                // java package hierarchy is not reflected in doc structure
187                final Tag[] docPageTags = classDoc.tags("checkstyle-docpage");
188                if ((docPageTags != null) && (docPageTags.length > 0)) {
189                    pageName = docPageTags[0].text();
190                }
191
192                final String descr = getDescription(classDoc);
193                final String checkName = getCheckName(classDoc);
194
195
196                fileWriter.println("<tr>"
197                        + "<td><a href=\""
198                        + "config_" + pageName + ".html#" + checkName
199                        + "\">" + checkName + "</a></td><td>"
200                        + descr
201                        + "</td></tr>");
202            }
203        }
204
205        fileWriter.println("</table>");
206        writeXdocsFooter(fileWriter);
207        fileWriter.close();
208        return true;
209    }
210
211    /**
212     * Calculates the human readable page name for a doc page.
213     *
214     * @param classDoc the doc page.
215     * @return the human readable page name for the doc page.
216     */
217    private static String getPageName(ClassDoc classDoc)
218    {
219        final String packageName = classDoc.containingPackage().name();
220        final String pageName =
221                packageName.substring(packageName.lastIndexOf('.') + 1);
222        if ("checks".equals(pageName)) {
223            return "misc";
224        }
225        return pageName;
226    }
227
228    /**
229     * Return the destination directory for this Javadoc run.
230     * @param options Javadoc commandline options
231     * @return the dest dir specified on the command line (or ant task)
232     */
233    public static String getDestDir(String[][] options)
234    {
235        for (final String[] opt : options) {
236            if (DEST_DIR_OPT.equalsIgnoreCase(opt[0])) {
237                return opt[1];
238            }
239        }
240        return null; // TODO: throw exception here ???
241    }
242
243    /**
244     * Returns option length (how many parts are in option).
245     * @param option option name to process
246     * @return option length (how many parts are in option).
247     */
248    public static int optionLength(String option)
249    {
250        if (DEST_DIR_OPT.equals(option)) {
251            return 2;
252        }
253        return 0;
254    }
255
256}