1package jdiff;
2
3import com.sun.javadoc.*;
4import com.sun.javadoc.ParameterizedType;
5import com.sun.javadoc.Type;
6
7import java.util.*;
8import java.io.*;
9import java.lang.reflect.*;
10
11/**
12 * Converts a Javadoc RootDoc object into a representation in an
13 * XML file.
14 *
15 * See the file LICENSE.txt for copyright details.
16 * @author Matthew Doar, mdoar@pobox.com
17 */
18public class RootDocToXML {
19
20    /** Default constructor. */
21    public RootDocToXML() {
22    }
23
24    /**
25     * Write the XML representation of the API to a file.
26     *
27     * @param root  the RootDoc object passed by Javadoc
28     * @return true if no problems encountered
29     */
30    public static boolean writeXML(RootDoc root) {
31    	String tempFileName = outputFileName;
32    	if (outputDirectory != null) {
33	    tempFileName = outputDirectory;
34	    if (!tempFileName.endsWith(JDiff.DIR_SEP))
35		tempFileName += JDiff.DIR_SEP;
36	    tempFileName += outputFileName;
37    	}
38
39        try {
40            FileOutputStream fos = new FileOutputStream(tempFileName);
41            outputFile = new PrintWriter(fos);
42            System.out.println("JDiff: writing the API to file '" + tempFileName + "'...");
43            if (root.specifiedPackages().length != 0 || root.specifiedClasses().length != 0) {
44                RootDocToXML apiWriter = new RootDocToXML();
45                apiWriter.emitXMLHeader();
46                apiWriter.logOptions();
47                apiWriter.processPackages(root);
48                apiWriter.emitXMLFooter();
49            }
50            outputFile.close();
51        } catch(IOException e) {
52            System.out.println("IO Error while attempting to create " + tempFileName);
53            System.out.println("Error: " +  e.getMessage());
54            System.exit(1);
55        }
56        // If validation is desired, write out the appropriate api.xsd file
57        // in the same directory as the XML file.
58        if (XMLToAPI.validateXML) {
59            writeXSD();
60        }
61        return true;
62    }
63
64    /**
65     * Write the XML Schema file used for validation.
66     */
67    public static void writeXSD() {
68        String xsdFileName = outputFileName;
69        if (outputDirectory == null) {
70	    int idx = xsdFileName.lastIndexOf('\\');
71	    int idx2 = xsdFileName.lastIndexOf('/');
72	    if (idx == -1 && idx2 == -1) {
73		xsdFileName = "";
74	    } else if (idx == -1 && idx2 != -1) {
75		xsdFileName = xsdFileName.substring(0, idx2);
76	    } else if (idx != -1  && idx2 == -1) {
77		xsdFileName = xsdFileName.substring(0, idx);
78	    } else if (idx != -1  && idx2 != -1) {
79		int max = idx2 > idx ? idx2 : idx;
80		xsdFileName = xsdFileName.substring(0, max);
81	    }
82	} else {
83	    xsdFileName = outputDirectory;
84	    if (!xsdFileName.endsWith(JDiff.DIR_SEP))
85		 xsdFileName += JDiff.DIR_SEP;
86	}
87        xsdFileName += "api.xsd";
88        try {
89            FileOutputStream fos = new FileOutputStream(xsdFileName);
90            PrintWriter xsdFile = new PrintWriter(fos);
91            // The contents of the api.xsd file
92            xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
93            xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
94            xsdFile.println("");
95            xsdFile.println("<xsd:annotation>");
96            xsdFile.println("  <xsd:documentation>");
97            xsdFile.println("  Schema for JDiff API representation.");
98            xsdFile.println("  </xsd:documentation>");
99            xsdFile.println("</xsd:annotation>");
100            xsdFile.println();
101            xsdFile.println("<xsd:element name=\"api\" type=\"apiType\"/>");
102            xsdFile.println("");
103            xsdFile.println("<xsd:complexType name=\"apiType\">");
104            xsdFile.println("  <xsd:sequence>");
105            xsdFile.println("    <xsd:element name=\"package\" type=\"packageType\" minOccurs='1' maxOccurs='unbounded'/>");
106            xsdFile.println("  </xsd:sequence>");
107            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
108            xsdFile.println("  <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>");
109            xsdFile.println("</xsd:complexType>");
110            xsdFile.println();
111            xsdFile.println("<xsd:complexType name=\"packageType\">");
112            xsdFile.println("  <xsd:sequence>");
113            xsdFile.println("    <xsd:choice maxOccurs='unbounded'>");
114            xsdFile.println("      <xsd:element name=\"class\" type=\"classType\"/>");
115            xsdFile.println("      <xsd:element name=\"interface\" type=\"classType\"/>");
116            xsdFile.println("    </xsd:choice>");
117            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
118            xsdFile.println("  </xsd:sequence>");
119            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
120            xsdFile.println("</xsd:complexType>");
121            xsdFile.println();
122            xsdFile.println("<xsd:complexType name=\"classType\">");
123            xsdFile.println("  <xsd:sequence>");
124            xsdFile.println("    <xsd:element name=\"implements\" type=\"interfaceTypeName\" minOccurs='0' maxOccurs='unbounded'/>");
125            xsdFile.println("    <xsd:element name=\"constructor\" type=\"constructorType\" minOccurs='0' maxOccurs='unbounded'/>");
126            xsdFile.println("    <xsd:element name=\"method\" type=\"methodType\" minOccurs='0' maxOccurs='unbounded'/>");
127            xsdFile.println("    <xsd:element name=\"field\" type=\"fieldType\" minOccurs='0' maxOccurs='unbounded'/>");
128            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
129            xsdFile.println("  </xsd:sequence>");
130            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
131            xsdFile.println("  <xsd:attribute name=\"extends\" type=\"xsd:string\" use='optional'/>");
132            xsdFile.println("  <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>");
133            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
134            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
135            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
136            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
137            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
138            xsdFile.println("</xsd:complexType>");
139            xsdFile.println();
140            xsdFile.println("<xsd:complexType name=\"interfaceTypeName\">");
141            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
142            xsdFile.println("</xsd:complexType>");
143            xsdFile.println();
144            xsdFile.println("<xsd:complexType name=\"constructorType\">");
145            xsdFile.println("  <xsd:sequence>");
146            xsdFile.println("    <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>");
147            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
148            xsdFile.println("  </xsd:sequence>");
149            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
150            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\" use='optional'/>");
151            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
152            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
153            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
154            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
155            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
156            xsdFile.println("</xsd:complexType>");
157            xsdFile.println();
158            xsdFile.println("<xsd:complexType name=\"paramsType\">");
159            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
160            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
161            xsdFile.println("</xsd:complexType>");
162            xsdFile.println();
163            xsdFile.println("<xsd:complexType name=\"exceptionType\">");
164            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
165            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
166            xsdFile.println("</xsd:complexType>");
167            xsdFile.println();
168            xsdFile.println("<xsd:complexType name=\"methodType\">");
169            xsdFile.println("  <xsd:sequence>");
170            xsdFile.println("    <xsd:element name=\"param\" type=\"paramsType\" minOccurs='0' maxOccurs='unbounded'/>");
171            xsdFile.println("    <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>");
172            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
173            xsdFile.println("  </xsd:sequence>");
174            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
175            xsdFile.println("  <xsd:attribute name=\"return\" type=\"xsd:string\" use='optional'/>");
176            xsdFile.println("  <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>");
177            xsdFile.println("  <xsd:attribute name=\"native\" type=\"xsd:boolean\"/>");
178            xsdFile.println("  <xsd:attribute name=\"synchronized\" type=\"xsd:boolean\"/>");
179            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
180            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
181            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
182            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
183            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
184            xsdFile.println("</xsd:complexType>");
185            xsdFile.println();
186            xsdFile.println("<xsd:complexType name=\"fieldType\">");
187            xsdFile.println("  <xsd:sequence>");
188            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
189            xsdFile.println("  </xsd:sequence>");
190            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
191            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
192            xsdFile.println("  <xsd:attribute name=\"transient\" type=\"xsd:boolean\"/>");
193            xsdFile.println("  <xsd:attribute name=\"volatile\" type=\"xsd:boolean\"/>");
194            xsdFile.println("  <xsd:attribute name=\"value\" type=\"xsd:string\" use='optional'/>");
195            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
196            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
197            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
198            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
199            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
200            xsdFile.println("</xsd:complexType>");
201            xsdFile.println();
202            xsdFile.println("</xsd:schema>");
203            xsdFile.close();
204        } catch(IOException e) {
205            System.out.println("IO Error while attempting to create " + xsdFileName);
206            System.out.println("Error: " +  e.getMessage());
207            System.exit(1);
208        }
209    }
210
211    /**
212     * Write the options which were used to generate this XML file
213     * out as XML comments.
214     */
215    public void logOptions() {
216        outputFile.print("<!-- ");
217        outputFile.print(" Command line arguments = " + Options.cmdOptions);
218        outputFile.println(" -->");
219    }
220
221    /**
222     * Process each package and the classes/interfaces within it.
223     *
224     * @param pd  an array of PackageDoc objects
225     */
226    public void processPackages(RootDoc root) {
227        PackageDoc[] specified_pd = root.specifiedPackages();
228	Map pdl = new TreeMap();
229        for (int i = 0; specified_pd != null && i < specified_pd.length; i++) {
230	    pdl.put(specified_pd[i].name(), specified_pd[i]);
231	}
232
233	// Classes may be specified separately, so merge their packages into the
234	// list of specified packages.
235        ClassDoc[] cd = root.specifiedClasses();
236	// This is lists of the specific classes to document
237	Map classesToUse = new HashMap();
238        for (int i = 0; cd != null && i < cd.length; i++) {
239	    PackageDoc cpd = cd[i].containingPackage();
240	    if (cpd == null && !packagesOnly) {
241		// If the RootDoc object has been created from a jar file
242		// this duplicates classes, so we have to be able to disable it.
243		// TODO this is still null?
244		cpd = root.packageNamed("anonymous");
245	    }
246            String pkgName = cpd.name();
247            String className = cd[i].name();
248	    if (trace) System.out.println("Found package " + pkgName + " for class " + className);
249	    if (!pdl.containsKey(pkgName)) {
250		if (trace) System.out.println("Adding new package " + pkgName);
251		pdl.put(pkgName, cpd);
252	    }
253
254	    // Keep track of the specific classes to be used for this package
255	    List classes;
256	    if (classesToUse.containsKey(pkgName)) {
257		classes = (ArrayList) classesToUse.get(pkgName);
258	    } else {
259		classes = new ArrayList();
260	    }
261	    classes.add(cd[i]);
262	    classesToUse.put(pkgName, classes);
263	}
264
265	PackageDoc[] pd = (PackageDoc[]) pdl.values().toArray(new PackageDoc[0]);
266        for (int i = 0; pd != null && i < pd.length; i++) {
267            String pkgName = pd[i].name();
268
269            // Check for an exclude tag in the package doc block, but not
270	    // in the package.htm[l] file.
271            if (!shownElement(pd[i], null))
272                continue;
273
274            if (trace) System.out.println("PROCESSING PACKAGE: " + pkgName);
275            outputFile.println("<package name=\"" + pkgName + "\">");
276
277            int tagCount = pd[i].tags().length;
278            if (trace) System.out.println("#tags: " + tagCount);
279
280            List classList;
281	    if (classesToUse.containsKey(pkgName)) {
282		// Use only the specified classes in the package
283		System.out.println("Using the specified classes");
284		classList = (ArrayList) classesToUse.get(pkgName);
285	    } else {
286		// Use all classes in the package
287		classList = new LinkedList(Arrays.asList(pd[i].allClasses()));
288	    }
289            Collections.sort(classList);
290            ClassDoc[] classes = new ClassDoc[classList.size()];
291            classes = (ClassDoc[])classList.toArray(classes);
292            processClasses(classes, pkgName);
293
294            addPkgDocumentation(root, pd[i], 2);
295
296            outputFile.println("</package>");
297        }
298    } // processPackages
299
300    /**
301     * Process classes and interfaces.
302     *
303     * @param cd An array of ClassDoc objects.
304     */
305    public void processClasses(ClassDoc[] cd, String pkgName) {
306        if (cd.length == 0)
307            return;
308        if (trace) System.out.println("PROCESSING CLASSES, number=" + cd.length);
309        for (int i = 0; i < cd.length; i++) {
310            String className = cd[i].name();
311            if (trace) System.out.println("PROCESSING CLASS/IFC: " + className);
312            // Only save the shown elements
313            if (!shownElement(cd[i], classVisibilityLevel))
314                continue;
315            boolean isInterface = false;
316            if (cd[i].isInterface())
317                isInterface = true;
318            if (isInterface) {
319                outputFile.println("  <!-- start interface " + pkgName + "." + className + " -->");
320                outputFile.print("  <interface name=\"" + className + "\"");
321            } else {
322                outputFile.println("  <!-- start class " + pkgName + "." + className + " -->");
323                outputFile.print("  <class name=\"" + className + "\"");
324            }
325            // Add attributes to the class element
326            Type parent = cd[i].superclassType();
327            if (parent != null)
328                outputFile.println(" extends=\"" + buildEmittableTypeString(parent) + "\"");
329            outputFile.println("    abstract=\"" + cd[i].isAbstract() + "\"");
330            addCommonModifiers(cd[i], 4);
331            outputFile.println(">");
332            // Process class members. (Treat inner classes as members.)
333            processInterfaces(cd[i].interfaceTypes());
334            processConstructors(cd[i].constructors());
335            processMethods(cd[i], cd[i].methods());
336            processFields(cd[i].fields());
337
338            addDocumentation(cd[i], 4);
339
340            if (isInterface) {
341                outputFile.println("  </interface>");
342                outputFile.println("  <!-- end interface " + pkgName + "." + className + " -->");
343            } else {
344                outputFile.println("  </class>");
345                outputFile.println("  <!-- end class " + pkgName + "." + className + " -->");
346            }
347            // Inner classes have already been added.
348            /*
349              ClassDoc[] ic = cd[i].innerClasses();
350              for (int k = 0; k < ic.length; k++) {
351              System.out.println("Inner class " + k + ", name = " + ic[k].name());
352              }
353            */
354        }//for
355    }//processClasses()
356
357    /**
358     * Add qualifiers for the program element as attributes.
359     *
360     * @param ped The given program element.
361     */
362    public void addCommonModifiers(ProgramElementDoc ped, int indent) {
363        addSourcePosition(ped, indent);
364        // Static and final and visibility on one line
365        for (int i = 0; i < indent; i++) outputFile.print(" ");
366        outputFile.print("static=\"" + ped.isStatic() + "\"");
367        outputFile.print(" final=\"" + ped.isFinal() + "\"");
368        // Visibility
369        String visibility = null;
370        if (ped.isPublic())
371            visibility = "public";
372        else if (ped.isProtected())
373            visibility = "protected";
374        else if (ped.isPackagePrivate())
375            visibility = "package";
376        else if (ped.isPrivate())
377            visibility = "private";
378        outputFile.println(" visibility=\"" + visibility + "\"");
379
380        // Deprecation on its own line
381        for (int i = 0; i < indent; i++) outputFile.print(" ");
382        boolean isDeprecated = false;
383        Tag[] ta = ((Doc)ped).tags("deprecated");
384        if (ta.length != 0) {
385            isDeprecated = true;
386        }
387        if (ta.length > 1) {
388            System.out.println("JDiff: warning: multiple @deprecated tags found in comments for " + ped.name() + ". Using the first one only.");
389            System.out.println("Text is: " + ((Doc)ped).getRawCommentText());
390        }
391        if (isDeprecated) {
392            String text = ta[0].text(); // Use only one @deprecated tag
393            if (text != null && text.compareTo("") != 0) {
394                int idx = endOfFirstSentence(text);
395                if (idx == 0) {
396                    // No useful comment
397                    outputFile.print("deprecated=\"deprecated, no comment\"");
398                } else {
399                    String fs = null;
400                    if (idx == -1)
401                        fs = text;
402                    else
403                        fs = text.substring(0, idx+1);
404                    String st = API.hideHTMLTags(fs);
405                    outputFile.print("deprecated=\"" + st + "\"");
406                }
407            } else {
408                outputFile.print("deprecated=\"deprecated, no comment\"");
409            }
410        } else {
411            outputFile.print("deprecated=\"not deprecated\"");
412        }
413
414    } //addQualifiers()
415
416    /**
417     * Insert the source code details, if available.
418     *
419     * @param ped The given program element.
420     */
421    public void addSourcePosition(ProgramElementDoc ped, int indent) {
422        if (!addSrcInfo)
423            return;
424        if (JDiff.javaVersion.startsWith("1.1") ||
425            JDiff.javaVersion.startsWith("1.2") ||
426            JDiff.javaVersion.startsWith("1.3")) {
427            return; // position() only appeared in J2SE1.4
428        }
429        try {
430            // Could cache the method for improved performance
431            Class c = ProgramElementDoc.class;
432            Method m = c.getMethod("position", (Class[]) null);
433            Object sp = m.invoke(ped, (Object[]) null);
434            if (sp != null) {
435                for (int i = 0; i < indent; i++) outputFile.print(" ");
436                outputFile.println("src=\"" + sp + "\"");
437            }
438        } catch (NoSuchMethodException e2) {
439            System.err.println("Error: method \"position\" not found");
440            e2.printStackTrace();
441        } catch (IllegalAccessException e4) {
442            System.err.println("Error: class not permitted to be instantiated");
443            e4.printStackTrace();
444        } catch (InvocationTargetException e5) {
445            System.err.println("Error: method \"position\" could not be invoked");
446            e5.printStackTrace();
447        } catch (Exception e6) {
448            System.err.println("Error: ");
449            e6.printStackTrace();
450        }
451    }
452
453    /**
454     * Process the interfaces implemented by the class.
455     *
456     * @param ifaces An array of ClassDoc objects
457     */
458    public void processInterfaces(Type[] ifaces) {
459        if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.length);
460        for (int i = 0; i < ifaces.length; i++) {
461            String ifaceName = buildEmittableTypeString(ifaces[i]);
462            if (trace) System.out.println("PROCESSING INTERFACE: " + ifaceName);
463            outputFile.println("    <implements name=\"" + ifaceName + "\"/>");
464        }//for
465    }//processInterfaces()
466
467    /**
468     * Process the constructors in the class.
469     *
470     * @param ct An array of ConstructorDoc objects
471     */
472    public void processConstructors(ConstructorDoc[] ct) {
473        if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ct.length);
474        for (int i = 0; i < ct.length; i++) {
475            String ctorName = ct[i].name();
476            if (trace) System.out.println("PROCESSING CONSTRUCTOR: " + ctorName);
477            // Only save the shown elements
478            if (!shownElement(ct[i], memberVisibilityLevel))
479                continue;
480            outputFile.print("    <constructor name=\"" + ctorName + "\"");
481
482            Parameter[] params = ct[i].parameters();
483            boolean first = true;
484            if (params.length != 0) {
485                outputFile.print(" type=\"");
486                for (int j = 0; j < params.length; j++) {
487                    if (!first)
488                        outputFile.print(", ");
489                    emitType(params[j].type());
490                    first = false;
491                }
492                outputFile.println("\"");
493            } else
494                outputFile.println();
495            addCommonModifiers(ct[i], 6);
496            outputFile.println(">");
497
498            // Generate the exception elements if any exceptions are thrown
499            processExceptions(ct[i].thrownExceptions());
500
501            addDocumentation(ct[i], 6);
502
503            outputFile.println("    </constructor>");
504        }//for
505    }//processConstructors()
506
507    /**
508     * Process all exceptions thrown by a constructor or method.
509     *
510     * @param cd An array of ClassDoc objects
511     */
512    public void processExceptions(ClassDoc[] cd) {
513        if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + cd.length);
514        for (int i = 0; i < cd.length; i++) {
515            String exceptionName = cd[i].name();
516            if (trace) System.out.println("PROCESSING EXCEPTION: " + exceptionName);
517            outputFile.print("      <exception name=\"" + exceptionName + "\" type=\"");
518            emitType(cd[i]);
519            outputFile.println("\"/>");
520        }//for
521    }//processExceptions()
522
523    /**
524     * Process the methods in the class.
525     *
526     * @param md An array of MethodDoc objects
527     */
528    public void processMethods(ClassDoc cd, MethodDoc[] md) {
529        if (trace) System.out.println("PROCESSING " +cd.name()+" METHODS, number = " + md.length);
530        for (int i = 0; i < md.length; i++) {
531            String methodName = md[i].name();
532            if (trace) System.out.println("PROCESSING METHOD: " + methodName);
533            // Skip <init> and <clinit>
534            if (methodName.startsWith("<"))
535                continue;
536            // Only save the shown elements
537            if (!shownElement(md[i], memberVisibilityLevel))
538                continue;
539            outputFile.print("    <method name=\"" + methodName + "\"");
540            com.sun.javadoc.Type retType = md[i].returnType();
541            if (retType.qualifiedTypeName().compareTo("void") == 0) {
542                // Don't add a return attribute if the return type is void
543                outputFile.println();
544            } else {
545                outputFile.print(" return=\"");
546                emitType(retType);
547                outputFile.println("\"");
548            }
549            outputFile.print("      abstract=\"" + md[i].isAbstract() + "\"");
550            outputFile.print(" native=\"" + md[i].isNative() + "\"");
551            outputFile.println(" synchronized=\"" + md[i].isSynchronized() + "\"");
552            addCommonModifiers(md[i], 6);
553            outputFile.println(">");
554            // Generate the parameter elements, if any
555            Parameter[] params = md[i].parameters();
556            for (int j = 0; j < params.length; j++) {
557                outputFile.print("      <param name=\"" + params[j].name() + "\"");
558                outputFile.print(" type=\"");
559                emitType(params[j].type());
560                outputFile.println("\"/>");
561            }
562
563            // Generate the exception elements if any exceptions are thrown
564            processExceptions(md[i].thrownExceptions());
565
566            addDocumentation(md[i], 6);
567
568            outputFile.println("    </method>");
569        }//for
570    }//processMethods()
571
572    /**
573     * Process the fields in the class.
574     *
575     * @param fd An array of FieldDoc objects
576     */
577    public void processFields(FieldDoc[] fd) {
578        if (trace) System.out.println("PROCESSING FIELDS, number=" + fd.length);
579        for (int i = 0; i < fd.length; i++) {
580            String fieldName = fd[i].name();
581            if (trace) System.out.println("PROCESSING FIELD: " + fieldName);
582            // Only save the shown elements
583            if (!shownElement(fd[i], memberVisibilityLevel))
584                continue;
585            outputFile.print("    <field name=\"" + fieldName + "\"");
586            outputFile.print(" type=\"");
587            emitType(fd[i].type());
588            outputFile.println("\"");
589            outputFile.print("      transient=\"" + fd[i].isTransient() + "\"");
590            outputFile.println(" volatile=\"" + fd[i].isVolatile() + "\"");
591/* JDK 1.4 and later */
592/*
593            String value = fd[i].constantValueExpression();
594            if (value != null)
595                outputFile.println(" value=\"" + value + "\"");
596*/
597            addCommonModifiers(fd[i], 6);
598            outputFile.println(">");
599
600            addDocumentation(fd[i], 6);
601
602            outputFile.println("    </field>");
603
604        }//for
605    }//processFields()
606
607    /**
608     * Emit the type name. Removed any prefixed warnings about ambiguity.
609     * The type maybe an array.
610     *
611     * @param type A Type object.
612     */
613    public void emitType(com.sun.javadoc.Type type) {
614        String name = buildEmittableTypeString(type);
615        if (name == null)
616            return;
617        outputFile.print(name);
618    }
619
620    /**
621     * Build the emittable type name. The type may be an array and/or
622     * a generic type.
623     *
624     * @param type A Type object
625     * @return The emittable type name
626     */
627    private String buildEmittableTypeString(com.sun.javadoc.Type type) {
628        if (type == null) {
629    	    return null;
630        }
631      // type.toString() returns the fully qualified name of the type
632      // including the dimension and the parameters we just need to
633      // escape the generic parameters brackets so that the XML
634      // generated is correct
635      String name = type.toString().replaceAll("<", "&lt;").replaceAll(">", "&gt;");
636      if (name.startsWith("<<ambiguous>>")) {
637          name = name.substring(13);
638      }
639      return name;
640    }
641
642    /**
643     * Emit the XML header.
644     */
645    public void emitXMLHeader() {
646        outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
647        outputFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
648        outputFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
649        outputFile.println("<!-- on " + new Date() + " -->");
650        outputFile.println();
651/* No need for this any longer, since doc block text is in an CDATA element
652        outputFile.println("<!-- XML Schema is used, but XHTML transitional DTD is needed for nbsp -->");
653        outputFile.println("<!-- entity definitions etc.-->");
654        outputFile.println("<!DOCTYPE api");
655        outputFile.println("     PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"");
656        outputFile.println("     \"" + baseURI + "/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
657*/
658        outputFile.println("<api");
659        outputFile.println("  xmlns:xsi='" + baseURI + "/2001/XMLSchema-instance'");
660        outputFile.println("  xsi:noNamespaceSchemaLocation='api.xsd'");
661        outputFile.println("  name=\"" + apiIdentifier + "\"");
662        outputFile.println("  jdversion=\"" + JDiff.version + "\">");
663        outputFile.println();
664    }
665
666    /**
667     * Emit the XML footer.
668     */
669    public void emitXMLFooter() {
670        outputFile.println();
671        outputFile.println("</api>");
672    }
673
674    /**
675     * Determine if the program element is shown, according to the given
676     * level of visibility.
677     *
678     * @param ped The given program element.
679     * @param visLevel The desired visibility level; "public", "protected",
680     *   "package" or "private". If null, only check for an exclude tag.
681     * @return boolean Set if this element is shown.
682     */
683    public boolean shownElement(Doc doc, String visLevel) {
684        // If a doc block contains @exclude or a similar such tag,
685        // then don't display it.
686	if (doExclude && excludeTag != null && doc != null) {
687            String rct = doc.getRawCommentText();
688            if (rct != null && rct.indexOf(excludeTag) != -1) {
689                return false;
690	    }
691	}
692	if (visLevel == null) {
693	    return true;
694	}
695	ProgramElementDoc ped = null;
696	if (doc instanceof ProgramElementDoc) {
697	    ped = (ProgramElementDoc)doc;
698	}
699        if (visLevel.compareTo("private") == 0)
700            return true;
701        // Show all that is not private
702        if (visLevel.compareTo("package") == 0)
703            return !ped.isPrivate();
704        // Show all that is not private or package
705        if (visLevel.compareTo("protected") == 0)
706            return !(ped.isPrivate() || ped.isPackagePrivate());
707        // Show all that is not private or package or protected,
708        // i.e. all that is public
709        if (visLevel.compareTo("public") == 0)
710            return ped.isPublic();
711        return false;
712    } //shownElement()
713
714    /**
715     * Strip out non-printing characters, replacing them with a character
716     * which will not change where the end of the first sentence is found.
717     * This character is the hash mark, '&#035;'.
718     */
719    public String stripNonPrintingChars(String s, Doc doc) {
720        if (!stripNonPrintables)
721            return s;
722        char[] sa = s.toCharArray();
723        for (int i = 0; i < sa.length; i++) {
724            char c = sa[i];
725            // TODO still have an issue with Unicode: 0xfc in java.lang.String.toUpperCase comments
726//            if (Character.isDefined(c))
727            if (Character.isLetterOrDigit(c))
728                continue;
729            // There must be a better way that is still platform independent!
730            if (c == ' ' ||
731                c == '.' ||
732                c == ',' ||
733                c == '\r' ||
734                c == '\t' ||
735                c == '\n' ||
736                c == '!' ||
737                c == '?' ||
738                c == ';' ||
739                c == ':' ||
740                c == '[' ||
741                c == ']' ||
742                c == '(' ||
743                c == ')' ||
744                c == '~' ||
745                c == '@' ||
746                c == '#' ||
747                c == '$' ||
748                c == '%' ||
749                c == '^' ||
750                c == '&' ||
751                c == '*' ||
752                c == '-' ||
753                c == '=' ||
754                c == '+' ||
755                c == '_' ||
756                c == '|' ||
757                c == '\\' ||
758                c == '/' ||
759                c == '\'' ||
760                c == '}' ||
761                c == '{' ||
762                c == '"' ||
763                c == '<' ||
764                c == '>' ||
765                c == '`'
766                )
767                continue;
768/* Doesn't seem to return the expected values?
769            int val = Character.getNumericValue(c);
770//            if (s.indexOf("which is also a test for non-printable") != -1)
771//                System.out.println("** Char " + i + "[" + c + "], val =" + val); //DEBUG
772            // Ranges from http://www.unicode.org/unicode/reports/tr20/
773            // Should really replace 0x2028 and  0x2029 with <br/>
774            if (val == 0x0 ||
775                inRange(val, 0x2028, 0x2029) ||
776                inRange(val, 0x202A, 0x202E) ||
777                inRange(val, 0x206A, 0x206F) ||
778                inRange(val, 0xFFF9, 0xFFFC) ||
779                inRange(val, 0xE0000, 0xE007F)) {
780                if (trace) {
781                    System.out.println("Warning: changed non-printing character  " + sa[i] + " in " + doc.name());
782                }
783                sa[i] = '#';
784            }
785*/
786            // Replace the non-printable character with a printable character
787            // which does not change the end of the first sentence
788            sa[i] = '#';
789        }
790        return new String(sa);
791    }
792
793    /** Return true if val is in the range [min|max], inclusive. */
794    public boolean inRange(int val, int min, int max) {
795        if (val < min)
796            return false;
797        if (val > max)
798            return false;
799        return true;
800    }
801
802    /**
803     * Add at least the first sentence from a doc block to the API. This is
804     * used by the report generator if no comment is provided.
805     * Need to make sure that HTML tags are not confused with XML tags.
806     * This could be done by stuffing the &lt; character to another string
807     * or by handling HTML in the parser. This second option seems neater. Note that
808     * XML expects all element tags to have either a closing "/>" or a matching
809     * end element tag. Due to the difficulties of converting incorrect HTML
810     * to XHTML, the first option is used.
811     */
812    public void addDocumentation(ProgramElementDoc ped, int indent) {
813        String rct = ((Doc)ped).getRawCommentText();
814        if (rct != null) {
815            rct = stripNonPrintingChars(rct, (Doc)ped);
816            rct = rct.trim();
817            if (rct.compareTo("") != 0 &&
818                rct.indexOf(Comments.placeHolderText) == -1 &&
819                rct.indexOf("InsertOtherCommentsHere") == -1) {
820                int idx = endOfFirstSentence(rct);
821                if (idx == 0)
822                    return;
823                for (int i = 0; i < indent; i++) outputFile.print(" ");
824                outputFile.println("<doc>");
825                for (int i = 0; i < indent; i++) outputFile.print(" ");
826                String firstSentence = null;
827                if (idx == -1)
828                    firstSentence = rct;
829                else
830                    firstSentence = rct.substring(0, idx+1);
831                boolean checkForAts = false;
832                if (checkForAts && firstSentence.indexOf("@") != -1 &&
833                    firstSentence.indexOf("@link") == -1) {
834                    System.out.println("Warning: @ tag seen in comment: " +
835                                       firstSentence);
836                }
837                String firstSentenceNoTags = API.stuffHTMLTags(firstSentence);
838                outputFile.println(firstSentenceNoTags);
839                for (int i = 0; i < indent; i++) outputFile.print(" ");
840                outputFile.println("</doc>");
841            }
842        }
843    }
844
845    /**
846     * Add at least the first sentence from a doc block for a package to the API. This is
847     * used by the report generator if no comment is provided.
848     * The default source tree may not include the package.html files, so
849     * this may be unavailable in many cases.
850     * Need to make sure that HTML tags are not confused with XML tags.
851     * This could be done by stuffing the &lt; character to another string
852     * or by handling HTML in the parser. This second option is neater. Note that
853     * XML expects all element tags to have either a closing "/>" or a matching
854     * end element tag.  Due to the difficulties of converting incorrect HTML
855     * to XHTML, the first option is used.
856     */
857    public void addPkgDocumentation(RootDoc root, PackageDoc pd, int indent) {
858        String rct = null;
859        String filename = pd.name();
860        try {
861            // See if the source path was specified as part of the
862            // options and prepend it if it was.
863            String srcLocation = null;
864            String[][] options = root.options();
865            for (int opt = 0; opt < options.length; opt++) {
866                if ((options[opt][0]).compareTo("-sourcepath") == 0) {
867                    srcLocation = options[opt][1];
868                    break;
869                }
870            }
871            filename = filename.replace('.', JDiff.DIR_SEP.charAt(0));
872            if (srcLocation != null) {
873                // Make a relative location absolute
874                if (srcLocation.startsWith("..")) {
875                    String curDir = System.getProperty("user.dir");
876                    while (srcLocation.startsWith("..")) {
877                        srcLocation = srcLocation.substring(3);
878                        int idx = curDir.lastIndexOf(JDiff.DIR_SEP);
879                        curDir = curDir.substring(0, idx+1);
880                    }
881                    srcLocation = curDir + srcLocation;
882                }
883                filename = srcLocation + JDiff.DIR_SEP + filename;
884            }
885            // Try both ".htm" and ".html"
886            filename += JDiff.DIR_SEP + "package.htm";
887            File f2 = new File(filename);
888            if (!f2.exists()) {
889                filename += "l";
890            }
891            FileInputStream f = new FileInputStream(filename);
892            BufferedReader d = new BufferedReader(new InputStreamReader(f));
893            String str = d.readLine();
894 	    // Ignore everything except the lines between <body> elements
895	    boolean inBody = false;
896	    while(str != null) {
897                if (!inBody) {
898		    if (str.toLowerCase().trim().startsWith("<body")) {
899			inBody = true;
900		    }
901		    str = d.readLine(); // Get the next line
902		    continue; // Ignore the line
903		} else {
904		    if (str.toLowerCase().trim().startsWith("</body")) {
905			inBody = false;
906			continue; // Ignore the line
907		    }
908		}
909                if (rct == null)
910                    rct = str + "\n";
911                else
912                    rct += str + "\n";
913                str = d.readLine();
914            }
915        }  catch(java.io.FileNotFoundException e) {
916            // If it doesn't exist, that's fine
917            if (trace)
918                System.out.println("No package level documentation file at '" + filename + "'");
919        } catch(java.io.IOException e) {
920            System.out.println("Error reading file \"" + filename + "\": " + e.getMessage());
921            System.exit(5);
922        }
923        if (rct != null) {
924            rct = stripNonPrintingChars(rct, (Doc)pd);
925            rct = rct.trim();
926            if (rct.compareTo("") != 0 &&
927                rct.indexOf(Comments.placeHolderText) == -1 &&
928                rct.indexOf("InsertOtherCommentsHere") == -1) {
929                int idx = endOfFirstSentence(rct);
930                if (idx == 0)
931                    return;
932                for (int i = 0; i < indent; i++) outputFile.print(" ");
933                outputFile.println("<doc>");
934                for (int i = 0; i < indent; i++) outputFile.print(" ");
935                String firstSentence = null;
936                if (idx == -1)
937                    firstSentence = rct;
938                else
939                    firstSentence = rct.substring(0, idx+1);
940                String firstSentenceNoTags = API.stuffHTMLTags(firstSentence);
941                outputFile.println(firstSentenceNoTags);
942                for (int i = 0; i < indent; i++) outputFile.print(" ");
943                outputFile.println("</doc>");
944            }
945        }
946    }
947
948    /**
949     * Find the index of the end of the first sentence in the given text,
950     * when writing out to an XML file.
951     * This is an extended version of the algorithm used by the DocCheck
952     * Javadoc doclet. It checks for @tags too.
953     *
954     * @param text The text to be searched.
955     * @return The index of the end of the first sentence. If there is no
956     *         end, return -1. If there is no useful text, return 0.
957     *         If the whole doc block comment is wanted (default), return -1.
958     */
959    public static int endOfFirstSentence(String text) {
960        return endOfFirstSentence(text, true);
961    }
962
963    /**
964     * Find the index of the end of the first sentence in the given text.
965     * This is an extended version of the algorithm used by the DocCheck
966     * Javadoc doclet. It checks for &#064;tags too.
967     *
968     * @param text The text to be searched.
969     * @param writingToXML Set to true when writing out XML.
970     * @return The index of the end of the first sentence. If there is no
971     *         end, return -1. If there is no useful text, return 0.
972     *         If the whole doc block comment is wanted (default), return -1.
973     */
974    public static int endOfFirstSentence(String text, boolean writingToXML) {
975        if (saveAllDocs && writingToXML)
976            return -1;
977	int textLen = text.length();
978	if (textLen == 0)
979	    return 0;
980        int index = -1;
981        // Handle some special cases
982        int fromindex = 0;
983        int ellipsis = text.indexOf(". . ."); // Handles one instance of this
984        if (ellipsis != -1)
985            fromindex = ellipsis + 5;
986        // If the first non-whitespace character is an @, go beyond it
987        int i = 0;
988        while (i < textLen && text.charAt(i) == ' ') {
989            i++;
990        }
991        if (text.charAt(i) == '@' && fromindex < textLen-1)
992            fromindex = i + 1;
993        // Use the brute force approach.
994        index = minIndex(index, text.indexOf("? ", fromindex));
995        index = minIndex(index, text.indexOf("?\t", fromindex));
996        index = minIndex(index, text.indexOf("?\n", fromindex));
997        index = minIndex(index, text.indexOf("?\r", fromindex));
998        index = minIndex(index, text.indexOf("?\f", fromindex));
999        index = minIndex(index, text.indexOf("! ", fromindex));
1000        index = minIndex(index, text.indexOf("!\t", fromindex));
1001        index = minIndex(index, text.indexOf("!\n", fromindex));
1002        index = minIndex(index, text.indexOf("!\r", fromindex));
1003        index = minIndex(index, text.indexOf("!\f", fromindex));
1004        index = minIndex(index, text.indexOf(". ", fromindex));
1005        index = minIndex(index, text.indexOf(".\t", fromindex));
1006        index = minIndex(index, text.indexOf(".\n", fromindex));
1007        index = minIndex(index, text.indexOf(".\r", fromindex));
1008        index = minIndex(index, text.indexOf(".\f", fromindex));
1009        index = minIndex(index, text.indexOf("@param", fromindex));
1010        index = minIndex(index, text.indexOf("@return", fromindex));
1011        index = minIndex(index, text.indexOf("@throw", fromindex));
1012        index = minIndex(index, text.indexOf("@serial", fromindex));
1013        index = minIndex(index, text.indexOf("@exception", fromindex));
1014        index = minIndex(index, text.indexOf("@deprecate", fromindex));
1015        index = minIndex(index, text.indexOf("@author", fromindex));
1016        index = minIndex(index, text.indexOf("@since", fromindex));
1017        index = minIndex(index, text.indexOf("@see", fromindex));
1018        index = minIndex(index, text.indexOf("@version", fromindex));
1019        if (doExclude && excludeTag != null)
1020            index = minIndex(index, text.indexOf(excludeTag));
1021        index = minIndex(index, text.indexOf("@vtexclude", fromindex));
1022        index = minIndex(index, text.indexOf("@vtinclude", fromindex));
1023        index = minIndex(index, text.indexOf("<p>", 2)); // Not at start
1024        index = minIndex(index, text.indexOf("<P>", 2)); // Not at start
1025        index = minIndex(index, text.indexOf("<blockquote", 2));  // Not at start
1026        index = minIndex(index, text.indexOf("<pre", fromindex)); // May contain anything!
1027        // Avoid the char at the start of a tag in some cases
1028        if (index != -1 &&
1029            (text.charAt(index) == '@' || text.charAt(index) == '<')) {
1030            if (index != 0)
1031                index--;
1032        }
1033
1034/* Not used for jdiff, since tags are explicitly checked for above.
1035        // Look for a sentence terminated by an HTML tag.
1036        index = minIndex(index, text.indexOf(".<", fromindex));
1037        if (index == -1) {
1038            // If period-whitespace etc was not found, check to see if
1039            // last character is a period,
1040            int endIndex = text.length()-1;
1041            if (text.charAt(endIndex) == '.' ||
1042                text.charAt(endIndex) == '?' ||
1043                text.charAt(endIndex) == '!')
1044                index = endIndex;
1045        }
1046*/
1047        return index;
1048    }
1049
1050    /**
1051     * Return the minimum of two indexes if > -1, and return -1
1052     * only if both indexes = -1.
1053     * @param i an int index
1054     * @param j an int index
1055     * @return an int equal to the minimum index > -1, or -1
1056     */
1057    public static int minIndex(int i, int j) {
1058        if (i == -1) return j;
1059        if (j == -1) return i;
1060        return Math.min(i,j);
1061    }
1062
1063    /**
1064     * The name of the file where the XML representing the API will be
1065     * stored.
1066     */
1067    public static String outputFileName = null;
1068
1069    /**
1070     * The identifier of the API being written out in XML, e.g.
1071     * &quotSuperProduct 1.3&quot;.
1072     */
1073    public static String apiIdentifier = null;
1074
1075    /**
1076     * The file where the XML representing the API will be stored.
1077     */
1078    private static PrintWriter outputFile = null;
1079
1080    /**
1081     * The name of the directory where the XML representing the API will be
1082     * stored.
1083     */
1084    public static String outputDirectory = null;
1085
1086    /**
1087     * Do not display a class  with a lower level of visibility than this.
1088     * Default is to display all public and protected classes.
1089     */
1090    public static String classVisibilityLevel = "protected";
1091
1092    /**
1093     * Do not display a member with a lower level of visibility than this.
1094     * Default is to display all public and protected members
1095     * (constructors, methods, fields).
1096     */
1097    public static String memberVisibilityLevel = "protected";
1098
1099    /**
1100     * If set, then save the entire contents of a doc block comment in the
1101     * API file. If not set, then just save the first sentence. Default is
1102     * that this is set.
1103     */
1104    public static boolean saveAllDocs = true;
1105
1106    /**
1107     * If set, exclude program elements marked with whatever the exclude tag
1108     * is specified as, e.g. "@exclude".
1109     */
1110    public static boolean doExclude = false;
1111
1112    /**
1113     * Exclude program elements marked with this String, e.g. "@exclude".
1114     */
1115    public static String excludeTag = null;
1116
1117    /**
1118     * The base URI for locating necessary DTDs and Schemas. By default, this
1119     * is "http://www.w3.org". A typical value to use local copies of DTD files
1120     * might be "file:///C:/jdiff/lib"
1121     */
1122    public static String baseURI = "http://www.w3.org";
1123
1124    /**
1125     * If set, then strip out non-printing characters from documentation.
1126     * Default is that this is set.
1127     */
1128    static boolean stripNonPrintables = true;
1129
1130    /**
1131     * If set, then add the information about the source file and line number
1132     * which is available in J2SE1.4. Default is that this is not set.
1133     */
1134    static boolean addSrcInfo = false;
1135
1136    /**
1137     * If set, scan classes with no packages.
1138     * If the source is  a jar file this may duplicates classes, so
1139     * disable it using the -packagesonly option. Default is that this is
1140     * not set.
1141     */
1142    static boolean packagesOnly = false;
1143
1144    /** Set to enable increased logging verbosity for debugging. */
1145    private static boolean trace = false;
1146
1147} //RootDocToXML
1148