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:
parent
d8f53b937e
commit
ddf214598c
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue