diff --git a/src/checkstyle/com/puppycrawl/tools/checkstyle/doclets/CheckDocsDoclet.java b/src/checkstyle/com/puppycrawl/tools/checkstyle/doclets/CheckDocsDoclet.java new file mode 100644 index 000000000..b735996cb --- /dev/null +++ b/src/checkstyle/com/puppycrawl/tools/checkstyle/doclets/CheckDocsDoclet.java @@ -0,0 +1,331 @@ +//////////////////////////////////////////////////////////////////////////////// +// checkstyle: Checks Java source code for adherence to a set of rules. +// Copyright (C) 2001-2004 Oliver Burn +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +//////////////////////////////////////////////////////////////////////////////// + +package com.puppycrawl.tools.checkstyle.doclets; + +import com.sun.javadoc.RootDoc; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.Tag; +import com.sun.javadoc.SeeTag; +import com.sun.javadoc.PackageDoc; + +import java.util.Map; +import java.util.HashMap; +import java.util.TreeMap; +import java.util.Set; +import java.util.Iterator; +import java.util.Collection; +import java.io.Writer; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.File; +import java.io.FileWriter; + +/** + * Doclet which is used to extract Anakia input files from the + * Javadoc of Check implementations, so the Check's docs are + * autogenerated. + * + * @author lkuehne + */ +public final class CheckDocsDoclet +{ + /** javadoc command line option for dest dir. */ + private static final String DEST_DIR_OPT = "-d"; + + /** Maps package names to DocumentationPages. */ + private static Map sDocumentationPages = new HashMap(); + + /** + * Collects the content of a page in the resulting documentation. + * Each Java package will result in one doc page. + * */ + private static final class DocumentationPage + { + /** The javadoc for the corresponding java package. */ + private PackageDoc mPackageDoc; + + /** maps check names to class doc, sorted. */ + private Map mChecks = new TreeMap(); + + /** + * The string inside a package doc + * to mark the beginning of a page title. + */ + private static final String PAGETITLE = + ""; + + /** + * The string inside a package doc + * to mark the end of a page title. + */ + private static final String PAGETITLE_END = ""; + + /** + * Creates a new Documentation page. + * @param aPackageDoc package.html for the corresponding java package + */ + private DocumentationPage(PackageDoc aPackageDoc) + { + mPackageDoc = aPackageDoc; + } + + /** @return the package name of the corresponding java package. */ + private String getPackageName() + { + return mPackageDoc.name(); + } + + /** + * Register a Checkstyle check's class documentation for inclusion + * on the DocumentationPage. + * + * @param aClassDoc the check's documentation as extracted by javadoc + */ + private void addCheck(ClassDoc aClassDoc) + { + final String strippedClassName = aClassDoc.typeName(); + final String checkName = strippedClassName.endsWith("Check") + ? strippedClassName.substring( + 0, strippedClassName.length() - "Check".length()) + : strippedClassName; + mChecks.put(checkName, aClassDoc); + } + + /** + * Write page. + * @param aWriter the target to write to + * @throws IOException if there are problems writing output + */ + private void write(Writer aWriter) throws IOException + { + // TODO: use velocity to implement this method??? + PrintWriter pw = new PrintWriter(aWriter); + pw.println(""); + pw.println(""); + pw.println(""); + pw.println("" + getTitle() + ""); + pw.println("Checkstyle Development Team"); + pw.println(""); + pw.println(""); + pw.flush(); + final Tag[] packageInlineTags = mPackageDoc.inlineTags(); + for (int i = 0; i < packageInlineTags.length; i++) { + Tag packageInlineTag = packageInlineTags[i]; + final String text = packageInlineTag.text(); + aWriter.write(text); + } + + + Set checkNames = mChecks.keySet(); + for (Iterator it = checkNames.iterator(); it.hasNext();) { + String checkName = (String) it.next(); + + pw.println("
"); + ClassDoc classDoc = (ClassDoc) mChecks.get(checkName); + final Tag[] classInlineTags = classDoc.inlineTags(); + for (int j = 0; j < classInlineTags.length; j++) { + Tag inlineTag = classInlineTags[j]; + if ("@see".equals(inlineTag.kind())) { + // this works fine for TokenType @links + // TODO: check for other link packageInlineTags + SeeTag seeTag = (SeeTag) inlineTag; + String memberName = seeTag.referencedMemberName(); + + aWriter.write(memberName != null + ? memberName + : seeTag.text()); + } + else { + aWriter.write(inlineTag.text()); + } + } + pw.println(); + pw.println("
\n"); + + } + + pw.println(""); + pw.println("
"); + } + + /** + * Removes an opening p tag from a StringBuffer. + * @param aText the text to process + */ + private void removeOpeningParagraphTag(final StringBuffer aText) + { + final String openTag = "

"; + final int tagLen = openTag.length(); + if (aText.length() > tagLen + && aText.substring(0, tagLen).equals(openTag)) + { + aText.delete(0, tagLen); + } + } + + /** + * Removes a dot from the end of a StringBuffer if there is one. + * @param aText the aText to process + */ + private void removeClosingDot(final StringBuffer aText) + { + final int lastIdx = aText.length() - 1; + if (aText.length() > 0 && aText.charAt(lastIdx) == '.') { + aText.delete(lastIdx, lastIdx + 1); + } + } + + /** + * Calculates the title of the DocumentationPage using the first + * sentence in the package javadoc. + * If parts of the first sentence are enclosed between + * {@link #PAGETITLE} and {@link #PAGETITLE_END}, only those parts + * are returned. + * + * @return the calculated title + */ + private String getTitle() + { + final Tag[] tags = mPackageDoc.firstSentenceTags(); + final String tagText = tags[0].text(); + final int pagetitleIdx = tagText.indexOf(PAGETITLE); + final StringBuffer text; + if (pagetitleIdx != -1) { + int titleEndIdx = tagText.indexOf(PAGETITLE_END, pagetitleIdx); + final int titleStartIdx = pagetitleIdx + PAGETITLE.length(); + text = new StringBuffer( + tagText.substring(titleStartIdx, titleEndIdx)); + } + else { + text = new StringBuffer(tagText); + } + removeClosingDot(text); + removeOpeningParagraphTag(text); + System.out.println("text = '" + text + "'"); + return text.toString().trim(); + } + } + + /** + * Finds or creates a documentation page where the content of + * a check's class documentation should be included. + * + * @param aClassDoc the class documentation + * @return the found or created page, registered in + * {@link #sDocumentationPages} + */ + private static DocumentationPage findDocumentationPage(ClassDoc aClassDoc) + { + final PackageDoc packageDoc = aClassDoc.containingPackage(); + final String packageName = packageDoc.name(); + DocumentationPage page = + (DocumentationPage) sDocumentationPages.get(packageName); + + if (page == null) { + page = new DocumentationPage(packageDoc); + sDocumentationPages.put(packageName, page); + } + + return page; + } + + /** + * Doclet entry point. + * @param aRoot parsed javadoc of all java files passed to the javadoc task + * @return true (TODO: semantics of the return value is not clear to me) + * @throws IOException if there are problems writing output + */ + public static boolean start(RootDoc aRoot) throws IOException + { + final ClassDoc[] classDocs = aRoot.classes(); + for (int i = 0; i < classDocs.length; i++) { + ClassDoc classDoc = classDocs[i]; + // TODO: introduce a "CheckstyleModule" interface + // so we can do better in the next line... + if (classDoc.typeName().endsWith("Check") + && !classDoc.isAbstract()) + { + DocumentationPage page = findDocumentationPage(classDoc); + page.addCheck(classDoc); + } + } + + final Collection pages = sDocumentationPages.values(); + final File destDir = new File(getDestDir(aRoot.options())); + for (Iterator it = pages.iterator(); it.hasNext();) { + DocumentationPage page = (DocumentationPage) it.next(); + String pageName = getPageName(page); + File outfile = new File(destDir, "config_" + pageName + ".xml"); + Writer writer = new FileWriter(outfile); + page.write(writer); + writer.close(); + } + + return true; + } + + /** + * Calculates the human readable page name for a doc page. + * + * @param aPage the doc page. + * @return the human readable page name for the doc page. + */ + private static String getPageName(DocumentationPage aPage) + { + final String packageName = aPage.getPackageName(); + String pageName = + packageName.substring(packageName.lastIndexOf('.') + 1); + if ("checks".equals(pageName)) { + return "misc"; + } + return pageName; + } + + /** + * Return the destination directory for this Javadoc run. + * @param aOptions Javadoc commandline options + * @return the dest dir specified on the command line (or ant task) + */ + public static String getDestDir(String[][] aOptions) + { + for (int i = 0; i < aOptions.length; i++) { + String[] opt = aOptions[i]; + if (DEST_DIR_OPT.equalsIgnoreCase(opt[0])) { + return opt[1]; + } + } + return null; // TODO: throw exception here ??? + } + + /** + * Returns option length (how many parts are in option). + * @param aOption option name to process + * @return option length (how many parts are in option). + */ + public static int optionLength(String aOption) + { + if (DEST_DIR_OPT.equals(aOption)) { + return 2; + } + return 0; + } + +}