FileDocCategorySizeDatePackage
HTMLReportGenerator.javaAPI DocAndroid 1.5 API100993Wed May 06 22:41:22 BST 2009jdiff

HTMLReportGenerator

public class HTMLReportGenerator extends Object
Emit HTML based on the changes between two sets of APIs. See the file LICENSE.txt for copyright details.
author
Matthew Doar, mdoar@pobox.com

Fields Summary
private Comments
existingComments_
The Comments object for existing comments.
private Comments
newComments_
The Comments object for freshly regenerated comments. This is populated during the generation of the report, and should be like existingComments_ but with unused comments marked as such, so that they can be commented out in XML when the new comments are written out to the comments file.
static String
reportFileName
The name of the file to which the top-level HTML file is written, and also the name of the subdirectory where most of the HTML appears, and also a prefix for the names of some of the files in that subdirectory.
static String
reportFileExt
The suffix of the file to which the HTML output is currently being written.
static PrintWriter
reportFile
The file to which the HTML output is currently being written.
static APIDiff
apiDiff
The object which represents the top of the tree of differences between two APIs. It is only used indirectly when emitting a navigation bar.
public static boolean
noCommentsOnRemovals
If set, then do not suggest comments for removals from the first sentence of the doc block of the old API.
public static boolean
noCommentsOnAdditions
If set, then do not suggest comments for additions from the first sentence of the doc block of the new API.
public static boolean
noCommentsOnChanges
If set, then do not suggest comments for changes from the first sentence of the doc block of the new API.
public static boolean
reportDocChanges
If set, then report changes in documentation (Javadoc comments) between the old and the new API. The default is that this is not set.
public static String
newDocPrefix
Define the prefix for HTML links to the existing set of Javadoc- generated documentation for the new API. E.g. For J2SE1.3.x, use "http://java.sun.com/j2se/1.3/docs/api/"
public static String
oldDocPrefix
Define the prefix for HTML links to the existing set of Javadoc- generated documentation for the old API.
public static boolean
doStats
To generate statistical output, set this to true.
public static String
outputDir
The destination directory for output files.
public static String
commentsDir
The destination directory for comments files (if not specified, uses outputDir)
public static String
docTitle
The title used on the first page of the report. By default, this is "API Differences Between <name of old API> and <name of new API>". It can be set by using the -doctitle option.
public static String
windowTitle
The browser window title for the report. By default, this is "API Differences Between <name of old API> and <name of new API>". It can be set by using the -windowtitle option.
static final String
bgcolor
The desired background color for JDiff tables.
private static final boolean
trace
Set to enable debugging output.
Constructors Summary
public HTMLReportGenerator()
Default constructor.

    
Methods Summary
public voidemitChanges(MemberDiff memberDiff, int memberType)
Emit all changes associated with a MemberDiff as an entry in a table.

param
memberType 0 = ctor, 1 = method, 2 = field

        reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"30%\">");
        boolean hasContent = false;
        // The type or return type changed
        if (memberDiff.oldType_.compareTo(memberDiff.newType_) != 0) {
            String shortOldType = simpleName(memberDiff.oldType_);
            String shortNewType = simpleName(memberDiff.newType_);
            if (memberType == 1) {
                reportFile.print("Change in return type from ");
            } else {
                reportFile.print("Change in type from ");
            }
            if (shortOldType.compareTo(shortNewType) == 0) {
                // The types differ in package name, so use the full name
                shortOldType = memberDiff.oldType_;
                shortNewType = memberDiff.newType_;
            }
            emitType(shortOldType);
            reportFile.print(" to ");
            emitType(shortNewType);
            reportFile.println(".<br>");
            hasContent = true;
        }
        // The signatures changed - only used by methods
        if (memberType == 1 &&
            memberDiff.oldSignature_ != null && 
            memberDiff.newSignature_ != null && 
            memberDiff.oldSignature_.compareTo(memberDiff.newSignature_) != 0) {
            String shortOldSignature = simpleName(memberDiff.oldSignature_);
            String shortNewSignature = simpleName(memberDiff.newSignature_);
            if (shortOldSignature.compareTo(shortNewSignature) == 0) {
                // The signatures differ in package names, so use the full form
                shortOldSignature = memberDiff.oldSignature_;
                shortNewSignature = memberDiff.newSignature_;
            }
            if (hasContent)
                reportFile.print(" "); 
            reportFile.print("Change in signature from ");
            if (shortOldSignature.compareTo("") == 0)
                shortOldSignature = "void";
            emitType(shortOldSignature);
            reportFile.print(" to ");
            if (shortNewSignature.compareTo("") == 0)
                shortNewSignature = "void";
            emitType(shortNewSignature);
            reportFile.println(".<br>");
            hasContent = true;
        }
        // The exceptions are only non-null in methods and constructors
        if (memberType != 2 &&
            memberDiff.oldExceptions_ != null && 
            memberDiff.newExceptions_ != null && 
            memberDiff.oldExceptions_.compareTo(memberDiff.newExceptions_) != 0) {
            if (hasContent)
                reportFile.print(" "); 
            // If either one of the exceptions has no spaces in it, or is 
            // equal to "no exceptions", then just display the whole 
            // exceptions texts.
            int spaceInOld = memberDiff.oldExceptions_.indexOf(" ");
            if (memberDiff.oldExceptions_.compareTo("no exceptions") == 0)
                spaceInOld = -1;
            int spaceInNew = memberDiff.newExceptions_.indexOf(" ");
            if (memberDiff.newExceptions_.compareTo("no exceptions") == 0)
                spaceInNew = -1;
            if (spaceInOld == -1 || spaceInNew == -1) {
                reportFile.print("Change in exceptions thrown from ");
                emitException(memberDiff.oldExceptions_); 
                reportFile.print(" to " );
                emitException(memberDiff.newExceptions_); 
                reportFile.println(".<br>");
            } else {
                // Too many exceptions become unreadable, so just show the 
                // individual changes. Catch the case where exceptions are
                // just reordered.
                boolean firstChange = true;
                int numRemoved = 0;
                StringTokenizer stOld = new StringTokenizer(memberDiff.oldExceptions_, ", ");
                while (stOld.hasMoreTokens()) {
                    String oldException = stOld.nextToken();
                    if (!memberDiff.newExceptions_.startsWith(oldException) &&
                        !(memberDiff.newExceptions_.indexOf(", " + oldException) != -1)) {
                        if (firstChange) {
                            reportFile.print("Change in exceptions: ");
                            firstChange = false;
                        }
                        if (numRemoved != 0)
                            reportFile.print(", ");
                        emitException(oldException);
                        numRemoved++;
                    }
                }
                if (numRemoved == 1)
                    reportFile.print(" was removed.");
                else if (numRemoved > 1)
                    reportFile.print(" were removed.");
                
                int numAdded = 0;
                StringTokenizer stNew = new StringTokenizer(memberDiff.newExceptions_, ", ");
                while (stNew.hasMoreTokens()) {
                    String newException = stNew.nextToken();
                    if (!memberDiff.oldExceptions_.startsWith(newException) &&
                        !(memberDiff.oldExceptions_.indexOf(", " + newException) != -1)) {
                        if (firstChange) {
                            reportFile.print("Change in exceptions: ");
                            firstChange = false;
                        }
                        if (numAdded != 0)
                            reportFile.println(", ");    
                        else
                            reportFile.println(" ");
                        emitException(newException);
                        numAdded++;
                    }
                }
                if (numAdded == 1)
                    reportFile.print(" was added");
                else if (numAdded > 1)
                    reportFile.print(" were added");
                else if (numAdded == 0 && numRemoved == 0 && firstChange)
                    reportFile.print("Exceptions were reordered");
                reportFile.println(".<br>");
            }
            // Note the changes between a comma-separated list of Strings
            hasContent = true;
        }

        if (memberDiff.documentationChange_ != null) {
            if (hasContent)
                reportFile.print(" "); 
            reportFile.print(memberDiff.documentationChange_);
            hasContent = true;
        }
            
        // Last, so no need for a <br>
        if (memberDiff.modifiersChange_ != null) {
            if (hasContent)
                reportFile.print(" "); 
            reportFile.println(memberDiff.modifiersChange_);
            hasContent = true;
        }
        reportFile.println("  </TD>");
    
public voidemitComment(java.lang.String commentID, java.lang.String possibleComment, int linkType)
Find any existing comment and emit it. Add the new comment to the list of new comments. The first instance of the string "@first" in a hand-written comment will be replaced by the first sentence from the associated doc block, if such exists. Also replace @link by an HTML link.

param
commentID The identifier for this comment.
param
possibleComment A possible comment from another source.
param
linkType 0 = remove, 1 = add, 2 = change

        if (noCommentsOnRemovals && linkType == 0) {
            reportFile.println("  <TD> </TD>");
            return;
        }
        if (noCommentsOnAdditions && linkType == 1) {
            reportFile.println("  <TD> </TD>");
            return;
        }
        if (noCommentsOnChanges && linkType == 2) {
            reportFile.println("  <TD> </TD>");
            return;
        }

        // We have to use this global hash table because the *Diff classes
        // do not store the possible comment from the new *API object.
        if (!noCommentsOnChanges && possibleComment == null) {
            possibleComment = (String)Comments.allPossibleComments.get(commentID);
        }
        // Just use the first sentence of the possible comment.
        if (possibleComment != null) {
            int fsidx = RootDocToXML.endOfFirstSentence(possibleComment, false);
            if (fsidx != -1 && fsidx != 0)
                possibleComment = possibleComment.substring(0, fsidx+1);
        }

        String comment = Comments.getComment(existingComments_, commentID);
        if (comment.compareTo(Comments.placeHolderText) == 0) {
            if (possibleComment != null && 
                possibleComment.indexOf("InsertOtherCommentsHere") == -1)
                reportFile.println("  <TD VALIGN=\"TOP\">" + possibleComment + "</TD>");
            else
                reportFile.println("  <TD> </TD>");
        } else {
            int idx = comment.indexOf("@first");
            if (idx == -1) {
                reportFile.println("  <TD VALIGN=\"TOP\">" + Comments.convertAtLinks(comment, "", null, null) + "</TD>");
            } else {
                reportFile.print("  <TD VALIGN=\"TOP\">" + comment.substring(0, idx));
                if (possibleComment != null && 
                    possibleComment.indexOf("InsertOtherCommentsHere") == -1)
                    reportFile.print(possibleComment);
                reportFile.println(comment.substring(idx + 6) + "</TD>");
            }
        }
        SingleComment newComment = new SingleComment(commentID, comment);
        newComments_.addComment(newComment);
    
public voidemitException(java.lang.String ex)
Emit a string which is an exception by surrounding it with <code> tags. If there is a space in the type, e.g. "String, File", then surround it with parentheses too. Do not add <code> tags or parentheses if the String is "no exceptions".

        if (ex.compareTo("no exceptions") == 0) {
            reportFile.print(ex);
        } else {
            if (ex.indexOf(' ") != -1) {
                reportFile.print("(<code>" + ex + "</code>)");
            } else {
                reportFile.print("<code>" + ex + "</code>");
            }
        }
    
public voidemitType(java.lang.String type)
Emit a string which is a type by surrounding it with <code> tags. If there is a space in the type, e.g. "String, File", then surround it with parentheses too.

        if (type.compareTo("") == 0)
            return;
        if (type.indexOf(' ") != -1) {
            reportFile.print("(<code>" + type + "</code>)");
        } else {
            reportFile.print("<code>" + type + "</code>");
        }
    
public static voidemitTypeWithNoParens(java.lang.String type)
Emit a string which is a type by surrounding it with <code> tags. Do not surround it with parentheses. Used to display methods' return types and field types.

        if (type.compareTo("") != 0)
            reportFile.print("<code>" + type + "</code>");
    
public static voidemitTypeWithParens(java.lang.String type)
Emit a string which is a type by surrounding it with <code> tags. Also surround it with parentheses too. Used to display methods' parameters. Suggestions for where a browser should break the text are provided with <br> and <nobr> tags.

        emitTypeWithParens(type, true);
    
public static voidemitTypeWithParens(java.lang.String type, boolean addBreaks)
Emit a string which is a type by surrounding it with <code> tags. Also surround it with parentheses too. Used to display methods' parameters.

        if (type.compareTo("") == 0)
            reportFile.print("()");
        else {
            int idx = type.indexOf(", ");
            if (!addBreaks || idx == -1) {
                reportFile.print("(<code>" + type + "</code>)");
            } else {
                // Make the browser break text at reasonable places
                String sepType = null;
                StringTokenizer st = new StringTokenizer(type, ", ");
                while (st.hasMoreTokens()) {
                    String p = st.nextToken();
                    if (sepType == null)
                        sepType = p;
                    else
                        sepType += ",</nobr> " + p + "<nobr>";                        
                }
                reportFile.print("(<code>" + sepType + "<nobr></code>)");
            }
        }
    
public voidgenerate(APIComparator comp, Comments existingComments)
Generate the report.

        String fullReportFileName = reportFileName;
        if (outputDir != null)
            fullReportFileName = outputDir + JDiff.DIR_SEP + reportFileName;
        System.out.println("JDiff: generating HTML report into the file '" + fullReportFileName + reportFileExt + "' and the subdirectory '" + fullReportFileName + "'");
        // May be null if no comments file exists yet
        existingComments_ = existingComments;
        // Where the new comments will be placed
        newComments_ = new Comments();
        // Writing to multiple files, so make sure the subdirectory exists
        File opdir = new File(fullReportFileName);
        if (!opdir.mkdir() && !opdir.exists()) {
            System.out.println("Error: could not create the subdirectory '" + fullReportFileName + "'");
            System.exit(3);
        }

        // Emit the documentation difference files
        if (!Diff.noDocDiffs) {
            // Documentation differences, one file per package
            Diff.emitDocDiffs(fullReportFileName);
        }

        // This is the top-level summary file, first in the right hand frame
        // or linked at the start to if no frames are used.
        String changesSummaryName = fullReportFileName + JDiff.DIR_SEP +
            reportFileName + "-summary" + reportFileExt;
        apiDiff = comp.apiDiff;
        try {
            FileOutputStream fos = new FileOutputStream(changesSummaryName);
            reportFile = new PrintWriter(fos);
            writeStartHTMLHeader();
            // Write out the title in he HTML header
            String oldAPIName = "Old API";
            if (apiDiff.oldAPIName_ != null)
                oldAPIName = apiDiff.oldAPIName_;
            String newAPIName = "New API";
            if (apiDiff.newAPIName_ != null)
                newAPIName = apiDiff.newAPIName_;
            if (windowTitle == null) 
                writeHTMLTitle("Android API Differences Report");
            else
                writeHTMLTitle(windowTitle);
            writeStyleSheetRef();
            writeText("</HEAD>");

            writeText("<body class=\"gc-documentation\">");

           // writeText("<div class=\"g-section g-tpl-180\">");
           // Add the nav bar for the summary page
            writeNavigationBar(reportFileName + "-summary", null, null, 
                               null, 0, true,
                               apiDiff.packagesRemoved.size() != 0, 
                               apiDiff.packagesAdded.size() != 0,
                               apiDiff.packagesChanged.size() != 0);
            
            // Write the title in the body with some formatting
            if (docTitle == null) {
	                //writeText("<center>");        
                writeText("  <div id=\"titleAligner\" style=\"vertical-align:top;padding:1em;margin-left:0;text-align:left;\">");
                writeText("    <H1 class=\"pagecontenth1\">API Differences Report</H1>");
                writeText("  </div>");
            } else {
                writeText("  <div id=\"titleAligner\" style=\"vertical-align:top;padding:1em;margin-left:0;text-align:left;\">");
                writeText("    <H1 class=\"pagecontenth1\">" + docTitle + "</H1>");
                writeText("  </div>");
            }

            writeText("<p>This document details the changes in the Android framework API. It shows ");
            writeText("additions, modifications, and removals for packages, classes, methods, and "); 
            writeText("fields. Each reference to an API change includes a brief description of the ");
            writeText("API and an explanation of the change and suggested workaround, where available.</p>");

            writeText("<p>The differences described in this report are based a comparison of the APIs ");
            writeText("whose versions are specified in the upper-right corner of this page. It compares a ");
            writeText("newer \"to\" API to an older \"from\" version, noting any changes relative to the ");
            writeText("older API. So, for example, indicated API removals are no longer present in the \"to\" ");
            writeText("API.</p>");

            writeText("<p>To navigate the report, use the \"Select a Diffs Index\" and \"Filter the Index\" ");
            writeText("controls on the left. The report uses text formatting to indicate <em>interface names</em>, ");
            writeText("<a href= ><tt>links to reference documentation</tt></a>, and <a href= >links to change ");
            writeText("description</a>. </p>");

            writeText("<p>For more information about the Android framework API and SDK, ");
            writeText("see the <a href=\"http://code.google.com/android/index.html\" target=\"_top\">Android product site</a>.</p>");

            // Write the contents and the other files as well
            writeReport(apiDiff);
            writeHTMLFooter();
            reportFile.close();
        } catch(IOException e) {
            System.out.println("IO Error while attempting to create " + changesSummaryName);
            System.out.println("Error: " + e.getMessage());
            System.exit(1);
        }

        // Now generate all the other files for multiple frames.
        //
        // The top-level changes.html frames file where everything starts.
        String tln = fullReportFileName + reportFileExt;
        // The file for the top-left frame.
        String tlf = fullReportFileName + JDiff.DIR_SEP + 
            "jdiff_topleftframe" + reportFileExt;
        // The default file for the bottom-left frame is the one with the
        // most information in it.
        String allDiffsIndexName = fullReportFileName + JDiff.DIR_SEP + 
            "alldiffs_index"; 
        // Other indexes for the bottom-left frame.
        String packagesIndexName = fullReportFileName + JDiff.DIR_SEP + 
            "packages_index"; 
        String classesIndexName = fullReportFileName + JDiff.DIR_SEP + 
            "classes_index"; 
        String constructorsIndexName = fullReportFileName + JDiff.DIR_SEP + 
            "constructors_index"; 
        String methodsIndexName = fullReportFileName + JDiff.DIR_SEP + 
            "methods_index"; 
        String fieldsIndexName = fullReportFileName + JDiff.DIR_SEP + 
            "fields_index"; 

        HTMLFiles hf = new HTMLFiles(this);
        hf.emitTopLevelFile(tln, apiDiff);
        hf.emitTopLeftFile(tlf);
        hf.emitHelp(fullReportFileName, apiDiff);
        hf.emitStylesheet();

        HTMLIndexes h = new HTMLIndexes(this);
        h.emitAllBottomLeftFiles(packagesIndexName, classesIndexName, 
                            constructorsIndexName, methodsIndexName,
                            fieldsIndexName, allDiffsIndexName, apiDiff);

        if (doStats) {
            // The file for the statistical report.
            String sf = fullReportFileName + JDiff.DIR_SEP + 
                "jdiff_statistics" + reportFileExt;
            HTMLStatistics stats = new HTMLStatistics(this);
            stats.emitStatistics(sf, apiDiff);
        }
    
public CommentsgetNewComments()
Accessor method for the freshly generated Comments object. The list of comments is sorted before the object is returned.


                              
       
        Collections.sort(newComments_.commentsList_);
        return newComments_;
    
public voidindent(int indent)
Emit some non-breaking space for indentation.

        for (int i = 0; i < indent; i++)
            reportFile.print(" ");
    
public java.lang.StringmakeTwoRows(java.lang.String name)
If a class or package name is considered to be too long for convenient display, insert
in the middle of it at a period.

        if (name.length() < 30)
            return name;
        int idx = name.indexOf(".", 20);
        if (idx == -1)
            return name;
        int len = name.length();
        String res = name.substring(0, idx+1) + "<br>" + name.substring(idx+1, len);
        return res;
    
public voidreportAllCtors(java.lang.String pkgName, ClassDiff classDiff)
Write out the details of constructors in a class.

        String className = classDiff.name_;
        writeText("<a NAME=\"constructors\"></a>"); // Named anchor
        // Report ctors which were removed in the new API
        if (classDiff.ctorsRemoved.size() != 0) {
            writeTableStart("Removed Constructors", 2);
            Iterator iter = classDiff.ctorsRemoved.iterator();
            while (iter.hasNext()) {
                ConstructorAPI ctorAPI = (ConstructorAPI)(iter.next());
                String ctorType = ctorAPI.type_;
                if (ctorType.compareTo("void") == 0)
                    ctorType = "";
                String id = className + "(" + ctorType + ")";
                if (trace) System.out.println("Constructor " + id + " was removed.");
                writeCtorTableEntry(pkgName, className, ctorType, 0, ctorAPI.doc_, false);
            }
            writeTableEnd();
        }

        // Report ctors which were added in the new API
        if (classDiff.ctorsAdded.size() != 0) {
            writeTableStart("Added Constructors", 2);
            Iterator iter = classDiff.ctorsAdded.iterator();
            while (iter.hasNext()) {
                ConstructorAPI ctorAPI = (ConstructorAPI)(iter.next());
                String ctorType = ctorAPI.type_;
                if (ctorType.compareTo("void") == 0)
                    ctorType = "";
                String id = className + "(" + ctorType + ")";
                if (trace) System.out.println("Constructor " + id + " was added.");
                writeCtorTableEntry(pkgName, className, ctorType, 1, ctorAPI.doc_, false);
            }
            writeTableEnd();
        }
        
        // Report ctors which were changed in the new API
        if (classDiff.ctorsChanged.size() != 0) {
            // Emit a table of changed classes, with links to the section
            // for each class.
            writeTableStart("Changed Constructors", 3);
            Iterator iter = classDiff.ctorsChanged.iterator();
            while (iter.hasNext()) {
                MemberDiff memberDiff = (MemberDiff)(iter.next());
                if (trace) System.out.println("Constructor for " + className +
                    " was changed from " + memberDiff.oldType_ + " to " + 
                    memberDiff.newType_);
                writeCtorChangedTableEntry(pkgName, className, memberDiff);
            }
            writeTableEnd();
        }
    
public voidreportAllFields(java.lang.String pkgName, ClassDiff classDiff)
Write out the details of fields in a class.

        writeText("<a NAME=\"fields\"></a>"); // Named anchor
        String className = classDiff.name_;
        // Report fields which were removed in the new API
        if (classDiff.fieldsRemoved.size() != 0) {
            writeTableStart("Removed Fields", 2);
            Iterator iter = classDiff.fieldsRemoved.iterator();
            while (iter.hasNext()) {
                FieldAPI fieldAPI = (FieldAPI)(iter.next());
                String fieldName = fieldAPI.name_;
                if (trace) System.out.println("Field " + fieldName + " was removed.");
                writeFieldTableEntry(pkgName, className, fieldAPI, 0, fieldAPI.doc_, false);
            }
            writeTableEnd();
        }
        
        // Report fields which were added in the new API
        if (classDiff.fieldsAdded.size() != 0) {
            writeTableStart("Added Fields", 2);
            Iterator iter = classDiff.fieldsAdded.iterator();
            while (iter.hasNext()) {
                FieldAPI fieldAPI = (FieldAPI)(iter.next());
                String fieldName = fieldAPI.name_;
                if (trace) System.out.println("Field " + fieldName + " was added.");
                writeFieldTableEntry(pkgName, className, fieldAPI, 1, fieldAPI.doc_, false);
            }
            writeTableEnd();
        }
        
        // Report fields which were changed in the new API
        if (classDiff.fieldsChanged.size() != 0) {
            // Emit a table of changed classes, with links to the section
            // for each class.
            writeTableStart("Changed Fields", 3);
            Iterator iter = classDiff.fieldsChanged.iterator();
            while (iter.hasNext()) {
                MemberDiff memberDiff = (MemberDiff)(iter.next());
                if (trace) System.out.println("Field " + pkgName + "." + className + "." + memberDiff.name_ + " was changed from " + memberDiff.oldType_ + " to " + memberDiff.newType_);
                writeFieldChangedTableEntry(pkgName, className, memberDiff);
            }
            writeTableEnd();
        }
        
    
public voidreportAllMethods(java.lang.String pkgName, ClassDiff classDiff)
Write out the details of methods in a class.

        writeText("<a NAME=\"methods\"></a>"); // Named anchor
        String className = classDiff.name_;
        // Report methods which were removed in the new API
        if (classDiff.methodsRemoved.size() != 0) {
            writeTableStart("Removed Methods", 2);
            Iterator iter = classDiff.methodsRemoved.iterator();
            while (iter.hasNext()) {
                MethodAPI methodAPI = (MethodAPI)(iter.next());
                String methodName = methodAPI.name_ + "(" + methodAPI.getSignature() + ")";
                if (trace) System.out.println("Method " + methodName + " was removed.");
                writeMethodTableEntry(pkgName, className, methodAPI, 0, methodAPI.doc_, false);
            }
            writeTableEnd();
        }

        // Report methods which were added in the new API
        if (classDiff.methodsAdded.size() != 0) {
            writeTableStart("Added Methods", 2);
            Iterator iter = classDiff.methodsAdded.iterator();
            while (iter.hasNext()) {
                MethodAPI methodAPI = (MethodAPI)(iter.next());
                String methodName = methodAPI.name_ + "(" + methodAPI.getSignature() + ")";
                if (trace) System.out.println("Method " + methodName + " was added.");
                writeMethodTableEntry(pkgName, className, methodAPI, 1, methodAPI.doc_, false);
            }
            writeTableEnd();
        }
        
        // Report methods which were changed in the new API
        if (classDiff.methodsChanged.size() != 0) {
            // Emit a table of changed methods.
            writeTableStart("Changed Methods", 3);
            Iterator iter = classDiff.methodsChanged.iterator();
            while (iter.hasNext()) {
                MemberDiff memberDiff = (MemberDiff)(iter.next());
                if (trace) System.out.println("Method " + memberDiff.name_ + 
                      " was changed.");
                writeMethodChangedTableEntry(pkgName, className, memberDiff);
            }
            writeTableEnd();
        }
    
public voidreportChangedClass(java.lang.String pkgName, ClassDiff[] classDiffs, int classIndex)
Write out the details of a changed class in a separate file.

        ClassDiff classDiff = classDiffs[classIndex];
        String className = classDiff.name_;

        PrintWriter oldReportFile = null;
        oldReportFile = reportFile;
        String localReportFileName = null;
        try {
            localReportFileName = reportFileName + JDiff.DIR_SEP + pkgName + "." + className + reportFileExt;
            if (outputDir != null)
                localReportFileName = outputDir + JDiff.DIR_SEP + localReportFileName;
            FileOutputStream fos = new FileOutputStream(localReportFileName);
            reportFile = new PrintWriter(fos);
            writeStartHTMLHeader();
            writeHTMLTitle(pkgName + "." + className);
            writeStyleSheetRef();
            writeText("</HEAD>");
            writeText("<BODY>");
        } catch(IOException e) {
            System.out.println("IO Error while attempting to create " + localReportFileName);
            System.out.println("Error: "+ e.getMessage());
            System.exit(1);
        }

        String classRef = pkgName + "." + className;
        classRef = classRef.replace('.", '/");
        if (className.indexOf('.") != -1) {
            classRef = pkgName + ".";
            classRef = classRef.replace('.", '/");
            classRef = newDocPrefix + classRef + className;
        } else {
            classRef = newDocPrefix + classRef;
        }
        // A link to the class in the new API
        String linkedClassName = "<A HREF=\"" + classRef + ".html\" target=\"_top\"><font size=\"+1\"><tt>" + className + "</tt></font></A>";
        String lcn = pkgName + "." + linkedClassName;
        //Links to the previous and next classes
        String prevClassRef = null;
        if (classIndex != 0) {
            prevClassRef = pkgName + "." + classDiffs[classIndex-1].name_ + reportFileExt;
        }
        // Create the HTML link to the next package
        String nextClassRef = null;
        if (classIndex < classDiffs.length - 1) {
            nextClassRef = pkgName + "." + classDiffs[classIndex+1].name_ + reportFileExt;
        }

        if (classDiff.isInterface_)
            lcn = "Interface " + lcn;
        else
            lcn = "Class " + lcn;
        boolean hasCtors = classDiff.ctorsRemoved.size() != 0 ||
            classDiff.ctorsAdded.size() != 0 ||
            classDiff.ctorsChanged.size() != 0;
        boolean hasMethods = classDiff.methodsRemoved.size() != 0 ||
            classDiff.methodsAdded.size() != 0 ||
            classDiff.methodsChanged.size() != 0;
        boolean hasFields = classDiff.fieldsRemoved.size() != 0 ||
            classDiff.fieldsAdded.size() != 0 ||
            classDiff.fieldsChanged.size() != 0;
        writeSectionHeader(lcn, pkgName, prevClassRef, nextClassRef, 
                           className, 2,
                           hasCtors, hasMethods, hasFields);

        if (classDiff.inheritanceChange_ != null)
            writeText("<p><font xsize=\"+1\">" + classDiff.inheritanceChange_ + "</font>");

        // Report changes in documentation
        if (reportDocChanges && classDiff.documentationChange_ != null) {
            String oldClassRef = null;
            if (oldDocPrefix != null) {
                oldClassRef = pkgName + "." + className;
                oldClassRef = oldClassRef.replace('.", '/");
                if (className.indexOf('.") != -1) {
                    oldClassRef = pkgName + ".";
                    oldClassRef = oldClassRef.replace('.", '/");
                    oldClassRef = oldDocPrefix + oldClassRef + className;
                } else {
                    oldClassRef = oldDocPrefix + oldClassRef;
                }
            }
            if (oldDocPrefix != null) 
                classDiff.documentationChange_ += "<A HREF=\"" + oldClassRef +
                    ".html\" target=\"_self\"><font size=\"+1\"><tt>old</tt></font></A> to ";
            else
                classDiff.documentationChange_ += "<font size=\"+1\"><tt>old</tt></font> to ";
            classDiff.documentationChange_ += "<A HREF=\"" + classRef + 
                ".html\" target=\"_self\"><font size=\"+1\"><tt>new</tt></font></A>. ";
            writeText(classDiff.documentationChange_);
        }

        if (classDiff.modifiersChange_ != null)
            writeText("<p>" + classDiff.modifiersChange_);

        reportAllCtors(pkgName, classDiff);
        reportAllMethods(pkgName, classDiff);
        reportAllFields(pkgName, classDiff);

        writeSectionFooter(pkgName, prevClassRef, nextClassRef, className, 2);
        writeHTMLFooter();
        reportFile.close();
        reportFile = oldReportFile;
    
public voidreportChangedPackage(PackageDiff[] pkgDiffs, int pkgIndex)
Write out the details of a changed package in a separate file.

        PackageDiff pkgDiff = pkgDiffs[pkgIndex];
        String pkgName = pkgDiff.name_;

        PrintWriter oldReportFile = null;
        oldReportFile = reportFile;
        String localReportFileName = null;
        try {
            // Prefix package files with pkg_ because there may be a class
            // with the same name.
            localReportFileName = reportFileName + JDiff.DIR_SEP + "pkg_" + pkgName + reportFileExt;
            if (outputDir != null)
                localReportFileName = outputDir + JDiff.DIR_SEP + localReportFileName;
            FileOutputStream fos = new FileOutputStream(localReportFileName);
            reportFile = new PrintWriter(fos);
            writeStartHTMLHeader();
            writeHTMLTitle(pkgName);
            writeStyleSheetRef();
            writeText("</HEAD>");
            writeText("<BODY>");
        } catch(IOException e) {
            System.out.println("IO Error while attempting to create " + localReportFileName);
            System.out.println("Error: "+ e.getMessage());
            System.exit(1);
        }

        String pkgRef = pkgName;
        pkgRef = pkgRef.replace('.", '/");
        pkgRef = newDocPrefix + pkgRef + "/package-summary";
        // A link to the package in the new API
        String linkedPkgName = "<A HREF=\"" + pkgRef + ".html\" target=\"_top\"><font size=\"+1\"><tt>" + pkgName + "</tt></font></A>";
        String prevPkgRef = null;
        if (pkgIndex != 0) {
            prevPkgRef = "pkg_" + pkgDiffs[pkgIndex-1].name_ + reportFileExt;
        }
        // Create the HTML link to the next package
        String nextPkgRef = null;
        if (pkgIndex < pkgDiffs.length - 1) {
            nextPkgRef = "pkg_" + pkgDiffs[pkgIndex+1].name_ + reportFileExt;
        }
        
        writeSectionHeader("Package " + linkedPkgName, pkgName, 
                           prevPkgRef, nextPkgRef,
                           null, 1,
                           pkgDiff.classesRemoved.size() != 0, 
                           pkgDiff.classesAdded.size() != 0,
                           pkgDiff.classesChanged.size() != 0);

        // Report changes in documentation
        if (reportDocChanges && pkgDiff.documentationChange_ != null) {
            String pkgDocRef = pkgName + "/package-summary";
            pkgDocRef = pkgDocRef.replace('.", '/");
            String oldPkgRef = pkgDocRef;
            String newPkgRef = pkgDocRef;
            if (oldDocPrefix != null)
                oldPkgRef = oldDocPrefix + oldPkgRef;
            else 
                oldPkgRef = null;
            newPkgRef = newDocPrefix + newPkgRef;
            if (oldPkgRef != null) 
                pkgDiff.documentationChange_ += "<A HREF=\"" + oldPkgRef +
                    ".html#package_description\" target=\"_self\"><font size=\"+1\"><tt>old</tt></font></A> to ";
            else
                pkgDiff.documentationChange_ += "<font size=\"+1\"><tt>old</tt></font> to ";
            pkgDiff.documentationChange_ += "<A HREF=\"" + newPkgRef + 
                ".html#package_description\" target=\"_self\"><font size=\"+1\"><tt>new</tt></font></A>. ";
            writeText(pkgDiff.documentationChange_);
        }

        // Report classes which were removed in the new API
        if (pkgDiff.classesRemoved.size() != 0) {
            // Determine the title for this section
            boolean hasClasses = false;
            boolean hasInterfaces = false;
            Iterator iter = pkgDiff.classesRemoved.iterator();
            while (iter.hasNext()) {
                ClassAPI classAPI = (ClassAPI)(iter.next());
                if (classAPI.isInterface_)
                    hasInterfaces = true;
                else
                    hasClasses = true;
            }
            if (hasInterfaces && hasClasses)
                writeTableStart("Removed Classes and Interfaces", 2);
            else if (!hasInterfaces && hasClasses)
                     writeTableStart("Removed Classes", 2);
            else if (hasInterfaces && !hasClasses)
                     writeTableStart("Removed Interfaces", 2);
            // Emit the table entries
            iter = pkgDiff.classesRemoved.iterator();
            while (iter.hasNext()) {
                ClassAPI classAPI = (ClassAPI)(iter.next());
                String className = classAPI.name_;
                if (trace) System.out.println("Class/Interface " + className + " was removed.");
                writeClassTableEntry(pkgName, className, 0, classAPI.isInterface_, classAPI.doc_, false);
            }
            writeTableEnd();
        }
        
        // Report classes which were added in the new API
        if (pkgDiff.classesAdded.size() != 0) {
            // Determine the title for this section
            boolean hasClasses = false;
            boolean hasInterfaces = false;
            Iterator iter = pkgDiff.classesAdded.iterator();
            while (iter.hasNext()) {
                ClassAPI classAPI = (ClassAPI)(iter.next());
                if (classAPI.isInterface_)
                    hasInterfaces = true;
                else
                    hasClasses = true;
            }
            if (hasInterfaces && hasClasses)
                writeTableStart("Added Classes and Interfaces", 2);
            else if (!hasInterfaces && hasClasses)
                     writeTableStart("Added Classes", 2);
            else if (hasInterfaces && !hasClasses)
                     writeTableStart("Added Interfaces", 2);
            // Emit the table entries
            iter = pkgDiff.classesAdded.iterator();
            while (iter.hasNext()) {
                ClassAPI classAPI = (ClassAPI)(iter.next());
                String className = classAPI.name_;
                if (trace) System.out.println("Class/Interface " + className + " was added.");
                writeClassTableEntry(pkgName, className, 1, classAPI.isInterface_, classAPI.doc_, false);
            }
            writeTableEnd();
        }

        // Report classes which were changed in the new API
        if (pkgDiff.classesChanged.size() != 0) {
            // Determine the title for this section
            boolean hasClasses = false;
            boolean hasInterfaces = false;
            Iterator iter = pkgDiff.classesChanged.iterator();
            while (iter.hasNext()) {
                ClassDiff classDiff = (ClassDiff)(iter.next());
                if (classDiff.isInterface_)
                    hasInterfaces = true;
                else
                    hasClasses = true;
            }
            if (hasInterfaces && hasClasses)
                writeTableStart("Changed Classes and Interfaces", 2);
            else if (!hasInterfaces && hasClasses)
                     writeTableStart("Changed Classes", 2);
            else if (hasInterfaces && !hasClasses)
                     writeTableStart("Changed Interfaces", 2);
            // Emit a table of changed classes, with links to the file
            // for each class.
            iter = pkgDiff.classesChanged.iterator();
            while (iter.hasNext()) {
                ClassDiff classDiff = (ClassDiff)(iter.next());
                String className = classDiff.name_;
                if (trace) System.out.println("Package " + pkgDiff.name_ + ", class/Interface " + className + " was changed.");
                writeClassTableEntry(pkgName, className, 2, classDiff.isInterface_, null, false);
            }
            writeTableEnd();
            // Now emit a separate file for each changed class and interface.
            ClassDiff[] classDiffs = new ClassDiff[pkgDiff.classesChanged.size()];
            classDiffs = (ClassDiff[])pkgDiff.classesChanged.toArray(classDiffs);
            for (int k = 0; k < classDiffs.length; k++) {
                reportChangedClass(pkgName, classDiffs, k);
            }
        }
        
        writeSectionFooter(pkgName, prevPkgRef, nextPkgRef, null, 1);
        writeHTMLFooter();
        reportFile.close();
        reportFile = oldReportFile;
    
public static java.lang.StringsimpleName(java.lang.String fqNames)
Return a String with the simple names of the classes in fqName. "java.lang.String" becomes "String", "java.lang.String, java.io.File" becomes "String, File" and so on. If fqName is null, return null. If fqName is "", return "".

        if (fqNames == null)
            return null;
        String res = "";
        boolean hasContent = false;
        // We parse the string step by step to ensure we take
        // fqNames that contains generics parameter in a whole.
        ArrayList<String> fqNamesList = new ArrayList<String>();
        int genericParametersDepth = 0;
        StringBuffer buffer = new StringBuffer();
        for (int i=0; i<fqNames.length(); i++) {
          char c = fqNames.charAt(i);
          if ('<" == c) {
            genericParametersDepth++;
          }
          if ('>" == c) {
            genericParametersDepth--;
          }
          if ('," != c || genericParametersDepth > 0) {
            buffer.append(c);
          } else if ('," == c) {
            fqNamesList.add(buffer.toString().trim());
            buffer = new StringBuffer(buffer.length());
          }
        }
        fqNamesList.add(buffer.toString().trim());
        for (String fqName : fqNamesList) {
            // Assume this will be used inside a <nobr> </nobr> set of tags.
            if (hasContent)
                res += ", "; 
            hasContent = true;
            // Look for text within '<' and '>' in case this is a invocation of a generic
            
            int firstBracket = fqName.indexOf('<");
            int lastBracket = fqName.lastIndexOf('>");
            String genericParameter = null;
            if (firstBracket != -1 && lastBracket != -1) {
              genericParameter = simpleName(fqName.substring(firstBracket + 1, lastBracket));
              fqName = fqName.substring(0, firstBracket);              
            }
            
            int lastDot = fqName.lastIndexOf('.");
            if (lastDot < 0) {
                res += fqName; // Already as simple as possible
            } else {
                res += fqName.substring(lastDot+1);
            }
            if (genericParameter != null)
              res += "<" + genericParameter + ">";            
        }
        return res;
    
public voidwriteClassTableEntry(java.lang.String pkgName, java.lang.String className, int linkType, boolean isInterface, java.lang.String possibleComment, boolean useOld)
Write a table entry for a class or interface. linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file

        if (!useOld) {
            reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
            reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
            reportFile.println("  <A NAME=\"" + className + "\"></A>"); // Named anchor
        }
        String fqName = pkgName + "." + className;
        String shownClassName = makeTwoRows(className);
        if (linkType == 0) {
            if (oldDocPrefix == null) {
                // No link
                if (isInterface)
                    reportFile.println("  <I>" + shownClassName + "</I>");
                else
                    reportFile.println("  " + shownClassName);
            } else {
                writeClassTableEntry(pkgName, className, 
                                     1, isInterface, 
                                     possibleComment, true);
            }
        } else if (linkType == 1) {
            // Link to HTML file for the class
            String classRef = fqName;
            // Deal with inner classes
            if (className.indexOf('.") != -1) {
                classRef = pkgName + ".";
                classRef = classRef.replace('.", '/");
                if (useOld)
                    classRef = oldDocPrefix + classRef + className;
                else
                    classRef = newDocPrefix + classRef + className;
            } else {
                classRef = classRef.replace('.", '/");
                if (useOld)
                    classRef = oldDocPrefix + classRef;
                else
                    classRef = newDocPrefix + classRef;
            }
            reportFile.print("  <nobr><A HREF=\"" + classRef + ".html\" target=\"_top\"><font size=\"+1\"><tt>");
            if (isInterface)
                reportFile.print("<I>" + shownClassName + "</I>");
            else
                reportFile.print(shownClassName);
            reportFile.println("</tt></font></A></nobr>");
        } else if (linkType == 2) {
            reportFile.print("  <nobr><A HREF=\"" + fqName + reportFileExt + "\">");
            if (isInterface)
                reportFile.print("<I>" + shownClassName + "</I>");
            else
                reportFile.print(shownClassName);
            reportFile.println("</A></nobr>");
        } 
        if (!useOld) {
            reportFile.println("  </TD>");
            emitComment(fqName, possibleComment, linkType);
            reportFile.println("</TR>");
        }
    
public voidwriteCtorChangedTableEntry(java.lang.String pkgName, java.lang.String className, MemberDiff memberDiff)
Write a table entry for a changed constructor.

        String fqName = pkgName + "." + className;
        String newSignature = memberDiff.newType_;
        if (newSignature.compareTo("void") == 0)
            newSignature = "";
        String commentID = fqName + ".ctor_changed(" + newSignature + ")";
        reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
        reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
        reportFile.println("  <A NAME=\"" + commentID + "\"></A>"); // Named anchor
        String memberRef = fqName.replace('.", '/");            
        String shownClassName = makeTwoRows(className);
        // Deal with inner classes
        if (className.indexOf('.") != -1) {
            memberRef = pkgName + ".";
            memberRef = memberRef.replace('.", '/");
            memberRef = newDocPrefix + memberRef + className;
        } else {
            memberRef = newDocPrefix + memberRef;
        }
        String newType = memberDiff.newType_;
        if (newType.compareTo("void") == 0)
            newType = "";
        String shortNewType = simpleName(memberDiff.newType_);
        // Constructors have the linked name, then the type in parentheses.
        reportFile.print("  <nobr><A HREF=\"" + memberRef + ".html#" + className + "(" + newType + ")\" target=\"_top\"><font size=\"+1\"><tt>");
        reportFile.print(shownClassName);
        reportFile.print("</tt></font></A>");
        emitTypeWithParens(shortNewType);
        reportFile.println("  </nobr>");
        reportFile.println("  </TD>");
        
        // Report changes in documentation
        if (reportDocChanges && memberDiff.documentationChange_ != null) {
            String oldMemberRef = null;
            String oldType = null;
            if (oldDocPrefix != null) {
                oldMemberRef = pkgName + "." + className;
                oldMemberRef = oldMemberRef.replace('.", '/");
                if (className.indexOf('.") != -1) {
                    oldMemberRef = pkgName + ".";
                    oldMemberRef = oldMemberRef.replace('.", '/");
                    oldMemberRef = oldDocPrefix + oldMemberRef + className;
                } else {
                    oldMemberRef = oldDocPrefix + oldMemberRef;
                }
                oldType = memberDiff.oldType_;
                if (oldType.compareTo("void") == 0)
                    oldType = "";
            }
            if (oldDocPrefix != null) 
                memberDiff.documentationChange_ += "<A HREF=\"" + 
                    oldMemberRef + ".html#" + className + "(" + oldType + 
                    ")\" target=\"_self\"><font size=\"+1\"><tt>old</tt></font></A> to ";
            else 
                memberDiff.documentationChange_ += "<font size=\"+1\"><tt>old</tt></font> to ";
            memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 
                ".html#" + className + "(" + newType + 
                ")\" target=\"_self\"><font size=\"+1\"><tt>new</tt></font></A>.<br>";
        }

        emitChanges(memberDiff, 0);
        emitComment(commentID, null, 2);

        reportFile.println("</TR>");
    
public voidwriteCtorTableEntry(java.lang.String pkgName, java.lang.String className, java.lang.String type, int linkType, java.lang.String possibleComment, boolean useOld)
Write a table entry for a constructor. linkType: 0 - no link by default, 1 = link to Javadoc HTML file

        String fqName = pkgName + "." + className;
        String shownClassName = makeTwoRows(className);
        String lt = "removed";
        if (linkType ==1)
            lt = "added";
        String commentID = fqName + ".ctor_" + lt + "(" + type + ")";
        if (!useOld) {
            reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
            reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
            reportFile.println("  <A NAME=\"" + commentID + "\"></A>"); // Named anchor
        }
        String shortType = simpleName(type);
        if (linkType == 0) {
            if (oldDocPrefix == null) {
                // No link
                reportFile.print("  <nobr>" + pkgName);
                emitTypeWithParens(shortType);
                reportFile.println("</nobr>");
            } else {
                writeCtorTableEntry(pkgName, className, 
                                    type, 1, 
                                    possibleComment, true);
            }
        } else if (linkType == 1) {
            // Link to HTML file for the package
            String memberRef = fqName.replace('.", '/");
            // Deal with inner classes
            if (className.indexOf('.") != -1) {
                memberRef = pkgName + ".";
                memberRef = memberRef.replace('.", '/");
                if (useOld) {
                    // oldDocPrefix is non-null at this point
                    memberRef = oldDocPrefix + memberRef + className;
                } else {
                    memberRef = newDocPrefix + memberRef + className;
                }
            } else {
                if (useOld) {
                    // oldDocPrefix is non-null at this point
                    memberRef = oldDocPrefix + memberRef;
                } else {
                    memberRef = newDocPrefix + memberRef;
                }
            }
            reportFile.print("  <nobr><A HREF=\"" + memberRef + ".html#" + className +
                             "(" + type + ")\" target=\"_top\"><font size=\"+1\"><tt>" + shownClassName + "</tt></font></A>");
            emitTypeWithParens(shortType);
            reportFile.println("</nobr>");
        }
        if (!useOld) {
            reportFile.println("  </TD>");
            emitComment(commentID, possibleComment, linkType);
            reportFile.println("</TR>");
        }
    
public voidwriteFieldChangedTableEntry(java.lang.String pkgName, java.lang.String className, MemberDiff memberDiff)
Write a table entry for a changed field.

        String memberName = memberDiff.name_;
        // Generally nowhere to break a member name anyway
        // String shownMemberName = makeTwoRows(memberName);
        String fqName = pkgName + "." + className;
        // Fields have unique names in a class
        String commentID = fqName + "." + memberName;
        reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");

        reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
        reportFile.println("  <A NAME=\"" + commentID + "\"></A>"); // Named anchor
        String memberRef = fqName.replace('.", '/");            
        // Deal with inner classes
        if (className.indexOf('.") != -1) {
            memberRef = pkgName + ".";
            memberRef = memberRef.replace('.", '/");
            memberRef = newDocPrefix + memberRef + className;
        } else {
            memberRef = newDocPrefix + memberRef;
        }
        // Javadoc generated HTML has no named anchors for fields
        // inherited from other classes, so link to the defining class' field.
        // Only copes with non-inner classes.
        if (className.indexOf('.") == -1 &&
            memberDiff.modifiersChange_ != null &&
            memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) {
            memberRef = memberDiff.inheritedFrom_;
            memberRef = memberRef.replace('.", '/"); 
            memberRef = newDocPrefix + memberRef;
        }

        String newType = memberDiff.newType_;
        String shortNewType = simpleName(newType); 
        reportFile.print("  <nobr>");
        emitTypeWithNoParens(shortNewType);
        reportFile.print(" <A HREF=\"" + memberRef + ".html#" + 
                         memberName + "\" target=\"_top\"><font size=\"+1\"><tt>");
        reportFile.print(memberName);
        reportFile.print("</tt></font></A></nobr>");
        reportFile.println("  </TD>");
        
        // Report changes in documentation
        if (reportDocChanges && memberDiff.documentationChange_ != null) {
            String oldMemberRef = null;
            if (oldDocPrefix != null) {
                oldMemberRef = pkgName + "." + className;
                oldMemberRef = oldMemberRef.replace('.", '/");
                if (className.indexOf('.") != -1) {
                    oldMemberRef = pkgName + ".";
                    oldMemberRef = oldMemberRef.replace('.", '/");
                    oldMemberRef = oldDocPrefix + oldMemberRef + className;
                } else {
                    oldMemberRef = oldDocPrefix + oldMemberRef;
                }
            }
            if (oldDocPrefix != null) 
                memberDiff.documentationChange_ += "<A HREF=\"" + 
                    oldMemberRef + ".html#" + memberName + "\" target=\"_self\"><font size=\"+1\"><tt>old</tt></font></A> to ";
            else
                memberDiff.documentationChange_ += "<font size=\"+1\"><tt>old</tt></font> to ";
            memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 
                ".html#" + memberName + "\" target=\"_self\"><font size=\"+1\"><tt>new</tt></font></A>.<br>";
        }

        emitChanges(memberDiff, 2);
        // Get the comment from the parent class if more appropriate
        if (memberDiff.modifiersChange_ != null) {
            int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from");
            if (parentIdx != -1) {
                // Change the commentID to pick up the appropriate method
                commentID = memberDiff.inheritedFrom_ + "." + memberName;
            }
        }
        emitComment(commentID, null, 2);
        
        reportFile.println("</TR>");
    
public voidwriteFieldTableEntry(java.lang.String pkgName, java.lang.String className, FieldAPI fieldAPI, int linkType, java.lang.String possibleComment, boolean useOld)
Write a table entry for a field. linkType: 0 - no link by default, 1 = link to Javadoc HTML file

        String fqName = pkgName + "." + className;
        // Fields can only appear in one table, so no need to specify _added etc
        String fieldName = fieldAPI.name_;
        // Generally nowhere to break a member name anyway
        // String shownFieldName = makeTwoRows(fieldName);
        String commentID = fqName + "." + fieldName;
        if (!useOld) {
            reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
            reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
            reportFile.println("  <A NAME=\"" + commentID + "\"></A>"); // Named anchor
        }
        String fieldType = fieldAPI.type_;
        if (fieldType.compareTo("void") == 0)
            fieldType = "";
        String shortFieldType = simpleName(fieldType);
        if (linkType == 0) {
            if (oldDocPrefix == null) {
                // No link.
                reportFile.print("  ");
                emitType(shortFieldType);
                reportFile.println(" " + fieldName);
            } else {
                writeFieldTableEntry(pkgName, className, 
                                     fieldAPI, 1, 
                                     possibleComment, true);
            }
        } else if (linkType == 1) {
            // Link to HTML file for the package.
            String memberRef = fqName.replace('.", '/");
            // Deal with inner classes
            if (className.indexOf('.") != -1) {
                memberRef = pkgName + ".";
                memberRef = memberRef.replace('.", '/");
                if (useOld)
                    memberRef = oldDocPrefix + memberRef + className;
                else
                    memberRef = newDocPrefix + memberRef + className;
            } else {
                if (useOld)
                    memberRef = oldDocPrefix + memberRef;
                else
                    memberRef = newDocPrefix + memberRef;
            }
            reportFile.print("  <nobr>");
            emitType(shortFieldType);
            reportFile.println(" <A HREF=\"" + memberRef + ".html#" + fieldName +
               "\" target=\"_top\"><font size=\"+1\"><tt>" + fieldName + "</tt></font></A></nobr>");
        }
        if (!useOld) {
            reportFile.println("  </TD>");
            emitComment(commentID, possibleComment, linkType);
            reportFile.println("</TR>");
        }
        
public voidwriteHTMLFooter()
Write the HTML footer.

    writeText("<script src=\"http://www.google-analytics.com/ga.js\" type=\"text/javascript\">");
    writeText("</script>");
    writeText("<script type=\"text/javascript\">");
    writeText("  try {");
    writeText("    var pageTracker = _gat._getTracker(\"UA-18071-1\");");
    writeText("    pageTracker._setAllowAnchor(true);");
    writeText("    pageTracker._initData();");
    writeText("    pageTracker._trackPageview();");
    writeText("  } catch(e) {}");
    writeText("</script>");
    writeText("</BODY>");
    writeText("</HTML>");
    
public voidwriteHTMLTitle(java.lang.String title)
Write the HTML title

        writeText("<TITLE>");
        writeText(title);
        writeText("</TITLE>");
    
public voidwriteMethodChangedTableEntry(java.lang.String pkgName, java.lang.String className, MemberDiff memberDiff)
Write a table entry for a changed method.

        String memberName = memberDiff.name_;
        // Generally nowhere to break a member name anyway
        // String shownMemberName = makeTwoRows(memberName);
        String fqName = pkgName + "." + className;
        String newSignature = memberDiff.newSignature_;
        String commentID = fqName + "." + memberName + "_changed(" + newSignature + ")";
        reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");

        reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
        reportFile.println("  <A NAME=\"" + commentID + "\"></A>"); // Named anchor
        String memberRef = fqName.replace('.", '/");            
        // Deal with inner classes
        if (className.indexOf('.") != -1) {
            memberRef = pkgName + ".";
            memberRef = memberRef.replace('.", '/");
            memberRef = newDocPrefix + memberRef + className;
        } else {
            memberRef = newDocPrefix + memberRef;
        }
        // Javadoc generated HTML has no named anchors for methods
        // inherited from other classes, so link to the defining class' method.
        // Only copes with non-inner classes.
        if (className.indexOf('.") == -1 &&
            memberDiff.modifiersChange_ != null &&
            memberDiff.modifiersChange_.indexOf("but is now inherited from") != -1) {
            memberRef = memberDiff.inheritedFrom_;
            memberRef = memberRef.replace('.", '/"); 
            memberRef = newDocPrefix + memberRef;
        }
        
        String newReturnType = memberDiff.newType_;
        String shortReturnType = simpleName(newReturnType); 
        String shortSignature = simpleName(newSignature);        
        reportFile.print("  <nobr>");
        emitTypeWithNoParens(shortReturnType); 
        reportFile.print(" <A HREF=\"" + memberRef + ".html#" + 
                         memberName + "(" + newSignature + ")\" target=\"_top\"><font size=\"+1\"><tt>");
        reportFile.print(memberName);
        reportFile.print("</tt></font></A>");
        emitTypeWithParens(shortSignature);
        reportFile.println("  </nobr>");
        reportFile.println("  </TD>");
        
        // Report changes in documentation
        if (reportDocChanges && memberDiff.documentationChange_ != null) {
            String oldMemberRef = null;
            String oldSignature = null;
            if (oldDocPrefix != null) {
                oldMemberRef = pkgName + "." + className;
                oldMemberRef = oldMemberRef.replace('.", '/");
                if (className.indexOf('.") != -1) {
                    oldMemberRef = pkgName + ".";
                    oldMemberRef = oldMemberRef.replace('.", '/");
                    oldMemberRef = oldDocPrefix + oldMemberRef + className;
                } else {
                    oldMemberRef = oldDocPrefix + oldMemberRef;
                }
                oldSignature = memberDiff.oldSignature_;
            }
            if (oldDocPrefix != null) 
                memberDiff.documentationChange_ += "<A HREF=\"" + 
                    oldMemberRef + ".html#" + memberName + "(" + 
                    oldSignature + ")\" target=\"_self\"><font size=\"+1\"><tt>old</tt></font></A> to ";
            else
                memberDiff.documentationChange_ += "<font size=\"+1\"><tt>old</tt></font> to ";
            memberDiff.documentationChange_ += "<A HREF=\"" + memberRef + 
                ".html#" + memberName + "(" + newSignature + 
                ")\" target=\"_self\"><font size=\"+1\"><tt>new</tt></font></A>.<br>";
        }

        emitChanges(memberDiff, 1);
        // Get the comment from the parent class if more appropriate
        if (memberDiff.modifiersChange_ != null) {
            int parentIdx = memberDiff.modifiersChange_.indexOf("now inherited from");
            if (parentIdx != -1) {
                // Change the commentID to pick up the appropriate method
                commentID = memberDiff.inheritedFrom_ + "." + memberName + 
                    "_changed(" + newSignature + ")";
            }
        }
        emitComment(commentID, null, 2);
        
        reportFile.println("</TR>");
    
public voidwriteMethodTableEntry(java.lang.String pkgName, java.lang.String className, MethodAPI methodAPI, int linkType, java.lang.String possibleComment, boolean useOld)
Write a table entry for a method. linkType: 0 - no link by default, 1 = link to Javadoc HTML file

        String fqName = pkgName + "." + className;
        String signature = methodAPI.getSignature(); 
        String methodName = methodAPI.name_;
        String lt = "removed";
        if (linkType ==1)
            lt = "added";
        String commentID = fqName + "." + methodName + "_" + lt + "(" + signature + ")";
        if (!useOld) {
            reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
            reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
            reportFile.println("  <A NAME=\"" + commentID + "\"></A>"); // Named anchor
        }
        if (signature.compareTo("void") == 0)
            signature = "";
        String shortSignature = simpleName(signature);
        String returnType = methodAPI.returnType_;
        String shortReturnType = simpleName(returnType);
        if (linkType == 0) {
            if (oldDocPrefix == null) {
                // No link
                reportFile.print("  <nobr>");
                emitType(shortReturnType);
                reportFile.print(" " + methodName);
                emitTypeWithParens(shortSignature);
                reportFile.println("</nobr>");
            } else {
                writeMethodTableEntry(pkgName, className, 
                                      methodAPI, 1, 
                                      possibleComment, true);
            }
        } else if (linkType == 1) {
            // Link to HTML file for the package
            String memberRef = fqName.replace('.", '/");
            // Deal with inner classes
            if (className.indexOf('.") != -1) {
                memberRef = pkgName + ".";
                memberRef = memberRef.replace('.", '/");
                if (useOld) {
                    // oldDocPrefix is non-null at this point
                    memberRef = oldDocPrefix + memberRef + className;
                } else {
                    memberRef = newDocPrefix + memberRef + className;
                }
            } else {
                if (useOld) {
                    // oldDocPrefix is non-null at this point
                    memberRef = oldDocPrefix + memberRef;
                } else {
                    memberRef = newDocPrefix + memberRef;
                }
            }
            reportFile.print("  <nobr>");
            emitType(shortReturnType);
            reportFile.print(" <A HREF=\"" + memberRef + ".html#" + methodName +
               "(" + signature + ")\" target=\"_top\"><font size=\"+1\"><tt>" + methodName + "</tt></font></A>");
            emitTypeWithParens(shortSignature);
            reportFile.println("</nobr>");
        }
        if (!useOld) {
            reportFile.println("  </TD>");
            emitComment(commentID, possibleComment, linkType);
            reportFile.println("</TR>");
        }
    
public voidwriteNavigationBar(java.lang.String pkgName, java.lang.String prevElemLink, java.lang.String nextElemLink, java.lang.String className, int level, boolean upperNavigationBar, boolean hasRemovals, boolean hasAdditions, boolean hasChanges)
Write a navigation bar section header.

param
pkgName The name of the current package, with no slashes or links in it.
param
prevElemLink An HTML link to the previous element (a package or class). May be null.
param
nextElemLink An HTML link to the next element (a package or class). May be null.
param
className The name of the current class, with no slashes or links in it. May be null.
param
level 0 = overview, 1 = package, 2 = class/interface


            String oldAPIName = "Old API";
            if (apiDiff.oldAPIName_ != null)
                oldAPIName = apiDiff.oldAPIName_;
            String newAPIName = "New API";
            if (apiDiff.newAPIName_ != null)
                newAPIName = apiDiff.newAPIName_;

            SimpleDateFormat formatter
              = new SimpleDateFormat ("yyyy.MM.dd HH:mm");
            Date day = new Date();

	    reportFile.println("<!-- Start of nav bar -->");

	    reportFile.println("<div id=\"gc-container\" style=\"padding-left:1em;padding-right:1em;\" id=\"pagecontent\">");
	    reportFile.println("<a name=\"top\"></a>");
	    reportFile.println("<div id=\"gc-header\">");
	    reportFile.println("  <div id=\"logo\"  style=\"padding-left:1em;\">");
	    reportFile.println("    <a href=\"../../../documentation.html\" target=\"_top\"><img style=\"border: 0;\" src=\"../../../assets-google/android-logo-sm.gif\" \"/></a>");
	    reportFile.println("  </div> <!-- End logo -->");
	    reportFile.println("  <div class=\"and-diff-id\">");
            reportFile.println("    <table class=\"diffspectable\">");
	    reportFile.println("      <tr>");
	    reportFile.println("        <td colspan=\"2\" class=\"diffspechead\">API Diff Specification</td>");
	    reportFile.println("      </tr>");
	    reportFile.println("      <tr>");
	    reportFile.println("        <td class=\"diffspec\" style=\"padding-top:.25em\">To Version:</td>");
	    reportFile.println("        <td class=\"diffvaluenew\" style=\"padding-top:.25em\">" + newAPIName + "</td>");
	    reportFile.println("      </tr>");
	    reportFile.println("      <tr>");
	    reportFile.println("        <td class=\"diffspec\">From Version:</td>");
	    reportFile.println("        <td class=\"diffvalueold\">" + oldAPIName + "</td>");
	    reportFile.println("      </tr>");
//	    reportFile.println("      <tr>");
//	    reportFile.println("        <td class=\"diffspec\">Product Type:</td>");
//	    reportFile.println("        <td class=\"diffvalue\">Generic</td>");
//	    reportFile.println("      </tr>");
	    reportFile.println("      <tr>");
	    reportFile.println("        <td class=\"diffspec\">Generated</td>");
	    reportFile.println("        <td class=\"diffvalue\">" + formatter.format( day ) + "</td>");
	    reportFile.println("      </tr>");
 	    reportFile.println("    </table>");
	    reportFile.println("  </div> <!-- End and-diff-id -->");

            if (doStats) {
	    	reportFile.println("  <div class=\"and-diff-id\">");
	    	reportFile.println("    <table class=\"diffspectable\">");
	    	reportFile.println("      <tr>");
	    	reportFile.println("        <td class=\"diffspec\" colspan=\"2\"><a href=\"jdiff_statistics.html\">Statistics</a></div>");
	    	reportFile.println("      </tr>");
 	    	reportFile.println("    </table>");
	    	reportFile.println("  </div> <!-- End and-diff-id -->");
	    }

	    reportFile.println("</div> <!-- End gc-header -->");
	    reportFile.println("<div id=\"codesiteContent\" style=\"margin-top: 70px;margin-bottom:80px;\">");

/*
	reportFile.println("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
        reportFile.println("  <TR>");
        reportFile.println("    <TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
        reportFile.println("    <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
        reportFile.println("    <TR ALIGN=\"center\" VALIGN=\"top\">");
        boolean atOverview = (level == 0);
        boolean atPackage = (level == 1);
        boolean atClass = (level == 2);

        // Always have a link to the Javadoc files
        if (atOverview) {
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><font size=\"+1\"><tt>" + apiDiff.newAPIName_ + "</tt></font></B></FONT></A> </TD>");
        } else if (atPackage) {
            String pkgRef = pkgName;
            pkgRef = pkgRef.replace('.', '/');
            pkgRef = newDocPrefix + pkgRef + "/package-summary";
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + pkgRef + ".html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><font size=\"+1\"><tt>" + apiDiff.newAPIName_ + "</tt></font></B></FONT></A> </TD>");
        } else if (atClass) {
            String classRef = pkgName + "." + className;
            classRef = classRef.replace('.', '/');
            if (className.indexOf('.') != -1) {
                classRef = pkgName + ".";
                classRef = classRef.replace('.', '/');
                classRef = newDocPrefix + classRef + className;
            } else {
                classRef = newDocPrefix + classRef;
            }
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + classRef + ".html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><font size=\"+1\"><tt>" + apiDiff.newAPIName_ + "</tt></font></B></FONT></A> </TD>");
        }

        if (atOverview) {
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\">  <FONT CLASS=\"NavBarFont1Rev\"><B>Overview</B></FONT> </TD>");
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">  <FONT CLASS=\"NavBarFont1\">Package</FONT> </TD>");
            reportFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\">  <FONT CLASS=\"NavBarFont1\">Class</FONT> </TD>");
        }

        String changesSummaryName = reportFileName + "-summary" + reportFileExt;
        if (atPackage) {
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + changesSummaryName + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A> </TD>");
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\">  <FONT CLASS=\"NavBarFont1Rev\"><B>Package</B></FONT> </TD>");
            reportFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\">  <FONT CLASS=\"NavBarFont1\">Class</FONT> </TD>");
        }
        if (atClass) {
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + changesSummaryName + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A> </TD>");
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"pkg_" + pkgName + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Package</B></FONT></A> </TD>");
            reportFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1Rev\">  <FONT CLASS=\"NavBarFont1Rev\"><B>Class</B></FONT> </TD>");
        }

        if (!Diff.noDocDiffs) {
            if (atPackage) {
                String id = (String)Diff.firstDiffOutput.get(pkgName + "!package");
                if (id != null)
                    reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt  + "#" + id + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A> </TD>");
                else
                    reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <FONT CLASS=\"NavBarFont1\">Text Changes</FONT> </TD>");
            } else if (atClass) {
                String id = (String)Diff.firstDiffOutput.get(pkgName + "." + className + "!class");
                if (id != null)
                    reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt  + "#" + id + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A> </TD>");
                else
                    reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <FONT CLASS=\"NavBarFont1\">Text Changes</FONT> </TD>");
            } else {
                reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A> </TD>");
            }
        }

        if (doStats) {
            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A> </TD>");
        }

        // Always have a link to the JDiff help file
        reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A> </TD>");
        reportFile.println("    </TR>");
        reportFile.println("    </TABLE>");
        reportFile.println("  </TD>");

        // The right hand side title, only added at the top
        if (upperNavigationBar) {
            reportFile.println("  <TD ALIGN=\"right\" VALIGN=\"top\" ROWSPAN=3><EM><b>Generated by<br><a href=\"" + JDiff.jDiffLocation + "\" class=\"staysblack\" target=\"_top\">JDiff</a></b></EM></TD>");
        } else {
            reportFile.println("  <TD ALIGN=\"right\" VALIGN=\"top\" ROWSPAN=3></TD>");
        }
        reportFile.println("</TR>");

        // Links for frames and no frames
        reportFile.println("<TR>");

        // All of the previous and next links, and the frames and non-frames 
        // links are in one table cell
        reportFile.println("  <TD BGCOLOR=\"" + bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");        
        // Display links to the previous and next packages or classes
        if (atPackage || atClass) {
            String elemName = "CLASS";
            if (className == null) {
                elemName = "PACKAGE";
            }
            if (prevElemLink == null) {
                reportFile.println(" <B>PREV " + elemName + "</B> ");
            } else {
                reportFile.println(" <A HREF=\"" + prevElemLink + "\"><B>PREV " + elemName + "</B></A>");
            }
            if (nextElemLink == null) {
                reportFile.println(" <B>NEXT " + elemName + "</B> ");
            } else {
                reportFile.println(" <A HREF=\"" + nextElemLink + "\"><B>NEXT " + elemName + "</B></A>");
            }
            reportFile.println("        ");
        } else {
            reportFile.println("    ");        
        }
        // Links for frames and non-frames.
        reportFile.println("  <A HREF=\"" + "../" + reportFileName + reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A>   ");
        if (className == null) {
            if (level == 0) {
                reportFile.println("   <A HREF=\"" + pkgName + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
            } else {
                reportFile.println("   <A HREF=\"pkg_" + pkgName + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
            }
        } else {
            reportFile.println("   <A HREF=\"" + pkgName + "." + className + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
        }

        // All of the details links are in one table cell
        if (atClass) {
            // Links to a class page's sections
            // The meaning of these three variable is overloaded
            boolean hasCtors = hasRemovals; 
            boolean hasMethods = hasAdditions; 
            boolean hasFields = hasChanges; 
            if (hasCtors || hasMethods || hasFields) {
                reportFile.println("  <TD BGCOLOR=\"" + bgcolor + "\" CLASS=\"NavBarCell3\"><FONT SIZE=\"-2\"> DETAIL:  ");
                if (hasCtors) {
                    reportFile.println("<a href=\"#constructors\">CONSTRUCTORS</a> | ");
                } else {
                    reportFile.println("CONSTRUCTORS | ");
                }
                if (hasMethods) {
                    reportFile.println("<a href=\"#methods\">METHODS</a> | ");
                } else {
                    reportFile.println("METHODS | ");
                }
                if (hasFields) {
                    reportFile.println("<a href=\"#fields\">FIELDS</a>");
                } else {
                    reportFile.println("FIELDS");
                }
                reportFile.println("  </FONT></TD>");
            } else {
                // Make the end of the table line match the length of the top
                reportFile.println("<TD BGCOLOR=\"0xFFFFFF\" CLASS=\"NavBarCell3\"></TD>"); 
            }
        } else {
            // Links to a package page's sections
            if (hasRemovals || hasAdditions || hasChanges) {
                reportFile.println("  <TD BGCOLOR=\"" + bgcolor + "\" CLASS=\"NavBarCell3\"><FONT SIZE=\"-2\"> DETAIL:  ");
                if (hasRemovals) {
                    reportFile.println("<a href=\"#Removed\">REMOVED</a> | ");
                } else {
                    reportFile.println("REMOVED | ");
                }
                if (hasAdditions) {
                    reportFile.println("<a href=\"#Added\">ADDED</a> | ");
                } else {
                    reportFile.println("ADDED | ");
                }
                if (hasChanges) {
                    reportFile.println("<a href=\"#Changed\">CHANGED</a>");
                } else {
                    reportFile.println("CHANGED");
                }
                reportFile.println("  </FONT></TD>");
            } else {
                // Make the end of the table line match the length of the top
                reportFile.println("<TD BGCOLOR=\"0xFFFFFF\" CLASS=\"NavBarCell3\"></TD>"); 
            }
        }

        reportFile.println("</TR>");
        reportFile.println("</TABLE>");
        reportFile.println("<HR>");
        reportFile.println("<!-- End of nav bar -->");
*/
    
public voidwritePackageTableEntry(java.lang.String pkgName, int linkType, java.lang.String possibleComment, boolean useOld)
Write a table entry for a package, with support for links to Javadoc for removed packages. linkType: 0 - no link by default, 1 = link to Javadoc HTML file, 2 = link to JDiff file

        if (!useOld) {
            reportFile.println("<TR BGCOLOR=\"" + bgcolor + "\" CLASS=\"TableRowColor\">");
            reportFile.println("  <TD VALIGN=\"TOP\" WIDTH=\"25%\">");
            reportFile.println("  <A NAME=\"" + pkgName + "\"></A>"); // Named anchor
        }
        //String shownPkgName = makeTwoRows(pkgName);
        if (linkType == 0) {
            if (oldDocPrefix == null) {
                // No link
                reportFile.print("  " + pkgName);
            } else {
                // Call this method again but this time to emit a link to the 
                // old program element.
                writePackageTableEntry(pkgName, 1, possibleComment, true);
            }
        } else if (linkType == 1) {
            // Link to HTML file for the package
            String pkgRef = pkgName;
            pkgRef = pkgRef.replace('.", '/");
            if (useOld)
                pkgRef = oldDocPrefix + pkgRef + "/package-summary";
            else
                pkgRef = newDocPrefix + pkgRef + "/package-summary";
            reportFile.println("  <nobr><A HREF=\"" + pkgRef + ".html\" target=\"_top\"><font size=\"+1\"><tt>" + pkgName + "</tt></font></A></nobr>");
        } else if (linkType == 2) {
            reportFile.println("  <nobr><A HREF=\"pkg_" + pkgName + reportFileExt + "\">" + pkgName + "</A></nobr>");
        } 
        if (!useOld) {
            reportFile.println("  </TD>");
            emitComment(pkgName, possibleComment, linkType);
            reportFile.println("</TR>");
        }
    
public voidwriteReport(APIDiff apiDiff)
Write the HTML report. The top section describes all the packages added (with links) and removed, and the changed packages section has links which takes you to a section for each package. This pattern continues for classes and constructors, methods and fields.


        // Report packages which were removed in the new API
        if (apiDiff.packagesRemoved.size() != 0) {
            writeTableStart("Removed Packages", 2);
            Iterator iter = apiDiff.packagesRemoved.iterator();
            while (iter.hasNext()) {
                PackageAPI pkgAPI = (PackageAPI)(iter.next());
                String pkgName = pkgAPI.name_;
                if (trace) System.out.println("Package " + pkgName + " was removed.");
                writePackageTableEntry(pkgName, 0, pkgAPI.doc_, false);
            }
            writeTableEnd();
        }
        
        // Report packages which were added in the new API
        if (apiDiff.packagesAdded.size() != 0) {
            writeTableStart("Added Packages", 2);
            Iterator iter = apiDiff.packagesAdded.iterator();
            while (iter.hasNext()) {
                PackageAPI pkgAPI = (PackageAPI)(iter.next());
                String pkgName = pkgAPI.name_;
                if (trace) System.out.println("Package " + pkgName + " was added.");
                writePackageTableEntry(pkgName, 1, pkgAPI.doc_, false);
            }
            writeTableEnd();
        }

        // Report packages which were changed in the new API
        if (apiDiff.packagesChanged.size() != 0) {
            // Emit a table of changed packages, with links to the file
            // for each package.
            writeTableStart("Changed Packages", 3);
            Iterator iter = apiDiff.packagesChanged.iterator();
            while (iter.hasNext()) {
                PackageDiff pkgDiff = (PackageDiff)(iter.next());
                String pkgName = pkgDiff.name_;
                if (trace) System.out.println("Package " + pkgName + " was changed.");
                writePackageTableEntry(pkgName, 2, null, false);
            }
            writeTableEnd();
            writeText("<!-- End of API section -->");

            // Now emit a separate file for each changed package.
            writeText("<!-- Start of packages section -->");
            PackageDiff[] pkgDiffs = new PackageDiff[apiDiff.packagesChanged.size()];
            pkgDiffs = (PackageDiff[])apiDiff.packagesChanged.toArray(pkgDiffs);
            for (int i = 0; i < pkgDiffs.length; i++) {
                reportChangedPackage(pkgDiffs, i);
            }
        }
            writeText("</div><!-- end pagecontent -->");
            writeText("</div><!-- end codesitecontent -->");
            writeText("<div style=\"padding-left: 10px; padding-right: 10px; margin-top: 0; padding-bottom: 15px;\">");
            writeText("  <table style=\"width: 100%; border: none;\"><tr>");
            writeText("    <td style=\"text-align:center;font-size: 10pt; border: none; color: ccc;\"> ");
            writeText("      <span>©2008 Google - ");
            writeText("            <a href=\"http://code.google.com\">Code Home</a> - ");
            writeText("            <a href=\"http://www.google.com/accounts/TOS\">Site Terms of Service</a> - "); 
            writeText("            <a href=\"http://www.google.com/privacy.html\">Privacy Policy</a> ");
            writeText("      </span>");
            writeText("      <div style=\"position:relative;margin-top:-2em;" );
            writeText("        font-size:8pt;color:aaa;text-align:right;\">");
            writeText("        <em>Generated by <a href=\"http://www.jdiff.org/\">JDiff</a></em><br><img ");
            writeText("        align=\"right\" src=\"../../../assets/jdiff_logo.gif\">");
            writeText("      </span>");
            writeText("    </td>");
            writeText(" </tr></table>");
            writeText("</div>");
            writeText("</div><!-- end gc-containter -->");
public voidwriteSectionFooter(java.lang.String packageName, java.lang.String prevElemLink, java.lang.String nextElemLink, java.lang.String className, int level)
Write a section footer, which includes a navigation bar.

param
packageName The name of the current package, with no slashes or links in it. may be null
param
prevElemLink An HTML link to the previous element (a package or class). May be null.
param
nextElemLink An HTML link to the next element (a package or class). May be null.
param
className The name of the current class, with no slashes or links in it. May be null
param
level 0 = overview, 1 = package, 2 = class/interface

            writeText("</div><!-- end codesitecontent -->");
            writeText("<div style=\"padding-left: 10px; padding-right: 10px; margin-top: 0; padding-bottom: 15px;\">");
            writeText("  <table style=\"width: 100%; border: none;\"><tr>");
            writeText("    <td style=\"text-align:center;font-size: 10pt; border: none; color: ccc;\"> ");
            writeText("      <span>©2008 Google - ");
            writeText("            <a href=\"http://code.google.com\">Code Home</a> - ");
            writeText("            <a href=\"http://www.google.com/accounts/TOS\">Site Terms of Service</a> - "); 
            writeText("            <a href=\"http://www.google.com/privacy.html\">Privacy Policy</a> ");
            writeText("      </span>");
            writeText("      <div style=\"xborder:1px solid red;position:relative;margin-top:-2em;" );
            writeText("        font-size:8pt;color:aaa;text-align:right;\">");
            writeText("        <em>Generated by <a href=\"http://www.jdiff.org/\">JDiff</a></em><br><img ");
            writeText("        align=\"right\" src=\"../../../assets/jdiff_logo.gif\">");
            writeText("      </span>");
            writeText("    </td>");
            writeText(" </tr></table>");
            writeText("</div>");
            writeText("</div><!-- end gc-containter -->");
/*
        reportFile.println("<HR>");
        writeNavigationBar(packageName, prevElemLink, nextElemLink, 
                           className, level, false,
                           false, false, false);
*/
    
public voidwriteSectionHeader(java.lang.String title, java.lang.String packageName, java.lang.String prevElemLink, java.lang.String nextElemLink, java.lang.String className, int level, boolean hasRemovals, boolean hasAdditions, boolean hasChanges)
Write a section header, which includes a navigation bar.

param
title Title of the header. Contains any links necessary.
param
packageName The name of the current package, with no slashes or links in it. May be null
param
prevElemLink An HTML link to the previous element (a package or class). May be null.
param
nextElemLink An HTML link to the next element (a package or class). May be null.
param
className The name of the current class, with no slashes or links in it. May be null.
param
level 0 = overview, 1 = package, 2 = class/interface

        writeNavigationBar(packageName, prevElemLink, nextElemLink,
                           className, level, true,
                           hasRemovals, hasAdditions, hasChanges);
        if (level != 0) {
            reportFile.println("<H2>");
            reportFile.println(title);
            reportFile.println("</H2>");
        }
    
public voidwriteStartHTMLHeader()
Write the start of the HTML header.

        writeStartHTMLHeader(false);
    
public voidwriteStartHTMLHeader(boolean addDate)
Write the start of the HTML header.

        writeText("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\"\"" + RootDocToXML.baseURI + "/TR/REC-html40/frameset.dtd\">");
        writeText("<HTML>");
        writeText("<HEAD>");
        writeText("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">");
        writeText("<!-- Generated by the JDiff Javadoc doclet -->");
        writeText("<!-- (" + JDiff.jDiffLocation + ") -->");
        if (addDate)
            writeText("<!-- on " + new Date() + " -->");
        writeText("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">");
        writeText("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">");
    
public voidwriteStartHTMLHeaderWithDate()
Write the start of the HTML header, together with the current date and time in an HTML comment.

        writeStartHTMLHeader(true);
    
public voidwriteStyleSheetRef()
Write the HTML style sheet reference for files in the subdirectory.

        writeStyleSheetRef(false);
    
public voidwriteStyleSheetRef(boolean inSameDir)
Write the HTML style sheet reference. If inSameDir is set, don't add "../" to the location.

        if (inSameDir) {
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/codesite/codesite.css\" />");
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/codesite/codesearch.css\" />");
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/codesite/semantic_headers.css\" />");
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/style.css\" />");
            writeText("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"stylesheet-jdiff.css\" TITLE=\"Style\">");
	}
        else {
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/codesite/codesite.css\" />");
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/codesite/codesearch.css\" />");
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/codesite/semantic_headers.css\" />");
            writeText("<link rel=\"stylesheet\" type=\"text/css\" href=\"../../../assets/style.css\" />");
            writeText("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"../stylesheet-jdiff.css\" TITLE=\"Style\">");
	}
// This doesn't work in non-windows browsers, so have to change the stylesheet
//        writeText("<!-- Override the color choice for the navigation bar -->");
//        writeText("<STYLE>");
//        //writeText(".NavBarCell1     { background-color:#FFFF99;} /* palegoldenrod */");
//        writeText(".NavBarCell1     { background-color:#FFFFCC;} /*  */");
//        writeText("</STYLE>");
    
public voidwriteTableEnd()
Write the end of a table.

        reportFile.println("</TABLE>");
        reportFile.println(" ");
    
public voidwriteTableStart(java.lang.String title, int colSpan)
Write the start of a table.

        reportFile.println("<p>");
        // Assumes that the first word of the title categorizes the table type
        // and that there is a space after the first word in the title
        int idx = title.indexOf(' ");
        String namedAnchor = title.substring(0, idx);
        reportFile.println("<a NAME=\"" + namedAnchor + "\"></a>"); // Named anchor
        reportFile.println("<TABLE summary=\"" + title+ "\" WIDTH=\"100%\">");
        reportFile.println("<TR>");
        reportFile.print("  <TH VALIGN=\"TOP\" COLSPAN=" + colSpan + ">");
        reportFile.println(title + "</FONT></TD>");
        reportFile.println("</TH>");
    
public voidwriteText()
Write a newline out.

        reportFile.println();
    
public voidwriteText(java.lang.String text)
Write some text out.

        reportFile.println(text);