001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (license2)
004: * Initial Developer: H2 Group
005: */
006: package org.h2.tools.doclet;
007:
008: import java.io.BufferedWriter;
009: import java.io.File;
010: import java.io.FileWriter;
011: import java.io.IOException;
012: import java.io.PrintWriter;
013: import java.util.Arrays;
014: import java.util.Comparator;
015: import java.util.HashSet;
016:
017: import org.h2.util.StringUtils;
018:
019: import com.sun.javadoc.ClassDoc;
020: import com.sun.javadoc.FieldDoc;
021: import com.sun.javadoc.MethodDoc;
022: import com.sun.javadoc.ParamTag;
023: import com.sun.javadoc.Parameter;
024: import com.sun.javadoc.RootDoc;
025: import com.sun.javadoc.Tag;
026: import com.sun.javadoc.ThrowsTag;
027: import com.sun.javadoc.Type;
028:
029: /**
030: * This class is a custom doclet implementation to generate the
031: * Javadoc for this product.
032: */
033: public class Doclet {
034:
035: private static final boolean INTERFACES_ONLY = Boolean
036: .getBoolean("h2.interfacesOnly");
037: private int errorCount;
038: private HashSet errors = new HashSet();
039:
040: public static boolean start(RootDoc root) throws IOException {
041: return new Doclet().startDoc(root);
042: }
043:
044: public boolean startDoc(RootDoc root) throws IOException {
045: ClassDoc[] classes = root.classes();
046: String[][] options = root.options();
047: String destDir = System.getProperty("h2.destDir",
048: "docs/javadoc");
049: for (int i = 0; i < options.length; i++) {
050: if (options[i][0].equals("destdir")) {
051: destDir = options[i][1];
052: }
053: }
054: for (int i = 0; i < classes.length; ++i) {
055: ClassDoc clazz = classes[i];
056: processClass(destDir, clazz);
057: }
058: if (errorCount > 0) {
059: throw new IOException("FAILED: " + errorCount
060: + " errors found");
061: }
062: return true;
063: }
064:
065: private static String getClass(String name) {
066: if (name.startsWith("Jdbc")) {
067: return name.substring(4);
068: }
069: return name;
070: }
071:
072: private void processClass(String destDir, ClassDoc clazz)
073: throws IOException {
074: String packageName = clazz.containingPackage().name();
075: String dir = destDir + "/" + packageName.replace('.', '/');
076: (new File(dir)).mkdirs();
077: String fileName = dir + "/" + clazz.name() + ".html";
078: String className = getClass(clazz.name());
079: FileWriter out = new FileWriter(fileName);
080: PrintWriter writer = new PrintWriter(new BufferedWriter(out));
081: writer
082: .println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" "
083: + "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
084: String language = "en";
085: writer.println("<html xmlns=\"http://www.w3.org/1999/xhtml\" "
086: + "lang=\"" + language + "\" xml:lang=\"" + language
087: + "\">");
088: writer
089: .println("<head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" /><title>");
090: writer.println(className);
091: writer
092: .println("</title><link rel=\"stylesheet\" type=\"text/css\" href=\"../../../stylesheet.css\" /></head><body>");
093: writer
094: .println("<table class=\"content\"><tr class=\"content\"><td class=\"content\"><div class=\"contentDiv\">");
095: writer.println("<h1>" + className + "</h1>");
096: writer
097: .println(formatText(clazz.commentText())
098: + "<br /><br />");
099:
100: // method overview
101: MethodDoc[] methods = clazz.methods();
102: Arrays.sort(methods, new Comparator() {
103: public int compare(Object a, Object b) {
104: return ((MethodDoc) a).name().compareTo(
105: ((MethodDoc) b).name());
106: }
107: });
108: boolean hasMethods = false;
109: for (int i = 0; i < methods.length; i++) {
110: MethodDoc method = methods[i];
111: String name = method.name();
112: if (skipMethod(method)) {
113: continue;
114: }
115: if (!hasMethods) {
116: writer
117: .println("<table><tr><th colspan=\"2\">Methods</th></tr>");
118: hasMethods = true;
119: }
120: String type = getTypeName(method.isStatic(), method
121: .returnType());
122: writer.println("<tr><td class=\"return\">" + type
123: + "</td><td class=\"method\">");
124: Parameter[] params = method.parameters();
125: StringBuffer buff = new StringBuffer();
126: buff.append('(');
127: for (int j = 0; j < params.length; j++) {
128: if (j > 0) {
129: buff.append(", ");
130: }
131: buff.append(getTypeName(false, params[j].type()));
132: buff.append(' ');
133: buff.append(params[j].name());
134: }
135: buff.append(')');
136: if (isDeprecated(method)) {
137: name = "<span class=\"deprecated\">" + name + "</span>";
138: }
139: writer.println("<a href=\"#r" + i + "\">" + name + "</a>"
140: + buff.toString());
141: String firstSentence = getFirstSentence(method
142: .firstSentenceTags());
143: if (firstSentence != null) {
144: writer.println("<div class=\"methodText\">"
145: + formatText(firstSentence) + "</div>");
146: }
147: writer.println("</td></tr>");
148: }
149: if (hasMethods) {
150: writer.println("</table>");
151: }
152:
153: // field overview
154: FieldDoc[] fields = clazz.fields();
155: if (clazz.interfaces().length > 0) {
156: fields = clazz.interfaces()[0].fields();
157: }
158: Arrays.sort(fields, new Comparator() {
159: public int compare(Object a, Object b) {
160: return ((FieldDoc) a).name().compareTo(
161: ((FieldDoc) b).name());
162: }
163: });
164: int fieldId = 0;
165: for (int i = 0; i < fields.length; i++) {
166: FieldDoc field = fields[i];
167: if (skipField(clazz, field)) {
168: continue;
169: }
170: String name = field.name();
171: String text = field.commentText();
172: if (text == null || text.trim().length() == 0) {
173: addError("Undocumented field (" + clazz.name()
174: + ".java:" + field.position().line() + ") "
175: + name);
176: }
177: if (text.startsWith("INTERNAL")) {
178: continue;
179: }
180: if (fieldId == 0) {
181: writer
182: .println("<br /><table><tr><th colspan=\"2\">Fields</th></tr>");
183: }
184: String type = getTypeName(true, field.type());
185: writer.println("<tr><td class=\"return\">" + type
186: + "</td><td class=\"method\">");
187: String constant = field.constantValueExpression();
188:
189: // add a link (a name) if there is a <code> tag
190: String link = getFieldLink(text, constant, clazz, name);
191: writer.print("<a href=\"#" + link + "\">" + name + "</a>");
192: if (constant == null) {
193: writer.println();
194: } else {
195: writer.println(" = " + constant);
196: }
197: writer.println("</td></tr>");
198: fieldId++;
199: }
200: if (fieldId > 0) {
201: writer.println("</table>");
202: }
203:
204: // message details
205: for (int i = 0; i < methods.length; i++) {
206: MethodDoc method = methods[i];
207: String name = method.name();
208: if (skipMethod(method)) {
209: continue;
210: }
211: String type = getTypeName(method.isStatic(), method
212: .returnType());
213: writer.println("<a name=\"r" + i + "\"></a>");
214: Parameter[] params = method.parameters();
215: StringBuffer buff = new StringBuffer();
216: buff.append('(');
217: for (int j = 0; j < params.length; j++) {
218: if (j > 0) {
219: buff.append(", ");
220: }
221: buff.append(getTypeName(false, params[j].type()));
222: buff.append(' ');
223: buff.append(params[j].name());
224: }
225: buff.append(')');
226: ClassDoc[] exceptions = method.thrownExceptions();
227: if (exceptions.length > 0) {
228: buff.append(" throws ");
229: for (int k = 0; k < exceptions.length; k++) {
230: if (k > 0) {
231: buff.append(", ");
232: }
233: buff.append(exceptions[k].typeName());
234: }
235: }
236: if (isDeprecated(method)) {
237: name = "<span class=\"deprecated\">" + name + "</span>";
238: }
239: writer.println("<h4>" + type
240: + " <span class=\"methodName\">" + name + "</span>"
241: + buff.toString() + "</h4>");
242: writer.println(formatText(method.commentText()));
243: ParamTag[] paramTags = method.paramTags();
244: boolean space = false;
245: for (int j = 0; j < paramTags.length; j++) {
246: if (!space) {
247: writer.println("<br /><br />");
248: space = true;
249: }
250: String p = paramTags[j].parameterName() + " - "
251: + paramTags[j].parameterComment();
252: if (j == 0) {
253: writer
254: .println("<div class=\"itemTitle\">Parameters:</div>");
255: }
256: writer.println("<div class=\"item\">" + p + "</div>");
257: }
258: Tag[] returnTags = method.tags("return");
259: if (returnTags != null && returnTags.length > 0) {
260: if (!space) {
261: writer.println("<br /><br />");
262: space = true;
263: }
264: writer
265: .println("<div class=\"itemTitle\">Returns:</div>");
266: writer.println("<div class=\"item\">"
267: + returnTags[0].text() + "</div>");
268: }
269: ThrowsTag[] throwsTags = method.throwsTags();
270: if (throwsTags != null && throwsTags.length > 0) {
271: if (!space) {
272: writer.println("<br /><br />");
273: space = true;
274: }
275: writer
276: .println("<div class=\"itemTitle\">Throws:</div>");
277: for (int j = 0; j < throwsTags.length; j++) {
278: String p = throwsTags[j].exceptionName();
279: String c = throwsTags[j].exceptionComment();
280: if (c.length() > 0) {
281: p += " - " + c;
282: }
283: writer.println("<div class=\"item\">" + p
284: + "</div>");
285: }
286: }
287: writer.println("<hr />");
288: }
289:
290: // field details
291: Arrays.sort(fields, new Comparator() {
292: public int compare(Object a, Object b) {
293: FieldDoc fa = (FieldDoc) a;
294: FieldDoc fb = (FieldDoc) b;
295: String ca = fa.constantValueExpression();
296: String cb = fb.constantValueExpression();
297: if (ca != null && cb != null) {
298: return ca.compareTo(cb);
299: }
300: return fa.name().compareTo(fb.name());
301: }
302: });
303: for (int i = 0; i < fields.length; i++) {
304: FieldDoc field = fields[i];
305: if (skipField(clazz, field)) {
306: continue;
307: }
308: String text = field.commentText();
309: if (text.startsWith("INTERNAL")) {
310: continue;
311: }
312: String name = field.name();
313: String constant = field.constantValueExpression();
314: String link = getFieldLink(text, constant, clazz, name);
315: writer.println("<a name=\"" + link + "\"></a>");
316: writer.println("<h4><span class=\"methodName\">" + name);
317: if (constant == null) {
318: writer.println();
319: } else {
320: writer.println(" = " + constant);
321: }
322: writer.println("</span></h4>");
323: if (text != null) {
324: writer.println("<div class=\"item\">"
325: + formatText(text) + "</div>");
326: }
327: writer.println("<hr />");
328: }
329:
330: writer.println("</div></td></tr></table></body></html>");
331: writer.close();
332: out.close();
333: }
334:
335: private String getFieldLink(String text, String constant,
336: ClassDoc clazz, String name) {
337: String link = constant != null ? constant : name.toLowerCase();
338: int linkStart = text.indexOf("<code>");
339: if (linkStart >= 0) {
340: int linkEnd = text.indexOf("</code>", linkStart);
341: link = text.substring(linkStart + "<code>".length(),
342: linkEnd);
343: if (constant != null && !constant.equals(link)) {
344: System.out.println("Wrong code tag? " + clazz.name()
345: + "." + name + " code: " + link + " constant: "
346: + constant);
347: errorCount++;
348: }
349: }
350: if (Character.isDigit(link.charAt(0))) {
351: link = "c" + link;
352: }
353: return link;
354: }
355:
356: private static String formatText(String text) {
357: if (text == null) {
358: return text;
359: }
360: text = StringUtils.replaceAll(text, "\n </pre>", "</pre>");
361: return text;
362: }
363:
364: private static boolean skipField(ClassDoc clazz, FieldDoc field) {
365: if (!field.isFinal() || !field.isStatic() || !field.isPublic()
366: || field.containingClass() != clazz) {
367: return true;
368: }
369: return false;
370: }
371:
372: private boolean skipMethod(MethodDoc method) {
373: ClassDoc clazz = method.containingClass();
374: boolean isInterface = clazz.isInterface()
375: || (clazz.isAbstract() && method.isAbstract());
376: if (INTERFACES_ONLY && !isInterface) {
377: return true;
378: }
379: String name = method.name();
380: if (!method.isPublic() || name.equals("finalize")) {
381: return true;
382: }
383: if (method.getRawCommentText().trim().startsWith(
384: "@deprecated INTERNAL")) {
385: return true;
386: }
387: String firstSentence = getFirstSentence(method
388: .firstSentenceTags());
389: String raw = method.getRawCommentText();
390: if (firstSentence != null
391: && firstSentence.startsWith("INTERNAL")) {
392: return true;
393: }
394: if ((firstSentence == null || firstSentence.trim().length() == 0)
395: && raw.indexOf("{@inheritDoc}") < 0) {
396: if (!doesOverride(method)) {
397: boolean setterOrGetter = name.startsWith("set")
398: && method.parameters().length == 1;
399: setterOrGetter |= name.startsWith("get")
400: && method.parameters().length == 0;
401: if (isInterface || !setterOrGetter) {
402: addError("Undocumented method " + " ("
403: + clazz.name() + ".java:"
404: + method.position().line() + ") " + clazz
405: + "." + name + " " + raw);
406: return true;
407: }
408: }
409: }
410: return false;
411: }
412:
413: private void addError(String s) {
414: if (errors.add(s)) {
415: System.out.println(s);
416: errorCount++;
417: }
418: }
419:
420: private boolean doesOverride(MethodDoc method) {
421: ClassDoc clazz = method.containingClass();
422: ClassDoc[] ifs = clazz.interfaces();
423: int pc = method.parameters().length;
424: String name = method.name();
425: for (int i = 0;; i++) {
426: ClassDoc c;
427: if (i < ifs.length) {
428: c = ifs[i];
429: } else {
430: clazz = clazz.super class();
431: if (clazz == null) {
432: break;
433: }
434: c = clazz;
435: }
436: MethodDoc[] ms = c.methods();
437: for (int j = 0; j < ms.length; j++) {
438: MethodDoc m = ms[j];
439: if (m.name().equals(name)
440: && m.parameters().length == pc) {
441: return true;
442: }
443: }
444: }
445: return false;
446: }
447:
448: private static String getFirstSentence(Tag[] tags) {
449: String firstSentence = null;
450: if (tags.length > 0) {
451: Tag first = tags[0];
452: firstSentence = first.text();
453: }
454: return firstSentence;
455: }
456:
457: private static String getTypeName(boolean isStatic, Type type) {
458: String s = type.typeName() + type.dimension();
459: if (isStatic) {
460: s = "static " + s;
461: }
462: return s;
463: }
464:
465: private static boolean isDeprecated(MethodDoc method) {
466: Tag[] tags = method.tags();
467: boolean deprecated = false;
468: for (int j = 0; j < tags.length; j++) {
469: Tag t = tags[j];
470: if (t.kind().equals("@deprecated")) {
471: deprecated = true;
472: }
473: }
474: return deprecated;
475: }
476: }
|