blob: 14aff258649f55f7285e89770e2c2d561468e60b [file] [log] [blame]
// Copyright 2007, 2008 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.tapestry5.mojo;
import com.sun.javadoc.*;
import com.sun.javadoc.AnnotationDesc.ElementValuePair;
import java.io.File;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 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.
*/
public class ParametersDoclet extends Doclet
{
static String OUTPUT_PATH_OPTION = "-o";
static String outputPath;
static class Worker
{
private PrintWriter out;
private final Pattern stripper = Pattern.compile("(<.*?>|&.*?;)", Pattern.DOTALL);
public void run(String outputPath, RootDoc root) throws Exception
{
File output = new File(outputPath);
out = new PrintWriter(output);
println("<component-parameters>");
for (ClassDoc cd : root.classes())
emitClass(cd);
println("</component-parameters>");
out.close();
}
private void emitClass(ClassDoc classDoc)
{
if (!classDoc.isPublic()) return;
// Components must be root classes, not nested classes.
if (classDoc.containingClass() != null) 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;
println("<class name=\"%s\" super-class=\"%s\">", classDoc.qualifiedTypeName(),
classDoc.superclass().qualifiedTypeName());
print("<description>");
printDescription(classDoc);
println("</description>", classDoc.commentText());
for (FieldDoc fd : classDoc.fields())
{
if (fd.isStatic()) continue;
if (!fd.isPrivate()) continue;
Map<String, String> annotationValues = findParameterAnnotation(fd);
if (annotationValues == null) continue;
String name = annotationValues.get("name");
if (name == null) name = fd.name().replaceAll("^[$_]*", "");
print("<parameter name=\"%s\" type=\"%s\" default=\"%s\" required=\"%s\" cache=\"%s\" default-prefix=\"%s\">",
name, fd.type().qualifiedTypeName(), get(annotationValues, "value", ""),
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>");
}
println("</class>");
}
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.tapestry5.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());
}
}
}
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;
}
}