You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2007/02/18 20:38:33 UTC
svn commit: r508958 - in
/tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry:
./ mojo/ mojo/ClassDescription.java mojo/ComponentReport.java
mojo/ParameterDescription.java mojo/ParametersDoclet.java
Author: hlship
Date: Sun Feb 18 11:38:32 2007
New Revision: 508958
URL: http://svn.apache.org/viewvc?view=rev&rev=508958
Log:
Finish initial import.
Added:
tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/
tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/
tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ClassDescription.java
tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ComponentReport.java
tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParameterDescription.java
tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParametersDoclet.java
Added: tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ClassDescription.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ClassDescription.java?view=auto&rev=508958
==============================================================================
--- tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ClassDescription.java (added)
+++ tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ClassDescription.java Sun Feb 18 11:38:32 2007
@@ -0,0 +1,58 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.mojo;
+
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
+
+import java.util.Map;
+
+public class ClassDescription
+{
+ private final String _superClassName;
+
+ private final String _className;
+
+ private final String _description;
+
+ private final Map<String, ParameterDescription> _parameters = newMap();
+
+ public ClassDescription(String className, String superClassName, String description)
+ {
+ _className = className;
+ _superClassName = superClassName;
+ _description = description;
+ }
+
+ public String getClassName()
+ {
+ return _className;
+ }
+
+ public String getDescription()
+ {
+ return _description;
+ }
+
+ public Map<String, ParameterDescription> getParameters()
+ {
+ return _parameters;
+ }
+
+ public String getSuperClassName()
+ {
+ return _superClassName;
+ }
+
+}
Added: tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ComponentReport.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ComponentReport.java?view=auto&rev=508958
==============================================================================
--- tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ComponentReport.java (added)
+++ tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ComponentReport.java Sun Feb 18 11:38:32 2007
@@ -0,0 +1,587 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.mojo;
+
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newList;
+import static org.apache.tapestry.ioc.internal.util.CollectionFactory.newMap;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import nu.xom.Builder;
+import nu.xom.Document;
+import nu.xom.Element;
+import nu.xom.Elements;
+
+import org.apache.commons.lang.SystemUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.reporting.AbstractMavenReport;
+import org.apache.maven.reporting.MavenReportException;
+import org.apache.tapestry.ioc.IOCUtilities;
+import org.apache.tapestry.ioc.internal.util.InternalUtils;
+import org.codehaus.doxia.sink.Sink;
+import org.codehaus.doxia.site.renderer.SiteRenderer;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.codehaus.plexus.util.cli.CommandLineUtils;
+import org.codehaus.plexus.util.cli.Commandline;
+import org.codehaus.plexus.util.cli.DefaultConsumer;
+
+/**
+ * The component report generates documentation about components and parameters within the current
+ * project.
+ * <p>
+ * It works in two steps. First it runs Javadoc with a custom Doclet that will locate the components
+ * and the parameters. This information is written to a temporary XML file.
+ * <p>
+ * The second stage reads the XML file and generates the final report page.
+ *
+ * @goal component-report
+ * @requiresDependencyResolution compile
+ * @execute phase="generate-sources"
+ */
+public class ComponentReport extends AbstractMavenReport
+{
+ /**
+ * Identifies the application root package. Only classes beneath the root package will be
+ * searched for components and parameters If a module contains multiple root packages then they
+ * should be seperated by commas.
+ *
+ * @parameter
+ * @required
+ */
+ private String rootPackage;
+
+ /**
+ * The Maven Project Object
+ *
+ * @parameter expression="${project}"
+ * @required
+ * @readonly
+ */
+ private MavenProject project;
+
+ /**
+ * Generates the site report
+ *
+ * @component
+ */
+ private SiteRenderer siteRenderer;
+
+ /**
+ * Location of the generated site.
+ *
+ * @parameter default-value="${project.reporting.outputDirectory}"
+ * @required
+ */
+ private String outputDirectory;
+
+ /**
+ * Working directory for temporary files.
+ *
+ * @parameter default-value="target"
+ * @required
+ */
+ private String workDirectory;
+
+ protected String getOutputDirectory()
+ {
+ return outputDirectory;
+ }
+
+ protected MavenProject getProject()
+ {
+ return project;
+ }
+
+ protected SiteRenderer getSiteRenderer()
+ {
+ return siteRenderer;
+ }
+
+ public String getDescription(Locale locale)
+ {
+ return "Tapestry component parameter reference documentation";
+ }
+
+ public String getName(Locale locale)
+ {
+ return "Component Reference";
+ }
+
+ public String getOutputName()
+ {
+ return "component-parameters";
+ }
+
+ @Override
+ protected void executeReport(Locale locale) throws MavenReportException
+ {
+ Map<String, ClassDescription> descriptions = runJavadoc();
+
+ getLog().info("Executing ComponentReport ...");
+
+ Sink sink = getSink();
+
+ sink.section1();
+ sink.sectionTitle1();
+ sink.text("Component Index");
+ sink.sectionTitle1_();
+ sink.list();
+
+ for (String className : InternalUtils.sortedKeys(descriptions))
+ {
+ String simpleName = IOCUtilities.toSimpleId(className);
+
+ sink.listItem();
+
+ // Something is convertin the name attribute of the anchors to lower case, so
+ // we'll follow suit.
+
+ sink.link("#" + className.toLowerCase());
+ sink.text(simpleName);
+ sink.link_();
+
+ sink.listItem_();
+ }
+
+ sink.list_();
+
+ for (String className : InternalUtils.sortedKeys(descriptions))
+ {
+ writeClassDescription(descriptions, sink, className);
+ }
+
+ }
+
+ private void writeClassDescription(Map<String, ClassDescription> descriptions, Sink sink,
+ String className)
+ {
+ ClassDescription cd = descriptions.get(className);
+
+ Map<String, ParameterDescription> parameters = newMap(cd.getParameters());
+ List<String> parents = newList();
+
+ String current = cd.getSuperClassName();
+
+ while (true)
+ {
+ ClassDescription superDescription = descriptions.get(current);
+
+ if (superDescription == null) break;
+
+ parents.add(current);
+ parameters.putAll(superDescription.getParameters());
+
+ current = superDescription.getSuperClassName();
+ }
+
+ Collections.reverse(parents);
+
+ sink.section2();
+
+ sink.sectionTitle2();
+ sink.anchor(className);
+ sink.text(className);
+ sink.anchor_();
+
+ sink.sectionTitle2_();
+
+ sink.paragraph();
+ sink.text(cd.getDescription());
+ sink.paragraph_();
+
+ sink.paragraph();
+
+ String javadocURL = String.format("apidocs/%s.html", className.replace('.', '/'));
+
+ sink.link(javadocURL);
+ sink.text("[JavaDoc]");
+ sink.link_();
+
+ sink.paragraph_();
+
+ if (!parents.isEmpty())
+ {
+ sink.sectionTitle3();
+ sink.text("Component inheritance");
+ sink.sectionTitle3_();
+
+ sink.list();
+ sink.listItem();
+
+ for (String name : parents)
+ {
+ sink.link("#" + name.toLowerCase());
+ sink.text(name);
+ sink.link_();
+
+ sink.list();
+ sink.listItem();
+ }
+
+ sink.text(className);
+
+ for (int i = 0; i <= parents.size(); i++)
+ {
+ sink.listItem_();
+ sink.list_();
+ }
+ }
+
+ if (!parameters.isEmpty())
+ {
+ List<String> flags = newList();
+
+ sink.sectionTitle3();
+ sink.text("Parameters");
+ sink.sectionTitle3_();
+
+ sink.table();
+ sink.tableRow();
+
+ for (String header : PARAMETER_HEADERS)
+ {
+ sink.tableHeaderCell();
+ sink.text(header);
+ sink.tableHeaderCell_();
+ }
+
+ sink.tableRow_();
+
+ for (String name : InternalUtils.sortedKeys(parameters))
+ {
+ ParameterDescription pd = parameters.get(name);
+
+ flags.clear();
+ if (pd.getRequired()) flags.add("Required");
+
+ if (!pd.getCache()) flags.add("NOT Cached");
+
+ sink.tableRow();
+
+ cell(sink, pd.getName());
+ cell(sink, pd.getType());
+ cell(sink, InternalUtils.join(flags));
+ cell(sink, pd.getDefaultValue());
+ cell(sink, pd.getDefaultPrefix());
+ cell(sink, pd.getDescription());
+
+ sink.tableRow_();
+
+ }
+
+ sink.table_();
+ }
+
+ sink.section2_();
+ }
+
+ private void cell(Sink sink, String value)
+ {
+ sink.tableCell();
+ sink.text(value);
+ sink.tableCell_();
+ }
+
+ private final static String[] PARAMETER_HEADERS =
+ { "Name", "Type", "Flags", "Default", "Default Prefix", "Description" };
+
+ private Map<String, ClassDescription> runJavadoc() throws MavenReportException
+ {
+ getLog().info("Running JavaDoc to collection component parameter data ...");
+
+ Commandline command = new Commandline();
+
+ try
+ {
+ command.setExecutable(pathToJavadoc());
+ }
+ catch (IOException ex)
+ {
+ throw new MavenReportException("Unable to locate javadoc command: " + ex.getMessage(),
+ ex);
+ }
+
+ String parametersPath = workDirectory + File.separator + "component-parameters.xml";
+
+ String[] arguments =
+ { "-private", "-o", parametersPath,
+
+ "-subpackages", rootPackage,
+
+ "-doclet", ParametersDoclet.class.getName(),
+
+ "-docletpath", docletPath(),
+
+ "-sourcepath", sourcePath(),
+
+ "-classpath", classPath() };
+
+ command.addArguments(arguments);
+
+ executeCommand(command);
+
+ return readXML(parametersPath);
+ }
+
+ @SuppressWarnings("unchecked")
+ private String sourcePath()
+ {
+ List<String> roots = (List<String>) project.getCompileSourceRoots();
+
+ return toArgumentPath(roots);
+ }
+
+ /**
+ * Needed to help locate this plugin's local JAR file for the -doclet argument.
+ *
+ * @parameter default-value="${localRepository}"
+ * @read-only
+ */
+ private ArtifactRepository localRepository;
+
+ /**
+ * Needed to help locate this plugin's local JAR file for the -doclet argument.
+ *
+ * @parameter default-value="${plugin.groupId}"
+ * @read-only
+ */
+ private String pluginGroupId;
+
+ /**
+ * Needed to help locate this plugin's local JAR file for the -doclet argument.
+ *
+ * @parameter default-value="${plugin.artifactId}"
+ * @read-only
+ */
+ private String pluginArtifactId;
+
+ /**
+ * Needed to help locate this plugin's local JAR file for the -doclet argument.
+ *
+ * @parameter default-value="${plugin.version}"
+ * @read-only
+ */
+ private String pluginVersion;
+
+ @SuppressWarnings("unchecked")
+ private String docletPath() throws MavenReportException
+ {
+ File file = new File(localRepository.getBasedir());
+
+ for (String term : pluginGroupId.split("\\."))
+ file = new File(file, term);
+
+ file = new File(file, pluginArtifactId);
+ file = new File(file, pluginVersion);
+
+ file = new File(file, String.format("%s-%s.jar", pluginArtifactId, pluginVersion));
+
+ return file.getAbsolutePath();
+ }
+
+ @SuppressWarnings("unchecked")
+ private String classPath() throws MavenReportException
+ {
+ List<Artifact> artifacts = (List<Artifact>) project.getCompileArtifacts();
+
+ return artifactsToArgumentPath(artifacts);
+ }
+
+ private String artifactsToArgumentPath(List<Artifact> artifacts) throws MavenReportException
+ {
+ List<String> paths = newList();
+
+ for (Artifact artifact : artifacts)
+ {
+ if (artifact.getScope().equals("test")) continue;
+
+ File file = artifact.getFile();
+
+ if (file == null)
+ throw new MavenReportException(
+ "Unable to execute Javadoc: compile dependencies are not fully resolved.");
+
+ paths.add(file.getAbsolutePath());
+ }
+
+ return toArgumentPath(paths);
+ }
+
+ private void executeCommand(Commandline command) throws MavenReportException
+ {
+ getLog().info(command.toString());
+
+ CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
+
+ try
+ {
+ int exitCode = CommandLineUtils.executeCommandLine(command, new DefaultConsumer(), err);
+
+ if (exitCode != 0)
+ {
+ String message = String.format(
+ "Javadoc exit code: %d - %s\nCommand line was: %s",
+ exitCode,
+ err.getOutput(),
+ command);
+
+ throw new MavenReportException(message);
+ }
+ }
+ catch (CommandLineException ex)
+ {
+ throw new MavenReportException("Unable to execute javadoc command: " + ex.getMessage(),
+ ex);
+ }
+
+ // ----------------------------------------------------------------------
+ // Handle Javadoc warnings
+ // ----------------------------------------------------------------------
+
+ if (StringUtils.isNotEmpty(err.getOutput()))
+ {
+ getLog().info("Javadoc Warnings");
+
+ StringTokenizer token = new StringTokenizer(err.getOutput(), "\n");
+ while (token.hasMoreTokens())
+ {
+ String current = token.nextToken().trim();
+
+ getLog().warn(current);
+ }
+ }
+ }
+
+ private String pathToJavadoc() throws IOException, MavenReportException
+ {
+ String executableName = SystemUtils.IS_OS_WINDOWS ? "javadoc.exe" : "javadoc";
+
+ File executable = initialGuessAtJavadocFile(executableName);
+
+ if (!executable.exists() || !executable.isFile())
+ throw new MavenReportException(String.format(
+ "Path %s does not exist or is not a file.",
+ executable));
+
+ return executable.getAbsolutePath();
+ }
+
+ private File initialGuessAtJavadocFile(String executableName)
+ {
+ if (SystemUtils.IS_OS_MAC_OSX)
+ return new File(SystemUtils.getJavaHome() + File.separator + "bin", executableName);
+
+ return new File(SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "bin",
+ executableName);
+ }
+
+ private String toArgumentPath(List<String> paths)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ String sep = "";
+
+ for (String path : paths)
+ {
+ builder.append(sep);
+ builder.append(path);
+
+ sep = SystemUtils.PATH_SEPARATOR;
+ }
+
+ return builder.toString();
+ }
+
+ public Map<String, ClassDescription> readXML(String path) throws MavenReportException
+ {
+ try
+ {
+ Builder builder = new Builder(false);
+
+ File input = new File(path);
+
+ Document doc = builder.build(input);
+
+ return buildMapFromDocument(doc);
+ }
+ catch (Exception ex)
+ {
+ throw new MavenReportException(String.format("Failure reading from %s: %s", path, ex
+ .getMessage()), ex);
+ }
+ }
+
+ private Map<String, ClassDescription> buildMapFromDocument(Document doc)
+ {
+ Map<String, ClassDescription> result = newMap();
+
+ Elements elements = doc.getRootElement().getChildElements("class");
+
+ for (int i = 0; i < elements.size(); i++)
+ {
+ Element element = elements.get(i);
+
+ String description = element.getFirstChildElement("description").getValue();
+
+ String className = element.getAttributeValue("name");
+ String superClassName = element.getAttributeValue("super-class");
+
+ ClassDescription cd = new ClassDescription(className, superClassName, description);
+
+ result.put(className, cd);
+
+ readParameters(cd, element);
+ }
+
+ return result;
+ }
+
+ private void readParameters(ClassDescription cd, Element classElement)
+ {
+ Elements elements = classElement.getChildElements("parameter");
+
+ for (int i = 0; i < elements.size(); i++)
+ {
+ Element node = elements.get(i);
+
+ String name = node.getAttributeValue("name");
+ String type = node.getAttributeValue("type");
+
+ int dotx = type.lastIndexOf('.');
+ if (dotx > 0 && type.substring(0, dotx).equals("java.lang"))
+ type = type.substring(dotx + 1);
+
+ String defaultValue = node.getAttributeValue("default");
+ boolean required = Boolean.parseBoolean(node.getAttributeValue("required"));
+ boolean cache = Boolean.parseBoolean(node.getAttributeValue("cache"));
+ String defaultPrefix = node.getAttributeValue("default-prefix");
+ String description = node.getValue();
+
+ ParameterDescription pd = new ParameterDescription(name, type, defaultValue,
+ defaultPrefix, required, cache, description);
+
+ cd.getParameters().put(name, pd);
+ }
+ }
+}
Added: tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParameterDescription.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParameterDescription.java?view=auto&rev=508958
==============================================================================
--- tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParameterDescription.java (added)
+++ tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParameterDescription.java Sun Feb 18 11:38:32 2007
@@ -0,0 +1,80 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.mojo;
+
+public class ParameterDescription
+{
+ private final String _name;
+
+ private final String _type;
+
+ private final String _defaultValue;
+
+ private final String _defaultPrefix;
+
+ private final boolean _required;
+
+ private final boolean _cache;
+
+ private final String _description;
+
+ public ParameterDescription(String name, String type, String defaultValue,
+ String defaultPrefix, boolean required, boolean cache, String description)
+ {
+ _name = name;
+ _type = type;
+ _defaultValue = defaultValue;
+ _defaultPrefix = defaultPrefix;
+ _required = required;
+ _cache = cache;
+ _description = description;
+ }
+
+ public boolean getCache()
+ {
+ return _cache;
+ }
+
+ public String getDefaultPrefix()
+ {
+ return _defaultPrefix;
+ }
+
+ public String getDefaultValue()
+ {
+ return _defaultValue;
+ }
+
+ public String getDescription()
+ {
+ return _description;
+ }
+
+ public String getName()
+ {
+ return _name;
+ }
+
+ public boolean getRequired()
+ {
+ return _required;
+ }
+
+ public String getType()
+ {
+ return _type;
+ }
+
+}
Added: tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParametersDoclet.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParametersDoclet.java?view=auto&rev=508958
==============================================================================
--- tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParametersDoclet.java (added)
+++ tapestry/tapestry5/tapestry-component-report/trunk/src/main/java/org/apache/tapestry/mojo/ParametersDoclet.java Sun Feb 18 11:38:32 2007
@@ -0,0 +1,337 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry.mojo;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import com.sun.javadoc.AnnotationDesc;
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.ConstructorDoc;
+import com.sun.javadoc.Doc;
+import com.sun.javadoc.DocErrorReporter;
+import com.sun.javadoc.Doclet;
+import com.sun.javadoc.FieldDoc;
+import com.sun.javadoc.LanguageVersion;
+import com.sun.javadoc.RootDoc;
+import com.sun.javadoc.SeeTag;
+import com.sun.javadoc.Tag;
+import com.sun.javadoc.AnnotationDesc.ElementValuePair;
+
+/**
+ * Generates an XML file that identifies all the classes that contain parameters, and all the
+ * parameters within each component class. This XML is later converted into part of the Maven
+ * generated HTML site.
+ * <p>
+ * To keep the -doclet parameter passed to javadoc simple, this class should not have any outside
+ * dependencies.
+ * <p>
+ * Works in two passes: First we find any classes that have a field that has the parameter
+ * annotation. Second we locate any subclasses of the initial set of classes, regardless of whether
+ * they have a parameter or not.
+ */
+public class ParametersDoclet extends Doclet
+{
+ static String OUTPUT_PATH_OPTION = "-o";
+
+ static String _outputPath;
+
+ static class Worker
+ {
+ private PrintWriter _out;
+
+ private RootDoc _root;
+
+ private final Set<String> _processedClassNames = new HashSet<String>();
+
+ /** Queue of class names to be processed. */
+
+ private final LinkedList<ClassDoc> _queue = new LinkedList<ClassDoc>();
+
+ private final Pattern _stripper = java.util.regex.Pattern.compile(
+ "(<.*?>|&.*?;)",
+ Pattern.DOTALL);
+
+ public void run(String outputPath, RootDoc root) throws Exception
+ {
+ _root = root;
+
+ File output = new File(outputPath);
+
+ _out = new PrintWriter(output);
+
+ println("<component-parameters>");
+
+ for (ClassDoc cd : root.classes())
+ {
+ emitClass(cd, true, false);
+ }
+
+ while (!_queue.isEmpty())
+ {
+ ClassDoc cd = _queue.removeFirst();
+
+ // Its in the queue because it has a parent class that has parameters
+ // but (if it wasn't already processed) it doesn't define parameters of its own,
+ // so we force it into the output regardless.
+
+ emitClass(cd, false, true);
+ }
+
+ println("</component-parameters>");
+
+ _out.close();
+ }
+
+ private void emitClass(ClassDoc classDoc, boolean queueSubclasses, boolean forceClassOutput)
+ {
+ String className = classDoc.name();
+
+ if (_processedClassNames.contains(className)) return;
+
+ if (!classDoc.isPublic()) return;
+
+ // Check for a no-args public constructor
+
+ boolean found = false;
+
+ for (ConstructorDoc cons : classDoc.constructors())
+ {
+ if (cons.isPublic() && cons.parameters().length == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) return;
+
+ boolean wroteClass = false;
+
+ for (FieldDoc fd : classDoc.fields())
+ {
+ if (fd.isStatic()) continue;
+
+ if (!fd.isPrivate()) continue;
+
+ Map<String, String> annotationValues = findParameterAnnotation(fd);
+
+ if (annotationValues == null) continue;
+
+ if (!wroteClass)
+ {
+ printClassDescriptionStart(classDoc);
+ wroteClass = true;
+ }
+
+ String name = annotationValues.get("name");
+ if (name == null) name = fd.name().replaceAll("^[$_]*", "");
+
+ String defaultValue = fd.constantValueExpression();
+ if (defaultValue == null) defaultValue = "";
+
+ print(
+ "<parameter name=\"%s\" type=\"%s\" default=\"%s\" required=\"%s\" cache=\"%s\" default-prefix=\"%s\">",
+ name,
+ fd.type().qualifiedTypeName(),
+ get(annotationValues, "value", defaultValue),
+ get(annotationValues, "required", "false"),
+ get(annotationValues, "cache", "true"),
+ get(annotationValues, "defaultPrefix", "prop"));
+
+ // Body of a parameter is the comment text.
+
+ printDescription(fd);
+
+ println("\n</parameter>");
+ }
+
+ if (wroteClass)
+ println("</class>");
+ else if (forceClassOutput)
+ {
+ printClassDescriptionStart(classDoc);
+ println("</class>");
+ }
+
+ if (wroteClass || forceClassOutput) _processedClassNames.add(className);
+
+ if (wroteClass && queueSubclasses)
+ {
+ for (ClassDoc potential : _root.classes())
+ {
+ // I believe subclassOf() will work even if there are intervening levels of
+ // heirarchy. This should mean that we don't have to queue up subclasses
+ // of classes that were added via the queue (rather than by the presence of
+ // parameter fields).
+
+ if (potential.subclassOf(classDoc)) _queue.addLast(potential);
+ }
+ }
+ }
+
+ private void printClassDescriptionStart(ClassDoc classDoc)
+ {
+ println(
+ "<class name=\"%s\" super-class=\"%s\">",
+ classDoc.qualifiedTypeName(),
+ classDoc.superclass().qualifiedTypeName());
+ print("<description>");
+ printDescription(classDoc);
+ println("</description>", classDoc.commentText());
+ }
+
+ private String get(Map<String, String> map, String key, String defaultValue)
+ {
+ if (map.containsKey(key)) return map.get(key);
+
+ return defaultValue;
+ }
+
+ private Map<String, String> findParameterAnnotation(FieldDoc fd)
+ {
+ for (AnnotationDesc annotation : fd.annotations())
+ {
+ if (annotation.annotationType().qualifiedTypeName().equals(
+ "org.apache.tapestry.annotations.Parameter"))
+ {
+ Map<String, String> result = new HashMap<String, String>();
+
+ for (ElementValuePair pair : annotation.elementValues())
+ result.put(pair.element().name(), pair.value().value().toString());
+
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ private void print(String format, Object... arguments)
+ {
+ String line = String.format(format, arguments);
+
+ _out.print(line);
+ }
+
+ private void println(String format, Object... arguments)
+ {
+ print(format, arguments);
+
+ _out.println();
+ }
+
+ private void printDescription(Doc holder)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ for (Tag tag : holder.inlineTags())
+ {
+ if (tag.name().equals("Text"))
+ {
+ builder.append(tag.text());
+ continue;
+ }
+
+ if (tag.name().equals("@link"))
+ {
+ SeeTag seeTag = (SeeTag) tag;
+
+ String label = seeTag.label();
+ if (label != null && !label.equals(""))
+ {
+ builder.append(label);
+ continue;
+ }
+
+ if (seeTag.referencedClassName() != null)
+ builder.append(seeTag.referencedClassName());
+
+ if (seeTag.referencedMemberName() != null)
+ {
+ builder.append("#");
+ builder.append(seeTag.referencedMemberName());
+ }
+
+ continue;
+ }
+ }
+
+ String text = builder.toString();
+
+ // Fix it up a little.
+
+ // Remove any simple open or close tags found in the text, as well as any XML entities.
+
+ String stripped = _stripper.matcher(text).replaceAll("");
+
+ _out.print(stripped);
+ }
+ }
+
+ /** Yes we are interested in annotations, etc. */
+ public static LanguageVersion languageVersion()
+ {
+ return LanguageVersion.JAVA_1_5;
+ }
+
+ public static int optionLength(String option)
+ {
+ if (option.equals(OUTPUT_PATH_OPTION)) return 2;
+
+ return 0;
+ }
+
+ public static boolean validOptions(String options[][], DocErrorReporter reporter)
+ {
+ for (String[] group : options)
+ {
+ if (group[0].equals(OUTPUT_PATH_OPTION)) _outputPath = group[1];
+
+ // Do we need to check for other unexpected options?
+ // TODO: Check for duplicate -o?
+ }
+
+ if (_outputPath == null)
+ reporter.printError(String.format("Usage: javadoc %s path", OUTPUT_PATH_OPTION));
+
+ return true;
+ }
+
+ public static boolean start(RootDoc root)
+ {
+ // Enough of this static method bullshit. What the fuck were they thinking?
+
+ try
+ {
+ new Worker().run(_outputPath, root);
+ }
+ catch (Exception ex)
+ {
+ root.printError(ex.getMessage());
+
+ return false;
+ }
+
+ return true;
+ }
+
+}