001: // Copyright 2007 The Apache Software Foundation
002: //
003: // Licensed under the Apache License, Version 2.0 (the "License");
004: // you may not use this file except in compliance with the License.
005: // You may obtain a copy of the License at
006: //
007: // http://www.apache.org/licenses/LICENSE-2.0
008: //
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014:
015: package org.apache.tapestry.mojo;
016:
017: import java.io.File;
018: import java.io.PrintWriter;
019: import java.util.HashMap;
020: import java.util.HashSet;
021: import java.util.LinkedList;
022: import java.util.Map;
023: import java.util.Set;
024: import java.util.regex.Pattern;
025:
026: import com.sun.javadoc.AnnotationDesc;
027: import com.sun.javadoc.ClassDoc;
028: import com.sun.javadoc.ConstructorDoc;
029: import com.sun.javadoc.Doc;
030: import com.sun.javadoc.DocErrorReporter;
031: import com.sun.javadoc.Doclet;
032: import com.sun.javadoc.FieldDoc;
033: import com.sun.javadoc.LanguageVersion;
034: import com.sun.javadoc.RootDoc;
035: import com.sun.javadoc.SeeTag;
036: import com.sun.javadoc.Tag;
037: import com.sun.javadoc.AnnotationDesc.ElementValuePair;
038:
039: /**
040: * Generates an XML file that identifies all the classes that contain parameters, and all the
041: * parameters within each component class. This XML is later converted into part of the Maven
042: * generated HTML site.
043: * <p>
044: * To keep the -doclet parameter passed to javadoc simple, this class should not have any outside
045: * dependencies.
046: * <p>
047: * Works in two passes: First we find any classes that have a field that has the parameter
048: * annotation. Second we locate any subclasses of the initial set of classes, regardless of whether
049: * they have a parameter or not.
050: */
051: public class ParametersDoclet extends Doclet {
052: static String OUTPUT_PATH_OPTION = "-o";
053:
054: static String _outputPath;
055:
056: static class Worker {
057: private PrintWriter _out;
058:
059: private RootDoc _root;
060:
061: private final Set<ClassDoc> _processed = new HashSet<ClassDoc>();
062:
063: private final Pattern _stripper = java.util.regex.Pattern
064: .compile("(<.*?>|&.*?;)", Pattern.DOTALL);
065:
066: public void run(String outputPath, RootDoc root)
067: throws Exception {
068: _root = root;
069:
070: File output = new File(outputPath);
071:
072: _out = new PrintWriter(output);
073:
074: println("<component-parameters>");
075:
076: for (ClassDoc cd : root.classes()) {
077: emitClass(cd, false);
078: }
079:
080: for (ClassDoc potential : _root.classes()) {
081: for (ClassDoc potentialParent : _processed) {
082: if (potential.subclassOf(potentialParent)) {
083: emitClass(potential, true);
084: break;
085: }
086: }
087: }
088:
089: println("</component-parameters>");
090:
091: _out.close();
092: }
093:
094: private void emitClass(ClassDoc classDoc,
095: boolean forceClassOutput) {
096: if (_processed.contains(classDoc))
097: return;
098:
099: if (!classDoc.isPublic())
100: return;
101:
102: // Check for a no-args public constructor
103:
104: boolean found = false;
105:
106: for (ConstructorDoc cons : classDoc.constructors()) {
107: if (cons.isPublic() && cons.parameters().length == 0) {
108: found = true;
109: break;
110: }
111: }
112:
113: if (!found)
114: return;
115:
116: boolean wroteClass = false;
117:
118: for (FieldDoc fd : classDoc.fields()) {
119: if (fd.isStatic())
120: continue;
121:
122: if (!fd.isPrivate())
123: continue;
124:
125: Map<String, String> annotationValues = findParameterAnnotation(fd);
126:
127: if (annotationValues == null)
128: continue;
129:
130: if (!wroteClass) {
131: printClassDescriptionStart(classDoc);
132: wroteClass = true;
133: }
134:
135: String name = annotationValues.get("name");
136: if (name == null)
137: name = fd.name().replaceAll("^[$_]*", "");
138:
139: print(
140: "<parameter name=\"%s\" type=\"%s\" default=\"%s\" required=\"%s\" cache=\"%s\" default-prefix=\"%s\">",
141: name, fd.type().qualifiedTypeName(), get(
142: annotationValues, "value", ""), get(
143: annotationValues, "required", "false"),
144: get(annotationValues, "cache", "true"), get(
145: annotationValues, "defaultPrefix",
146: "prop"));
147:
148: // Body of a parameter is the comment text.
149:
150: printDescription(fd);
151:
152: println("\n</parameter>");
153: }
154:
155: if (wroteClass)
156: println("</class>");
157: else if (forceClassOutput) {
158: printClassDescriptionStart(classDoc);
159: println("</class>");
160: }
161:
162: if (wroteClass || forceClassOutput)
163: _processed.add(classDoc);
164:
165: }
166:
167: private void printClassDescriptionStart(ClassDoc classDoc) {
168: println("<class name=\"%s\" super-class=\"%s\">", classDoc
169: .qualifiedTypeName(), classDoc.super class()
170: .qualifiedTypeName());
171: print("<description>");
172: printDescription(classDoc);
173: println("</description>", classDoc.commentText());
174: }
175:
176: private String get(Map<String, String> map, String key,
177: String defaultValue) {
178: if (map.containsKey(key))
179: return map.get(key);
180:
181: return defaultValue;
182: }
183:
184: private Map<String, String> findParameterAnnotation(FieldDoc fd) {
185: for (AnnotationDesc annotation : fd.annotations()) {
186: if (annotation
187: .annotationType()
188: .qualifiedTypeName()
189: .equals(
190: "org.apache.tapestry.annotations.Parameter")) {
191: Map<String, String> result = new HashMap<String, String>();
192:
193: for (ElementValuePair pair : annotation
194: .elementValues())
195: result.put(pair.element().name(), pair.value()
196: .value().toString());
197:
198: return result;
199: }
200: }
201:
202: return null;
203: }
204:
205: private void print(String format, Object... arguments) {
206: String line = String.format(format, arguments);
207:
208: _out.print(line);
209: }
210:
211: private void println(String format, Object... arguments) {
212: print(format, arguments);
213:
214: _out.println();
215: }
216:
217: private void printDescription(Doc holder) {
218: StringBuilder builder = new StringBuilder();
219:
220: for (Tag tag : holder.inlineTags()) {
221: if (tag.name().equals("Text")) {
222: builder.append(tag.text());
223: continue;
224: }
225:
226: if (tag.name().equals("@link")) {
227: SeeTag seeTag = (SeeTag) tag;
228:
229: String label = seeTag.label();
230: if (label != null && !label.equals("")) {
231: builder.append(label);
232: continue;
233: }
234:
235: if (seeTag.referencedClassName() != null)
236: builder.append(seeTag.referencedClassName());
237:
238: if (seeTag.referencedMemberName() != null) {
239: builder.append("#");
240: builder.append(seeTag.referencedMemberName());
241: }
242:
243: continue;
244: }
245: }
246:
247: String text = builder.toString();
248:
249: // Fix it up a little.
250:
251: // Remove any simple open or close tags found in the text, as well as any XML entities.
252:
253: String stripped = _stripper.matcher(text).replaceAll("");
254:
255: _out.print(stripped);
256: }
257: }
258:
259: /** Yes we are interested in annotations, etc. */
260: public static LanguageVersion languageVersion() {
261: return LanguageVersion.JAVA_1_5;
262: }
263:
264: public static int optionLength(String option) {
265: if (option.equals(OUTPUT_PATH_OPTION))
266: return 2;
267:
268: return 0;
269: }
270:
271: public static boolean validOptions(String options[][],
272: DocErrorReporter reporter) {
273: for (String[] group : options) {
274: if (group[0].equals(OUTPUT_PATH_OPTION))
275: _outputPath = group[1];
276:
277: // Do we need to check for other unexpected options?
278: // TODO: Check for duplicate -o?
279: }
280:
281: if (_outputPath == null)
282: reporter.printError(String.format("Usage: javadoc %s path",
283: OUTPUT_PATH_OPTION));
284:
285: return true;
286: }
287:
288: public static boolean start(RootDoc root) {
289: // Enough of this static method bullshit. What the fuck were they thinking?
290:
291: try {
292: new Worker().run(_outputPath, root);
293: } catch (Exception ex) {
294: root.printError(ex.getMessage());
295:
296: return false;
297: }
298:
299: return true;
300: }
301:
302: }
|