3
This software is provided 'as-is', without any express or implied
4
warranty. In no event will the authors be held liable for any damages
5
arising from the use of this software.
7
Permission is granted to anyone to use this software for any purpose,
8
including commercial applications, and to alter it and redistribute it
16
import javax.xml.parsers.*;
19
import org.xml.sax.helpers.*;
21
import com.sun.javadoc.*;
22
import com.sun.tools.javadoc.Main;
25
* Java Doclet for generating .NET XML API documentation.
27
* The current implementation may has not been tested
28
* with (and thus may not support) the following features:
29
* {@code}; should be converted to <c> tags
34
* references to package documentation
40
* <b>Usage reports "javadoc" instead of "ikvmdoc"</b>:
42
* usage: javadoc [options] [packagenames] [sourcefiles] [@files]
46
* usage: ikvmdoc [options] [packagenames] [sourcefiles] [@files]
49
* HTML tag parsing is not forgiving; should be made more fault tolerant
51
* Javadoc HTML -> .NET tag conversions that should be considered/evaluated:
52
* <code>true</code> -> <see langref="true"/>
53
* <code>false</code> -> <see langref="false"/>
54
* <code>null</code> -> <see langref="null"/>
56
* @author Brian Heineman
58
public class IKVMDoc extends Doclet {
60
* Map of Java data types to .NET data types.
62
private static final Map<String, String> DATA_TYPE_MAPPING = new HashMap<String, String>();
65
* Name of the assembly file parameter.
67
private static final String ASSEMBLY_PARAMETER = "-assembly";
70
* Name of the HTML parameter.
72
private static final String HTML_PARAMETER = "-nohtml";
75
* Name of the strict final field semantics parameter.
77
private static final String STRICT_FINAL_FIELD_SEMANTICS_PARAMETER = "-strictfinalfieldsemantics";
80
* Name of the author parameter.
82
private static final String AUTHOR_PARAMETER = "-author";
85
* Name of the deprecated parameter.
87
private static final String DEPRECATED_PARAMETER = "-nodeprecated";
90
* Name of the since parameter.
92
private static final String SINCE_PARAMETER = "-nosince";
95
* Name of the version parameter.
97
private static final String VERSION_PARAMETER = "-version";
100
* The assembly file the .NET documentation will be generated for.
102
private static File ASSEMBLY_FILE;
105
* Indicates if HTML should be included in the .NET XML documentation.
106
* Default is <code>true</code> to reflect the standard doclet behavior.
108
* NOTE: The Java Runtime API contains invalid HTML which requires this
109
* option to be set to <code>false</code> when generating its
110
* .NET XML documentation.
112
private static boolean OUTPUT_HTML = true;
115
* Indicates if the author information should be included in the .NET XML documentation.
116
* Default is <code>false</code> to reflect the standard doclet behavior.
118
private static boolean OUTPUT_AUTHOR = false;
121
* Indicates if the deprecated information should be included in the .NET XML documentation.
122
* Default is <code>true</code> to reflect the standard doclet behavior.
124
private static boolean OUTPUT_DEPRECATED = true;
127
* Indicates if the since information should be included in the .NET XML documentation.
128
* Default is <code>true</code> to reflect the standard doclet behavior.
130
private static boolean OUTPUT_SINCE = true;
133
* Indicates if the version information should be included in the .NET XML documentation.
134
* Default is <code>false</code> to reflect the standard doclet behavior.
136
private static boolean OUTPUT_VERSION = false;
139
* The reported used to report failures to.
141
private static DocErrorReporter ERROR_REPORTER;
144
// Populate the Java->.NET data type mappings
145
DATA_TYPE_MAPPING.put("boolean", "System.Boolean");
146
DATA_TYPE_MAPPING.put("byte", "System.Byte");
147
DATA_TYPE_MAPPING.put("char", "System.Char");
148
DATA_TYPE_MAPPING.put("short", "System.Int16");
149
DATA_TYPE_MAPPING.put("int", "System.Int32");
150
DATA_TYPE_MAPPING.put("long", "System.Int64");
151
DATA_TYPE_MAPPING.put("float", "System.Single");
152
DATA_TYPE_MAPPING.put("double", "System.Double");
153
DATA_TYPE_MAPPING.put("java.lang.Object", "System.Object");
154
DATA_TYPE_MAPPING.put("java.lang.String", "System.String");
155
DATA_TYPE_MAPPING.put("java.lang.Throwable", "System.Exception");
159
* Generate the .NET XML Documentation.
161
* @param root represents the root of the program structure information for one run of javadoc
162
* @return <code>true</code> on success; <code>false</code> on failure
164
public static boolean start(RootDoc root) {
165
String assemblyName = ASSEMBLY_FILE.getName();
166
int extensionIndex = assemblyName.lastIndexOf('.');
168
if (extensionIndex != -1) {
169
assemblyName = assemblyName.substring(0, extensionIndex);
172
File documentationFile = new File(ASSEMBLY_FILE.getParent(), assemblyName + ".xml");
173
PrintWriter pw = null;
176
FileOutputStream fos = new FileOutputStream(documentationFile);
177
OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
178
BufferedWriter bw = new BufferedWriter(osw);
179
pw = new PrintWriter(bw);
182
pw.println("<?xml version=\"1.0\"?>");
185
pw.println("<assembly>");
188
pw.print(assemblyName);
189
pw.println("</name>");
191
pw.println("</assembly>");
193
pw.println("<members>");
195
ClassDoc[] classes = root.classes();
197
for (ClassDoc classDoc : classes) {
203
pw.println("</members>");
204
pw.println("</doc>");
207
validate(documentationFile);
208
} catch (Exception e) {
221
* Prints the member documentation.
223
* @param pw the writer to print the documentation to
224
* @param programElementDoc the member to document
226
private static void print(PrintWriter pw, ProgramElementDoc programElementDoc) {
228
* Implementation of proposed @exclude tag: http://java.sun.com/j2se/javadoc/proposed-tags.html
230
if (programElementDoc.tags("@exclude").length > 0) {
235
pw.print("<member name=\"");
236
printReference(pw, programElementDoc, true);
240
pw.print("<summary>");
242
if (OUTPUT_DEPRECATED) {
243
printTags(pw, programElementDoc, "DEPRECATED:", "@deprecated");
246
printComment(pw, programElementDoc, programElementDoc.inlineTags());
249
printTags(pw, programElementDoc, "Author:", "@author");
252
if (OUTPUT_VERSION) {
253
printTags(pw, programElementDoc, "Version:", "@version");
257
printTags(pw, programElementDoc, "Since:", "@since");
260
printTags(pw, programElementDoc, "Serial:", "@serial");
261
printTags(pw, programElementDoc, "Serial Field:", "@serialField");
262
printTags(pw, programElementDoc, "Serial Data:", "@serialData");
264
pw.println("</summary>");
266
if (programElementDoc instanceof ExecutableMemberDoc) {
267
ExecutableMemberDoc executableMemberDoc = (ExecutableMemberDoc) programElementDoc;
269
printParamTags(pw, executableMemberDoc);
270
printReturnTag(pw, executableMemberDoc);
271
printThrowsTags(pw, executableMemberDoc);
274
printSeeTags(pw, programElementDoc);
277
pw.println("</member>");
279
// Document class members
280
if (programElementDoc instanceof ClassDoc) {
281
ClassDoc classDoc = (ClassDoc) programElementDoc;
282
FieldDoc[] fields = classDoc.fields();
284
for (FieldDoc fieldDoc : fields) {
288
ConstructorDoc[] constructors = classDoc.constructors();
290
for (ConstructorDoc constructorDoc : constructors) {
291
print(pw, constructorDoc);
294
MethodDoc[] methods = classDoc.methods();
296
for (MethodDoc methodDoc : methods) {
297
print(pw, methodDoc);
303
* Prints the parameter documentation.
305
* @param pw the writer to print the parameter documentation to
306
* @param memberDoc the member to document the parameters for
308
private static void printParameters(PrintWriter pw, ExecutableMemberDoc memberDoc) {
309
Parameter[] parameters = memberDoc.parameters();
311
if (parameters.length > 0) {
315
for (int i = 0; i < parameters.length; i++) {
316
Type parameterType = parameters[i].type();
322
String qualifiedTypeName = parameterType.qualifiedTypeName();
323
String mappedDataType = DATA_TYPE_MAPPING.get(qualifiedTypeName);
325
// Print the mapped data type if there is one
326
if (mappedDataType != null) {
327
pw.print(mappedDataType);
329
pw.print(qualifiedTypeName);
332
pw.print(parameterType.dimension());
335
if (parameters.length > 0) {
341
* Prints the parameter documentation.
343
* @param pw the writer to print the parameter documentation to
344
* @param memberDoc the member to document the parameters for
346
private static void printParamTags(PrintWriter pw, ExecutableMemberDoc memberDoc) {
347
ParamTag[] paramTags = memberDoc.paramTags();
349
for (ParamTag paramTag : paramTags) {
351
pw.print("<param name=\"");
352
pw.print(paramTag.parameterName());
354
printComment(pw, memberDoc, paramTag.inlineTags());
355
pw.println("</param>");
360
* Prints the return documentation.
362
* @param pw the writer to print the return documentation to
363
* @param memberDoc the member to document the return for
365
private static void printReturnTag(PrintWriter pw, ExecutableMemberDoc memberDoc) {
366
Tag[] returnDoc = memberDoc.tags("@return");
368
if (returnDoc.length == 1) {
370
pw.print("<returns>");
371
printComment(pw, memberDoc, returnDoc[0].inlineTags());
372
pw.println("</returns>");
373
} else if (returnDoc.length > 1) {
374
ERROR_REPORTER.printError("More than one return tag specified for '" + memberDoc.qualifiedName() + "'");
379
* Prints the exception documentation.
381
* @param pw the writer to print the exception documentation to
382
* @param memberDoc the member to document the exceptions for
384
private static void printThrowsTags(PrintWriter pw, ExecutableMemberDoc memberDoc) {
385
ThrowsTag[] throwsTags = memberDoc.throwsTags();
387
for (ThrowsTag throwsTag : throwsTags) {
388
ClassDoc exceptionDoc = throwsTag.exception();
390
if (exceptionDoc == null) {
391
ERROR_REPORTER.printError("Unable to locate class '" + throwsTag.exceptionName() + "' for '" + memberDoc.qualifiedName() + "'");
396
pw.print("<exception cref=\"");
397
printReference(pw, exceptionDoc, true);
399
printComment(pw, memberDoc, throwsTag.inlineTags());
400
pw.println("</exception>");
405
* Prints the see tag documentation.
407
* @param pw the writer to print the see tag documentation to
408
* @param memberDoc the member to document the see tags for
410
private static void printSeeTags(PrintWriter pw, ProgramElementDoc memberDoc) {
411
SeeTag[] seeTags = memberDoc.seeTags();
413
for (SeeTag seeTag : seeTags) {
414
printSeeTag(pw, memberDoc, seeTag, true);
419
* Prints the specified see tag.
421
* @param pw the writer to print the see tag to
422
* @param memberDoc the member to document the see tag for
423
* @param seeTag the see tags to print
424
* @param asSeeAlso <code>true</code> if a "seealso" tag should be printed;
425
* <code>false</code> if a "see" tag should be printed
427
private static void printSeeTag(PrintWriter pw, ProgramElementDoc memberDoc, SeeTag seeTag, boolean asSeeAlso) {
428
String label = seeTag.label();
429
String text = seeTag.text();
430
boolean isAnchor = (text.startsWith("<a") && text.endsWith("</a>"));
432
ProgramElementDoc referencedMemberDoc = seeTag.referencedMember();
435
endIndex = text.indexOf('>') + 1;
437
if (endIndex == text.length()) {
438
ERROR_REPORTER.printError("Invalid anchor '" + text + "' for '" + memberDoc.qualifiedName() + "'");
444
// If the member reference is null, attempt to use the referenced class
445
if (referencedMemberDoc == null) {
446
referencedMemberDoc = seeTag.referencedClass();
449
if (referencedMemberDoc == null) {
450
ERROR_REPORTER.printError("Unable to locate reference '" + text + "' for '" + memberDoc.qualifiedName() + "'");
452
if (label == null || label.trim().length() == 0) {
455
printText(pw, label);
462
String type = (asSeeAlso) ? "seealso" : "see";
472
pw.print(text.substring(2, endIndex));
473
printText(pw, text.substring(endIndex, text.length() - 4));
475
pw.print(" cref=\"");
476
printReference(pw, referencedMemberDoc, true);
479
if (label == null || label.trim().length() == 0) {
480
printReference(pw, referencedMemberDoc, false);
482
printText(pw, label);
496
* Prints the documentation for the specified tag.
498
* @param pw the writer to print the tag documentation to
499
* @param referenceDoc the member to document the tags for
500
* @param label the label to print for the tag documentation
501
* @param tagName the name of the tags to print the documentation for
503
private static void printTags(PrintWriter pw, ProgramElementDoc referenceDoc, String label, String tagName) {
504
Tag[] tags = referenceDoc.tags(tagName);
506
for (Tag tag : tags) {
507
pw.print("<para><c>");
510
printComment(pw, referenceDoc, tag.inlineTags());
511
pw.println("</para>");
516
* Prints the specified reference.
518
* @param pw the writer to print the reference to
519
* @param referenceDoc the reference to print
520
* @param includeType <code>true</code> if the type identifier should be included;
521
* <code>false</code> if the type identifier should be omitted
523
private static void printReference(PrintWriter pw, ProgramElementDoc referenceDoc, boolean includeType) {
524
ClassDoc classDoc = (referenceDoc.isClass() || referenceDoc.isInterface()) ? (ClassDoc) referenceDoc : referenceDoc.containingClass();
527
if (referenceDoc.isField()) {
528
if (referenceDoc.isFinal() && !classDoc.isInterface()) {
533
} else if (referenceDoc.isConstructor() || referenceDoc.isMethod()) {
540
pw.print(classDoc.qualifiedName());
542
if (referenceDoc.isField()) {
543
if (classDoc.isInterface()) {
544
pw.print(".__Fields.");
549
pw.print(referenceDoc.name());
550
} else if (referenceDoc.isConstructor()) {
552
printParameters(pw, (ConstructorDoc) referenceDoc);
553
} else if (referenceDoc.isMethod()){
555
pw.print(referenceDoc.name());
556
printParameters(pw, (MethodDoc) referenceDoc);
561
* Prints comment tags.
563
* @param pw the writer to print the comment tags to
564
* @param memberDoc the member to print the comment tags for
565
* @param commentTags the comment tags to print
567
private static void printComment(PrintWriter pw, ProgramElementDoc memberDoc, Tag[] commentTags) {
568
for (Tag tag : commentTags) {
569
if (tag instanceof SeeTag) {
570
SeeTag seeTag = (SeeTag) tag;
572
printSeeTag(pw, memberDoc, seeTag, false);
574
String text = tag.text();
576
printText(pw, memberDoc, text);
582
* Prints the specified javadoc text in a .NET XML documentation format.
584
* @param pw the writer to print the comment tags to
585
* @param memberDoc the member to print the comment tags for
586
* @param text the text to print
588
private static void printText(PrintWriter pw, ProgramElementDoc memberDoc, String text) {
589
char[] characters = text.toCharArray();
591
for (int index = 0; index < characters.length; index++) {
592
char character = characters[index];
596
int x = Character.offsetByCodePoints(text, 0, index);
598
System.out.println("x = " + x);
601
int endIndex = text.indexOf('>', index);
603
// Handle invalid HTML (use of "<" or "<>" in text)
604
if (endIndex == -1 || endIndex - index < 2) {
609
String tag = text.substring(index + 1, endIndex).trim().toLowerCase();
610
boolean isEndTag = false;
611
boolean isStandAloneTag = false;
613
if (tag.length() > 1) {
614
if (tag.startsWith("/")) {
615
tag = tag.substring(1);
617
} else if (tag.endsWith("/")) {
618
tag = tag.substring(0, tag.length() - 1);
619
isStandAloneTag = true;
624
* Process/convert HTML tags to .NET XML
627
if ("p".equals(tag)) {
628
// Translate <p> to <para/>; ignore end tags
634
} else if ("br".equals(tag) || "hr".equals(tag) || "img".equals(tag)) {
642
} else if (OUTPUT_HTML) {
643
if ("code".equals(tag)) {
644
// Translate "code" tags to "c" tags
646
} else if ("li".equals(tag)) {
647
// Translate "li" tags to "item" tags
649
} else if ("ol".equals(tag)) {
650
// Translate "ol" tags to "list" tags
654
tag += " type=\"number\"";
656
} else if ("pre".equals(tag)) {
657
// Translate "pre" tags to "code" tags
659
} else if ("ul".equals(tag)) {
660
// Translate "ul" tags to "list" tags
664
tag += " type=\"bullet\"";
676
if (isStandAloneTag) {
691
// TODO: Update to handle HTML escape sequences ( , &#nnnn;, etc)
707
* Prints the specified text and escapes any XML characters.
709
* @param pw the writer to print the text to
710
* @param text the text to print
712
private static void printText(PrintWriter pw, String text) {
713
char[] characters = text.toCharArray();
715
for (int index = 0; index < characters.length; index++) {
716
char character = characters[index];
726
// TODO: Update to handle HTML escape sequences ( , &#nnnn;, etc)
742
* Prints an indentation a specified number of times.
744
* @param pw the writer to print the indentations to
745
* @param indentations the number of indentations to print
747
public static void printIndent(PrintWriter pw, int indentations) {
748
for (int i = 0; i < indentations; i++) {
754
* Validates the specified file is well formed XML.
756
* @param file the file to validate
757
* @throws Exception if a failure occurs while validating the file
759
private static void validate(File file) throws Exception {
760
SAXParserFactory factory = SAXParserFactory.newInstance();
761
SAXParser parser = factory.newSAXParser();
763
parser.parse(file, new DefaultHandler() {
764
public void error(SAXParseException e) throws SAXException {
768
public void fatalError(SAXParseException e) throws SAXException {
769
ERROR_REPORTER.printError(e.getMessage());
770
ERROR_REPORTER.printError("Line: " + e.getLineNumber() + ", Column: " + e.getColumnNumber());
777
* Execute IKVMDoc without the use of the javadoc executable.
779
* @param args the program arguments
781
public static void main(String[] args) {
782
Main.execute("ikvmdoc", IKVMDoc.class.getName(), args);
786
* Check for ikvmdoc specific options. Returns the number of arguments that must be specified on the command
787
* line for the given option. For example, "-assembly IKVM.OpenJDK.ClassLibrary.dll" would return 2.
789
* @param option the option to evaluate and return the number of arguments for
790
* @return number of arguments on the command line for an option including the option name itself.
791
* Zero return means option not known. Negative value means error occurred.
793
public static int optionLength(String option) {
794
if (ASSEMBLY_PARAMETER.equals(option)) {
796
} else if (STRICT_FINAL_FIELD_SEMANTICS_PARAMETER.equals(option)) {
798
} else if (HTML_PARAMETER.equals(option)) {
800
} else if (AUTHOR_PARAMETER.equals(option)) {
802
} else if (DEPRECATED_PARAMETER.equals(option)) {
804
} else if (SINCE_PARAMETER.equals(option)) {
806
} else if (VERSION_PARAMETER.equals(option)) {
814
* Check that ikvmdoc options have the correct arguments.
816
* @param options the options to check
817
* @param reporter the error reported used to report any failures to
818
* @return <code>true</code> if the options are valid;
819
* <code>false</code> if the options are invalid
821
public static boolean validOptions(String[][] options, DocErrorReporter reporter) {
822
ERROR_REPORTER = reporter;
824
for (String[] option : options) {
825
if (ASSEMBLY_PARAMETER.equals(option[0])) {
826
ASSEMBLY_FILE = new File(option[1]);
828
if (!ASSEMBLY_FILE.isFile() || !ASSEMBLY_FILE.exists()) {
829
reporter.printError("The assembly file specified '" + ASSEMBLY_FILE.getAbsolutePath() + "' is invalid.");
832
} else if (HTML_PARAMETER.equals(option[0])) {
834
} else if (AUTHOR_PARAMETER.equals(option[0])) {
835
OUTPUT_AUTHOR = true;
836
} else if (DEPRECATED_PARAMETER.equals(option[0])) {
837
OUTPUT_DEPRECATED = false;
838
} else if (SINCE_PARAMETER.equals(option[0])) {
839
OUTPUT_SINCE = false;
840
} else if (VERSION_PARAMETER.equals(option[0])) {
841
OUTPUT_VERSION = true;