Added doclet to autogenerate check documentation from the check's javadoc.

This is a first step for fixing bug #760014 (duplicate check documentation code)
This commit is contained in:
Lars Kühne 2004-05-08 07:14:28 +00:00
parent d8f53b937e
commit ddf214598c
1 changed files with 331 additions and 0 deletions

View File

@ -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 =
"<span class=\"xdocspagetitle\">";
/**
* The string inside a package doc
* to mark the end of a page title.
*/
private static final String PAGETITLE_END = "</span>";
/**
* 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("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
pw.println("<document>");
pw.println("<properties>");
pw.println("<title>" + getTitle() + "</title>");
pw.println("<author email=\"checkstyle-devel@lists.sourceforge.net"
+ "\">Checkstyle Development Team</author>");
pw.println("</properties>");
pw.println("<body>");
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("<section name=\"" + checkName + "\">");
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("</section>\n");
}
pw.println("</body>");
pw.println("</document>");
}
/**
* Removes an opening p tag from a StringBuffer.
* @param aText the text to process
*/
private void removeOpeningParagraphTag(final StringBuffer aText)
{
final String openTag = "<p>";
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;
}
}