auto import from //depot/cupcake/@135843
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..16f62cc
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(filter-out %/JDiffAntTask.java,$(LOCAL_SRC_FILES))
+
+LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
+
+LOCAL_MODULE := jdiff
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/MODULE_LICENSE_LGPL b/MODULE_LICENSE_LGPL
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_LGPL
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..fab48ed
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,506 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
+
+
diff --git a/README.txt b/README.txt
new file mode 100755
index 0000000..9f7529e
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,59 @@
+
+                           JDiff Doclet
+                           ------------
+
+                           Matthew Doar
+                          mdoar@pobox.com
+
+
+The JDiff doclet is used to generate a report describing the
+difference between two public Java APIs. 
+
+The file jdiff.html contains the reference page for JDiff.  The latest
+version of JDiff can be downloaded at:
+http://sourceforge.net/projects/javadiff
+
+To use the Ant task on your own project, see example.xml. More examples
+of using JDiff to compare the public APIs of J2SE1.3 and J2SE1.4 can
+be seen at http://www.jdiff.org
+
+For an example with the source distribution, run "ant" and
+look at the HTML output in ./build/reports/example/changes.html 
+The page at ./build/reports/example/changes/com.acme.sp.SPImpl.html
+shows what a typical page of changes looks like. 
+
+System Requirements
+-------------------
+
+JDiff has been tested with all releases of Java since J2SE1.2 but
+releases of JDiff after 1.10.0 focus on JDK1.5.
+
+License
+-------
+
+JDiff is licensed under the Lesser GNU General Public License (LGPL).
+See the file LICENSE.txt.
+
+Acknowledgements
+----------------
+
+JDiff uses Stuart D. Gathman's Java translation of Gene Myers' O(ND) 
+difference algorithm.
+
+JDiff uses Xerces 1.4.2 from http://www.apache.org.
+
+JDiff also includes a script to use the classdoc application from
+http://classdoc.sourceforge.net or http://www.jensgulden.de, by Jens
+Gulden, (mail@jensgulden.de), to call a doclet such as jdiff on a .jar
+file rather than on source.
+
+Many thanks to the reviewers at Sun and Vitria who gave feedback on early
+versions of JDiff output, and also to the distillers of Laphroaig, and to
+Arturo Fuente for his consistently fine cigars which helped inspire
+much of this work.
+
+
+Footnote:
+
+If you are looking for a generalized diff tool for XML, try diffmk from
+http://wwws.sun.com/software/xml/developers/diffmk/
diff --git a/build.xml b/build.xml
new file mode 100755
index 0000000..3dd9657
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="jdiff" default="jar" basedir=".">
+
+<description>
+Builds Android's jdiff.jar and places it in device/extlibs/jdiff-1.1.0/. 
+</description>
+	 
+
+<target name="init" description="Properties used by other targets">
+  <property name="extlibs.dir" value="${basedir}/../../extlibs/jdiff-1.1.0/" />
+  <property name="src.dir" value="${basedir}/src" />
+  <property name="build.dir" value="build" />
+  <property name="classes.dir" value="${build.dir}/classes" />
+  <property name="lib.dir" value="${build.dir}/lib" />
+  <property name="version" value="1.1.0" />
+  <path id="class.path">
+    <pathelement location="{basedir}/../../extlibs/xerces.jar" />
+  </path>
+  <property name="debug" value="true" />
+  <property name="JDIFF_HOME" value="${extlibs.dir}" />
+</target>
+
+<target name="create.dirs" depends="init">
+  <mkdir dir="${build.dir}" />
+  <mkdir dir="${lib.dir}" />
+  <mkdir dir="${classes.dir}" />
+</target>
+
+<target	name="clean" depends="init">
+  <delete includeEmptyDirs="true"
+          verbose="false"
+          quiet="true">
+    <fileset dir="${build.dir}" />
+  </delete>
+</target>
+
+<target name="compile" depends="create.dirs" description="Compile everything except the Ant task">
+  <javac srcdir="${src.dir}"
+         destdir="${classes.dir}"
+         debug="${debug}"
+         excludes="jdiff/JDiffAntTask.java"
+         includeJavaRuntime="yes">
+    <classpath>
+      <path refid="class.path" />
+    </classpath>
+<!--<compilerarg value="-Xlint:unchecked"/>   -->
+  </javac>
+</target>	
+
+<!-- Place the jdiff.jar file in <branch-root>/device/extlibs/jdiff-.1.0/ (under version control) -->
+	<target name="jar" depends="compile">
+  <jar jarfile="${basedir}/jdiff.jar" 
+       basedir="${classes.dir}"
+       includes="*jdiff/*.class"
+       excludes="*jdiff/JDiffAntTask*.class" />
+</target>
+
+</project>
diff --git a/src/api.xsd b/src/api.xsd
new file mode 100755
index 0000000..bb2368f
--- /dev/null
+++ b/src/api.xsd
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+<xsd:annotation>
+  <xsd:documentation>
+  Schema for JDiff API representation.
+  </xsd:documentation>
+</xsd:annotation>
+
+<xsd:element name="api" type="apiType"/>
+
+<xsd:complexType name="apiType">
+  <xsd:sequence>
+    <xsd:element name="package" type="packageType" minOccurs='1' maxOccurs='unbounded'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="jdversion" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="packageType">
+  <xsd:sequence>
+    <xsd:choice maxOccurs='unbounded'>
+      <xsd:element name="class" type="classType"/>
+      <xsd:element name="interface" type="classType"/>
+    </xsd:choice>
+    <xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="classType">
+  <xsd:sequence>
+    <xsd:element name="implements" type="interfaceTypeName" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="constructor" type="constructorType" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="method" type="methodType" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="field" type="fieldType" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="extends" type="xsd:string" use='optional'/>
+  <xsd:attribute name="abstract" type="xsd:boolean"/>
+  <xsd:attribute name="src" type="xsd:string" use='optional'/>
+  <xsd:attribute name="static" type="xsd:boolean"/>
+  <xsd:attribute name="final" type="xsd:boolean"/>
+  <xsd:attribute name="deprecated" type="xsd:string"/>
+  <xsd:attribute name="visibility" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="interfaceTypeName">
+  <xsd:attribute name="name" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="constructorType">
+  <xsd:sequence>
+    <xsd:element name="exception" type="exceptionType" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="type" type="xsd:string" use='optional'/>
+  <xsd:attribute name="src" type="xsd:string" use='optional'/>
+  <xsd:attribute name="static" type="xsd:boolean"/>
+  <xsd:attribute name="final" type="xsd:boolean"/>
+  <xsd:attribute name="deprecated" type="xsd:string"/>
+  <xsd:attribute name="visibility" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="paramsType">
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="type" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="exceptionType">
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="type" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="methodType">
+  <xsd:sequence>
+    <xsd:element name="param" type="paramsType" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="exception" type="exceptionType" minOccurs='0' maxOccurs='unbounded'/>
+    <xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="return" type="xsd:string" use='optional'/>
+  <xsd:attribute name="abstract" type="xsd:boolean"/>
+  <xsd:attribute name="native" type="xsd:boolean"/>
+  <xsd:attribute name="synchronized" type="xsd:boolean"/>
+  <xsd:attribute name="src" type="xsd:string" use='optional'/>
+  <xsd:attribute name="static" type="xsd:boolean"/>
+  <xsd:attribute name="final" type="xsd:boolean"/>
+  <xsd:attribute name="deprecated" type="xsd:string"/>
+  <xsd:attribute name="visibility" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="fieldType">
+  <xsd:sequence>
+    <xsd:element name="doc" type="xsd:string" minOccurs='0' maxOccurs='1'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="type" type="xsd:string"/>
+  <xsd:attribute name="transient" type="xsd:boolean"/>
+  <xsd:attribute name="volatile" type="xsd:boolean"/>
+  <xsd:attribute name="value" type="xsd:string" use='optional'/>
+  <xsd:attribute name="src" type="xsd:string" use='optional'/>
+  <xsd:attribute name="static" type="xsd:boolean"/>
+  <xsd:attribute name="final" type="xsd:boolean"/>
+  <xsd:attribute name="deprecated" type="xsd:string"/>
+  <xsd:attribute name="visibility" type="xsd:string"/>
+</xsd:complexType>
+
+</xsd:schema>
diff --git a/src/comments.xsd b/src/comments.xsd
new file mode 100755
index 0000000..cfee740
--- /dev/null
+++ b/src/comments.xsd
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="iso-8859-1" standalone="no"?>
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
+<xsd:annotation>
+  <xsd:documentation>
+  Schema for JDiff comments.
+  </xsd:documentation>
+</xsd:annotation>
+
+<xsd:element name="comments" type="commentsType"/>
+
+<xsd:complexType name="commentsType">
+  <xsd:sequence>
+    <xsd:element name="comment" type="commentType" minOccurs='0' maxOccurs='unbounded'/>
+  </xsd:sequence>
+  <xsd:attribute name="name" type="xsd:string"/>
+  <xsd:attribute name="jdversion" type="xsd:string"/>
+</xsd:complexType>
+
+<xsd:complexType name="commentType">
+  <xsd:sequence>
+    <xsd:element name="identifier" type="identifierType" minOccurs='1' maxOccurs='unbounded'/>
+    <xsd:element name="text" type="xsd:string" minOccurs='1' maxOccurs='1'/>
+  </xsd:sequence>
+</xsd:complexType>
+
+<xsd:complexType name="identifierType">
+  <xsd:attribute name="id" type="xsd:string"/>
+</xsd:complexType>
+
+</xsd:schema>
\ No newline at end of file
diff --git a/src/jdiff/API.java b/src/jdiff/API.java
new file mode 100755
index 0000000..2e8f117
--- /dev/null
+++ b/src/jdiff/API.java
@@ -0,0 +1,429 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * The internal representation of an API. 
+ * 
+ * RootDoc could have been used for representing this, but 
+ * you cannot serialize a RootDoc object - see
+ *  http://developer.java.sun.com/developer/bugParade/bugs/4125581.html
+ * You might be able use Javadoc.Main() to create another RootDoc, but the 
+ * methods are package private. You can run javadoc in J2SE1.4, see:
+ *  http://java.sun.com/j2se/1.4/docs/tooldocs/javadoc/standard-doclet.html#runningprogrammatically
+ * but you still can't get the RootDoc object.
+ *
+ * The advantage of writing out an XML representation of each API is that
+ * later runs of JDiff don't have to have Javadoc scan all the files again,
+ * a possibly lengthy process. XML also permits other source code in 
+ * languages other than Java to be scanned to produce XML, and then versions
+ * of JDiff can be used to create documents describing the difference in those
+ * APIs.
+ * 
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class API {
+
+    /** 
+     * The list of all the top-level packages. 
+     * Each package contains classes, each class contains members, and so on.
+     */
+    public List packages_; // PackageAPI[]
+
+    /** 
+     * The list of all the classes. 
+     * This is used to generate the methods and fields which are inherited,
+     * rather than storing them in the XML file.
+     */
+    public Hashtable classes_;
+
+    /** 
+     * The String which identifies this API, e.g. &quotSuperProduct 1.3&quot;.
+     */
+    public String name_ = null;
+
+    /** The current package being added to during parsing. */
+    public PackageAPI currPkg_ = null;
+    /** The current class being added to during parsing. */
+    public ClassAPI currClass_ = null;
+    /** The current constructor being added to during parsing. */
+    public ConstructorAPI currCtor_ = null;
+    /** The current method being added to during parsing. */
+    public MethodAPI currMethod_ = null;
+    /** The current field being added to during parsing. */
+    public FieldAPI currField_ = null;
+
+    /** Default constructor. */
+    public API() {
+        packages_ = new ArrayList(); //PackageAPI[]
+        classes_ = new Hashtable(); //ClassAPI
+    }   
+  
+//
+// Methods to display the contents of an API object.
+//
+
+    /** Amount by which to increment each indentation. */
+    public static final int indentInc = 2;
+
+    /** Display the contents of the API object. */
+    public void dump() {
+        int indent = 0;
+        Iterator iter = packages_.iterator();
+        while (iter.hasNext()) {
+            dumpPackage((PackageAPI)(iter.next()), indent);
+        }
+    }
+
+    /** 
+     * Display the contents of a PackageAPI object.
+     *
+     * @param pkg The given PackageAPI object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public void dumpPackage(PackageAPI pkg, int indent) {
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        System.out.println("Package Name: " + pkg.name_);
+        Iterator iter = pkg.classes_.iterator();
+        while (iter.hasNext()) {
+            dumpClass((ClassAPI)(iter.next()), indent + indentInc);
+        }
+        // Display documentation
+        if (pkg.doc_ != null) {
+            System.out.print("Package doc block:");
+            System.out.println("\"" + pkg.doc_ + "\"");
+        }
+    }
+
+    /** 
+     * Display the contents of a ClassAPI object.
+     *
+     * @param c The given ClassAPI object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public static void dumpClass(ClassAPI c, int indent) {
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        if (c.isInterface_)
+            System.out.println("Interface name: " + c.name_);
+        else
+            System.out.println("Class Name: " + c.name_);
+        if (c.extends_ != null) {
+            for (int i = 0; i < indent; i++) System.out.print(" ");
+            System.out.println("Extends: " + c.extends_);
+        }
+        if (c.implements_.size() != 0) {
+            for (int i = 0; i < indent; i++) System.out.print(" ");
+            System.out.println("Implements: ");
+            Iterator iter = c.implements_.iterator();
+            while (iter.hasNext()) {
+                String interfaceImpl = (String)(iter.next());
+                for (int i = 0; i < indent + 2; i++) System.out.print(" ");
+                System.out.println("  " + interfaceImpl);
+            }
+        }
+        // Dump modifiers specific to a class
+        if (c.isAbstract_)
+            System.out.print("abstract ");
+        // Dump modifiers common to all
+        dumpModifiers(c.modifiers_, indent);
+        // Dump ctors
+        Iterator iter = c.ctors_.iterator();
+        while (iter.hasNext()) {
+            dumpCtor((ConstructorAPI)(iter.next()), indent + indentInc);
+        }
+        // Dump methods
+        iter = c.methods_.iterator();
+        while (iter.hasNext()) {
+            dumpMethod((MethodAPI)(iter.next()), indent + indentInc);
+        }
+        // Dump fields
+        iter = c.fields_.iterator();
+        while (iter.hasNext()) {
+            dumpField((FieldAPI)(iter.next()), indent + indentInc);
+        }
+        // Display documentation
+        if (c.doc_ != null) {
+            System.out.print("Class doc block:");
+            System.out.println("\"" + c.doc_ + "\"");
+        } else
+            System.out.println();
+    }
+
+    /** 
+     * Display the contents of the Modifiers object.
+     *
+     * @param c The given Modifiers object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public static void dumpModifiers(Modifiers m, int indent) {
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        if (m.isStatic)
+            System.out.print("static ");
+        if (m.isFinal)
+            System.out.print("final ");
+        if (m.visibility != null)
+            System.out.print("visibility = " + m.visibility + " ");
+        // Flush the line
+        System.out.println();
+    }
+
+    /** 
+     * Display the contents of a constructor.
+     *
+     * @param c The given constructor object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public static void dumpCtor(ConstructorAPI c, int indent) {
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        System.out.println("Ctor type: " + c.type_);
+        // Display exceptions 
+        System.out.print("exceptions: " + c.exceptions_ + " ");
+        // Dump modifiers common to all
+        dumpModifiers(c.modifiers_, indent);
+        // Display documentation
+        if (c.doc_ != null) {
+            System.out.print("Ctor doc block:");
+            System.out.println("\"" + c.doc_ + "\"");
+        }
+    }
+
+    /** 
+     * Display the contents of a MethodAPI object.
+     *
+     * @param m The given MethodAPI object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public static void dumpMethod(MethodAPI m, int indent) {
+        if (m.inheritedFrom_ != null)
+            return;
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        System.out.print("Method Name: " + m.name_);
+        if (m.inheritedFrom_ != null)
+            System.out.println(", inherited from: " + m.inheritedFrom_);
+        if (m.returnType_ != null)
+            System.out.println(", return type: " + m.returnType_);
+        else
+            System.out.println();
+        // Dump modifiers specific to a method
+        if (m.isAbstract_)
+            System.out.print("abstract ");
+        if (m.isNative_)
+            System.out.print("native ");
+        if (m.isSynchronized_)
+            System.out.print("synchronized ");
+        // Display exceptions 
+        System.out.print("exceptions: " + m.exceptions_ + " ");
+        // Dump modifiers common to all
+        dumpModifiers(m.modifiers_, indent);
+      
+        Iterator iter = m.params_.iterator();
+        while (iter.hasNext()) {
+            dumpParam((ParamAPI)(iter.next()), indent + indentInc);
+        }
+        // Display documentation
+        if (m.doc_ != null) {
+            System.out.print("Method doc block:");
+            System.out.println("\"" + m.doc_ + "\"");
+        }
+    }
+
+    /** 
+     * Display the contents of a field.
+     * Does not show inherited fields.
+     *
+     * @param f The given field object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public static void dumpField(FieldAPI f, int indent) {
+        if (f.inheritedFrom_ != null)
+            return;
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        System.out.println("Field Name: " + f.name_ + ", type: " + f.type_);
+        if (f.inheritedFrom_ != null)
+            System.out.println(", inherited from: " + f.inheritedFrom_);
+        if (f.isTransient_)
+            System.out.print("transient ");
+        if (f.isVolatile_)
+            System.out.print("volatile ");
+        // Dump modifiers common to all
+        dumpModifiers(f.modifiers_, indent);
+        // Display documentation
+        if (f.doc_ != null)
+            System.out.print("Field doc block:");
+            System.out.println("\"" + f.doc_ + "\"");
+    }
+
+    /** 
+     * Display the contents of a parameter.
+     *
+     * @param p The given parameter object.
+     * @param indent The number of spaces to indent the output.
+     */
+    public static void dumpParam(ParamAPI p, int indent) {
+        for (int i = 0; i < indent; i++) System.out.print(" ");
+        System.out.println("Param Name: " + p.name_ + ", type: " + p.type_);
+    }
+
+    /** 
+     * Convert all HTML tags to text by placing them inside a CDATA element.
+     * Characters still have to be valid Unicode characters as defined by the 
+     * parser.
+     */
+    public static String stuffHTMLTags(String htmlText) {
+        if (htmlText.indexOf("]]>") != -1) {
+            System.out.println("Warning: illegal string ]]> found in text. Ignoring the comment.");
+            return "";
+        }
+        return "<![CDATA[" + htmlText + "]]>";
+    }
+
+    /**
+     * Convert all HTML tags to text by stuffing text into the HTML tag
+     * to stop it being an HTML or XML tag. E.g. &quot;<code>foo</code>&quot;
+     * becomes &quot;lEsS_tHaNcode>foolEsS_tHaN/code>&quot;. Replace all &lt; 
+     * characters
+     * with the string "lEsS_tHaN". Also replace &amp; character with the  
+     * string "aNd_cHaR" to avoid text entities. Also replace &quot; 
+     * character with the  
+     * string "qUoTe_cHaR".
+     */
+    public static String hideHTMLTags(String htmlText) {
+        StringBuffer sb = new StringBuffer(htmlText);
+        int i = 0;
+        while (i < sb.length()) {
+            if (sb.charAt(i) == '<') {
+                sb.setCharAt(i ,'l');
+                sb.insert(i+1, "EsS_tHaN");
+            } else if (sb.charAt(i) == '&') {
+                sb.setCharAt(i ,'a');
+                sb.insert(i+1, "Nd_cHaR");
+            } else if (sb.charAt(i) == '"') {
+                sb.setCharAt(i ,'q');
+                sb.insert(i+1, "uote_cHaR");
+            }
+            i++;
+        }
+        return sb.toString();
+    }
+
+    /** 
+     * Convert text with stuffed HTML tags ("lEsS_tHaN", etc) into HTML text.
+     */
+    public static String showHTMLTags(String text) {
+        StringBuffer sb = new StringBuffer(text);
+        StringBuffer res = new StringBuffer();
+        int len = sb.length();
+        res.setLength(len);
+        int i = 0;
+        int resIdx = 0;
+        while (i < len) {
+            char c = sb.charAt(i);
+            if (len - i > 8 && c == 'l' && 
+                sb.charAt(i+1) == 'E' &&
+                sb.charAt(i+2) == 's' &&
+                sb.charAt(i+3) == 'S' &&
+                sb.charAt(i+4) == '_' &&
+                sb.charAt(i+5) == 't' &&
+                sb.charAt(i+6) == 'H' &&
+                sb.charAt(i+7) == 'a' &&
+                sb.charAt(i+8) == 'N') {
+                res.setCharAt(resIdx ,'<');
+                i += 8;
+            } else if (len - i > 9 && c == 'q' && 
+                sb.charAt(i+1) == 'U' &&
+                sb.charAt(i+2) == 'o' &&
+                sb.charAt(i+3) == 'T' &&
+                sb.charAt(i+4) == 'e' &&
+                sb.charAt(i+5) == '_' &&
+                sb.charAt(i+6) == 'c' &&
+                sb.charAt(i+7) == 'H' &&
+                sb.charAt(i+8) == 'a' &&
+                sb.charAt(i+9) == 'R') {
+                res.setCharAt(resIdx ,'"');
+                i += 9;
+            } else if (len - i > 7 && c == 'a' && 
+                sb.charAt(i+1) == 'N' &&
+                sb.charAt(i+2) == 'd' &&
+                sb.charAt(i+3) == '_' &&
+                sb.charAt(i+4) == 'c' &&
+                sb.charAt(i+5) == 'H' &&
+                sb.charAt(i+6) == 'a' &&
+                sb.charAt(i+7) == 'R') {
+                res.setCharAt(resIdx ,'&');
+                i += 7;
+            } else {
+                res.setCharAt(resIdx, c);
+            }
+            i++;
+            resIdx++;
+        }
+        res.setLength(resIdx);
+        return res.toString();
+    }
+
+    /** 
+     * <b>NOT USED</b>. 
+     *
+     * Replace all instances of <p> with <p/>. Just for the small number
+     * of HMTL tags which don't require a matching end tag.
+     * Also make HTML conform to the simple HTML requirements such as 
+     * no double hyphens. Double hyphens are replaced by - and the character
+     * entity for a hyphen.
+     *
+     * Cases where this fails and has to be corrected in the XML by hand: 
+     *  Attributes' values missing their double quotes , e.g. size=-2
+     *  Mangled HTML tags e.g. &lt;ttt> 
+     *
+     * <p><b>NOT USED</b>. There is often too much bad HTML in
+     * doc blocks to try to handle every case correctly. Better just to
+     * stuff the *lt; and &amp: characters with stuffHTMLTags(). Though
+     * the resulting XML is not as elegant, it does the job with less
+     * intervention by the user.
+     */
+    public static String convertHTMLTagsToXHTML(String htmlText) {
+        StringBuffer sb = new StringBuffer(htmlText);
+        int i = 0;
+        boolean inTag = false;
+        String tag = null;
+        // Needs to re-evaluate this length at each loop
+        while (i < sb.length()) {
+            char c = sb.charAt(i);
+            if (inTag) {
+                if (c == '>') {
+                    // OPTION Could fail at or fix some errorneous tags here
+                    // Make the best guess as to whether this tag is terminated
+                    if (Comments.isMinimizedTag(tag) &&
+                        htmlText.indexOf("</" + tag + ">", i) == -1)
+                        sb.insert(i, "/");
+                    inTag = false;
+                } else {
+                    // OPTION could also make sure that attribute values are
+                    // surrounded by quotes.
+                    tag += c;
+                }
+            }
+            if (c == '<') {
+                inTag = true;
+                tag = "";
+            }
+            // -- is not allowed in XML, but !-- is part of an comment,
+            // and --> is also part of a comment
+            if (c == '-' && i > 0 && sb.charAt(i-1) == '-') {
+                if (!(i > 1 && sb.charAt(i-2) == '!')) { 
+                    sb.setCharAt(i, '&');
+                    sb.insert(i+1, "#045;");
+                    i += 5;
+                }
+            }
+            i++;
+        }
+        if (inTag) {
+            // Oops. Someone forgot to close their HTML tag, e.g. "<code."
+            // Close it for them.
+            sb.insert(i, ">");
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/jdiff/APIComparator.java b/src/jdiff/APIComparator.java
new file mode 100755
index 0000000..b0f42ed
--- /dev/null
+++ b/src/jdiff/APIComparator.java
@@ -0,0 +1,934 @@
+package jdiff;
+
+import java.util.*;
+
+/**
+ * This class contains method to compare two API objects.
+ * The differences are stored in an APIDiff object.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class APIComparator {
+
+    /** 
+     * Top-level object representing the differences between two APIs. 
+     * It is this object which is used to generate the report later on.
+     */
+    public APIDiff apiDiff;
+
+    /** 
+     * Package-level object representing the differences between two packages. 
+     * This object is also used to determine which file to write documentation
+     * differences into.
+     */
+    public PackageDiff pkgDiff;
+
+    /** Default constructor. */
+    public APIComparator() {
+        apiDiff = new APIDiff();
+    }   
+
+    /** For easy local access to the old API object. */
+    private static API oldAPI_;
+    /** For easy local access to the new API object. */
+    private static API newAPI_;
+
+    /** 
+     * Compare two APIs. 
+     */
+    public void compareAPIs(API oldAPI, API newAPI) {
+        System.out.println("JDiff: comparing the old and new APIs ...");
+        oldAPI_ = oldAPI;
+        newAPI_ = newAPI;
+
+        double differs = 0.0;
+
+        apiDiff.oldAPIName_ = oldAPI.name_;
+        apiDiff.newAPIName_ = newAPI.name_;
+
+        Collections.sort(oldAPI.packages_);
+        Collections.sort(newAPI.packages_);
+
+        // Find packages which were removed in the new API
+        Iterator iter = oldAPI.packages_.iterator();
+        while (iter.hasNext()) {
+            PackageAPI oldPkg = (PackageAPI)(iter.next());
+            // This search is looking for an *exact* match. This is true in
+            // all the *API classes.
+            int idx = Collections.binarySearch(newAPI.packages_, oldPkg);
+            if (idx < 0) {
+                // If there an instance of a package with the same name 
+                // in both the old and new API, then treat it as changed,
+                // rather than removed and added. There will never be more than
+                // one instance of a package with the same name in an API.
+                int existsNew = newAPI.packages_.indexOf(oldPkg);
+                if (existsNew != -1) {
+                    // Package by the same name exists in both APIs
+                    // but there has been some or other change.
+                    differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(existsNew)));
+                }  else {
+                    if (trace)
+                        System.out.println("Package " + oldPkg.name_ + " was removed");
+                    apiDiff.packagesRemoved.add(oldPkg);
+                    differs += 1.0;
+                }
+            } else {
+                // The package exists unchanged in name or doc, but may 
+                // differ in classes and their members, so it still needs to 
+                // be compared.
+                differs += 2.0 * comparePackages(oldPkg, (PackageAPI)(newAPI.packages_.get(idx)));
+            }
+        } // while (iter.hasNext())
+
+        // Find packages which were added or changed in the new API
+        iter = newAPI.packages_.iterator();
+        while (iter.hasNext()) {
+            PackageAPI newPkg = (PackageAPI)(iter.next());
+            int idx = Collections.binarySearch(oldAPI.packages_, newPkg);
+            if (idx < 0) {
+                // See comments above
+                int existsOld = oldAPI.packages_.indexOf(newPkg);
+                if (existsOld != -1) {
+                    // Don't mark a package as added or compare it 
+                    // if it was already marked as changed
+                } else {
+                    if (trace)
+                        System.out.println("Package " + newPkg.name_ + " was added");
+                    apiDiff.packagesAdded.add(newPkg);
+                    differs += 1.0;
+                }
+            } else {
+                // It will already have been compared above.
+            }
+        } // while (iter.hasNext())
+
+        // Now that the numbers of members removed and added are known
+        // we can deduce more information about changes.
+        MergeChanges.mergeRemoveAdd(apiDiff);
+
+// The percent change statistic reported for all elements in each API is  
+// defined recursively as follows:
+// 
+// %age change = 100 * (added + removed + 2*changed)
+//               -----------------------------------
+//               sum of public elements in BOTH APIs
+//
+// The definition ensures that if all classes are removed and all new classes
+// added, the change will be 100%.
+// Evaluation of the visibility of elements has already been done when the 
+// XML was written out.
+// Note that this doesn't count changes in the modifiers of classes and 
+// packages. Other changes in members are counted.
+        Long denom = new Long(oldAPI.packages_.size() + newAPI.packages_.size());
+        // This should never be zero because an API always has packages?
+        if (denom.intValue() == 0) {
+            System.out.println("Error: no packages found in the APIs.");
+            return;
+        }
+        if (trace)
+            System.out.println("Top level changes: " + differs + "/" + denom.intValue());
+        differs = (100.0 * differs)/denom.doubleValue();
+
+        // Some differences such as documentation changes are not tracked in 
+        // the difference statistic, so a value of 0.0 does not mean that there
+        // were no differences between the APIs.
+        apiDiff.pdiff = differs;
+        Double percentage = new Double(differs);
+        int approxPercentage = percentage.intValue();
+        if (approxPercentage == 0)
+            System.out.println(" Approximately " + percentage + "% difference between the APIs");
+        else
+            System.out.println(" Approximately " + approxPercentage + "% difference between the APIs");
+
+        Diff.closeDiffFile();
+    }   
+
+    /** 
+     * Compare two packages.
+     */
+    public double comparePackages(PackageAPI oldPkg, PackageAPI newPkg) {
+        if (trace)
+            System.out.println("Comparing old package " + oldPkg.name_ + 
+                               " and new package " + newPkg.name_);
+        pkgDiff = new PackageDiff(oldPkg.name_);
+        double differs = 0.0;
+
+        Collections.sort(oldPkg.classes_);
+        Collections.sort(newPkg.classes_);
+      
+        // Find classes which were removed in the new package
+        Iterator iter = oldPkg.classes_.iterator();
+        while (iter.hasNext()) {
+            ClassAPI oldClass = (ClassAPI)(iter.next());
+            // This search is looking for an *exact* match. This is true in
+            // all the *API classes.
+            int idx = Collections.binarySearch(newPkg.classes_, oldClass);
+            if (idx < 0) {
+                // If there an instance of a class with the same name 
+                // in both the old and new package, then treat it as changed,
+                // rather than removed and added. There will never be more than
+                // one instance of a class with the same name in a package.
+                int existsNew = newPkg.classes_.indexOf(oldClass);
+                if (existsNew != -1) {
+                    // Class by the same name exists in both packages
+                    // but there has been some or other change.
+                    differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(existsNew)), pkgDiff);
+                }  else {
+                    if (trace)
+                        System.out.println("  Class " + oldClass.name_ + " was removed");
+                    pkgDiff.classesRemoved.add(oldClass);
+                    differs += 1.0;
+                }
+            } else {
+                // The class exists unchanged in name or modifiers, but may 
+                // differ in members, so it still needs to be compared.
+                differs += 2.0 * compareClasses(oldClass, (ClassAPI)(newPkg.classes_.get(idx)), pkgDiff);
+            }
+        } // while (iter.hasNext())
+
+        // Find classes which were added or changed in the new package
+        iter = newPkg.classes_.iterator();
+        while (iter.hasNext()) {
+            ClassAPI newClass = (ClassAPI)(iter.next());
+            int idx = Collections.binarySearch(oldPkg.classes_, newClass);
+            if (idx < 0) {
+                // See comments above
+                int existsOld = oldPkg.classes_.indexOf(newClass);
+                if (existsOld != -1) {
+                    // Don't mark a class as added or compare it 
+                    // if it was already marked as changed
+                } else {
+                    if (trace)
+                        System.out.println("  Class " + newClass.name_ + " was added");
+                    pkgDiff.classesAdded.add(newClass);
+                    differs += 1.0;
+                }
+            } else {
+                // It will already have been compared above.
+            }
+        } // while (iter.hasNext())
+
+        // Check if the only change was in documentation. Bug 472521.
+        boolean differsFlag = false;
+        if (docChanged(oldPkg.doc_, newPkg.doc_)) {
+            String link = "<a href=\"pkg_" + oldPkg.name_ + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+            String id = oldPkg.name_ + "!package";
+            String title = link + "Package <b>" + oldPkg.name_ + "</b></a>";
+            pkgDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, null, oldPkg.doc_, newPkg.doc_, id, title);
+            differsFlag = true;
+        }
+
+        // Only add to the parent Diff object if some difference has been found
+        if (differs != 0.0 || differsFlag) 
+            apiDiff.packagesChanged.add(pkgDiff);
+
+        Long denom = new Long(oldPkg.classes_.size() + newPkg.classes_.size());
+        // This should never be zero because a package always has classes?
+        if (denom.intValue() == 0) {
+            System.out.println("Warning: no classes found in the package " + oldPkg.name_);
+            return 0.0;
+        }
+        if (trace)
+            System.out.println("Package " + pkgDiff.name_ + " had a difference of " + differs + "/" + denom.intValue());
+        pkgDiff.pdiff = 100.0 * differs/denom.doubleValue();
+        return differs/denom.doubleValue();
+    } // comparePackages()
+
+    /** 
+     * Compare two classes. 
+     *
+     * Need to compare constructors, methods and fields.
+     */
+    public double compareClasses(ClassAPI oldClass, ClassAPI newClass, PackageDiff pkgDiff) {
+        if (trace)
+            System.out.println("  Comparing old class " + oldClass.name_ + 
+                               " and new class " + newClass.name_);
+        boolean differsFlag = false;
+        double differs = 0.0;
+        ClassDiff classDiff = new ClassDiff(oldClass.name_);
+        classDiff.isInterface_ = newClass.isInterface_; // Used in the report
+
+        // Track changes in modifiers - class or interface
+        if (oldClass.isInterface_ != newClass.isInterface_) {
+            classDiff.modifiersChange_  = "Changed from ";
+            if (oldClass.isInterface_)
+                classDiff.modifiersChange_ += "an interface to a class.";
+            else
+                classDiff.modifiersChange_ += "a class to an interface.";
+            differsFlag = true;
+        }
+        // Track changes in inheritance
+        String inheritanceChange = ClassDiff.diff(oldClass, newClass);
+        if (inheritanceChange != null) {
+            classDiff.inheritanceChange_ = inheritanceChange;
+            differsFlag = true;
+        }
+        // Abstract or not
+        if (oldClass.isAbstract_ != newClass.isAbstract_) {
+            String changeText = "";
+            if (oldClass.isAbstract_)
+                changeText += "Changed from abstract to non-abstract.";
+            else
+                changeText += "Changed from non-abstract to abstract.";
+            classDiff.addModifiersChange(changeText);
+            differsFlag = true;
+        }
+        // Track changes in documentation
+        if (docChanged(oldClass.doc_, newClass.doc_)) {
+            String fqName = pkgDiff.name_ + "." + classDiff.name_;
+            String link = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+            String id = pkgDiff.name_ + "." + classDiff.name_ + "!class";
+            String title = link + "Class <b>" + classDiff.name_ + "</b></a>";
+            classDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_,
+ classDiff.name_, oldClass.doc_, newClass.doc_, id, title);
+            differsFlag = true;
+        }
+        // All other modifiers
+        String modifiersChange = oldClass.modifiers_.diff(newClass.modifiers_);
+        if (modifiersChange != null) {
+            differsFlag = true;
+            if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
+                System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + pkgDiff.name_ + "." + newClass.name_);
+                
+            }
+        }
+        classDiff.addModifiersChange(modifiersChange);
+        
+        // Track changes in members
+        boolean differsCtors = 
+            compareAllCtors(oldClass, newClass, classDiff);
+        boolean differsMethods = 
+            compareAllMethods(oldClass, newClass, classDiff);
+        boolean differsFields = 
+            compareAllFields(oldClass, newClass, classDiff);
+        if (differsCtors || differsMethods || differsFields) 
+            differsFlag = true;
+        
+        if (trace) {
+            System.out.println("  Ctors differ? " + differsCtors + 
+                ", Methods differ? " + differsMethods + 
+                ", Fields differ? " + differsFields);
+        }
+
+        // Only add to the parent if some difference has been found
+        if (differsFlag) 
+            pkgDiff.classesChanged.add(classDiff);
+
+        // Get the numbers of affected elements from the classDiff object
+         differs = 
+            classDiff.ctorsRemoved.size() + classDiff.ctorsAdded.size() +
+            classDiff.ctorsChanged.size() +
+            classDiff.methodsRemoved.size() + classDiff.methodsAdded.size() +
+            classDiff.methodsChanged.size() +
+            classDiff.fieldsRemoved.size() + classDiff.fieldsAdded.size() +
+            classDiff.fieldsChanged.size();
+         Long denom = new Long(
+             oldClass.ctors_.size() + 
+             numLocalMethods(oldClass.methods_) + 
+             numLocalFields(oldClass.fields_) +
+             newClass.ctors_.size() + 
+             numLocalMethods(newClass.methods_) + 
+             numLocalFields(newClass.fields_));
+         if (denom.intValue() == 0) {
+             // This is probably a placeholder interface, but documentation
+             // or modifiers etc may have changed
+             if (differsFlag) {
+                 classDiff.pdiff = 0.0; // 100.0 is too much
+                 return 1.0;
+             } else {
+                 return 0.0;
+             }
+         }
+         // Handle the case where the only change is in documentation or
+         // the modifiers
+         if (differsFlag && differs == 0.0) {
+             differs = 1.0;
+         }
+         if (trace)
+             System.out.println("  Class " + classDiff.name_ + " had a difference of " + differs + "/" + denom.intValue());
+         classDiff.pdiff = 100.0 * differs/denom.doubleValue();
+         return differs/denom.doubleValue();
+    } // compareClasses()
+
+    /** 
+     * Compare all the constructors in two classes. 
+     *
+     * The compareTo method in the ConstructorAPI class acts only upon the type.
+     */
+    public boolean compareAllCtors(ClassAPI oldClass, ClassAPI newClass, 
+                                   ClassDiff classDiff) {
+        if (trace)
+            System.out.println("    Comparing constructors: #old " + 
+              oldClass.ctors_.size() + ", #new " + newClass.ctors_.size());
+        boolean differs = false;
+        boolean singleCtor = false; // Set if there is only one ctor
+        
+        Collections.sort(oldClass.ctors_);
+        Collections.sort(newClass.ctors_);
+      
+        // Find ctors which were removed in the new class
+        Iterator iter = oldClass.ctors_.iterator();
+        while (iter.hasNext()) {
+            ConstructorAPI oldCtor = (ConstructorAPI)(iter.next());
+            int idx = Collections.binarySearch(newClass.ctors_, oldCtor);
+            if (idx < 0) {
+                int oldSize = oldClass.ctors_.size();
+                int newSize = newClass.ctors_.size();
+                if (oldSize == 1 && oldSize == newSize) {
+                    // If there is one constructor in the oldClass and one
+                    // constructor in the new class, then mark it as changed
+                    MemberDiff memberDiff = new MemberDiff(oldClass.name_);
+                    memberDiff.oldType_ = oldCtor.type_;
+                    memberDiff.oldExceptions_ = oldCtor.exceptions_;
+                    ConstructorAPI newCtor  = (ConstructorAPI)(newClass.ctors_.get(0));
+                    memberDiff.newType_ = newCtor.type_;
+                    memberDiff.newExceptions_ = newCtor.exceptions_;
+                    // Track changes in documentation
+                    if (docChanged(oldCtor.doc_, newCtor.doc_)) {
+                        String type = memberDiff.newType_;
+                        if (type.compareTo("void") == 0)
+                            type = "";
+                        String fqName = pkgDiff.name_ + "." + classDiff.name_;
+                        String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+                        String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
+                        String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
+                        String title = link1 + "Class <b>" + classDiff.name_ + 
+                            "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
+                        memberDiff.documentationChange_ = Diff.saveDocDiffs(
+                            pkgDiff.name_, classDiff.name_, oldCtor.doc_, newCtor.doc_, id, title);
+                    }
+                    String modifiersChange = oldCtor.modifiers_.diff(newCtor.modifiers_);
+                    if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
+                        System.out.println("JDiff: warning: change from deprecated to undeprecated for a constructor in class" + newClass.name_);
+                    }
+                    memberDiff.addModifiersChange(modifiersChange);
+                    if (trace)
+                        System.out.println("    The single constructor was changed");
+                    classDiff.ctorsChanged.add(memberDiff);
+                    singleCtor = true;
+                } else {
+                    if (trace)
+                        System.out.println("    Constructor " + oldClass.name_ + " was removed");
+                    classDiff.ctorsRemoved.add(oldCtor);
+                }
+                differs = true;
+            }
+        } // while (iter.hasNext())
+
+        // Find ctors which were added in the new class
+        iter = newClass.ctors_.iterator();
+        while (iter.hasNext()) {
+            ConstructorAPI newCtor = (ConstructorAPI)(iter.next());
+            int idx = Collections.binarySearch(oldClass.ctors_, newCtor);
+            if (idx < 0) {
+                if (!singleCtor) {
+                    if (trace)
+                        System.out.println("    Constructor " + oldClass.name_ + " was added");
+                    classDiff.ctorsAdded.add(newCtor);
+                    differs = true;
+                }
+            }
+        } // while (iter.hasNext())
+
+        return differs;
+    } // compareAllCtors()
+
+    /** 
+     * Compare all the methods in two classes. 
+     *
+     * We have to deal with the cases where:
+     *  - there is only one method with a given name, but its signature changes
+     *  - there is more than one method with the same name, and some of them 
+     *    may have signature changes
+     * The simplest way to deal with this is to make the MethodAPI comparator
+     * check the params and return type, as well as the name. This means that
+     * changing a parameter's type would cause the method to be seen as 
+     * removed and added. To avoid this for the simple case, check for before 
+     * recording a method as removed or added.
+     */
+    public boolean compareAllMethods(ClassAPI oldClass, ClassAPI newClass, ClassDiff classDiff) {
+        if (trace)
+            System.out.println("    Comparing methods: #old " + 
+                               oldClass.methods_.size() + ", #new " +
+                               newClass.methods_.size());
+        boolean differs = false;
+        
+        Collections.sort(oldClass.methods_);
+        Collections.sort(newClass.methods_);
+      
+        // Find methods which were removed in the new class
+        Iterator iter = oldClass.methods_.iterator();
+        while (iter.hasNext()) {
+            MethodAPI oldMethod = (MethodAPI)(iter.next());
+            int idx = -1;
+            MethodAPI[] methodArr = new MethodAPI[newClass.methods_.size()];
+            methodArr = (MethodAPI[])newClass.methods_.toArray(methodArr);
+            for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
+                MethodAPI newMethod = methodArr[methodIdx];
+                if (oldMethod.compareTo(newMethod) == 0) {
+                    idx  = methodIdx;
+                    break;
+                }
+            }
+// NOTE: there was a problem with the binarySearch for 
+// java.lang.Byte.toString(byte b) returning -16 when the compareTo method
+// returned 0 on entry 13. Changed to use arrays instead, so maybe it was
+// an issue with methods having another List of params used indirectly by 
+// compareTo(), unlike constructors and fields?
+//            int idx = Collections.binarySearch(newClass.methods_, oldMethod);
+            if (idx < 0) {
+                // If there is only one instance of a method with this name 
+                // in both the old and new class, then treat it as changed,
+                // rather than removed and added.
+                // Find how many instances of this method name there are in
+                // the old and new class. The equals comparator is just on 
+                // the method name.
+                int startOld = oldClass.methods_.indexOf(oldMethod); 
+                int endOld = oldClass.methods_.lastIndexOf(oldMethod);
+                int startNew = newClass.methods_.indexOf(oldMethod); 
+                int endNew = newClass.methods_.lastIndexOf(oldMethod);
+
+                if (startOld != -1 && startOld == endOld && 
+                    startNew != -1 && startNew == endNew) {
+                    MethodAPI newMethod = (MethodAPI)(newClass.methods_.get(startNew));
+                    // Only one method with that name exists in both packages,
+                    // so it is valid to compare the two methods. We know it 
+                    // has changed, because the binarySearch did not find it.
+                    if (oldMethod.inheritedFrom_ == null || 
+                        newMethod.inheritedFrom_ == null) {
+                        // We also know that at least one of the methods is 
+                        // locally defined.
+                        compareMethods(oldMethod, newMethod, classDiff);
+                        differs = true;
+                    }
+                } else if (oldMethod.inheritedFrom_ == null) {
+                    // Only concerned with locally defined methods
+                    if (trace)
+                        System.out.println("    Method " + oldMethod.name_ + 
+                                           "(" + oldMethod.getSignature() + 
+                                           ") was removed");
+                    classDiff.methodsRemoved.add(oldMethod);
+                    differs = true;
+                }
+            }
+        } // while (iter.hasNext())
+
+        // Find methods which were added in the new class
+        iter = newClass.methods_.iterator();
+        while (iter.hasNext()) {
+            MethodAPI newMethod = (MethodAPI)(iter.next());
+            // Only concerned with locally defined methods
+            if (newMethod.inheritedFrom_ != null)
+                continue;
+            int idx = -1;
+            MethodAPI[] methodArr = new MethodAPI[oldClass.methods_.size()];
+            methodArr = (MethodAPI[])oldClass.methods_.toArray(methodArr);
+            for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
+                MethodAPI oldMethod = methodArr[methodIdx];
+                if (newMethod.compareTo(oldMethod) == 0) {
+                    idx  = methodIdx;
+                    break;
+                }
+            }
+// See note above about searching an array instead of binarySearch
+//            int idx = Collections.binarySearch(oldClass.methods_, newMethod);
+            if (idx < 0) {
+                // See comments above
+                int startOld = oldClass.methods_.indexOf(newMethod); 
+                int endOld = oldClass.methods_.lastIndexOf(newMethod);
+                int startNew = newClass.methods_.indexOf(newMethod); 
+                int endNew = newClass.methods_.lastIndexOf(newMethod);
+
+                if (startOld != -1 && startOld == endOld && 
+                    startNew != -1 && startNew == endNew) {
+                    // Don't mark a method as added if it was marked as changed
+                    // The comparison will have been done just above here.
+                } else {
+                    if (trace)
+                        System.out.println("    Method " + newMethod.name_ + 
+                                           "(" + newMethod.getSignature() + ") was added");
+                    classDiff.methodsAdded.add(newMethod);
+                    differs = true;
+                }
+            }
+        } // while (iter.hasNext())
+
+        return differs;
+    } // compareAllMethods()
+
+    /** 
+     * Compare two methods which have the same name. 
+     */
+    public boolean compareMethods(MethodAPI oldMethod, MethodAPI newMethod, ClassDiff classDiff) {
+        MemberDiff methodDiff = new MemberDiff(oldMethod.name_);
+        boolean differs = false;
+        // Check changes in return type
+        methodDiff.oldType_ = oldMethod.returnType_;
+        methodDiff.newType_ = newMethod.returnType_;
+        if (oldMethod.returnType_.compareTo(newMethod.returnType_) != 0) {
+            differs = true;
+        }
+        // Check changes in signature
+        String oldSig = oldMethod.getSignature();
+        String newSig = newMethod.getSignature();
+        methodDiff.oldSignature_ = oldSig;
+        methodDiff.newSignature_ = newSig;
+        if (oldSig.compareTo(newSig) != 0) {
+            differs = true;
+        }
+        // Changes in inheritance
+        int inh = changedInheritance(oldMethod.inheritedFrom_, newMethod.inheritedFrom_);
+        if (inh != 0)
+            differs = true;
+        if (inh == 1) {
+            methodDiff.addModifiersChange("Method was locally defined, but is now inherited from " + linkToClass(newMethod, true) + ".");
+            methodDiff.inheritedFrom_ = newMethod.inheritedFrom_;
+        } else if (inh == 2) {
+            methodDiff.addModifiersChange("Method was inherited from " + linkToClass(oldMethod, false) + ", but is now defined locally.");
+        } else if (inh == 3) {
+            methodDiff.addModifiersChange("Method was inherited from " + 
+                                          linkToClass(oldMethod, false) + ", and is now inherited from " + linkToClass(newMethod, true) + ".");
+            methodDiff.inheritedFrom_ = newMethod.inheritedFrom_;
+        }
+        // Abstract or not
+        if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
+            String changeText = "";
+            if (oldMethod.isAbstract_)
+                changeText += "Changed from abstract to non-abstract.";
+            else
+                changeText += "Changed from non-abstract to abstract.";
+            methodDiff.addModifiersChange(changeText);
+            differs = true;
+        }
+        // Native or not
+        if (Diff.showAllChanges && 
+	    oldMethod.isNative_ != newMethod.isNative_) {
+            String changeText = "";
+            if (oldMethod.isNative_)
+                changeText += "Changed from native to non-native.";
+            else
+                changeText += "Changed from non-native to native.";
+            methodDiff.addModifiersChange(changeText);
+            differs = true;
+        }
+        // Synchronized or not
+        if (Diff.showAllChanges && 
+	    oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
+            String changeText = "";
+            if (oldMethod.isSynchronized_)
+                changeText += "Changed from synchronized to non-synchronized.";
+            else
+                changeText += "Changed from non-synchronized to synchronized.";
+            methodDiff.addModifiersChange(changeText);
+            differs = true;
+        }
+
+        // Check changes in exceptions thrown
+        methodDiff.oldExceptions_ = oldMethod.exceptions_;
+        methodDiff.newExceptions_ = newMethod.exceptions_;
+        if (oldMethod.exceptions_.compareTo(newMethod.exceptions_) != 0) {
+            differs = true;
+        }
+
+        // Track changes in documentation
+        if (docChanged(oldMethod.doc_, newMethod.doc_)) {
+            String sig = methodDiff.newSignature_;
+            if (sig.compareTo("void") == 0)
+                sig = "";
+            String fqName = pkgDiff.name_ + "." + classDiff.name_;
+            String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+            String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
+            String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
+            String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
+                link2 + HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + newMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
+            methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldMethod.doc_, newMethod.doc_, id, title);
+            differs = true;
+        }
+
+        // All other modifiers
+        String modifiersChange = oldMethod.modifiers_.diff(newMethod.modifiers_);
+        if (modifiersChange != null) {
+            differs = true;
+            if (modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
+                System.out.println("JDiff: warning: change from deprecated to undeprecated for method " +  classDiff.name_ + "." + newMethod.name_);
+                
+            }
+        }
+        methodDiff.addModifiersChange(modifiersChange);
+
+        // Only add to the parent if some difference has been found
+        if (differs) {
+            if (trace) {
+                System.out.println("    Method " + newMethod.name_ + 
+                    " was changed: old: " + 
+                   oldMethod.returnType_ + "(" + oldSig + "), new: " +
+                   newMethod.returnType_ + "(" + newSig + ")");
+                if (methodDiff.modifiersChange_ != null)
+                    System.out.println("    Modifier change: " + methodDiff.modifiersChange_);
+            }
+            classDiff.methodsChanged.add(methodDiff);
+        }
+
+        return differs;
+    } // compareMethods()
+
+    /** 
+     * Compare all the fields in two classes. 
+     */
+    public boolean compareAllFields(ClassAPI oldClass, ClassAPI newClass, 
+                                    ClassDiff classDiff) {
+        if (trace)
+            System.out.println("    Comparing fields: #old " + 
+                               oldClass.fields_.size() + ", #new " 
+                               + newClass.fields_.size());
+        boolean differs = false;
+        
+        Collections.sort(oldClass.fields_);
+        Collections.sort(newClass.fields_);
+      
+        // Find fields which were removed in the new class
+        Iterator iter = oldClass.fields_.iterator();
+        while (iter.hasNext()) {
+            FieldAPI oldField = (FieldAPI)(iter.next());
+            int idx = Collections.binarySearch(newClass.fields_, oldField);
+            if (idx < 0) {
+                // If there an instance of a field with the same name 
+                // in both the old and new class, then treat it as changed,
+                // rather than removed and added. There will never be more than
+                // one instance of a field with the same name in a class.
+                int existsNew = newClass.fields_.indexOf(oldField);
+                if (existsNew != -1) {
+                    FieldAPI newField = (FieldAPI)(newClass.fields_.get(existsNew));
+                    if (oldField.inheritedFrom_ == null || 
+                        newField.inheritedFrom_ == null) {
+                        // We also know that one of the fields is locally defined.
+                        MemberDiff memberDiff = new MemberDiff(oldField.name_);
+                        memberDiff.oldType_ = oldField.type_;
+                        memberDiff.newType_ = newField.type_;
+                        // Changes in inheritance
+                        int inh = changedInheritance(oldField.inheritedFrom_, newField.inheritedFrom_);
+                        if (inh != 0)
+                            differs = true;
+                        if (inh == 1) {
+                            memberDiff.addModifiersChange("Field was locally defined, but is now inherited from " + linkToClass(newField, true) + ".");
+                            memberDiff.inheritedFrom_ = newField.inheritedFrom_;
+                        } else if (inh == 2) {
+                            memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", but is now defined locally.");
+                        } else if (inh == 3) {
+                            memberDiff.addModifiersChange("Field was inherited from " + linkToClass(oldField, false) + ", and is now inherited from " + linkToClass(newField, true) + ".");
+                            memberDiff.inheritedFrom_ = newField.inheritedFrom_;
+                        }
+                        // Transient or not
+                        if (oldField.isTransient_ != newField.isTransient_) {
+                            String changeText = "";
+                            if (oldField.isTransient_)
+                                changeText += "Changed from transient to non-transient.";
+                            else
+                                changeText += "Changed from non-transient to transient.";
+                            memberDiff.addModifiersChange(changeText);
+                            differs = true;
+                        }
+                        // Volatile or not
+                        if (oldField.isVolatile_ != newField.isVolatile_) {
+                            String changeText = "";
+                            if (oldField.isVolatile_)
+                                changeText += "Changed from volatile to non-volatile.";
+                            else
+                                changeText += "Changed from non-volatile to volatile.";
+                            memberDiff.addModifiersChange(changeText);
+                            differs = true;
+                        }
+                        // Change in value of the field
+                        if (oldField.value_ != null &&
+                            newField.value_ != null &&
+                            oldField.value_.compareTo(newField.value_) != 0) {
+                            String changeText = "Changed in value from " + oldField.value_
+                                + " to " + newField.value_ +".";
+                            memberDiff.addModifiersChange(changeText);
+                            differs = true;
+                        }
+                        // Track changes in documentation
+                        if (docChanged(oldField.doc_, newField.doc_)) {
+                            String fqName = pkgDiff.name_ + "." + classDiff.name_;
+                            String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+                            String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + newField.name_ + "\" class=\"hiddenlink\">";
+                            String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + newField.name_;
+                            String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
+                                link2 + HTMLReportGenerator.simpleName(memberDiff.newType_) + " <b>" + newField.name_ + "</b></a>";
+                            memberDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, oldField.doc_, newField.doc_, id, title);
+                            differs = true;
+                        }
+                        
+                        // Other differences
+                        String modifiersChange = oldField.modifiers_.diff(newField.modifiers_);
+                        memberDiff.addModifiersChange(modifiersChange);
+                        if (modifiersChange != null && modifiersChange.indexOf("Change from deprecated to undeprecated") != -1) {
+                            System.out.println("JDiff: warning: change from deprecated to undeprecated for class " + newClass.name_ + ", field " + newField.name_);
+                        }
+                        if (trace)
+                            System.out.println("    Field " + newField.name_ + " was changed");
+                        classDiff.fieldsChanged.add(memberDiff);
+                        differs = true;
+                    }
+                } else if (oldField.inheritedFrom_ == null) {
+                    if (trace)
+                        System.out.println("    Field " + oldField.name_ + " was removed");
+                    classDiff.fieldsRemoved.add(oldField);
+                    differs = true;
+                }
+            }
+        } // while (iter.hasNext())
+
+        // Find fields which were added in the new class
+        iter = newClass.fields_.iterator();
+        while (iter.hasNext()) {
+            FieldAPI newField = (FieldAPI)(iter.next());
+            // Only concerned with locally defined fields
+            if (newField.inheritedFrom_ != null)
+                continue;
+            int idx = Collections.binarySearch(oldClass.fields_, newField);
+            if (idx < 0) {
+                // See comments above
+                int existsOld = oldClass.fields_.indexOf(newField);
+                if (existsOld != -1) {
+                    // Don't mark a field as added if it was marked as changed
+                } else {
+                    if (trace)
+                        System.out.println("    Field " + newField.name_ + " was added");
+                    classDiff.fieldsAdded.add(newField);
+                    differs = true;
+                }
+            }
+        } // while (iter.hasNext())
+
+        return differs;
+    } // compareFields()
+
+    /** 
+     * Decide if two blocks of documentation changed. 
+     *
+     * @return true if both are non-null and differ, 
+     *              or if one is null and the other is not.
+     */
+    public static boolean docChanged(String oldDoc, String newDoc) {
+        if (!HTMLReportGenerator.reportDocChanges)
+            return false; // Don't even count doc changes as changes
+        if (oldDoc == null && newDoc != null)
+            return true;
+        if (oldDoc != null && newDoc == null)
+            return true;
+        if (oldDoc != null && newDoc != null && oldDoc.compareTo(newDoc) != 0)
+            return true;
+        return false;
+    }
+
+    /** 
+     * Decide if two elements changed where they were defined. 
+     *
+     * @return 0 if both are null, or both are non-null and are the same.
+     *         1 if the oldInherit was null and newInherit is non-null.
+     *         2 if the oldInherit was non-null and newInherit is null.
+     *         3 if the oldInherit was non-null and newInherit is non-null 
+     *           and they differ.
+     */
+    public static int changedInheritance(String oldInherit, String newInherit) {
+        if (oldInherit == null && newInherit == null)
+            return 0;
+        if (oldInherit == null && newInherit != null)
+            return 1;
+        if (oldInherit != null && newInherit == null)
+            return 2;
+        if (oldInherit.compareTo(newInherit) == 0)
+            return 0;
+        else
+            return 3;
+    }
+
+    /** 
+     * Generate a link to the Javadoc page for the given method.
+     */
+    public static String linkToClass(MethodAPI m, boolean useNew) {
+        String sig = m.getSignature();
+        if (sig.compareTo("void") == 0)
+            sig = "";
+        return linkToClass(m.inheritedFrom_, m.name_, sig, useNew);
+    }
+
+    /** 
+     * Generate a link to the Javadoc page for the given field.
+     */
+    public static String linkToClass(FieldAPI m, boolean useNew) {
+        return linkToClass(m.inheritedFrom_, m.name_, null, useNew);
+    }
+
+    /** 
+     * Given the name of the class, generate a link to a relevant page.
+     * This was originally for inheritance changes, so the JDiff page could 
+     * be a class changes page, or a section in a removed or added classes 
+     * table. Since there was no easy way to tell which type the link
+     * should be, it is now just a link to the relevant Javadoc page.
+     */
+    public static String linkToClass(String className, String memberName, 
+                                     String memberType, boolean useNew) {
+        if (!useNew && HTMLReportGenerator.oldDocPrefix == null) {
+            return "<tt>" + className + "</tt>"; // No link possible
+        }
+        API api = oldAPI_;
+        String prefix = HTMLReportGenerator.oldDocPrefix;
+        if (useNew) {
+            api = newAPI_;
+            prefix = HTMLReportGenerator.newDocPrefix;
+        }
+        ClassAPI cls = (ClassAPI)api.classes_.get(className);
+        if (cls == null) {
+            if (useNew)
+                System.out.println("Warning: class " + className + " not found in the new API when creating Javadoc link");
+            else
+                System.out.println("Warning: class " + className + " not found in the old API when creating Javadoc link");
+            return "<tt>" + className + "</tt>";
+        }
+        int clsIdx = className.indexOf(cls.name_);
+        if (clsIdx != -1) {
+            String pkgRef = className.substring(0, clsIdx);
+            pkgRef = pkgRef.replace('.', '/');
+            String res = "<a href=\"" + prefix + pkgRef + cls.name_ + ".html#" + memberName;
+            if (memberType != null)
+                res += "(" + memberType + ")";
+            res += "\" target=\"_top\">" + "<tt>" + cls.name_ + "</tt></a>";
+            return res;
+        }
+        return "<tt>" + className + "</tt>";
+    }    
+
+    /** 
+     * Return the number of methods which are locally defined.
+     */
+    public int numLocalMethods(List methods) {
+        int res = 0;
+        Iterator iter = methods.iterator();
+        while (iter.hasNext()) {
+            MethodAPI m = (MethodAPI)(iter.next());
+            if (m.inheritedFrom_ == null) 
+                res++;
+        }
+        return res;
+    }
+
+    /** 
+     * Return the number of fields which are locally defined.
+     */
+    public int numLocalFields(List fields) {
+        int res = 0;
+        Iterator iter = fields.iterator();
+        while (iter.hasNext()) {
+            FieldAPI f = (FieldAPI)(iter.next());
+            if (f.inheritedFrom_ == null) 
+                res++;
+        }
+        return res;
+    }
+
+    /** Set to enable increased logging verbosity for debugging. */
+    private boolean trace = false;
+}
diff --git a/src/jdiff/APIDiff.java b/src/jdiff/APIDiff.java
new file mode 100755
index 0000000..4b4fd26
--- /dev/null
+++ b/src/jdiff/APIDiff.java
@@ -0,0 +1,40 @@
+package jdiff;
+
+import java.util.*;
+import com.sun.javadoc.*;
+
+/**
+ * The class contains the changes between two API objects; packages added,
+ * removed and changed. The packages are represented by PackageDiff objects,
+ * which contain the changes in each package, and so on.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class APIDiff {
+
+    /** Packages added in the new API. */
+    public List packagesAdded = null; // PackageAPI[]
+    /** Packages removed in the new API. */
+    public List packagesRemoved = null; // PackageAPI[]
+    /** Packages changed in the new API. */
+    public List packagesChanged = null; // PackageDiff[]
+
+    /** Name of the old API. */
+    public static String oldAPIName_;
+    /** Name of the old API. */
+    public static String newAPIName_;
+
+    /* The overall percentage difference between the two APIs. */
+    public double pdiff = 0.0;
+
+    /** Default constructor. */
+    public APIDiff() {
+        oldAPIName_ = null;
+        newAPIName_ = null;
+        packagesAdded = new ArrayList(); // PackageAPI[]
+        packagesRemoved = new ArrayList(); // PackageAPI[]
+        packagesChanged = new ArrayList(); // PackageDiff[]
+    }   
+}
+
diff --git a/src/jdiff/APIHandler.java b/src/jdiff/APIHandler.java
new file mode 100755
index 0000000..be1a6fc
--- /dev/null
+++ b/src/jdiff/APIHandler.java
@@ -0,0 +1,362 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/* For SAX parsing in APIHandler */
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Handle the parsing of an XML file and the generation of an API object.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class APIHandler extends DefaultHandler {
+
+    /** The API object which is populated from the XML file. */
+    public API api_;
+
+    /** Default constructor. */
+    public APIHandler(API api, boolean createGlobalComments) {
+        api_ = api;
+        createGlobalComments_ = createGlobalComments;
+        tagStack = new LinkedList();
+    }   
+
+    /** If set, then check that each comment is a sentence. */
+    public static boolean checkIsSentence = false;
+
+    /** 
+     * Contains the name of the current package element type
+     * where documentation is being added. Also used as the level
+     * at which to add documentation into an element, i.e. class-level
+     * or package-level.
+     */
+    private String currentElement = null;
+
+    /** If set, then create the global list of comments. */
+    private boolean createGlobalComments_ = false;
+
+    /** Set if inside a doc element. */
+    private boolean inDoc = false;
+
+    /** The current comment text being assembled. */
+    private String currentText = null;
+
+    /** The current text from deprecation, null if empty. */
+    private String currentDepText = null;
+
+    /** 
+     * The stack of SingleComment objects awaiting the comment text 
+     * currently being assembled. 
+     */
+    private LinkedList tagStack = null;
+
+    /** Called at the start of the document. */
+    public void startDocument() {
+    }
+    
+    /** Called when the end of the document is reached. */
+    public void endDocument() {
+        if (trace)
+            api_.dump();
+        System.out.println(" finished");
+    }
+
+    /** Called when a new element is started. */
+    public void startElement(java.lang.String uri, java.lang.String localName,
+                             java.lang.String qName, Attributes attributes) {
+	 // The change to JAXP compliance produced this change.
+	if (localName.equals(""))
+	    localName = qName;
+        if (localName.compareTo("api") == 0) {
+            String apiName = attributes.getValue("name");
+            String version = attributes.getValue("jdversion"); // Not used yet
+            XMLToAPI.nameAPI(apiName);
+        } else if (localName.compareTo("package") == 0) {
+            currentElement = localName;
+            String pkgName = attributes.getValue("name");
+            XMLToAPI.addPackage(pkgName);
+        } else if (localName.compareTo("class") == 0) {
+            currentElement = localName;
+            String className = attributes.getValue("name");
+            String parentName = attributes.getValue("extends");
+            boolean isAbstract = false;
+            if (attributes.getValue("abstract").compareTo("true") == 0)
+                isAbstract = true;
+            XMLToAPI.addClass(className, parentName, isAbstract, getModifiers(attributes));
+        } else if (localName.compareTo("interface") == 0) {
+            currentElement = localName;
+            String className = attributes.getValue("name");
+            String parentName = attributes.getValue("extends");
+            boolean isAbstract = false;
+            if (attributes.getValue("abstract").compareTo("true") == 0)
+                isAbstract = true;
+            XMLToAPI.addInterface(className, parentName, isAbstract, getModifiers(attributes));
+        } else if (localName.compareTo("implements") == 0) {
+            String interfaceName = attributes.getValue("name");
+            XMLToAPI.addImplements(interfaceName);
+        } else if (localName.compareTo("constructor") == 0) {
+            currentElement = localName;
+            String ctorType = attributes.getValue("type");
+            XMLToAPI.addCtor(ctorType, getModifiers(attributes));
+        } else if (localName.compareTo("method") == 0) {
+            currentElement = localName;
+            String methodName = attributes.getValue("name");
+            String returnType = attributes.getValue("return");
+            boolean isAbstract = false;
+            if (attributes.getValue("abstract").compareTo("true") == 0)
+                isAbstract = true;
+            boolean isNative = false;
+            if (attributes.getValue("native").compareTo("true") == 0)
+                isNative = true;
+            boolean isSynchronized = false;
+            if (attributes.getValue("synchronized").compareTo("true") == 0)
+                isSynchronized = true;
+            XMLToAPI.addMethod(methodName, returnType, isAbstract, isNative, 
+                               isSynchronized, getModifiers(attributes));
+        } else if (localName.compareTo("field") == 0) {
+            currentElement = localName;
+            String fieldName = attributes.getValue("name");
+            String fieldType = attributes.getValue("type");
+            boolean isTransient = false;
+            if (attributes.getValue("transient").compareTo("true") == 0)
+                isTransient = true;
+            boolean isVolatile = false;
+            if (attributes.getValue("volatile").compareTo("true") == 0)
+                isVolatile = true;
+            String value = attributes.getValue("value");
+            XMLToAPI.addField(fieldName, fieldType, isTransient, isVolatile, 
+                              value, getModifiers(attributes));
+        } else if (localName.compareTo("param") == 0) {
+            String paramName = attributes.getValue("name");
+            String paramType = attributes.getValue("type");
+            XMLToAPI.addParam(paramName, paramType);
+        } else if (localName.compareTo("exception") == 0) {
+            String paramName = attributes.getValue("name");
+            String paramType = attributes.getValue("type");
+            XMLToAPI.addException(paramName, paramType, currentElement);
+        } else if (localName.compareTo("doc") == 0) {
+            inDoc = true;
+            currentText = null;
+        } else {
+            if (inDoc) {
+                // Start of an element, probably an HTML element
+                addStartTagToText(localName, attributes);
+            } else {
+                System.out.println("Error: unknown element type: " + localName);
+                System.exit(-1);
+            }
+        }
+    }
+    
+    /** Called when the end of an element is reached. */
+    public void endElement(java.lang.String uri, java.lang.String localName, 
+                           java.lang.String qName) {
+	if (localName.equals(""))
+	    localName = qName;
+        // Deal with the end of doc blocks
+        if (localName.compareTo("doc") == 0) {
+            inDoc = false;
+            // Add the assembled comment text to the appropriate current
+            // program element, as determined by currentElement.
+            addTextToComments();
+        } else if (inDoc) {
+            // An element was found inside the HTML text
+            addEndTagToText(localName);
+        } else if (currentElement.compareTo("constructor") == 0 && 
+                   localName.compareTo("constructor") == 0) {
+            currentElement = "class";
+        } else if (currentElement.compareTo("method") == 0 && 
+                   localName.compareTo("method") == 0) {
+            currentElement = "class";
+        } else if (currentElement.compareTo("field") == 0 && 
+                   localName.compareTo("field") == 0) {
+            currentElement = "class";
+        } else if (currentElement.compareTo("class") == 0 ||
+                   currentElement.compareTo("interface") == 0) {
+            // Feature request 510307 and bug 517383: duplicate comment ids.
+            // The end of a member element leaves the currentElement at the 
+            // "class" level, but the next class may in fact be an interface
+            // and so the currentElement here will be "interface".
+            if (localName.compareTo("class") == 0 || 
+                localName.compareTo("interface") == 0) {
+                currentElement = "package";
+            }
+        }
+    }
+
+    /** Called to process text. */
+    public void characters(char[] ch, int start, int length) {
+         if (inDoc) {
+            String chunk = new String(ch, start, length);
+            if (currentText == null)
+                currentText = chunk;
+            else
+                currentText += chunk;
+         }
+    }
+
+    /** 
+     * Trim the current text, check it is a sentence and add it to the
+     * current program element. 
+     */
+    public void addTextToComments() {
+        // Eliminate any whitespace at each end of the text.
+        currentText = currentText.trim();        
+        // Convert any @link tags to HTML links.
+        if (convertAtLinks) {
+            currentText = Comments.convertAtLinks(currentText, currentElement, 
+                                                  api_.currPkg_, api_.currClass_);
+        }
+        // Check that it is a sentence
+        if (checkIsSentence && !currentText.endsWith(".") && 
+            currentText.compareTo(Comments.placeHolderText) != 0) {
+            System.out.println("Warning: text of comment does not end in a period: " + currentText);
+        }
+        // The construction of the commentID assumes that the 
+        // documentation is the final element to be parsed. The format matches
+        // the format used in the report generator to look up comments in the
+        // the existingComments object.
+        String commentID = null;
+        // Add this comment to the current API element.
+        if (currentElement.compareTo("package") == 0) {
+            api_.currPkg_.doc_ = currentText;
+            commentID = api_.currPkg_.name_;
+        } else if (currentElement.compareTo("class") == 0 ||
+                   currentElement.compareTo("interface") == 0) {
+            api_.currClass_.doc_ = currentText;
+            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_;
+        } else if (currentElement.compareTo("constructor") == 0) {
+            api_.currCtor_.doc_ = currentText;
+            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
+                ".ctor_changed(";
+            if (api_.currCtor_.type_.compareTo("void") == 0)
+                commentID = commentID + ")";
+            else
+                commentID = commentID + api_.currCtor_.type_ + ")";
+        } else if (currentElement.compareTo("method") == 0) {
+            api_.currMethod_.doc_ = currentText;
+            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
+                "." + api_.currMethod_.name_ + "_changed(" + 
+                api_.currMethod_.getSignature() + ")";
+        } else if (currentElement.compareTo("field") == 0) {
+            api_.currField_.doc_ = currentText;
+            commentID = api_.currPkg_.name_ + "." + api_.currClass_.name_ +
+                "." + api_.currField_.name_;
+        }            
+        // Add to the list of possible comments for use when an
+        // element has changed (not removed or added).
+        if (createGlobalComments_ && commentID != null) {
+            String ct = currentText;
+            // Use any deprecation text as the possible comment, ignoring 
+            // any other comment text.
+            if (currentDepText != null) {
+                ct = currentDepText;
+                currentDepText = null; // Never reuse it. Bug 469794
+            }
+            String ctOld = (String)(Comments.allPossibleComments.put(commentID, ct));
+            if (ctOld != null) {
+                System.out.println("Error: duplicate comment id: " + commentID);
+                System.exit(5);
+            }
+        }
+    }
+
+    /** 
+     * Add the start tag to the current comment text. 
+     */
+    public void addStartTagToText(String localName, Attributes attributes) {
+        // Need to insert the HTML tag into the current text
+        String currentHTMLTag = localName;
+        // Save the tag in a stack
+        tagStack.add(currentHTMLTag);
+        String tag = "<" + currentHTMLTag;
+        // Now add all the attributes into the current text
+        int len = attributes.getLength();
+        for (int i = 0; i < len; i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+            tag += " " + name + "=\"" + value+ "\"";
+        }
+
+        // End the tag
+        if (Comments.isMinimizedTag(currentHTMLTag)) {
+            tag += "/>";
+        } else {
+            tag += ">";
+        }
+        // Now insert the HTML tag into the current text
+        if (currentText == null)
+            currentText = tag;
+        else
+            currentText += tag;
+    }
+
+    /** 
+     * Add the end tag to the current comment text. 
+     */
+    public void addEndTagToText(String localName) {
+        // Close the current HTML tag
+        String currentHTMLTag = (String)(tagStack.removeLast());
+        if (!Comments.isMinimizedTag(currentHTMLTag))
+            currentText += "</" + currentHTMLTag + ">";
+    }
+
+    /** Extra modifiers which are common to all program elements. */
+    public Modifiers getModifiers(Attributes attributes) {
+        Modifiers modifiers = new Modifiers();
+        modifiers.isStatic = false;
+        if (attributes.getValue("static").compareTo("true") == 0)
+            modifiers.isStatic = true;
+        modifiers.isFinal = false;
+        if (attributes.getValue("final").compareTo("true") == 0)
+            modifiers.isFinal = true;
+        modifiers.isDeprecated = false;
+        String cdt = attributes.getValue("deprecated");
+        if (cdt.compareTo("not deprecated") == 0) {
+            modifiers.isDeprecated = false;
+            currentDepText = null;
+        } else if (cdt.compareTo("deprecated, no comment") == 0) {
+            modifiers.isDeprecated = true;
+            currentDepText = null;
+        } else {
+            modifiers.isDeprecated = true;
+            currentDepText = API.showHTMLTags(cdt);
+        }
+        modifiers.visibility = attributes.getValue("visibility");
+        return modifiers;
+    }
+
+    public void warning(SAXParseException e) {
+        System.out.println("Warning (" + e.getLineNumber() + "): parsing XML API file:" + e);
+        e.printStackTrace();
+    }
+
+    public void error(SAXParseException e) {
+        System.out.println("Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
+        e.printStackTrace();
+        System.exit(1);
+    }
+    
+    public void fatalError(SAXParseException e) {
+        System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML API file:" + e);
+        e.printStackTrace();
+        System.exit(1);
+    }    
+
+    /** 
+     * If set, then attempt to convert @link tags to HTML links. 
+     * A few of the HTML links may be broken links.
+     */
+    private static boolean convertAtLinks = true;
+
+    /** Set to enable increased logging verbosity for debugging. */
+    private static boolean trace = false;
+
+}
diff --git a/src/jdiff/ClassAPI.java b/src/jdiff/ClassAPI.java
new file mode 100755
index 0000000..9490cc7
--- /dev/null
+++ b/src/jdiff/ClassAPI.java
@@ -0,0 +1,91 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent a class, analogous to ClassDoc in the 
+ * Javadoc doclet API. 
+ * 
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this class.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class ClassAPI implements Comparable {
+
+    /** Name of the class, not fully qualified. */
+    public String name_;
+
+    /** Set if this class is an interface. */
+    public boolean isInterface_;
+
+    /** Set if this class is abstract. */
+    boolean isAbstract_ = false;
+
+    /** Modifiers for this class. */
+    public Modifiers modifiers_;
+
+    /** Name of the parent class, or null if there is no parent. */
+    public String extends_; // Can only extend zero or one class or interface
+
+    /** Interfaces implemented by this class. */
+    public List implements_; // String[]
+
+    /** Constructors in this class. */
+    public List ctors_; // ConstructorAPI[]
+
+    /** Methods in this class. */
+    public List methods_; // MethodAPI[]
+
+    /** Fields in this class. */
+    public List fields_; //FieldAPI[]
+
+    /** The doc block, default is null. */
+    public String doc_ = null;
+
+    /** Constructor. */
+    public ClassAPI(String name, String parent, boolean isInterface, 
+                    boolean isAbstract, Modifiers modifiers) {
+        name_ = name;
+        extends_ = parent;
+        isInterface_ = isInterface;
+        isAbstract_ = isAbstract;
+        modifiers_ = modifiers;
+
+        implements_ = new ArrayList(); // String[]
+        ctors_ = new ArrayList(); // ConstructorAPI[]
+        methods_ = new ArrayList(); // MethodAPI[]
+        fields_ = new ArrayList(); // FieldAPI[]
+    }
+
+    /** Compare two ClassAPI objects by all the known information. */
+    public int compareTo(Object o) {
+        ClassAPI oClassAPI = (ClassAPI)o;
+        int comp = name_.compareTo(oClassAPI.name_);
+        if (comp != 0)
+            return comp;
+        if (isInterface_ != oClassAPI.isInterface_)
+            return -1;
+        if (isAbstract_ != oClassAPI.isAbstract_)
+            return -1;
+        comp = modifiers_.compareTo(oClassAPI.modifiers_);
+        if (comp != 0)
+            return comp;
+        if (APIComparator.docChanged(doc_, oClassAPI.doc_))
+            return -1;
+        return 0;
+    }  
+
+    /** 
+     * Tests two methods for equality using just the class name, 
+     * used by indexOf(). 
+     */
+    public boolean equals(Object o) {
+        if (name_.compareTo(((ClassAPI)o).name_) == 0)
+            return true;
+        return false;
+    }
+    
+}
diff --git a/src/jdiff/ClassDiff.java b/src/jdiff/ClassDiff.java
new file mode 100755
index 0000000..8742ab1
--- /dev/null
+++ b/src/jdiff/ClassDiff.java
@@ -0,0 +1,154 @@
+package jdiff;
+
+import java.util.*;
+import com.sun.javadoc.*;
+
+/**
+ * The changes between two classes.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class ClassDiff {
+
+    /** Name of the class. */
+    public String name_;
+
+    /** Set if this class is an interface in the new API. */
+    public boolean isInterface_;
+
+    /** 
+     * A string describing the changes in inheritance. 
+     */
+    public String inheritanceChange_ = null;
+
+    /** 
+     * A string describing the changes in documentation. 
+     */
+    public String documentationChange_ = null;
+
+    /** 
+     * A string describing the changes in modifiers. 
+     * Changes can be in whether this is a class or interface, whether it is
+     * abstract, static, final, and in its visibility.
+     */
+    public String modifiersChange_ = null;
+
+    /** Constructors added in the new API. */
+    public List ctorsAdded = null;
+    /** Constructors removed in the new API. */
+    public List ctorsRemoved = null;
+    /** Constructors changed in the new API. */
+    public List ctorsChanged = null;
+
+    /** Methods added in the new API. */
+    public List methodsAdded = null;
+    /** Methods removed in the new API. */
+    public List methodsRemoved = null;
+    /** Methods changed in the new API. */
+    public List methodsChanged = null;
+
+    /** Fields added in the new API. */
+    public List fieldsAdded = null;
+    /** Fields removed in the new API. */
+    public List fieldsRemoved = null;
+    /** Fields changed in the new API. */
+    public List fieldsChanged = null;
+
+    /* The percentage difference for this class. */
+    public double pdiff = 0.0;
+
+    /** Default constructor. */
+    public ClassDiff(String name) {
+        name_ = name;
+        isInterface_ = false;
+
+        ctorsAdded = new ArrayList(); // ConstructorAPI[]
+        ctorsRemoved = new ArrayList(); // ConstructorAPI[]
+        ctorsChanged = new ArrayList(); // MemberDiff[]
+
+        methodsAdded = new ArrayList(); // MethodAPI[]
+        methodsRemoved = new ArrayList(); // MethodAPI[]
+        methodsChanged = new ArrayList(); // MemberDiff[]
+
+        fieldsAdded = new ArrayList(); // FieldAPI[]
+        fieldsRemoved = new ArrayList(); // FieldAPI[]
+        fieldsChanged = new ArrayList(); // MemberDiff[]
+    }   
+
+    /** 
+     * Compare the inheritance details of two classes and produce 
+     * a String for the inheritanceChanges_ field in this class.
+     * If there is no difference, null is returned.
+     */
+    public static String diff(ClassAPI oldClass, ClassAPI newClass) {
+        Collections.sort(oldClass.implements_);
+        Collections.sort(newClass.implements_);
+        String res = "";
+        boolean hasContent = false;
+        if (oldClass.extends_ != null && newClass.extends_ != null &&
+            oldClass.extends_.compareTo(newClass.extends_) != 0) {
+            res += "The superclass changed from <code>" + oldClass.extends_ + "</code> to <code>" + newClass.extends_ + "</code>.<br>";
+            hasContent = true;
+        }
+        // Check for implemented interfaces which were removed
+        String removedInterfaces = "";
+        int numRemoved = 0;
+        Iterator iter = oldClass.implements_.iterator();
+        while (iter.hasNext()) {
+            String oldInterface = (String)(iter.next());
+            int idx = Collections.binarySearch(newClass.implements_, oldInterface);
+            if (idx < 0) {
+                if (numRemoved != 0)
+                    removedInterfaces += ", ";
+                removedInterfaces += oldInterface;
+                numRemoved++;
+            }
+        }
+        String addedInterfaces = "";
+        int numAdded = 0;
+        iter = newClass.implements_.iterator();
+        while (iter.hasNext()) {
+            String newInterface = (String)(iter.next());
+            int idx = Collections.binarySearch(oldClass.implements_, newInterface);
+            if (idx < 0) {
+                if (numAdded != 0)
+                    addedInterfaces += ", ";
+                addedInterfaces += newInterface;
+                numAdded++;
+            }
+        }
+        if (numRemoved != 0) {
+            if (hasContent)
+                res += " ";
+            if (numRemoved == 1)
+                res += "Removed interface <code>" + removedInterfaces + "</code>.<br>";
+            else
+                res += "Removed interfaces <code>" + removedInterfaces + "</code>.<br>";
+            hasContent = true;
+        }
+        if (numAdded != 0) {
+            if (hasContent)
+                res += " ";
+            if (numAdded == 1)
+                res += "Added interface <code>" + addedInterfaces + "</code>.<br>";
+            else
+                res += "Added interfaces <code>" + addedInterfaces + "</code>.<br>";
+            hasContent = true;
+        }
+        if (res.compareTo("") == 0)
+            return null;
+        return res;
+    }
+
+    /** Add a change in the modifiers. */
+    public void addModifiersChange(String commonModifierChanges) {
+        if (commonModifierChanges != null) {
+            if (modifiersChange_ == null)
+                modifiersChange_ = commonModifierChanges;
+            else
+                modifiersChange_ += " " + commonModifierChanges;
+        }
+    }
+}
+
diff --git a/src/jdiff/Comments.java b/src/jdiff/Comments.java
new file mode 100755
index 0000000..066e4f0
--- /dev/null
+++ b/src/jdiff/Comments.java
@@ -0,0 +1,551 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/* For SAX XML parsing */
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.*;
+
+/**
+ * Creates a Comments from an XML file. The Comments object is the internal 
+ * representation of the comments for the changes.
+ * All methods in this class for populating a Comments object are static.
+ * 
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class Comments {
+
+    /** 
+     * All the possible comments known about, accessible by the commentID.
+     */
+    public static Hashtable allPossibleComments = new Hashtable();
+
+    /** The old Comments object which is populated from the file read in. */ 
+    private static Comments oldComments_ = null;
+
+    /** Default constructor. */
+    public Comments() {
+        commentsList_ = new ArrayList(); // SingleComment[]
+    }   
+  
+    // The list of comments elements associated with this objects
+    public List commentsList_ = null; // SingleComment[]
+
+    /** 
+     * Read the file where the XML for comments about the changes between
+     * the old API and new API is stored and create a Comments object for 
+     * it. The Comments object may be null if no file exists.
+     */
+    public static Comments readFile(String filename) {
+        // If validation is desired, write out the appropriate comments.xsd 
+        // file in the same directory as the comments XML file.
+        if (XMLToAPI.validateXML) {
+            writeXSD(filename);
+        }
+
+        // If the file does not exist, return null
+        File f = new File(filename);
+        if (!f.exists())
+            return null;
+
+        // The instance of the Comments object which is populated from the file. 
+        oldComments_ = new Comments();
+        try {
+            DefaultHandler handler = new CommentsHandler(oldComments_);
+            XMLReader parser = null;
+            try {
+                String parserName = System.getProperty("org.xml.sax.driver");
+                if (parserName == null) {
+                    parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
+                } else {
+                    // Let the underlying mechanisms try to work out which 
+                    // class to instantiate
+                    parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
+                }
+            } catch (SAXException saxe) {
+                System.out.println("SAXException: " + saxe);
+                saxe.printStackTrace();
+                System.exit(1);
+            }
+
+            if (XMLToAPI.validateXML) {
+                parser.setFeature("http://xml.org/sax/features/namespaces", true);
+                parser.setFeature("http://xml.org/sax/features/validation", true);
+                parser.setFeature("http://apache.org/xml/features/validation/schema", true);
+            }
+            parser.setContentHandler(handler);
+            parser.setErrorHandler(handler);
+            parser.parse(new InputSource(new FileInputStream(new File(filename))));
+        } catch(org.xml.sax.SAXNotRecognizedException snre) {
+            System.out.println("SAX Parser does not recognize feature: " + snre);
+            snre.printStackTrace();
+            System.exit(1);
+        } catch(org.xml.sax.SAXNotSupportedException snse) {
+            System.out.println("SAX Parser feature is not supported: " + snse);
+            snse.printStackTrace();
+            System.exit(1);
+        } catch(org.xml.sax.SAXException saxe) {
+            System.out.println("SAX Exception parsing file '" + filename + "' : " + saxe);
+            saxe.printStackTrace();
+            System.exit(1);
+        } catch(java.io.IOException ioe) {
+            System.out.println("IOException parsing file '" + filename + "' : " + ioe);
+            ioe.printStackTrace();
+            System.exit(1);
+        }
+
+        Collections.sort(oldComments_.commentsList_);
+        return oldComments_;
+    } //readFile()
+
+    /**
+     * Write the XML Schema file used for validation.
+     */
+    public static void writeXSD(String filename) {
+        String xsdFileName = filename;
+        int idx = xsdFileName.lastIndexOf('\\');
+        int idx2 = xsdFileName.lastIndexOf('/');
+        if (idx == -1 && idx2 == -1) {
+            xsdFileName = "";
+        } else if (idx == -1 && idx2 != -1) {
+            xsdFileName = xsdFileName.substring(0, idx2+1);
+        } else if (idx != -1  && idx2 == -1) {
+            xsdFileName = xsdFileName.substring(0, idx+1);
+        } else if (idx != -1  && idx2 != -1) {
+            int max = idx2 > idx ? idx2 : idx;
+            xsdFileName = xsdFileName.substring(0, max+1);
+        }
+        xsdFileName += "comments.xsd";
+        try {
+            FileOutputStream fos = new FileOutputStream(xsdFileName);
+            PrintWriter xsdFile = new PrintWriter(fos);
+            // The contents of the comments.xsd file
+            xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
+            xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
+            xsdFile.println();
+            xsdFile.println("<xsd:annotation>");
+            xsdFile.println("  <xsd:documentation>");
+            xsdFile.println("  Schema for JDiff comments.");
+            xsdFile.println("  </xsd:documentation>");
+            xsdFile.println("</xsd:annotation>");
+            xsdFile.println();
+            xsdFile.println("<xsd:element name=\"comments\" type=\"commentsType\"/>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"commentsType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"comment\" type=\"commentType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"commentType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"identifier\" type=\"identifierType\" minOccurs='1' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"text\" type=\"xsd:string\" minOccurs='1' maxOccurs='1'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"identifierType\">");
+            xsdFile.println("  <xsd:attribute name=\"id\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("</xsd:schema>");
+            xsdFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + xsdFileName);
+            System.out.println("Error: " +  e.getMessage());
+            System.exit(1);
+        }
+    }
+
+//
+// Methods to add data to a Comments object. Called by the XML parser and the 
+// report generator.
+//
+
+    /**
+     * Add the SingleComment object to the list of comments kept by this 
+     * object. 
+     */
+    public void addComment(SingleComment comment) {
+        commentsList_.add(comment); 
+    }
+
+//
+// Methods to get data from a Comments object. Called by the report generator
+//
+
+    /** 
+     * The text placed into XML comments file where there is no comment yet.
+     * It never appears in reports.
+     */
+    public static final String placeHolderText = "InsertCommentsHere";
+    
+    /** 
+     * Return the comment associated with the given id in the Comment object.
+     * If there is no such comment, return the placeHolderText.
+     */
+    public static String getComment(Comments comments, String id) {
+        if (comments == null)
+            return placeHolderText;
+        SingleComment key = new SingleComment(id, null);
+        int idx = Collections.binarySearch(comments.commentsList_, key);
+        if (idx < 0) {
+            return placeHolderText;
+        } else {
+            int startIdx = comments.commentsList_.indexOf(key);
+            int endIdx = comments.commentsList_.indexOf(key);
+            int numIdx = endIdx - startIdx + 1;
+            if (numIdx != 1) {
+                System.out.println("Warning: " + numIdx + " identical ids in the existing comments file. Using the first instance.");
+            }
+            SingleComment singleComment = (SingleComment)(comments.commentsList_.get(idx));
+            // Convert @link tags to links
+            return singleComment.text_;
+        }
+    }
+
+    /** 
+     * Convert @link tags to HTML links. 
+     */
+    public static String convertAtLinks(String text, String currentElement, 
+                                        PackageAPI pkg, ClassAPI cls) {
+        if (text == null)
+            return null;
+	
+        StringBuffer result = new StringBuffer();
+        
+        int state = -1;
+        
+        final int NORMAL_TEXT = -1;
+        final int IN_LINK = 1;
+        final int IN_LINK_IDENTIFIER = 2;
+        final int IN_LINK_IDENTIFIER_REFERENCE = 3;
+        final int IN_LINK_IDENTIFIER_REFERENCE_PARAMS = 6;
+        final int IN_LINK_LINKTEXT = 4;
+        final int END_OF_LINK = 5;
+
+        StringBuffer identifier = null;
+        StringBuffer identifierReference = null;
+        StringBuffer linkText = null;
+        
+        // Figure out relative reference if required.
+        String ref = "";
+        if (currentElement.compareTo("class") == 0 ||
+            currentElement.compareTo("interface") == 0) {
+	    ref = pkg.name_ + "." + cls.name_ + ".";
+        } else if (currentElement.compareTo("package") == 0) {
+	    ref = pkg.name_ + ".";
+        }
+        ref = ref.replace('.', '/');        
+        
+        for (int i=0; i < text.length(); i++) {
+	    char c = text.charAt( i);
+	    char nextChar = i < text.length()-1 ? text.charAt( i+1) : (char)-1;
+	    int remainingChars = text.length() - i;
+          
+	    switch (state) {
+	    case NORMAL_TEXT:
+		if (c == '{' && remainingChars >= 5) {
+		    if ("{@link".equals(text.substring(i, i + 6))) {
+			state = IN_LINK;
+			identifier = null;
+			identifierReference = null;
+			linkText = null;
+			i += 5;
+			continue;
+		    }
+		}
+		result.append( c);
+		break;
+	    case IN_LINK:
+		if (Character.isWhitespace(nextChar)) continue;
+		if (nextChar == '}') {
+		    // End of the link
+		    state = END_OF_LINK;
+		} else if (!Character.isWhitespace(nextChar)) {
+		    state = IN_LINK_IDENTIFIER;
+		}
+		break;
+            case IN_LINK_IDENTIFIER:
+		if (identifier == null) {
+		    identifier = new StringBuffer();
+		}
+            
+		if (c == '#') {
+		    // We have a reference.
+		    state = IN_LINK_IDENTIFIER_REFERENCE;
+		    // Don't append #
+		    continue;
+		} else if (Character.isWhitespace(c)) {
+		    // We hit some whitespace: the next character is the beginning
+		    // of the link text.
+		    state = IN_LINK_LINKTEXT;
+		    continue;
+		}
+		identifier.append(c);              
+		// Check for a } that ends the link.
+		if (nextChar == '}') {
+		    state = END_OF_LINK;
+		}
+		break;
+            case IN_LINK_IDENTIFIER_REFERENCE:
+		if (identifierReference == null) {
+		    identifierReference = new StringBuffer();
+		}
+		if (Character.isWhitespace(c)) {
+		    state = IN_LINK_LINKTEXT;
+		    continue;
+		}
+		identifierReference.append(c);
+              
+		if (c == '(') {
+		    state = IN_LINK_IDENTIFIER_REFERENCE_PARAMS;
+		}
+              
+		if (nextChar == '}') {
+		    state = END_OF_LINK;
+		}
+		break;
+            case IN_LINK_IDENTIFIER_REFERENCE_PARAMS:
+		// We're inside the parameters of a reference. Spaces are allowed.
+		if (c == ')') {
+		    state = IN_LINK_IDENTIFIER_REFERENCE;
+		}
+		identifierReference.append(c);
+		if (nextChar == '}') {
+		    state = END_OF_LINK;
+		}
+		break;
+            case IN_LINK_LINKTEXT:
+		if (linkText == null) linkText = new StringBuffer();
+              
+		linkText.append(c);
+              
+		if (nextChar == '}') {
+		    state = END_OF_LINK;
+		}
+		break;
+            case END_OF_LINK:
+		if (identifier != null) {
+		    result.append("<A HREF=\"");
+		    result.append(HTMLReportGenerator.newDocPrefix);
+		    result.append(ref);
+		    result.append(identifier.toString().replace('.', '/'));
+		    result.append(".html");
+		    if (identifierReference != null) {
+			result.append("#");
+			result.append(identifierReference);
+		    }
+		    result.append("\">");   // target=_top?
+                
+		    result.append("<TT>");
+		    if (linkText != null) {
+			result.append(linkText);
+		    } else {
+			result.append(identifier);
+			if (identifierReference != null) {
+			    result.append(".");
+			    result.append(identifierReference);
+			}
+		    }
+		    result.append("</TT>");
+		    result.append("</A>");
+		}
+		state = NORMAL_TEXT;
+		break;
+	    }
+        }
+        return result.toString();
+    }
+
+//
+// Methods to write a Comments object out to a file.
+//
+
+    /**
+     * Write the XML representation of comments to a file.
+     *
+     * @param outputFileName The name of the comments file.
+     * @param oldComments The old comments on the changed APIs.
+     * @param newComments The new comments on the changed APIs.
+     * @return true if no problems encountered
+     */
+    public static boolean writeFile(String outputFileName, 
+                                    Comments newComments) {
+        try {
+            FileOutputStream fos = new FileOutputStream(outputFileName);
+            outputFile = new PrintWriter(fos);
+            newComments.emitXMLHeader(outputFileName);
+            newComments.emitComments();
+            newComments.emitXMLFooter();
+            outputFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + outputFileName);
+            System.out.println("Error: "+ e.getMessage());
+            System.exit(1);
+        }
+        return true;
+    }
+    
+    /**
+     * Write the Comments object out in XML.
+     */
+    public void emitComments() {
+        Iterator iter = commentsList_.iterator();
+        while (iter.hasNext()) {
+            SingleComment currComment = (SingleComment)(iter.next());
+            if (!currComment.isUsed_)
+                outputFile.println("<!-- This comment is no longer used ");
+            outputFile.println("<comment>");
+            outputFile.println("  <identifier id=\"" + currComment.id_ + "\"/>");
+            outputFile.println("  <text>");
+            outputFile.println("    " + currComment.text_);
+            outputFile.println("  </text>");
+            outputFile.println("</comment>");
+            if (!currComment.isUsed_)
+                outputFile.println("-->");
+        }        
+    }
+
+    /** 
+     * Dump the contents of a Comments object out for inspection.
+     */
+    public void dump() {
+        Iterator iter = commentsList_.iterator();
+        int i = 0;
+        while (iter.hasNext()) {
+            i++;
+            SingleComment currComment = (SingleComment)(iter.next());
+            System.out.println("Comment " + i);
+            System.out.println("id = " + currComment.id_);
+            System.out.println("text = \"" + currComment.text_ + "\"");
+            System.out.println("isUsed = " + currComment.isUsed_);
+        }        
+    }
+
+    /**
+     * Emit messages about which comments are now unused and which are new.
+     */
+    public static void noteDifferences(Comments oldComments, Comments newComments) {
+        if (oldComments == null) {
+            System.out.println("Note: all the comments have been newly generated");
+            return;
+        }
+        
+        // See which comment ids are no longer used and add those entries to 
+        // the new comments, marking them as unused.
+        Iterator iter = oldComments.commentsList_.iterator();
+        while (iter.hasNext()) {
+            SingleComment oldComment = (SingleComment)(iter.next());
+            int idx = Collections.binarySearch(newComments.commentsList_, oldComment);
+            if (idx < 0) {
+                System.out.println("Warning: comment \"" + oldComment.id_ + "\" is no longer used.");
+                oldComment.isUsed_ = false;
+                newComments.commentsList_.add(oldComment);
+            }
+        }        
+        
+    }
+
+    /**
+     * Emit the XML header.
+     */
+    public void emitXMLHeader(String filename) {
+        outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
+        outputFile.println("<comments");
+        outputFile.println("  xmlns:xsi='" + RootDocToXML.baseURI + "/2001/XMLSchema-instance'");
+        outputFile.println("  xsi:noNamespaceSchemaLocation='comments.xsd'");
+        // Extract the identifier from the filename by removing the suffix
+        int idx = filename.lastIndexOf('.');
+        String apiIdentifier = filename.substring(0, idx);
+        // Also remove the output directory and directory separator if present
+        if (HTMLReportGenerator.commentsDir != null)
+	    apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.commentsDir.length()+1);
+        else if (HTMLReportGenerator.outputDir != null)
+            apiIdentifier = apiIdentifier.substring(HTMLReportGenerator.outputDir.length()+1);
+        // Also remove "user_comments_for_"
+        apiIdentifier = apiIdentifier.substring(18);
+        outputFile.println("  name=\"" + apiIdentifier + "\"");
+        outputFile.println("  jdversion=\"" + JDiff.version + "\">");
+        outputFile.println();
+        outputFile.println("<!-- Use this file to enter an API change description. For example, when you remove a class, ");
+        outputFile.println("     you can enter a comment for that class that points developers to the replacement class. ");
+        outputFile.println("     You can also provide a change summary for modified API, to give an overview of the changes ");
+        outputFile.println("     why they were made, workarounds, etc.  -->");
+        outputFile.println();
+        outputFile.println("<!-- When the API diffs report is generated, the comments in this file get added to the tables of ");
+        outputFile.println("     removed, added, and modified packages, classes, methods, and fields. This file does not ship ");
+        outputFile.println("     with the final report. -->");
+        outputFile.println();
+        outputFile.println("<!-- The id attribute in an identifier element identifies the change as noted in the report. ");
+        outputFile.println("     An id has the form package[.class[.[ctor|method|field].signature]], where [] indicates optional ");
+        outputFile.println("     text. A comment element can have multiple identifier elements, which will will cause the same ");
+        outputFile.println("     text to appear at each place in the report, but will be converted to separate comments when the ");
+        outputFile.println("     comments file is used. -->");
+        outputFile.println();
+        outputFile.println("<!-- HTML tags in the text field will appear in the report. You also need to close p HTML elements, ");
+        outputFile.println("     used for paragraphs - see the top-level documentation. -->");
+        outputFile.println();
+        outputFile.println("<!-- You can include standard javadoc links in your change descriptions. You can use the @first command  ");
+        outputFile.println("     to cause jdiff to include the first line of the API documentation. You also need to close p HTML ");
+        outputFile.println("     elements, used for paragraphs - see the top-level documentation. -->");
+        outputFile.println();
+    }
+
+    /**
+     * Emit the XML footer.
+     */
+    public void emitXMLFooter() {
+        outputFile.println();
+        outputFile.println("</comments>");
+    }
+
+    private static List oldAPIList = null;
+    private static List newAPIList = null;
+
+    /** 
+     * Return true if the given HTML tag has no separate </tag> end element. 
+     *
+     * If you want to be able to use sloppy HTML in your comments, then you can
+     * add the element, e.g. li back into the condition here. However, if you 
+     * then become more careful and do provide the closing tag, the output is 
+     * generally just the closing tag, which is incorrect.
+     *
+     * tag.equalsIgnoreCase("tr") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("th") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("td") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("dt") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("dd") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("img") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("code") || // Is sometimes minimized (error)
+     * tag.equalsIgnoreCase("font") || // Is sometimes minimized (error)
+     * tag.equalsIgnoreCase("ul") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("ol") || // Is sometimes minimized
+     * tag.equalsIgnoreCase("li") // Is sometimes minimized
+     */
+    public static boolean isMinimizedTag(String tag) {
+        if (tag.equalsIgnoreCase("p") ||
+            tag.equalsIgnoreCase("br") ||
+            tag.equalsIgnoreCase("hr")
+            ) {
+            return true;
+	}
+        return false;
+    }
+
+    /** 
+     * The file where the XML representing the new Comments object is stored. 
+     */
+    private static PrintWriter outputFile = null;
+    
+}
+
+
diff --git a/src/jdiff/CommentsHandler.java b/src/jdiff/CommentsHandler.java
new file mode 100755
index 0000000..8061fbe
--- /dev/null
+++ b/src/jdiff/CommentsHandler.java
@@ -0,0 +1,210 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/* For SAX XML parsing */
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Handle the parsing of an XML file and the generation of a Comments object.
+ *
+ * All HTML written for the comments sections in the report must
+ * use tags such as &lt;p/&gt; rather than just &lt;p&gt;, since the XML
+ * parser used requires that or matching end elements.
+ *
+ * From http://www.w3.org/TR/2000/REC-xhtml1-20000126:
+ * "Empty elements must either have an end tag or the start tag must end with /&lt;". 
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class CommentsHandler extends DefaultHandler {
+
+    /** The Comments object which is populated from the XML file. */
+    public Comments comments_ = null;
+
+    /** The current SingleComment object being populated. */
+    private List currSingleComment_ = null; // SingleComment[]
+
+    /** Set if in text. */
+    private boolean inText = false;
+
+    /** The current text which is being assembled from chunks. */
+    private String currentText = null;
+    
+    /** The stack of SingleComments still waiting for comment text. */
+    private LinkedList tagStack = null;
+
+    /** Default constructor. */
+    public CommentsHandler(Comments comments) {
+        comments_ = comments;
+        tagStack = new LinkedList();
+    }   
+
+    public void startDocument() {
+    }
+    
+    public void endDocument() {
+        if (trace)
+            comments_.dump();
+    }
+
+    public void startElement(java.lang.String uri, java.lang.String localName,
+                             java.lang.String qName, Attributes attributes) {
+	// The change to JAXP compliance produced this change.
+	if (localName.equals(""))
+	    localName = qName;
+        if (localName.compareTo("comments") == 0) {
+            String commentsName = attributes.getValue("name");
+            String version = attributes.getValue("jdversion"); // Not used yet
+            if (commentsName == null) {
+                System.out.println("Error: no identifier found in the comments XML file.");
+                System.exit(3);
+            }
+            // Check the given names against the names of the APIs
+            int idx1 = JDiff.oldFileName.lastIndexOf('.');
+            int idx2 = JDiff.newFileName.lastIndexOf('.');
+            String filename2 = JDiff.oldFileName.substring(0, idx1) + 
+                "_to_" + JDiff.newFileName.substring(0, idx2);
+            if (filename2.compareTo(commentsName) != 0) {
+                System.out.println("Warning: API identifier in the comments XML file (" + filename2 + ") differs from the name of the file.");
+            }
+        } else if (localName.compareTo("comment") == 0) {
+            currSingleComment_ = new ArrayList(); // SingleComment[];
+        } else if (localName.compareTo("identifier") == 0) {
+            // May have multiple identifiers for one comment's text
+            String id = attributes.getValue("id");
+            SingleComment newComment = new SingleComment(id, null);
+            // Store it here until we can add text to it
+            currSingleComment_.add(newComment);
+        } else if (localName.compareTo("text") == 0) {
+            inText = true;
+            currentText = null;
+        } else {
+            if (inText) {
+                // Start of an element, probably an HTML element
+                addStartTagToText(localName, attributes);
+            } else {
+                System.out.println("Error: unknown element type: " + localName);
+                System.exit(-1);
+            }
+        }
+    }
+    
+    public void endElement(java.lang.String uri, java.lang.String localName, 
+                           java.lang.String qName) {
+	if (localName.equals(""))
+	    localName = qName;
+        if (localName.compareTo("text") == 0) {
+            inText = false;
+            addTextToComments();
+        } else if (inText) {
+            addEndTagToText(localName);
+        }
+
+    }
+    
+    /** Deal with a chunk of text. The text may come in multiple chunks. */
+    public void characters(char[] ch, int start, int length) {
+        if (inText) {
+            String chunk = new String(ch, start, length);
+            if (currentText == null)
+                currentText = chunk;
+            else
+                currentText += chunk;
+        }
+    }
+
+    /** 
+     * Trim the current text, check it is a sentence and add it to all 
+     * the comments which are waiting for it. 
+     */
+    public void addTextToComments() {
+        // Eliminate any whitespace at each end of the text.
+        currentText = currentText.trim();
+        // Check that it is a sentence
+        if (!currentText.endsWith(".") &&
+            !currentText.endsWith("?") &&
+            !currentText.endsWith("!") && 
+            currentText.compareTo(Comments.placeHolderText) != 0) {
+            System.out.println("Warning: text of comment does not end in a period: " + currentText);
+        }
+        // Add this comment to all the SingleComments waiting for it
+        Iterator iter = currSingleComment_.iterator();
+        while (iter.hasNext()) {
+            SingleComment currComment = (SingleComment)(iter.next());
+            if (currComment.text_ == null)
+                currComment.text_ = currentText;
+            else
+                currComment.text_ += currentText;
+            comments_.addComment(currComment);
+        }
+    }
+
+    /** 
+     * Add the start tag to the current comment text. 
+     */
+    public void addStartTagToText(String localName, Attributes attributes) {
+        // Need to insert the HTML tag into the current text
+        String currentHTMLTag = localName;
+        // Save the tag in a stack
+        tagStack.add(currentHTMLTag);
+        String tag = "<" + currentHTMLTag;
+        // Now add all the attributes into the current text
+        int len = attributes.getLength();
+        for (int i = 0; i < len; i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+            tag += " " + name + "=\"" + value+ "\"";
+        }
+
+        // End the tag
+        if (Comments.isMinimizedTag(currentHTMLTag)) {
+            tag += "/>";
+        } else {
+            tag += ">";
+        }
+        // Now insert the HTML tag into the current text
+        if (currentText == null)
+            currentText = tag;
+        else
+            currentText += tag;
+    }
+
+    /** 
+     * Add the end tag to the current comment text. 
+     */
+    public void addEndTagToText(String localName) {
+        // Close the current HTML tag
+        String currentHTMLTag = (String)(tagStack.removeLast());
+        if (!Comments.isMinimizedTag(currentHTMLTag))
+            currentText += "</" + currentHTMLTag + ">";
+    }
+
+    public void warning(SAXParseException e) {
+        System.out.println("Warning (" + e.getLineNumber() + "): parsing XML comments file:" + e);
+        e.printStackTrace();
+    }
+
+    public void error(SAXParseException e) {
+        System.out.println("Error (" + e.getLineNumber() + "): parsing XML comments file:" + e);
+        e.printStackTrace();
+        System.exit(1);
+    }
+    
+    public void fatalError(SAXParseException e) {
+        System.out.println("Fatal Error (" + e.getLineNumber() + "): parsing XML comments file:" + e);
+        e.printStackTrace();
+        System.exit(1);
+    }    
+
+    /** Set to enable increased logging verbosity for debugging. */
+    private static final boolean trace = false;
+
+}
+
diff --git a/src/jdiff/CompareClassPdiffs.java b/src/jdiff/CompareClassPdiffs.java
new file mode 100755
index 0000000..bd0bf95
--- /dev/null
+++ b/src/jdiff/CompareClassPdiffs.java
@@ -0,0 +1,25 @@
+package jdiff;
+
+import java.util.*;
+
+/** 
+ * Class to compare two ClassDiff objects.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class CompareClassPdiffs implements Comparator {
+    /** 
+     * Compare two class diffs by their percentage difference,
+     * and then by name.
+     */
+    public int compare(Object obj1, Object obj2){
+        ClassDiff c1 = (ClassDiff)obj1;
+        ClassDiff c2 = (ClassDiff)obj2;
+        if (c1.pdiff < c2.pdiff)
+            return 1;
+        if (c1.pdiff > c2.pdiff)
+            return -1;
+        return c1.name_.compareTo(c2.name_);
+    }
+}
diff --git a/src/jdiff/ComparePkgPdiffs.java b/src/jdiff/ComparePkgPdiffs.java
new file mode 100755
index 0000000..187e7bf
--- /dev/null
+++ b/src/jdiff/ComparePkgPdiffs.java
@@ -0,0 +1,25 @@
+package jdiff;
+
+import java.util.*;
+
+/** 
+ * Class to compare two PackageDiff objects.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class ComparePkgPdiffs implements Comparator {
+    /** 
+     * Compare two package diffs by their percentage difference,
+     * and then by name.
+     */
+    public int compare(Object obj1, Object obj2){
+        PackageDiff p1 = (PackageDiff)obj1;
+        PackageDiff p2 = (PackageDiff)obj2;
+        if (p1.pdiff < p2.pdiff)
+            return 1;
+        if (p1.pdiff > p2.pdiff)
+            return -1;
+        return p1.name_.compareTo(p2.name_);
+    }
+}
diff --git a/src/jdiff/ConstructorAPI.java b/src/jdiff/ConstructorAPI.java
new file mode 100755
index 0000000..7390a4d
--- /dev/null
+++ b/src/jdiff/ConstructorAPI.java
@@ -0,0 +1,66 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent a constructor, analogous to ConstructorDoc in the 
+ * Javadoc doclet API. 
+ *
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this constructor.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class ConstructorAPI implements Comparable {
+    /** 
+     * The type of the constructor, being all the parameter types
+     * separated by commas.
+     */
+    public String type_ = null;
+    
+    /** 
+     * The exceptions thrown by this constructor, being all the exception types
+     * separated by commas. "no exceptions" if no exceptions are thrown.
+     */
+    public String exceptions_ = "no exceptions";
+    
+    /** Modifiers for this class. */
+    public Modifiers modifiers_;
+
+    /** The doc block, default is null. */
+    public String doc_ = null;
+
+    /** Constructor. */
+    public ConstructorAPI(String type, Modifiers modifiers) {
+        type_ = type;
+        modifiers_ = modifiers;
+    }
+
+    /** Compare two ConstructorAPI objects by type and modifiers. */
+    public int compareTo(Object o) {
+        ConstructorAPI constructorAPI = (ConstructorAPI)o;
+        int comp = type_.compareTo(constructorAPI.type_);
+        if (comp != 0)
+            return comp;
+        comp = exceptions_.compareTo(constructorAPI.exceptions_);
+        if (comp != 0)
+            return comp;
+        comp = modifiers_.compareTo(constructorAPI.modifiers_);
+        if (comp != 0)
+            return comp;
+        if (APIComparator.docChanged(doc_, constructorAPI.doc_))
+            return -1;
+        return 0;
+    }
+
+    /** 
+     * Tests two constructors, using just the type, used by indexOf(). 
+     */
+    public boolean equals(Object o) {
+        if (type_.compareTo(((ConstructorAPI)o).type_) == 0)
+            return true;
+        return false;
+    }
+}  
diff --git a/src/jdiff/Diff.java b/src/jdiff/Diff.java
new file mode 100755
index 0000000..a9c9776
--- /dev/null
+++ b/src/jdiff/Diff.java
@@ -0,0 +1,654 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to generate colored differences between two sections of HTML text.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class Diff {
+
+    /** 
+     * Save the differences between the two strings in a DiffOutput object
+     * for later use.
+     * 
+     * @param id A per-package unique identifier for each documentation 
+     *           change.
+     */ 
+    static String saveDocDiffs(String pkgName, String className, 
+                               String oldDoc, String newDoc, 
+                               String id, String title) {
+        // Generate the string which will link to this set of diffs
+        if (noDocDiffs)
+            return "Documentation changed from ";
+        if (oldDoc == null || newDoc == null) {
+            return "Documentation changed from ";
+        }
+
+        // Generate the differences. 
+        generateDiffs(pkgName, className, oldDoc, newDoc, id, title);
+
+        return "Documentation <a href=\"" + diffFileName + pkgName +
+            HTMLReportGenerator.reportFileExt + "#" + id + 
+            "\">changed</a> from ";
+    }
+    
+    /** 
+     * Generate the differences. 
+     */
+    static void generateDiffs(String pkgName, String className,
+                              String oldDoc, String newDoc, 
+                              String id, String title) {
+        String[] oldDocWords = parseDoc(oldDoc);
+        String[] newDocWords = parseDoc(newDoc);
+
+        DiffMyers diff = new DiffMyers(oldDocWords, newDocWords);
+        DiffMyers.change script = diff.diff_2(false);
+        script = mergeDiffs(oldDocWords, newDocWords, script);
+        String text = "<A NAME=\"" + id + "\"></A>" + title + "<br><br>";
+        // Generate the differences in blockquotes to cope with unterminated 
+        // HTML tags
+        text += "<blockquote>";
+        text = addDiffs(oldDocWords, newDocWords, script, text);
+        text += "</blockquote>";
+        docDiffs.add(new DiffOutput(pkgName, className, id, title, text));
+    }
+
+    /** 
+     * Convert the string to an array of strings, but don't break HTML tags up.
+     */
+    static String[] parseDoc(String doc) {
+        String delimiters = " .,;:?!(){}[]\"'~@#$%^&*+=_-|\\<>/";
+        StringTokenizer st = new StringTokenizer(doc, delimiters, true);
+        List docList = new ArrayList();
+        boolean inTag = false;
+        String tag = null;
+        while (st.hasMoreTokens()) {
+            String tok = st.nextToken();
+            if (!inTag) {
+                if (tok.compareTo("<") == 0) {
+                    tag = tok;
+                    if (st.hasMoreTokens()) {
+                        // See if this really is a tag
+                        tok = st.nextToken();
+                        char ch = tok.charAt(0);
+                        if (Character.isLetter(ch) || ch == '/') {
+                            inTag = true;
+                            tag += tok;
+                        }
+                    }
+                    if (!inTag)
+                      docList.add(tag);
+                } else { 
+                    docList.add(tok);
+                }
+            } else {
+                // Add all tokens to the tag until the closing > is seen
+                if (tok.compareTo(">") == 0) {
+                    inTag = false;
+                    tag += tok;
+                    docList.add(tag);
+                } else { 
+                    tag += tok;
+                }
+            }
+        }   
+        if (inTag) {
+            // An unterminated tag, or more likely, < used instead of &lt;
+            // There are no nested tags such as <a <b>> in HTML
+            docList.add(tag);
+        }
+        String[] docWords = new String[docList.size()];
+        docWords = (String[])docList.toArray(docWords);
+        return docWords;
+    }
+
+    /** 
+     * For improved readability, merge changes of the form 
+     *  "delete 1, insert 1, space, delete 1, insert 1"
+     * to 
+     *  "delete 3, insert 3" (including the space).
+     *
+     * @param oldDocWords The original documentation as a String array
+     * @param newDocWords The new documentation as a String array
+     */
+    static DiffMyers.change mergeDiffs(String[] oldDocWords, String[] newDocWords, 
+                                       DiffMyers.change script) {
+        if (script.link == null)
+            return script; // Only one change
+        DiffMyers.change hunk = script;
+        DiffMyers.change lasthunk = null; // Set to the last potential hunk
+        int startOld = 0;
+        for (; hunk != null; hunk = hunk.link) {
+            int deletes = hunk.deleted;
+            int inserts = hunk.inserted;
+            if (lasthunk == null) {
+                if (deletes == 1 && inserts == 1) {
+                    // This is the start of a potential merge
+                    lasthunk = hunk;
+                } 
+                continue;
+            } else {
+                int first0 = hunk.line0; // Index of first deleted word
+                int first1 = hunk.line1; // Index of first inserted word
+                if (deletes == 1 && inserts == 1 && 
+                    oldDocWords[first0 - 1].compareTo(" ") == 0 && 
+                    newDocWords[first1 - 1].compareTo(" ") == 0 &&
+                    first0 == lasthunk.line0 + lasthunk.deleted + 1 &&
+                    first1 == lasthunk.line1 + lasthunk.inserted + 1) {
+                    // Merge this change into the last change
+                    lasthunk.deleted += 2;
+                    lasthunk.inserted += 2;
+                    lasthunk.link = hunk.link;
+                } else {
+                    lasthunk = null;
+                }
+            }
+        }            
+        return script;
+    }
+
+    /** 
+     * Add the differences to the text passed in. The old documentation is 
+     * edited using the edit script provided by the DiffMyers object.
+     * Do not display diffs in HTML tags.
+     *
+     * @param oldDocWords The original documentation as a String array
+     * @param newDocWords The new documentation as a String array
+     * @return The text for this documentation difference
+     */
+    static String addDiffs(String[] oldDocWords, String[] newDocWords, 
+                           DiffMyers.change script, String text) {
+        String res = text;
+        DiffMyers.change hunk = script;
+        int startOld = 0;
+        if (trace) {
+            System.out.println("Old Text:");
+            for (int i = 0; i < oldDocWords.length; i++) {
+                System.out.print(oldDocWords[i]);
+            }
+            System.out.println(":END");
+            System.out.println("New Text:");
+            for (int i = 0; i < newDocWords.length; i++) {
+                System.out.print(newDocWords[i]);
+            }
+            System.out.println(":END");
+        }
+
+        for (; hunk != null; hunk = hunk.link) {
+            int deletes = hunk.deleted;
+            int inserts = hunk.inserted;
+            if (deletes == 0 && inserts == 0) {
+                continue; // Not clear how this would occur, but handle it
+            }
+
+            // Determine the range of word and delimiter numbers involved 
+            // in each file.
+            int first0 = hunk.line0; // Index of first deleted word
+            // Index of last deleted word, invalid if deletes == 0
+            int last0 = hunk.line0 + hunk.deleted - 1; 
+            int first1 = hunk.line1; // Index of first inserted word
+            // Index of last inserted word, invalid if inserts == 0
+            int last1 = hunk.line1 + hunk.inserted - 1;
+            
+            if (trace) {
+                System.out.println("HUNK: ");
+                System.out.println("inserts: " + inserts);
+                System.out.println("deletes: " + deletes);
+                System.out.println("first0: " + first0);
+                System.out.println("last0: " + last0);
+                System.out.println("first1: " + first1);
+                System.out.println("last1: " + last1);
+            }
+
+            // Emit the original document up to this change
+            for (int i = startOld; i < first0; i++) {
+                res += oldDocWords[i];
+            }
+            // Record where to start the next hunk from
+            startOld = last0 + 1;
+            // Emit the deleted words, but struck through
+            // but do not emit deleted HTML tags
+            if (deletes != 0) {
+                boolean inStrike = false;
+                for (int i = first0; i <= last0; i++) {
+                    if (!oldDocWords[i].startsWith("<") && 
+                        !oldDocWords[i].endsWith(">")) {
+                        if (!inStrike) {
+                            if (deleteEffect == 0)
+                                res += "<strike>";
+                            else if (deleteEffect == 1)
+                                res += "<span style=\"background: #FFCCCC\">";
+                            inStrike = true;
+                        }
+                        res += oldDocWords[i];
+                    }
+                }
+                if (inStrike) {
+                    if (deleteEffect == 0)
+                        res += "</strike>";
+                    else if (deleteEffect == 1)
+                        res += "</span>";
+                }
+            }
+            // Emit the inserted words, but do not emphasise new HTML tags
+            if (inserts != 0) {
+                boolean inEmph = false;
+                for (int i = first1; i <= last1; i++) {
+                    if (!newDocWords[i].startsWith("<") && 
+                        !newDocWords[i].endsWith(">")) {
+                        if (!inEmph) {
+                            if (insertEffect == 0)
+                                res += "<font color=\"red\">";
+                            else if (insertEffect == 1)
+                                res += "<span style=\"background: #FFFF00\">";
+                            inEmph = true;
+                        }
+                    }
+                    res += newDocWords[i];
+                }
+                if (inEmph) {
+                    if (insertEffect == 0)
+                        res += "</font>";
+                    else if (insertEffect == 1)
+                        res += "</span>";
+                }
+            }
+        } //for (; hunk != null; hunk = hunk.link)
+        // Print out the remaining part of the old text
+        for (int i = startOld; i < oldDocWords.length; i++) {
+            res += oldDocWords[i];
+        }
+        return res;
+    }
+
+    /** 
+     * Emit all the documentation differences into one file per package.
+     */ 
+    static void emitDocDiffs(String fullReportFileName) {
+        Collections.sort(docDiffs);
+
+        DiffOutput[] docDiffsArr = new DiffOutput[docDiffs.size()];
+        docDiffsArr = (DiffOutput[])docDiffs.toArray(docDiffsArr);
+
+        for (int i = 0; i < docDiffsArr.length; i++) {
+            DiffOutput diffOutput = docDiffsArr[i];
+            if (currPkgName == null || 
+                currPkgName.compareTo(diffOutput.pkgName_) != 0) {
+                // Open a different file for each package, add the HTML header,
+                // the navigation bar and some preamble.
+                if (currPkgName != null)
+                    closeDiffFile(); // Close the existing file
+                // Create the HTML link to the previous package
+                String prevPkgName = currPkgName;
+                if (currPkgName != null) {
+                    prevPkgName = diffFileName + docDiffsArr[i-1].pkgName_ +
+                    HTMLReportGenerator.reportFileExt;
+                }
+                // Set the current package name
+                currPkgName = diffOutput.pkgName_;
+                // Create the HTML link to the next package
+                String nextPkgName = null;
+                for (int j = i; j < docDiffsArr.length; j++) {
+                    if (currPkgName.compareTo(docDiffsArr[j].pkgName_) != 0) {
+                        nextPkgName = diffFileName + docDiffsArr[j].pkgName_ +
+                            HTMLReportGenerator.reportFileExt;
+                        break;
+                    }
+                }
+
+                String fullDiffFileName = fullReportFileName + 
+                    JDiff.DIR_SEP + diffFileName + currPkgName +
+                    HTMLReportGenerator.reportFileExt;
+                // Create the output file
+                try {
+                    FileOutputStream fos = new FileOutputStream(fullDiffFileName);
+                    diffFile = new PrintWriter(fos);
+                    
+                    // Write the HTML header
+                    diffFile.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\"\"" + RootDocToXML.baseURI + "/TR/REC-html40/frameset.dtd\">");
+                    diffFile.println("<HTML>");
+                    diffFile.println("<HEAD>");
+                    diffFile.println("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">");
+                    diffFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
+                    diffFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
+//                    diffFile.println("<!-- on " + new Date() + " -->");
+                    diffFile.println("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">");
+                    diffFile.println("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">");
+                    diffFile.println("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"" + "../" + "stylesheet-jdiff.css\" TITLE=\"Style\">");
+                    diffFile.println("<TITLE>");
+                    diffFile.println(currPkgName + " Documentation Differences");
+                    diffFile.println("</TITLE>");
+                    diffFile.println("</HEAD>");
+                    diffFile.println("<BODY>");
+                                        
+                    // Write the navigation bar
+                    diffFile.println("<!-- Start of nav bar -->");
+                    diffFile.println("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
+                    diffFile.println("<TR>");
+                    diffFile.println("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
+                    diffFile.println("  <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
+                    diffFile.println("    <TR ALIGN=\"center\" VALIGN=\"top\">");
+                    // Always have a link to the Javadoc files
+                    String pkgRef = currPkgName;
+                    pkgRef = pkgRef.replace('.', '/');
+                    pkgRef = HTMLReportGenerator.newDocPrefix + pkgRef + "/package-summary";
+                    diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + pkgRef + ".html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + APIDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
+                    diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + HTMLReportGenerator.reportFileName + "-summary" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
+                    diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
+                    diffFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
+                    if (!Diff.noDocDiffs) {
+                        diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
+                    }
+                    if (HTMLReportGenerator.doStats) {
+                        diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
+                    }
+                    diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&nbsp;</TD>");
+                    diffFile.println("    </TR>");
+                    diffFile.println("  </TABLE>");
+                    diffFile.println("</TD>");
+                    
+                    // The right hand side title
+                    diffFile.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>");
+                    diffFile.println("</TR>");
+                    
+                    // Links for previous and next, and frames and no frames
+                    diffFile.println("<TR>");
+                    diffFile.println("  <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
+                    if (prevPkgName != null)
+                        diffFile.println("  <A HREF=\"" + prevPkgName + "\"><B>PREV PACKAGE</B></A>  &nbsp;");
+                    else
+                        diffFile.println("  <B>PREV PACKAGE</B>  &nbsp;");
+                    if (nextPkgName != null)
+                        diffFile.println("  &nbsp;<A HREF=\"" + nextPkgName + "\"><B>NEXT PACKAGE</B></A>");
+                    else
+                        diffFile.println("  &nbsp;<B>NEXT PACKAGE</B>");
+                    diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
+                    diffFile.println("  <A HREF=\"" + "../" + HTMLReportGenerator.reportFileName + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A>  &nbsp;");
+                    diffFile.println("  &nbsp;<A HREF=\"" + diffFileName + currPkgName + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
+                    diffFile.println("  <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\">&nbsp;</TD>");
+                    diffFile.println("</TR>");
+                    
+                    diffFile.println("</TABLE>");
+                    diffFile.println("<HR>");
+                    diffFile.println("<!-- End of nav bar -->");
+                    
+                    diffFile.println("<h2>");
+                    diffFile.println(currPkgName + " Documentation Differences");
+                    diffFile.println("</h2>");
+                    diffFile.println();
+                    diffFile.println("<blockquote>");
+                    diffFile.println("This file contains all the changes in documentation in the package <code>" + currPkgName + "</code> as colored differences.");
+                    if (deleteEffect == 0)
+                        diffFile.println("Deletions are shown <strike>like this</strike>, and");
+                    else if (deleteEffect == 1)
+                        diffFile.println("Deletions are shown <span style=\"background: #FFCCCC\">like this</span>, and");
+                    if (insertEffect == 0)
+                        diffFile.println("additions are shown in red <font color=\"red\">like this</font>.");
+                    else if (insertEffect == 1)
+                        diffFile.println("additions are shown <span style=\"background: #FFFF00\">like this</span>.");
+                    diffFile.println("</blockquote>");
+                    
+                    diffFile.println("<blockquote>");
+                    diffFile.println("If no deletions or additions are shown in an entry, the HTML tags will be what has changed. The <i>new</i> HTML tags are shown in the differences. ");
+                    diffFile.println("If no documentation existed, and then some was added in a later version, this change is noted in the appropriate class pages of differences, but the change is not shown on this page. Only changes in existing text are shown here. ");
+                    diffFile.println("Similarly, documentation which was inherited from another class or interface is not shown here.");
+                    diffFile.println("</blockquote>");
+                    
+                    diffFile.println("<blockquote>");
+                    diffFile.println(" Note that an HTML error in the new documentation may cause the display of other documentation changes to be presented incorrectly. For instance, failure to close a &lt;code&gt; tag will cause all subsequent paragraphs to be displayed differently.");
+                    diffFile.println("</blockquote>");
+                    diffFile.println("<hr>");
+                    diffFile.println();
+                    
+                } catch(IOException e) {
+                    System.out.println("IO Error while attempting to create " + fullDiffFileName);
+                    System.out.println("Error: " + e.getMessage());
+                    System.exit(1);
+                }
+            } // if (currPkgName == null || currPkgName.compareTo(diffOutput.pkgName_) != 0)
+            // Now add the documentation difference text
+            diffFile.println(diffOutput.text_);
+            // Separate with a horizontal line
+            if (i != docDiffsArr.length - 1 && 
+                diffOutput.className_ != null && 
+                docDiffsArr[i+1].className_ != null &&
+                diffOutput.className_.compareTo(docDiffsArr[i+1].className_) != 0)
+                diffFile.println("<hr align=\"left\" width=\"100%\">");
+//            else
+//                diffFile.println("<hr align=\"left\" width=\"50%\">");
+        } // for (i = 0;
+        if (currPkgName != null)
+            closeDiffFile(); // Close the existing file
+
+        // Emit the single file which is the index to all documentation changes
+        emitDocDiffIndex(fullReportFileName, docDiffsArr);
+    }
+
+    /** 
+     * Emit the single file which is the index to all documentation changes.
+     */
+    public static void emitDocDiffIndex(String fullReportFileName, 
+                                        DiffOutput[] docDiffsArr) { 
+
+        String fullDiffFileName = fullReportFileName + 
+            JDiff.DIR_SEP + diffFileName + "index" +
+            HTMLReportGenerator.reportFileExt;
+
+        // Create the output file
+        try {
+            FileOutputStream fos = new FileOutputStream(fullDiffFileName);
+            diffFile = new PrintWriter(fos);
+            
+            // Write the HTML header
+            diffFile.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Frameset//EN\"\"" + RootDocToXML.baseURI + "/TR/REC-html40/frameset.dtd\">");
+            diffFile.println("<HTML>");
+            diffFile.println("<HEAD>");
+            diffFile.println("<meta name=\"generator\" content=\"JDiff v" + JDiff.version + "\">");
+            diffFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
+            diffFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
+//            diffFile.println("<!-- on " + new Date() + " -->");
+            diffFile.println("<meta name=\"description\" content=\"" + JDiff.jDiffDescription + "\">");
+            diffFile.println("<meta name=\"keywords\" content=\"" + JDiff.jDiffKeywords + "\">");
+            diffFile.println("<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"" + "../" + "stylesheet-jdiff.css\" TITLE=\"Style\">");
+            diffFile.println("<TITLE>");
+            diffFile.println("All Documentation Differences");
+            diffFile.println("</TITLE>");
+            diffFile.println("</HEAD>");
+            diffFile.println("<BODY>");
+                        
+            // Write the navigation bar
+            diffFile.println("<!-- Start of nav bar -->");
+            diffFile.println("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
+            diffFile.println("<TR>");
+            diffFile.println("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
+            diffFile.println("  <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
+            diffFile.println("    <TR ALIGN=\"center\" VALIGN=\"top\">");
+            // Always have a link to the Javadoc files
+            diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + HTMLReportGenerator.newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + APIDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
+            diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + HTMLReportGenerator.reportFileName + "-summary" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
+            diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
+            diffFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
+            if (!Diff.noDocDiffs) {
+                diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> <FONT CLASS=\"NavBarFont1Rev\"><B>Text Changes</B></FONT>&nbsp;</TD>");
+            }
+            if (HTMLReportGenerator.doStats) {
+                diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
+            }
+            diffFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + HTMLReportGenerator.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&nbsp;</TD>");
+            diffFile.println("    </TR>");
+            diffFile.println("  </TABLE>");
+            diffFile.println("</TD>");
+            
+            // The right hand side title
+            diffFile.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>");
+            diffFile.println("</TR>");
+            
+            // Links for frames and no frames
+            diffFile.println("<TR>");
+            diffFile.println("  <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
+            diffFile.println("  <A HREF=\"" + "../" + HTMLReportGenerator.reportFileName + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A>  &nbsp;");
+            diffFile.println("  &nbsp;<A HREF=\"" + diffFileName + "index" + HTMLReportGenerator.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
+            diffFile.println("  <TD BGCOLOR=\"" + HTMLReportGenerator.bgcolor + "\" CLASS=\"NavBarCell2\">&nbsp;</TD>");
+            diffFile.println("</TR>");
+            
+            diffFile.println("</TABLE>");
+            diffFile.println("<HR>");
+            diffFile.println("<!-- End of nav bar -->");
+            
+            diffFile.println("<h2>");
+            diffFile.println("All Documentation Differences");
+            diffFile.println("</h2>");
+            diffFile.println();
+            
+            // For each package and class, add the first DiffOutput to
+            // the hash table. Used when generating navigation bars.
+            boolean firstPackage = true; // Set for the first package
+            boolean firstClass = true; // Set for first class in a package
+            boolean firstCtor = true; // Set for first ctor in a class
+            boolean firstMethod = true; // Set for first method in a class
+            boolean firstField = true; // Set for first field in a class
+            for (int i = 0; i < docDiffsArr.length; i++) {
+                DiffOutput diffOutput = docDiffsArr[i];
+                String link = "<a href=\"" + Diff.diffFileName + diffOutput.pkgName_ + HTMLReportGenerator.reportFileExt + "#" + diffOutput.id_ + "\">";
+
+                // See if the package name changed
+                if (firstPackage || diffOutput.pkgName_.compareTo(docDiffsArr[i-1].pkgName_) != 0) {
+                    if (firstPackage) {
+                        firstPackage = false;
+                    } else {
+                        diffFile.println("<br>");
+                    }
+                    firstClass = true;
+                    firstCtor = true;
+                    firstMethod = true;
+                    firstField = true;
+                    String id = diffOutput.pkgName_ + "!package";
+                    firstDiffOutput.put(id, id);
+                    if (diffOutput.className_ == null) {
+                        diffFile.println("<A NAME=\"" + id + "\"></A>" + link + "Package <b>" + diffOutput.pkgName_ + "</b></a><br>");
+                    } else {
+                        diffFile.println("<A NAME=\"" + id + "\"></A>" + "Package <b>" + diffOutput.pkgName_ + "</b><br>");
+                    }
+                }
+                // See if the class name changed
+                if (diffOutput.className_ != null && 
+                    (firstClass || 
+                     diffOutput.className_.compareTo(docDiffsArr[i-1].className_) != 0)) {
+                    if (firstClass) {
+                        firstClass = false;
+                    } else {
+                        diffFile.println("<br>");
+                    }
+                    firstCtor = true;
+                    firstMethod = true;
+                    firstField = true;
+                    String id = diffOutput.pkgName_ + "." + diffOutput.className_ + "!class";
+                    firstDiffOutput.put(id, id);
+                    if (diffOutput.id_.endsWith("!class")) {
+                        diffFile.println("<A NAME=\"" + id + "\"></A>&nbsp;&nbsp;Class " + link + diffOutput.className_ + "</a><br>");
+                    } else {
+                        diffFile.println("<A NAME=\"" + id + "\"></A>&nbsp;&nbsp;Class " + diffOutput.className_ + "<br>");
+                    }
+                }
+                // Work out what kind of member this is, and
+                // display it appropriately
+                if (diffOutput.className_ != null && 
+                    !diffOutput.id_.endsWith("!class")) {
+                    int ctorIdx = diffOutput.id_.indexOf(".ctor");
+                    if (ctorIdx != -1) {
+                        diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;" + link + diffOutput.className_ + diffOutput.id_.substring(ctorIdx + 5) + "</a><br>");
+                    } else {
+                        int methodIdx = diffOutput.id_.indexOf(".dmethod.");
+                        if (methodIdx != -1) {
+                            diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;"  + "Method " + link + diffOutput.id_.substring(methodIdx + 9) + "</a><br>");
+                        } else {
+                            int fieldIdx = diffOutput.id_.indexOf(".field.");
+                            if (fieldIdx != -1) {
+                                diffFile.println("&nbsp;&nbsp;&nbsp;&nbsp;" + "Field " + link + diffOutput.id_.substring(fieldIdx + 7) + "</a><br>");
+                            }
+                        } //if (methodIdx != -1)
+                    } //if (ctorIdx != -1)
+                } //diffOutput.className_ != null
+            }
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + fullDiffFileName);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+        closeDiffFile();
+    }
+
+    /** 
+     * Emit the HTML footer and close the diff file. 
+     */
+    public static void closeDiffFile() { 
+        if (diffFile != null) {
+            // Write the HTML footer
+            diffFile.println();
+            diffFile.println("</BODY>");
+            diffFile.println("</HTML>");
+            diffFile.close();
+        }
+    }
+
+    /** 
+     * Current file where documentation differences are written as colored
+     * differences.
+     */
+    public static PrintWriter diffFile = null;
+
+    /** 
+     * Base name of the current file where documentation differences are 
+     * written as colored differences.
+     */
+    public static String diffFileName = "docdiffs_";
+
+    /** 
+     * The name of the current package, used to create diffFileName.
+     */
+    private static String currPkgName = null;
+
+    /** 
+     * If set, then do not generate colored diffs for documentation. 
+     * Default is true.
+     */
+    public static boolean noDocDiffs = true;
+
+    /** 
+     * Define the type of emphasis for deleted words.
+     * 0 strikes the words through.
+     * 1 outlines the words in light grey.
+     */
+    public static int deleteEffect = 0;
+
+    /** 
+     * Define the type of emphasis for inserted words.
+     * 0 colors the words red.
+     * 1 outlines the words in yellow, like a highlighter.
+     */
+    public static int insertEffect = 1;
+
+    /** 
+     * For each package and class, the first DiffOutput is added to
+     * this hash table. Used when generating navigation bars.
+     */
+    public static Hashtable firstDiffOutput = new Hashtable();
+
+    /** 
+     * If set, then show changes in implementation-related modifiers such as
+     * native and synchronized. For more information, see 
+     * http://java.sun.com/j2se/1.4.1/docs/tooldocs/solaris/javadoc.html#generatedapideclarations
+     */
+    public static boolean showAllChanges = false;
+
+    /** The list of documentation differences. */
+    private static List docDiffs = new ArrayList(); // DiffOutput[]
+        
+    /** Set to enable increased logging verbosity for debugging. */
+    private static boolean trace = false;
+        
+}  
diff --git a/src/jdiff/DiffMyers.java b/src/jdiff/DiffMyers.java
new file mode 100755
index 0000000..2e4683a
--- /dev/null
+++ b/src/jdiff/DiffMyers.java
@@ -0,0 +1,850 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** A class to compare vectors of objects.  The result of comparison
+    is a list of <code>change</code> objects which form an
+    edit script.  The objects compared are traditionally lines
+    of text from two files.  Comparison options such as "ignore
+    whitespace" are implemented by modifying the <code>equals</code>
+    and <code>hashcode</code> methods for the objects compared.
+<p>
+   The basic algorithm is described in: </br>
+   "An O(ND) Difference Algorithm and its Variations", Eugene Myers,
+   Algorithmica Vol. 1 No. 2, 1986, p 251.  
+<p>
+   This class outputs different results from GNU diff 1.15 on some
+   inputs.  Our results are actually better (smaller change list, smaller
+   total size of changes), but it would be nice to know why.  Perhaps
+   there is a memory overwrite bug in GNU diff 1.15.
+
+  @author Stuart D. Gathman, translated from GNU diff 1.15
+    Copyright (C) 2000  Business Management Systems, Inc.
+<p>
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 1, or (at your option)
+    any later version.
+<p>
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+<p>
+    You should have received a copy of the <a href=COPYING.txt>
+    GNU General Public License</a>
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+
+public class DiffMyers
+{
+
+  /** Prepare to find differences between two arrays.  Each element of
+      the arrays is translated to an "equivalence number" based on
+      the result of <code>equals</code>.  The original Object arrays
+      are no longer needed for computing the differences.  They will
+      be needed again later to print the results of the comparison as
+      an edit script, if desired.
+   */
+  public DiffMyers(Object[] a,Object[] b)
+  {
+    Hashtable h = new Hashtable(a.length + b.length);
+    filevec[0] = new file_data(a,h);
+    filevec[1] = new file_data(b,h);
+  }
+
+  /** 1 more than the maximum equivalence value used for this or its
+     sibling file. */
+  private int equiv_max = 1;
+
+  /** When set to true, the comparison uses a heuristic to speed it up.
+    With this heuristic, for files with a constant small density
+    of changes, the algorithm is linear in the file size.  */
+  public boolean heuristic = false;
+
+  /** When set to true, the algorithm returns a guarranteed minimal
+      set of changes.  This makes things slower, sometimes much slower. */
+  public boolean no_discards = false;
+
+  private int[] xvec, yvec;        /* Vectors being compared. */
+  private int[] fdiag;                /* Vector, indexed by diagonal, containing
+                                   the X coordinate of the point furthest
+                                   along the given diagonal in the forward
+                                   search of the edit matrix. */
+  private int[] bdiag;                /* Vector, indexed by diagonal, containing
+                                   the X coordinate of the point furthest
+                                   along the given diagonal in the backward
+                                   search of the edit matrix. */
+  private int fdiagoff, bdiagoff;
+  private final file_data[] filevec = new file_data[2];
+  private int cost;
+
+  /** Find the midpoint of the shortest edit script for a specified
+     portion of the two files.
+
+     We scan from the beginnings of the files, and simultaneously from the ends,
+     doing a breadth-first search through the space of edit-sequence.
+     When the two searches meet, we have found the midpoint of the shortest
+     edit sequence.
+
+     The value returned is the number of the diagonal on which the midpoint lies.
+     The diagonal number equals the number of inserted lines minus the number
+     of deleted lines (counting only lines before the midpoint).
+     The edit cost is stored into COST; this is the total number of
+     lines inserted or deleted (counting only lines before the midpoint).
+
+     This function assumes that the first lines of the specified portions
+     of the two files do not match, and likewise that the last lines do not
+     match.  The caller must trim matching lines from the beginning and end
+     of the portions it is going to specify.
+
+     Note that if we return the "wrong" diagonal value, or if
+     the value of bdiag at that diagonal is "wrong",
+     the worst this can do is cause suboptimal diff output.
+     It cannot cause incorrect diff output.  */
+
+  private int diag (int xoff, int xlim, int yoff, int ylim)
+  {
+    final int[] fd = fdiag;        // Give the compiler a chance.
+    final int[] bd = bdiag;        // Additional help for the compiler.
+    final int[] xv = xvec;                // Still more help for the compiler.
+    final int[] yv = yvec;                // And more and more . . .
+    final int dmin = xoff - ylim;        // Minimum valid diagonal.
+    final int dmax = xlim - yoff;        // Maximum valid diagonal.
+    final int fmid = xoff - yoff;        // Center diagonal of top-down search.
+    final int bmid = xlim - ylim;        // Center diagonal of bottom-up search.
+    int fmin = fmid, fmax = fmid;        // Limits of top-down search.
+    int bmin = bmid, bmax = bmid;        // Limits of bottom-up search.
+    /* True if southeast corner is on an odd
+                                     diagonal with respect to the northwest. */
+    final boolean odd = (fmid - bmid & 1) != 0;        
+
+    fd[fdiagoff + fmid] = xoff;
+    bd[bdiagoff + bmid] = xlim;
+
+    for (int c = 1;; ++c)
+      {
+        int d;                        /* Active diagonal. */
+        boolean big_snake = false;
+
+        /* Extend the top-down search by an edit step in each diagonal. */
+        if (fmin > dmin)
+          fd[fdiagoff + --fmin - 1] = -1;
+        else
+          ++fmin;
+        if (fmax < dmax)
+          fd[fdiagoff + ++fmax + 1] = -1;
+        else
+          --fmax;
+        for (d = fmax; d >= fmin; d -= 2)
+          {
+            int x, y, oldx, tlo = fd[fdiagoff + d - 1], thi = fd[fdiagoff + d + 1];
+
+            if (tlo >= thi)
+              x = tlo + 1;
+            else
+              x = thi;
+            oldx = x;
+            y = x - d;
+            while (x < xlim && y < ylim && xv[x] == yv[y]) {
+              ++x; ++y;
+            }
+            if (x - oldx > 20)
+              big_snake = true;
+            fd[fdiagoff + d] = x;
+            if (odd && bmin <= d && d <= bmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
+              {
+                cost = 2 * c - 1;
+                return d;
+              }
+          }
+
+        /* Similar extend the bottom-up search. */
+        if (bmin > dmin)
+          bd[bdiagoff + --bmin - 1] = Integer.MAX_VALUE;
+        else
+          ++bmin;
+        if (bmax < dmax)
+          bd[bdiagoff + ++bmax + 1] = Integer.MAX_VALUE;
+        else
+          --bmax;
+        for (d = bmax; d >= bmin; d -= 2)
+          {
+            int x, y, oldx, tlo = bd[bdiagoff + d - 1], thi = bd[bdiagoff + d + 1];
+
+            if (tlo < thi)
+              x = tlo;
+            else
+              x = thi - 1;
+            oldx = x;
+            y = x - d;
+            while (x > xoff && y > yoff && xv[x - 1] == yv[y - 1]) {
+              --x; --y;
+            }
+            if (oldx - x > 20)
+              big_snake = true;
+            bd[bdiagoff + d] = x;
+            if (!odd && fmin <= d && d <= fmax && bd[bdiagoff + d] <= fd[fdiagoff + d])
+              {
+                cost = 2 * c;
+                return d;
+              }
+          }
+
+        /* Heuristic: check occasionally for a diagonal that has made
+           lots of progress compared with the edit distance.
+           If we have any such, find the one that has made the most
+           progress and return it as if it had succeeded.
+
+           With this heuristic, for files with a constant small density
+           of changes, the algorithm is linear in the file size.  */
+
+        if (c > 200 && big_snake && heuristic)
+          {
+            int best = 0;
+            int bestpos = -1;
+
+            for (d = fmax; d >= fmin; d -= 2)
+              {
+                int dd = d - fmid;
+                if ((fd[fdiagoff + d] - xoff)*2 - dd > 12 * (c + (dd > 0 ? dd : -dd)))
+                  {
+                    if (fd[fdiagoff + d] * 2 - dd > best
+                        && fd[fdiagoff + d] - xoff > 20
+                        && fd[fdiagoff + d] - d - yoff > 20)
+                      {
+                        int k;
+                        int x = fd[fdiagoff + d];
+
+                        /* We have a good enough best diagonal;
+                           now insist that it end with a significant snake.  */
+                        for (k = 1; k <= 20; k++)
+                          if (xvec[x - k] != yvec[x - d - k])
+                            break;
+
+                        if (k == 21)
+                          {
+                            best = fd[fdiagoff + d] * 2 - dd;
+                            bestpos = d;
+                          }
+                      }
+                  }
+              }
+            if (best > 0)
+              {
+                cost = 2 * c - 1;
+                return bestpos;
+              }
+
+            best = 0;
+            for (d = bmax; d >= bmin; d -= 2)
+              {
+                int dd = d - bmid;
+                if ((xlim - bd[bdiagoff + d])*2 + dd > 12 * (c + (dd > 0 ? dd : -dd)))
+                  {
+                    if ((xlim - bd[bdiagoff + d]) * 2 + dd > best
+                        && xlim - bd[bdiagoff + d] > 20
+                        && ylim - (bd[bdiagoff + d] - d) > 20)
+                      {
+                        /* We have a good enough best diagonal;
+                           now insist that it end with a significant snake.  */
+                        int k;
+                        int x = bd[bdiagoff + d];
+
+                        for (k = 0; k < 20; k++)
+                          if (xvec[x + k] != yvec[x - d + k])
+                            break;
+                        if (k == 20)
+                          {
+                            best = (xlim - bd[bdiagoff + d]) * 2 + dd;
+                            bestpos = d;
+                          }
+                      }
+                  }
+              }
+            if (best > 0)
+              {
+                cost = 2 * c - 1;
+                return bestpos;
+              }
+          }
+      }
+  }
+
+  /** Compare in detail contiguous subsequences of the two files
+     which are known, as a whole, to match each other.
+
+     The results are recorded in the vectors filevec[N].changed_flag, by
+     storing a 1 in the element for each line that is an insertion or deletion.
+
+     The subsequence of file 0 is [XOFF, XLIM) and likewise for file 1.
+
+     Note that XLIM, YLIM are exclusive bounds.
+     All line numbers are origin-0 and discarded lines are not counted.  */
+
+  private void compareseq (int xoff, int xlim, int yoff, int ylim) {
+    /* Slide down the bottom initial diagonal. */
+    while (xoff < xlim && yoff < ylim && xvec[xoff] == yvec[yoff]) {
+      ++xoff; ++yoff;
+    }
+    /* Slide up the top initial diagonal. */
+    while (xlim > xoff && ylim > yoff && xvec[xlim - 1] == yvec[ylim - 1]) {
+      --xlim; --ylim;
+    }
+    
+    /* Handle simple cases. */
+    if (xoff == xlim)
+      while (yoff < ylim)
+        filevec[1].changed_flag[1+filevec[1].realindexes[yoff++]] = true;
+    else if (yoff == ylim)
+      while (xoff < xlim)
+        filevec[0].changed_flag[1+filevec[0].realindexes[xoff++]] = true;
+    else
+      {
+        /* Find a point of correspondence in the middle of the files.  */
+
+        int d = diag (xoff, xlim, yoff, ylim);
+        int c = cost;
+        int f = fdiag[fdiagoff + d];
+        int b = bdiag[bdiagoff + d];
+
+        if (c == 1)
+          {
+            /* This should be impossible, because it implies that
+               one of the two subsequences is empty,
+               and that case was handled above without calling `diag'.
+               Let's verify that this is true.  */
+            throw new IllegalArgumentException("Empty subsequence");
+          }
+        else
+          {
+            /* Use that point to split this problem into two subproblems.  */
+            compareseq (xoff, b, yoff, b - d);
+            /* This used to use f instead of b,
+               but that is incorrect!
+               It is not necessarily the case that diagonal d
+               has a snake from b to f.  */
+            compareseq (b, xlim, b - d, ylim);
+          }
+      }
+  }
+
+  /** Discard lines from one file that have no matches in the other file.
+   */
+
+  private void discard_confusing_lines() {
+    filevec[0].discard_confusing_lines(filevec[1]);
+    filevec[1].discard_confusing_lines(filevec[0]);
+  }
+
+  private boolean inhibit = false;
+
+  /** Adjust inserts/deletes of blank lines to join changes
+     as much as possible.
+   */
+
+  private void shift_boundaries() {
+    if (inhibit)
+      return;
+    filevec[0].shift_boundaries(filevec[1]);
+    filevec[1].shift_boundaries(filevec[0]);
+  }
+
+  /** Scan the tables of which lines are inserted and deleted,
+     producing an edit script in reverse order.  */
+
+  private change build_reverse_script() {
+    change script = null;
+    final boolean[] changed0 = filevec[0].changed_flag;
+    final boolean[] changed1 = filevec[1].changed_flag;
+    final int len0 = filevec[0].buffered_lines;
+    final int len1 = filevec[1].buffered_lines;
+
+    /* Note that changedN[len0] does exist, and contains 0.  */
+
+    int i0 = 0, i1 = 0;
+
+    while (i0 < len0 || i1 < len1)
+      {
+        if (changed0[1+i0] || changed1[1+i1])
+          {
+            int line0 = i0, line1 = i1;
+
+            /* Find # lines changed here in each file.  */
+            while (changed0[1+i0]) ++i0;
+            while (changed1[1+i1]) ++i1;
+
+            /* Record this change.  */
+            script = new change(line0, line1, i0 - line0, i1 - line1, script);
+          }
+
+        /* We have reached lines in the two files that match each other.  */
+        i0++; i1++;
+      }
+
+    return script;
+  }
+
+  /** Scan the tables of which lines are inserted and deleted,
+     producing an edit script in forward order.  */
+
+  private change build_script() {
+    change script = null;
+    final boolean[] changed0 = filevec[0].changed_flag;
+    final boolean[] changed1 = filevec[1].changed_flag;
+    final int len0 = filevec[0].buffered_lines;
+    final int len1 = filevec[1].buffered_lines;
+    int i0 = len0, i1 = len1;
+
+    /* Note that changedN[-1] does exist, and contains 0.  */
+
+    while (i0 >= 0 || i1 >= 0)
+      {
+        if (changed0[i0] || changed1[i1])
+          {
+            int line0 = i0, line1 = i1;
+
+            /* Find # lines changed here in each file.  */
+            while (changed0[i0]) --i0;
+            while (changed1[i1]) --i1;
+
+            /* Record this change.  */
+            script = new change(i0, i1, line0 - i0, line1 - i1, script);
+          }
+
+        /* We have reached lines in the two files that match each other.  */
+        i0--; i1--;
+      }
+
+    return script;
+  }
+
+  /* Report the differences of two files.  DEPTH is the current directory
+     depth. */
+  public change diff_2(final boolean reverse) {
+
+    /* Some lines are obviously insertions or deletions
+       because they don't match anything.  Detect them now,
+       and avoid even thinking about them in the main comparison algorithm.  */
+
+    discard_confusing_lines ();
+
+    /* Now do the main comparison algorithm, considering just the
+       undiscarded lines.  */
+
+    xvec = filevec[0].undiscarded;
+    yvec = filevec[1].undiscarded;
+
+    int diags =
+      filevec[0].nondiscarded_lines + filevec[1].nondiscarded_lines + 3;
+    fdiag = new int[diags];
+    fdiagoff = filevec[1].nondiscarded_lines + 1;
+    bdiag = new int[diags];
+    bdiagoff = filevec[1].nondiscarded_lines + 1;
+
+    compareseq (0, filevec[0].nondiscarded_lines,
+                0, filevec[1].nondiscarded_lines);
+    fdiag = null;
+    bdiag = null;
+
+    /* Modify the results slightly to make them prettier
+       in cases where that can validly be done.  */
+
+    shift_boundaries ();
+
+    /* Get the results of comparison in the form of a chain
+       of `struct change's -- an edit script.  */
+
+    if (reverse)
+      return build_reverse_script();
+    else
+      return build_script();
+  }
+
+  /** The result of comparison is an "edit script": a chain of change objects.
+     Each change represents one place where some lines are deleted
+     and some are inserted.
+     
+     LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+     DELETED is the number of lines deleted here from file 0.
+     INSERTED is the number of lines inserted here in file 1.
+
+     If DELETED is 0 then LINE0 is the number of the line before
+     which the insertion was done; vice versa for INSERTED and LINE1.  */
+
+  public static class change {
+    /** Previous or next edit command. */
+    public change link;                
+    /** # lines of file 1 changed here.  */
+    public int inserted;        
+    /** # lines of file 0 changed here.  */
+    public int deleted;                
+    /** Line number of 1st deleted line.  */
+    public final int line0;                
+    /** Line number of 1st inserted line.  */
+    public final int line1;                
+
+    /** Cons an additional entry onto the front of an edit script OLD.
+       LINE0 and LINE1 are the first affected lines in the two files (origin 0).
+       DELETED is the number of lines deleted here from file 0.
+       INSERTED is the number of lines inserted here in file 1.
+
+       If DELETED is 0 then LINE0 is the number of the line before
+       which the insertion was done; vice versa for INSERTED and LINE1.  */
+    change(int line0, int line1, int deleted, int inserted, change old) {
+      this.line0 = line0;
+      this.line1 = line1;
+      this.inserted = inserted;
+      this.deleted = deleted;
+      this.link = old;
+      //System.err.println(line0+","+line1+","+inserted+","+deleted);
+    }
+  }
+
+  /** Data on one input file being compared.  
+   */
+
+  class file_data {
+
+    /** Allocate changed array for the results of comparison.  */
+    void clear() {
+      /* Allocate a flag for each line of each file, saying whether that line
+         is an insertion or deletion.
+         Allocate an extra element, always zero, at each end of each vector.
+       */
+      changed_flag = new boolean[buffered_lines + 2];
+    }
+
+    /** Return equiv_count[I] as the number of lines in this file
+       that fall in equivalence class I.
+         @return the array of equivalence class counts.
+     */
+    int[] equivCount() {
+      int[] equiv_count = new int[equiv_max];
+      for (int i = 0; i < buffered_lines; ++i)
+        ++equiv_count[equivs[i]];
+      return equiv_count;
+    }
+
+    /** Discard lines that have no matches in another file.
+
+       A line which is discarded will not be considered by the actual
+       comparison algorithm; it will be as if that line were not in the file.
+       The file's `realindexes' table maps virtual line numbers
+       (which don't count the discarded lines) into real line numbers;
+       this is how the actual comparison algorithm produces results
+       that are comprehensible when the discarded lines are counted.
+<p>
+       When we discard a line, we also mark it as a deletion or insertion
+       so that it will be printed in the output.  
+      @param f the other file   
+     */
+    void discard_confusing_lines(file_data f) {
+      clear();
+    /* Set up table of which lines are going to be discarded. */
+      final byte[] discarded = discardable(f.equivCount());
+
+    /* Don't really discard the provisional lines except when they occur
+       in a run of discardables, with nonprovisionals at the beginning
+       and end.  */
+      filterDiscards(discarded);
+
+    /* Actually discard the lines. */
+      discard(discarded);
+    }
+
+    /** Mark to be discarded each line that matches no line of another file.
+       If a line matches many lines, mark it as provisionally discardable.  
+       @see equivCount()
+       @param counts The count of each equivalence number for the other file.
+       @return 0=nondiscardable, 1=discardable or 2=provisionally discardable
+               for each line
+     */
+
+    private byte[] discardable(final int[] counts) {
+      final int end = buffered_lines;
+      final byte[] discards = new byte[end];
+      final int[] equivs = this.equivs;
+      int many = 5;
+      int tem = end / 64;
+
+      /* Multiply MANY by approximate square root of number of lines.
+         That is the threshold for provisionally discardable lines.  */
+      while ((tem = tem >> 2) > 0)
+        many *= 2;
+
+      for (int i = 0; i < end; i++)
+        {
+          int nmatch;
+          if (equivs[i] == 0)
+            continue;
+          nmatch = counts[equivs[i]];
+          if (nmatch == 0)
+            discards[i] = 1;
+          else if (nmatch > many)
+            discards[i] = 2;
+        }
+      return discards;
+    }
+
+    /** Don't really discard the provisional lines except when they occur
+       in a run of discardables, with nonprovisionals at the beginning
+       and end.  */
+
+    private void filterDiscards(final byte[] discards) {
+        final int end = buffered_lines;
+
+        for (int i = 0; i < end; i++)
+          {
+            /* Cancel provisional discards not in middle of run of discards.  */
+            if (discards[i] == 2)
+              discards[i] = 0;
+            else if (discards[i] != 0)
+              {
+                /* We have found a nonprovisional discard.  */
+                int j;
+                int length;
+                int provisional = 0;
+
+                /* Find end of this run of discardable lines.
+                   Count how many are provisionally discardable.  */
+                for (j = i; j < end; j++)
+                  {
+                    if (discards[j] == 0)
+                      break;
+                    if (discards[j] == 2)
+                      ++provisional;
+                  }
+
+                /* Cancel provisional discards at end, and shrink the run.  */
+                while (j > i && discards[j - 1] == 2) {
+                  discards[--j] = 0; --provisional;
+                }
+
+                /* Now we have the length of a run of discardable lines
+                   whose first and last are not provisional.  */
+                length = j - i;
+
+                /* If 1/4 of the lines in the run are provisional,
+                   cancel discarding of all provisional lines in the run.  */
+                if (provisional * 4 > length)
+                  {
+                    while (j > i)
+                      if (discards[--j] == 2)
+                        discards[j] = 0;
+                  }
+                else
+                  {
+                    int consec;
+                    int minimum = 1;
+                    int tem = length / 4;
+
+                    /* MINIMUM is approximate square root of LENGTH/4.
+                       A subrun of two or more provisionals can stand
+                       when LENGTH is at least 16.
+                       A subrun of 4 or more can stand when LENGTH >= 64.  */
+                    while ((tem = tem >> 2) > 0)
+                      minimum *= 2;
+                    minimum++;
+
+                    /* Cancel any subrun of MINIMUM or more provisionals
+                       within the larger run.  */
+                    for (j = 0, consec = 0; j < length; j++)
+                      if (discards[i + j] != 2)
+                        consec = 0;
+                      else if (minimum == ++consec)
+                        /* Back up to start of subrun, to cancel it all.  */
+                        j -= consec;
+                      else if (minimum < consec)
+                        discards[i + j] = 0;
+
+                    /* Scan from beginning of run
+                       until we find 3 or more nonprovisionals in a row
+                       or until the first nonprovisional at least 8 lines in.
+                       Until that point, cancel any provisionals.  */
+                    for (j = 0, consec = 0; j < length; j++)
+                      {
+                        if (j >= 8 && discards[i + j] == 1)
+                          break;
+                        if (discards[i + j] == 2) {
+                          consec = 0; discards[i + j] = 0;
+                        }
+                        else if (discards[i + j] == 0)
+                          consec = 0;
+                        else
+                          consec++;
+                        if (consec == 3)
+                          break;
+                      }
+
+                    /* I advances to the last line of the run.  */
+                    i += length - 1;
+
+                    /* Same thing, from end.  */
+                    for (j = 0, consec = 0; j < length; j++)
+                      {
+                        if (j >= 8 && discards[i - j] == 1)
+                          break;
+                        if (discards[i - j] == 2) {
+                          consec = 0; discards[i - j] = 0;
+                        }
+                        else if (discards[i - j] == 0)
+                          consec = 0;
+                        else
+                          consec++;
+                        if (consec == 3)
+                          break;
+                      }
+                  }
+              }
+          }
+      }
+
+    /** Actually discard the lines.
+      @param discards flags lines to be discarded
+     */
+    private void discard(final byte[] discards) {
+      final int end = buffered_lines;
+      int j = 0;
+      for (int i = 0; i < end; ++i)
+        if (no_discards || discards[i] == 0)
+          {
+            undiscarded[j] = equivs[i];
+            realindexes[j++] = i;
+          }
+        else
+          changed_flag[1+i] = true;
+      nondiscarded_lines = j;
+    }
+
+    file_data(Object[] data,Hashtable h) {
+      buffered_lines = data.length;
+
+      equivs = new int[buffered_lines]; 
+      undiscarded = new int[buffered_lines];
+      realindexes = new int[buffered_lines];
+
+      for (int i = 0; i < data.length; ++i) {
+        Integer ir = (Integer)h.get(data[i]);
+        if (ir == null)
+          h.put(data[i],new Integer(equivs[i] = equiv_max++));
+        else
+          equivs[i] = ir.intValue();
+      }
+    }
+
+    /** Adjust inserts/deletes of blank lines to join changes
+       as much as possible.
+
+       We do something when a run of changed lines include a blank
+       line at one end and have an excluded blank line at the other.
+       We are free to choose which blank line is included.
+       `compareseq' always chooses the one at the beginning,
+       but usually it is cleaner to consider the following blank line
+       to be the "change".  The only exception is if the preceding blank line
+       would join this change to other changes.  
+      @param f the file being compared against
+    */
+
+    void shift_boundaries(file_data f) {
+      final boolean[] changed = changed_flag;
+      final boolean[] other_changed = f.changed_flag;
+      int i = 0;
+      int j = 0;
+      int i_end = buffered_lines;
+      int preceding = -1;
+      int other_preceding = -1;
+
+      for (;;)
+        {
+          int start, end, other_start;
+
+          /* Scan forwards to find beginning of another run of changes.
+             Also keep track of the corresponding point in the other file.  */
+
+          while (i < i_end && !changed[1+i])
+            {
+              while (other_changed[1+j++])
+                /* Non-corresponding lines in the other file
+                   will count as the preceding batch of changes.  */
+                other_preceding = j;
+              i++;
+            }
+
+          if (i == i_end)
+            break;
+
+          start = i;
+          other_start = j;
+
+          for (;;)
+            {
+              /* Now find the end of this run of changes.  */
+
+              while (i < i_end && changed[1+i]) i++;
+              end = i;
+
+              /* If the first changed line matches the following unchanged one,
+                 and this run does not follow right after a previous run,
+                 and there are no lines deleted from the other file here,
+                 then classify the first changed line as unchanged
+                 and the following line as changed in its place.  */
+
+              /* You might ask, how could this run follow right after another?
+                 Only because the previous run was shifted here.  */
+
+              if (end != i_end
+                  && equivs[start] == equivs[end]
+                  && !other_changed[1+j]
+                  && end != i_end
+                  && !((preceding >= 0 && start == preceding)
+                       || (other_preceding >= 0
+                           && other_start == other_preceding)))
+                {
+                  changed[1+end++] = true;
+                  changed[1+start++] = false;
+                  ++i;
+                  /* Since one line-that-matches is now before this run
+                     instead of after, we must advance in the other file
+                     to keep in synch.  */
+                  ++j;
+                }
+              else
+                break;
+            }
+
+          preceding = i;
+          other_preceding = j;
+        }
+    }
+
+    /** Number of elements (lines) in this file. */
+    final int buffered_lines;
+
+    /** Vector, indexed by line number, containing an equivalence code for
+       each line.  It is this vector that is actually compared with that
+       of another file to generate differences. */
+    private final int[]            equivs;
+
+    /** Vector, like the previous one except that
+       the elements for discarded lines have been squeezed out.  */
+    final int[]           undiscarded;
+
+    /** Vector mapping virtual line numbers (not counting discarded lines)
+       to real ones (counting those lines).  Both are origin-0.  */
+    final int[]           realindexes;
+
+    /** Total number of nondiscarded lines. */
+    int                    nondiscarded_lines;
+
+    /** Array, indexed by real origin-1 line number,
+       containing true for a line that is an insertion or a deletion.
+       The results of comparison are stored here.  */
+    boolean[]            changed_flag;
+
+  }    
+    
+}
diff --git a/src/jdiff/DiffOutput.java b/src/jdiff/DiffOutput.java
new file mode 100755
index 0000000..9b2c13c
--- /dev/null
+++ b/src/jdiff/DiffOutput.java
@@ -0,0 +1,54 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent a single documentation difference.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class DiffOutput implements Comparable {
+
+    /** The package name for this difference. */
+    public String pkgName_ = null;
+
+    /** The class name for this difference, may be null. */
+    public String className_ = null;
+
+    /** The HTML named anchor identifier for this difference. */
+    public String id_ = null;
+
+    /** The title for this difference. */
+    public String title_ = null;
+
+    /** The text for this difference, with deleted and added words marked. */
+    public String text_ = null;
+
+    /** Constructor. */
+    public DiffOutput(String pkgName, String className, String id, 
+                      String title, String text) {
+        pkgName_ = pkgName;
+        className_ = className;
+        id_ = id;
+        title_ = title;
+        text_ = text;
+    }
+
+    /** 
+     * Compare two DiffOutput objects, so they will appear in the correct
+     * package. 
+     */
+    public int compareTo(Object o) {
+        DiffOutput oDiffOutput = (DiffOutput)o;
+        int comp = pkgName_.compareTo(oDiffOutput.pkgName_);
+        if (comp != 0)
+            return comp;
+        // Always put the package-level output at the top - not yet working
+//        if (id_.compareTo("package") == 0)
+//            return -1;
+        return id_.compareTo(oDiffOutput.id_);
+    }    
+        
+}  
diff --git a/src/jdiff/FieldAPI.java b/src/jdiff/FieldAPI.java
new file mode 100755
index 0000000..151e729
--- /dev/null
+++ b/src/jdiff/FieldAPI.java
@@ -0,0 +1,107 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent a field, analogous to FieldDoc in the 
+ * Javadoc doclet API. 
+ * 
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this field.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class FieldAPI implements Comparable {
+    /** Name of the field. */
+    public String name_;
+
+    /** Type of the field. */
+    public String type_;
+
+    /** 
+     * The fully qualified name of the class or interface this field is
+     * inherited from. If this is null, then the field is defined locally 
+     * in this class or interface.
+     */
+    public String inheritedFrom_ = null;
+
+    /** Set if this field is transient. */
+    public boolean isTransient_ = false;
+
+    /** Set if this field is volatile. */
+    public boolean isVolatile_ = false;
+
+    /** If non-null, this is the value of this field. */
+    public String value_ = null;
+
+    /** Modifiers for this class. */
+    public Modifiers modifiers_;
+
+    /** The doc block, default is null. */
+    public String doc_ = null;
+
+    /** Constructor. */
+    public FieldAPI(String name, String type, 
+                    boolean isTransient, boolean isVolatile, 
+                    String value, Modifiers modifiers) {
+        name_ = name;
+        type_ = type;
+        isTransient_ = isTransient;
+        isVolatile_ = isVolatile;
+        value_ = value;
+        modifiers_ = modifiers;
+    }
+
+    /** Copy constructor. */
+    public FieldAPI(FieldAPI f) {
+        name_ = f.name_;
+        type_ = f.type_;
+        inheritedFrom_ = f.inheritedFrom_;
+        isTransient_ = f.isTransient_;
+        isVolatile_ = f.isVolatile_;
+        value_ = f.value_;
+        modifiers_ = f.modifiers_; // Note: shallow copy
+        doc_ = f.doc_;
+    }
+
+    /** Compare two FieldAPI objects, including name, type and modifiers. */
+    public int compareTo(Object o) {
+        FieldAPI oFieldAPI = (FieldAPI)o;
+        int comp = name_.compareTo(oFieldAPI.name_);
+        if (comp != 0)
+            return comp;
+        comp = type_.compareTo(oFieldAPI.type_);
+        if (comp != 0)
+            return comp;
+        if (APIComparator.changedInheritance(inheritedFrom_, oFieldAPI.inheritedFrom_) != 0)
+            return -1;
+        if (isTransient_ != oFieldAPI.isTransient_) {
+            return -1;
+        }
+        if (isVolatile_ != oFieldAPI.isVolatile_) {
+            return -1;
+        }
+        if (value_ != null && oFieldAPI.value_ != null) {
+            comp = value_.compareTo(oFieldAPI.value_);
+            if (comp != 0)
+                return comp;
+        }
+        comp = modifiers_.compareTo(oFieldAPI.modifiers_);
+        if (comp != 0)
+            return comp;
+        if (APIComparator.docChanged(doc_, oFieldAPI.doc_))
+            return -1;
+        return 0;
+    }
+  
+    /** 
+     * Tests two fields, using just the field name, used by indexOf().
+     */
+    public boolean equals(Object o) {
+        if (name_.compareTo(((FieldAPI)o).name_) == 0)
+            return true;
+        return false;
+    }
+}  
diff --git a/src/jdiff/HTMLFiles.java b/src/jdiff/HTMLFiles.java
new file mode 100755
index 0000000..a141e6c
--- /dev/null
+++ b/src/jdiff/HTMLFiles.java
@@ -0,0 +1,368 @@
+package jdiff;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * Emit HTML files for the supporting infrastructure for the HTML report.
+ * Examples are stylesheets, help files, frame files.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class HTMLFiles {
+
+    /** Constructor. */
+    public HTMLFiles(HTMLReportGenerator h) {
+        h_ = h;
+    }   
+
+    /** The HTMLReportGenerator instance used to write HTML. */
+    private HTMLReportGenerator h_ = null;
+
+    /** 
+     * Emit the top-level changes.html frames file where everything starts.
+     */
+    public void emitTopLevelFile(String tln,
+                                 APIDiff apiDiff) {
+        try {
+            FileOutputStream fos = new FileOutputStream(tln);
+            h_.reportFile = new PrintWriter(fos);
+            // Write out the HTML header
+            h_.writeStartHTMLHeaderWithDate();
+            // Write out the title
+            String oldAPIName = "Old API";
+            if (apiDiff.oldAPIName_ != null)
+                oldAPIName = apiDiff.oldAPIName_;
+            String newAPIName = "New API";
+            if (apiDiff.newAPIName_ != null)
+                newAPIName = apiDiff.newAPIName_;
+            if (h_.windowTitle == null) 
+                h_.writeHTMLTitle("API Differences between " + oldAPIName + " and " + newAPIName);
+            else
+                h_.writeHTMLTitle(h_.windowTitle);
+            // Note that the stylesheet is in the same directory
+            h_.writeStyleSheetRef(true);
+            h_.writeText("</HEAD>");
+            // Note that the top-level frame file doesn't have the BODY tag
+            h_.writeText("<FRAMESET COLS=\"20%,80%\">");
+            h_.writeText("<frameset rows=\"196,**\">");
+
+            // Convert filenames to web links
+            String tlfLink = h_.reportFileName + "/jdiff_topleftframe" + h_.reportFileExt;
+            String allDiffsLink = h_.reportFileName + "/alldiffs_index_all" + h_.reportFileExt;
+            String csnLink = h_.reportFileName + "/" + h_.reportFileName + "-summary" + h_.reportFileExt;
+
+            h_.writeText("    <FRAME SRC=\"" + tlfLink + "\" SCROLLING=\"no\" NAME=\"topleftframe\">");
+            h_.writeText("    <FRAME SRC=\"" + allDiffsLink + "\" SCROLLING=\"auto\" NAME=\"bottomleftframe\">");
+            h_.writeText("  </FRAMESET>");
+            h_.writeText("  <FRAME SRC=\"" + csnLink + "\" SCROLLING=\"auto\" NAME=\"rightframe\">");
+            h_.writeText("</FRAMESET>");
+            h_.writeText("<NOFRAMES>");
+            h_.writeText("<H2>");
+            h_.writeText("Frame Alert");
+            h_.writeText("</H2>\n");
+            h_.writeText("<P>");
+            h_.writeText("This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client.");
+            h_.writeText("<BR>");
+            h_.writeText("Link to <A HREF=\"" + csnLink + "\" target=\"_top\">Non-frame version.</A>");
+            h_.writeText("</NOFRAMES>");
+            h_.writeText("</HTML>");
+            h_.reportFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + tln);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /** Emit a top left frame with all the links to the index files. */
+    public void emitTopLeftFile(String tlf) {
+        try {
+            FileOutputStream fos = new FileOutputStream(tlf);
+            h_.reportFile = new PrintWriter(fos);
+            h_.writeStartHTMLHeader();
+            h_.writeHTMLTitle("Android Diffs Index");
+            h_.writeStyleSheetRef();
+            h_.writeText("</HEAD>");
+            h_.writeText("<BODY>");
+
+            h_.writeText("<table summary=\"Links to all index files\" BORDER=\"0\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
+            h_.writeText("<TR>");
+            h_.writeText("  <th class=\"indexHeader\" nowrap>");
+            h_.writeText("  Select a Diffs Index:</th>");
+            h_.writeText("</TR>");
+            h_.writeText("<TR>");
+            h_.writeText("  <TD><FONT CLASS=\"indexText\" size=\"-1\"><A HREF=\"alldiffs_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">All Differences</A></FONT><br></TD>");
+            h_.writeText("</TR>");
+            h_.writeText("<TR>");
+            h_.writeText("  <TD NOWRAP><FONT CLASS=\"indexText\" size=\"-1\"><A HREF=\"packages_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Package</A></FONT><br></TD>");
+            h_.writeText("</TR>");
+            h_.writeText("<TR>");
+            h_.writeText("  <TD NOWRAP><FONT CLASS=\"indexText\" size=\"-1\"><A HREF=\"classes_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Class</A></FONT><br></TD>");
+            h_.writeText("</TR>");
+            h_.writeText("<TR>");
+            h_.writeText("  <TD NOWRAP><FONT CLASS=\"indexText\" size=\"-1\"><A HREF=\"constructors_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Constructor</A></FONT><br></TD>");
+            h_.writeText("</TR>");
+            h_.writeText("<TR>");
+            h_.writeText("  <TD NOWRAP><FONT CLASS=\"indexText\" size=\"-1\"><A HREF=\"methods_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Method</A></FONT><br></TD>");
+            h_.writeText("</TR>");
+            h_.writeText("<TR>");
+            h_.writeText("  <TD NOWRAP><FONT CLASS=\"indexText\" size=\"-1\"><A HREF=\"fields_index_all" + h_.reportFileExt + "\" TARGET=\"bottomleftframe\">By Field</A></FONT><br></TD>");
+            h_.writeText("</TR>");
+            h_.writeText("</TABLE>");
+
+            h_.writeHTMLFooter();
+            h_.reportFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + tlf);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /** Emit the help file. */
+    public void emitHelp(String fullReportFileName, APIDiff apiDiff) {
+        String helpFileName = fullReportFileName + JDiff.DIR_SEP + "jdiff_help" + h_.reportFileExt;
+        try {
+            FileOutputStream fos = new FileOutputStream(helpFileName);
+            h_.reportFile = new PrintWriter(fos);
+            h_.writeStartHTMLHeader();
+            h_.writeHTMLTitle("JDiff Help");
+            h_.writeStyleSheetRef();
+            h_.writeText("</HEAD>");
+            h_.writeText("<BODY>");
+            // Write a customized navigation bar for the help page
+            h_.writeText("<!-- Start of nav bar -->");
+            h_.writeText("<TABLE summary=\"Navigation bar\" BORDER=\"0\" WIDTH=\"100%\" CELLPADDING=\"1\" CELLSPACING=\"0\">");
+            h_.writeText("<TR>");
+            h_.writeText("<TD COLSPAN=2 BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\">");
+            h_.writeText("  <TABLE summary=\"Navigation bar\" BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"3\">");
+            h_.writeText("    <TR ALIGN=\"center\" VALIGN=\"top\">");
+            // Always have a link to the Javadoc files
+            h_.writeText("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + h_.newDocPrefix + "index.html\" target=\"_top\"><FONT CLASS=\"NavBarFont1\"><B><tt>" + apiDiff.newAPIName_ + "</tt></B></FONT></A>&nbsp;</TD>");
+            h_.writeText("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
+            h_.writeText("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
+            h_.writeText("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
+            if (!Diff.noDocDiffs) {
+                h_.writeText("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
+            }
+            if (h_.doStats) {
+                h_.writeText("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</TD>");
+            }
+            h_.writeText("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Help</B></FONT>&nbsp;</TD>");
+            h_.writeText("    </TR>");
+            h_.writeText("  </TABLE>");
+            h_.writeText("</TD>");
+
+            // The right hand side title
+            h_.writeText("<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>");
+            h_.writeText("</TR>");
+            
+            // Links for frames and no frames
+            h_.writeText("<TR>");
+            h_.writeText("  <TD BGCOLOR=\"" + h_.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\"></FONT>");
+            h_.writeText("</TD>");
+            h_.writeText("  <TD BGCOLOR=\"" + h_.bgcolor + "\" CLASS=\"NavBarCell2\"><FONT SIZE=\"-2\">");
+            h_.writeText("  <A HREF=\"" + "../" + h_.reportFileName + h_.reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A>  &nbsp;");
+            h_.writeText("  &nbsp;<A HREF=\"jdiff_help" + h_.reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
+            h_.writeText("</TR>");
+            
+            h_.writeText("</TABLE>");
+            h_.writeText("<HR>");
+            h_.writeText ("<!-- End of nav bar -->");
+
+            h_.writeText("<center>");        
+            h_.writeText("<H1>JDiff Documentation</H1>");
+            h_.writeText("</center>");        
+
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("JDiff is a <a href=\"http://java.sun.com/j2se/javadoc/\" target=\"_top\">Javadoc</a> doclet which generates a report of the API differences between two versions of a product. It does not report changes in Javadoc comments, or changes in what a class or method does. ");
+            h_.writeText("This help page describes the different parts of the output from JDiff.");
+            h_.writeText("</BLOCKQUOTE>");
+
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText(" See the reference page in the <a href=\"" + JDiff.jDiffLocation + "\">source for JDiff</a> for information about how to generate a report like this one.");
+            h_.writeText("</BLOCKQUOTE>");
+
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("The indexes shown in the top-left frame help show each type of change in more detail. The index \"All Differences\" contains all the differences between the APIs, in alphabetical order. ");
+            h_.writeText("These indexes all use the same format:");
+            h_.writeText("<ul>");
+            h_.writeText("<li>Removed packages, classes, constructors, methods and fields are <strike>struck through</strike>.</li>");
+            h_.writeText("<li>Added packages, classes, constructors, methods and fields appear in <b>bold</b>.</li>");
+            h_.writeText("<li>Changed packages, classes, constructors, methods and fields appear in normal text.</li>");
+            h_.writeText("</ul>");
+            h_.writeText("</BLOCKQUOTE>");
+
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("You can always tell when you are reading a JDiff page, rather than a Javadoc page, by the color of the index bar and the color of the background. ");
+            h_.writeText("Links which take you to a Javadoc page are always in a <tt>typewriter</tt> font. ");
+            h_.writeText("Just like Javadoc, all interface names are in <i>italic</i>, and class names are not italicized. Where there are multiple entries in an index with the same name, the heading for them is also in italics, but is not a link.");
+            h_.writeText("</BLOCKQUOTE>");
+
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3><b><tt>Javadoc</tt></b></H3>");
+            h_.writeText("This is a link to the <a href=\"" + h_.newDocPrefix + "index.html\" target=\"_top\">top-level</a> Javadoc page for the new version of the product.");
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Overview</H3>");        
+            h_.writeText("The <a href=\"" + h_.reportFileName + "-summary" + 
+                      h_.reportFileExt + "\">overview</a> is the top-level summary of what was removed, added and changed between versions.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Package</H3>");        
+            h_.writeText("This is a link to the package containing the current changed class or interface.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Class</H3>");        
+            h_.writeText("This is highlighted when you are looking at the changed class or interface.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Text Changes</H3>");        
+            h_.writeText("This is a link to the top-level index of all documentation changes for the current package or class. ");        
+            h_.writeText("If it is not present, then there are no documentation changes for the current package or class. ");        
+            h_.writeText("This link can be removed entirely by not using the <code>-docchanges</code> option.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Statistics</H3>");        
+            h_.writeText("This is a link to a page which shows statistics about the changes between the two APIs.");        
+            h_.writeText("This link can be removed entirely by not using the <code>-stats</code> option.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Help</H3>");        
+            h_.writeText("A link to this Help page for JDiff.");
+            h_.writeText("</BLOCKQUOTE>");
+
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Prev/Next</H3>");        
+            h_.writeText("These links take you to the previous  and next changed package or class.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H3>Frames/No Frames</H3>");        
+            h_.writeText("These links show and hide the HTML frames. All pages are available with or without frames.");        
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeText("<BLOCKQUOTE>");
+            h_.writeText("<H2>Complex Changes</H2>");
+            h_.writeText("There are some complex changes which can occur between versions, for example, when two or more methods with the same name change simultaneously, or when a method or field is moved into or from a superclass. ");
+            h_.writeText("In these cases, the change will be seen as a removal and an addition, rather than as a change. Unexpected removals or additions are often part of one of these type of changes. ");
+            h_.writeText("</BLOCKQUOTE>");
+            
+            h_.writeHTMLFooter();
+            h_.reportFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + helpFileName);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /** Emit the CSS external stylesheet file. */
+    public void emitStylesheet() {
+        String stylesheetFileName = "stylesheet-jdiff.css";
+        if (h_.outputDir != null)
+            stylesheetFileName = h_.outputDir + JDiff.DIR_SEP + stylesheetFileName;
+        try {
+            FileOutputStream fos = new FileOutputStream(stylesheetFileName);
+            h_.reportFile = new PrintWriter(fos);
+            h_.writeText();
+            h_.writeText("/* (" + JDiff.jDiffLocation + ") */");
+            //h_.writeText("/* on " + new Date() + " */");
+            h_.writeText();
+            //h_.writeText("/* Define colors, fonts and other style attributes here to override the defaults  */");
+            //h_.writeText();
+            //h_.writeText("/* Page background color */");
+            //h_.writeText("body { background-color: " + h_.bgcolor + "; font-family: arial; }");
+            //First argument after backgroun: is for older Netscape browsers
+            //For more information, see http://css.nu/pointers/bugs.html and 
+            //http://www.richinstyle.com/bugs/netscape4.html
+            //h_.writeText("body { background: #CCFFFF url(background.gif); font-family: arial; }");
+            //h_.writeText();
+            //h_.writeText("/* Table colors */");
+            //h_.writeText(".TableHeadingColor     { background: #CCCCFF } /* Dark mauve */");
+            //h_.writeText(".TableSubHeadingColor  { background: #EEEEFF } /* Light mauve */");
+            //h_.writeText(".TableRowColor         { background: #FFFFFF } /* White */");
+            //h_.writeText();
+            //h_.writeText("/* Font used in left-hand frame lists */");
+            //h_.writeText(".FrameTitleFont   { font-size: normal; font-family: normal }");
+            //h_.writeText(".FrameHeadingFont { font-size: normal; font-family: normal }");
+            //h_.writeText(".FrameItemFont    { font-size: normal; font-family: normal }");
+            //h_.writeText();
+            //h_.writeText("/* Example of smaller, sans-serif font in frames */");
+            //h_.writeText("/* .FrameItemFont  { font-size: 10pt; font-family: Helvetica, Arial, sans-serif } */");
+            //h_.writeText();
+            //h_.writeText("/* Navigation bar fonts and colors */");
+            //h_.writeText(".NavBarCell1    { background-color:#FFFFCC;} /* Changed to yellowish to make difference from Javadoc clear */");
+            //h_.writeText(".NavBarCell1Rev { background-color:#00008B;}/* Dark Blue */");
+            //h_.writeText(".NavBarFont1    { font-family: Arial, Helvetica, sans-serif; color:#000000;}");
+            //h_.writeText(".NavBarFont1Rev { font-family: Arial, Helvetica, sans-serif; color:#FFFFFF;}");
+            //h_.writeText();
+            //h_.writeText(".NavBarCell2    { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF;}");
+            //h_.writeText(".NavBarCell3    { font-family: Arial, Helvetica, sans-serif; background-color:#FFFFFF;}");
+            //h_.writeText();
+            h_.writeText("/* ");
+            h_.writeText(" Links which become blue when hovered upon and show that they have been ");
+            h_.writeText(" visited. ");
+            h_.writeText("*/");
+	    h_.writeText(".hiddenlink {text-decoration:none;}");
+            h_.writeText("a.hiddenlink:link      {color: black; text-decoration: none}");
+            h_.writeText("a.hiddenlink:visited   {color: purple; text-decoration: none}");
+            h_.writeText("a.hiddenlink:hover     {color: blue; text-decoration: underline;}");
+            h_.writeText();
+            h_.writeText("/* ");
+            h_.writeText(" Links which become blue when hovered upon but do not show that they have ");
+            h_.writeText(" been visited. ");
+            h_.writeText("*/");
+            h_.writeText("a.staysblack:link     {color: black; text-decoration: none}");
+            h_.writeText("a.staysblack:visited  {color: black; text-decoration: none}");
+            h_.writeText("a.staysblack:hover    {color: blue; text-decoration: underline;}");
+            h_.writeText("");
+            h_.writeText("div.and-diff-id {border: 1px solid #eee;position:relative;float:right;clear:both;padding:0px;}");
+            h_.writeText("table.diffspectable {border:1px;padding:0px;margin:0px;}");
+            h_.writeText(".diffspectable tr {border:0px;padding:0px;}");
+            h_.writeText(".diffspectable td  {background-color:eee;border:0px;font-size:90%;font-weight:normal;padding:0px;padding-left:1px;padding-right:1px;text-align:center;color:777;}");
+            h_.writeText("td.diffvalueold {color:orange;background-color:white;border:0px;font-size:80%;font-style:normal;text-align:left;padding:0px;padding-left:1px;padding-right:1px;line-height:.95em;}");
+            h_.writeText("td.diffvaluenew {color:green;background-color:white;border:0px;font-size:80%;font-weight:normal;text-align:left;padding:0px;padding-left:1px;padding-right:1px;line-height:.95em;}");
+            h_.writeText("td.diffvalue {color:444;background-color:white;border:0px;font-size:80%;font-weight:normal;text-align:left;padding:0px;padding-left:1px;padding-right:1px;line-height:.95em;}");
+            h_.writeText("td.diffspec {background-color:white;border:0px;font-size:80%;font-weight:normal;padding:1px;color:444;text-align:right;padding-right:.5em;line-height:.95em;}");
+            h_.writeText("tt {font-size:11pt;font-family:monospace;}");
+            h_.writeText("code {font-size:11pt;font-family:monospace;}");
+            h_.writeText(".indexHeader {font-size:11pt;line-height:.8em;}");
+            h_.writeText(".indexText {font-size:9pt;line-height:.8em;padding-left:1em;}");
+
+            h_.writeText(".pagecontenth1 {");
+            h_.writeText("  line-height: 130%;");
+            h_.writeText("  font-size: 170%;");
+            h_.writeText("  xmargin: 0 0 0 -10px;");
+            h_.writeText("  padding: .8em 0 0;");
+            h_.writeText("  border: none;");
+            h_.writeText("  background: none;");
+            h_.writeText("}");
+            h_.writeText("");
+            h_.writeText(".pagecontenth2 {");
+            h_.writeText("  font-size: 130%;");
+            h_.writeText("  font-weight: bold;");
+            h_.writeText("  margin: 2em 0 0 -10px; ");
+            h_.writeText("  padding: 1px 3px;");
+            h_.writeText("  position: relative;");
+            h_.writeText("  border-top: 1px solid #3366CC;");
+            h_.writeText("  background-color: #e5ecf9;");
+            h_.writeText("}");
+            h_.reportFile.close();
+
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + stylesheetFileName);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+}
diff --git a/src/jdiff/HTMLIndexes.java b/src/jdiff/HTMLIndexes.java
new file mode 100755
index 0000000..d39e023
--- /dev/null
+++ b/src/jdiff/HTMLIndexes.java
@@ -0,0 +1,1097 @@
+package jdiff;
+
+import java.util.*;
+import java.io.*;
+
+/**
+ * Emit HTML indexes which appear in the bottom left frame in the report. 
+ * All indexes are links to JDiff-generated pages.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class HTMLIndexes {
+
+    /** Constructor. */
+    public HTMLIndexes(HTMLReportGenerator h) {
+        h_ = h;
+    }
+
+    /** The HTMLReportGenerator instance used to write HTML. */
+    private HTMLReportGenerator h_ = null;
+    
+    /** Emit all the bottom left frame index files. */
+    public void emitAllBottomLeftFiles(String packagesIndexName, 
+                                       String classesIndexName, 
+                                       String constructorsIndexName, 
+                                       String methodsIndexName,
+                                       String fieldsIndexName, 
+                                       String allDiffsIndexName, 
+                                       APIDiff apiDiff) {
+        
+        // indexType values: 0 = removals only, 1 = additions only, 
+        // 2 = changes only. 3 = all differences. Run all differences
+        // first for all program element types so we know whether there
+        // are any removals etc for the allDiffs index.
+        emitBottomLeftFile(packagesIndexName, apiDiff, 3, "Package");
+        emitBottomLeftFile(classesIndexName, apiDiff, 3, "Class");
+        emitBottomLeftFile(constructorsIndexName, apiDiff, 3, "Constructor");
+        emitBottomLeftFile(methodsIndexName, apiDiff, 3, "Method");
+        emitBottomLeftFile(fieldsIndexName, apiDiff, 3, "Field");
+        // The allindex must be done last, since it uses the results from 
+        // the previous ones
+        emitBottomLeftFile(allDiffsIndexName, apiDiff, 3, "All");
+        // Now generate the other indexes
+        for (int indexType = 0; indexType < 3; indexType++) {
+            emitBottomLeftFile(packagesIndexName, apiDiff, indexType, "Package");
+            emitBottomLeftFile(classesIndexName, apiDiff, indexType, "Class");
+            emitBottomLeftFile(constructorsIndexName, apiDiff, indexType, "Constructor");
+            emitBottomLeftFile(methodsIndexName, apiDiff, indexType, "Method");
+            emitBottomLeftFile(fieldsIndexName, apiDiff, indexType, "Field");
+            emitBottomLeftFile(allDiffsIndexName, apiDiff, indexType, "All");
+        }
+        if (missingSincesFile != null)
+            missingSincesFile.close();
+    }
+
+    /** 
+     * Emit a single bottom left frame with the given kind of differences for 
+     * the given program element type in an alphabetical index. 
+     *
+     * @param indexBaseName The base name of the index file.
+     * @param apiDiff The root element containing all the API differences.
+     * @param indexType 0 = removals only, 1 = additions only,
+     *                  2 = changes only, 3 = all differences, 
+     * @param programElementType "Package", "Class", "Constructor",
+     *                           "Method", "Field" or "All".  
+     */
+    public void emitBottomLeftFile(String indexBaseName, 
+                                   APIDiff apiDiff, int indexType, 
+                                   String programElementType) {
+        String filename = indexBaseName;
+        try {
+            String title = "Indexes"; 
+            if (indexType == 0) {
+                filename += "_removals" + h_.reportFileExt;
+                title = programElementType + " Removals Index";
+            } else if (indexType == 1) {
+                filename += "_additions" + h_.reportFileExt;
+                title = programElementType + " Additions Index";
+            } else if (indexType == 2) {
+                filename += "_changes" + h_.reportFileExt;
+                title = programElementType + " Changes Index";
+            } else if (indexType == 3) {
+                filename += "_all" + h_.reportFileExt;
+                title = programElementType + " Differences Index";
+            }
+                
+            FileOutputStream fos = new FileOutputStream(filename);
+            h_.reportFile = new PrintWriter(fos);
+            h_.writeStartHTMLHeader();
+            h_.writeHTMLTitle(title);
+            h_.writeStyleSheetRef();
+            h_.writeText("</HEAD>");
+            h_.writeText("<BODY>");
+            
+            if (programElementType.compareTo("Package") == 0) {
+                emitPackagesIndex(apiDiff, indexType);
+            } else if (programElementType.compareTo("Class") == 0) {
+                emitClassesIndex(apiDiff, indexType);
+            } else if (programElementType.compareTo("Constructor") == 0) {
+                emitConstructorsIndex(apiDiff, indexType);
+            } else if (programElementType.compareTo("Method") == 0) {
+                emitMethodsIndex(apiDiff, indexType);
+            } else if (programElementType.compareTo("Field") == 0) {
+                emitFieldsIndex(apiDiff, indexType);
+            } else if (programElementType.compareTo("All") == 0) {
+                emitAllDiffsIndex(apiDiff, indexType);
+            } else{
+                System.out.println("Error: unknown program element type.");
+                System.exit(3);
+            }
+            
+            h_.writeHTMLFooter();
+            h_.reportFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + filename);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /** 
+     * Generate a small header of letters which link to each section, but
+     * do not emit a linked letter for the current section. Finish the list off
+     * with a link to the top of the index.
+     * Caching the results of this function would save about 10s with large APIs.
+     */
+    private void generateLetterIndex(List list, char currChar, boolean larger) {
+        if (larger)
+            return; // Currently not using the larger functionality
+        int size = -2;
+	if (larger)
+            size = -1;
+        Iterator iter = null;
+        if (isAllNames)
+            iter = allNames.iterator();
+        else
+            iter = list.iterator();
+        char oldsw = '\0';
+        while (iter.hasNext()) {
+            Index entry = (Index)(iter.next());
+            char sw = entry.name_.charAt(0);
+            char swu = Character.toUpperCase(sw);
+            if (swu != Character.toUpperCase(oldsw)) {
+                // Don't emit a reference to the current letter
+                if (Character.toUpperCase(sw) != Character.toUpperCase(currChar)) {
+                    if (swu == '_') {
+                        h_.writeText("<a href=\"#" + swu + "\"><font size=\"" + size + "\">" + "underscore" + "</font></a> ");
+                    } else {
+                        h_.writeText("<a href=\"#" + swu + "\"><font size=\"" + size + "\">" + swu + "</font></a> ");
+                    }
+                }
+                oldsw = sw;
+            }
+        }
+        h_.writeText(" <a href=\"#topheader\"><font size=\"" + size + "\">TOP</font></a>");
+        h_.writeText("<p><div style=\"line-height:1.5em;color:black\">");
+    }
+
+    /** 
+     * Emit a header for an index, including suitable links for removed, 
+     * added and changes sub-indexes. 
+     */
+    private void emitIndexHeader(String indexName, int indexType,
+                                 boolean hasRemovals, 
+                                 boolean hasAdditions, boolean hasChanges) {
+        String linkIndexName = indexName.toLowerCase();
+        boolean isAllDiffs = false;
+        if (indexName.compareTo("All Differences") == 0) {
+            linkIndexName = "alldiffs";
+            isAllDiffs = true;
+        }
+        h_.writeText("<a NAME=\"topheader\"></a>"); // Named anchor
+        h_.writeText("<table summary=\"Index for " +  indexName + "\" width=\"100%\" class=\"index\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">");
+        h_.writeText("  <tr>");
+        h_.writeText("  <th class=\"indexHeader\">");
+        h_.writeText("    Filter the Index:");
+        h_.writeText("  </th>");
+        h_.writeText("  </tr>");
+        h_.writeText("  <tr>");
+        h_.writeText("  <td class=\"indexText\" style=\"line-height:1.5em;padding-left:2em;\">");
+//        h_.writeText("  <div style=\"line-height:1.25em;padding-left:1em;>\">");
+//        h_.writeText("  <FONT SIZE=\"-1\">");
+        // The index name is also a hidden link to the *index_all page
+        if (indexType == 3) {
+             h_.writeText("<b>" + indexName + "</b>"); }
+        else if (isAllDiffs) {
+            h_.writeText("<a href=\"" + linkIndexName + "_index_all" + h_.reportFileExt + "\" class=\"hiddenlink\">" + indexName + "</a>");
+        }
+        else {
+            h_.writeText("<a href=\"" + linkIndexName + "_index_all" + h_.reportFileExt + "\" class=\"staysblack\">All " + indexName + "</a>");
+        }
+//        h_.writeText("  </FONT>");
+
+        h_.writeText("  <br>");
+//        h_.writeText("  <FONT SIZE=\"-1\">");
+        if (hasRemovals) {
+          if (indexType == 0) {
+            h_.writeText("<b>Removals</b>");
+          } else {
+            h_.writeText("<A HREF=\"" + linkIndexName + "_index_removals" + h_.reportFileExt + "\" class=\"hiddenlink\">Removals</A>");
+          }
+        } else {
+            h_.writeText("<font color=\"#999999\">Removals</font>");
+        }
+//        h_.writeText("  </FONT>");
+
+        h_.writeText("  <br>");
+//      h_.writeText("  <FONT SIZE=\"-1\">");
+        if (hasAdditions) {
+          if (indexType == 1) {
+            h_.writeText("<b>Additions</b>");
+          } else {
+            h_.writeText("<A HREF=\"" + linkIndexName + "_index_additions" + h_.reportFileExt + "\"class=\"hiddenlink\">Additions</A>");
+          }
+        } else {
+            h_.writeText("<font color=\"#999999\">Additions</font>");
+        }
+//        h_.writeText("  </FONT>");
+
+        h_.writeText("  <br>");
+//         h_.writeText("  <FONT SIZE=\"-1\">");
+        if (hasChanges) {
+          if (indexType == 2) {
+            h_.writeText("<b>Changes</b>");
+          } else {
+            h_.writeText("<A HREF=\"" + linkIndexName + "_index_changes" + h_.reportFileExt + "\"class=\"hiddenlink\">Changes</A>");
+          }
+        } else {
+            h_.writeText("<font color=\"#999999\">Changes</font>");
+        }
+//        h_.writeText("  </FONT>");
+//        h_.writeText("  </div>");
+        h_.writeText("  </td>");
+        h_.writeText("  </tr>");
+        h_.writeText("</table>");
+        h_.writeText("<font size=\"-2\"><strong>Bold</strong>&nbsp;indicates&nbsp;New;&nbsp;<strike>Strike</strike>&nbsp;indicates&nbsp;deleted</font>");
+        h_.writeText("  </br>");
+
+    }
+
+    /** Emit the index of packages, which appears in the bottom left frame. */
+    public void emitPackagesIndex(APIDiff apiDiff, int indexType) {
+        // Add all the names of packages to a new list, to be sorted later
+        packageNames = new ArrayList(); // Index[]
+        boolean hasRemovals = false;
+        if (apiDiff.packagesRemoved.size() != 0)
+            hasRemovals = true;
+        boolean hasAdditions = false;
+        if (apiDiff.packagesAdded.size() != 0)
+            hasAdditions = true;
+        boolean hasChanges = false;
+        if (apiDiff.packagesChanged.size() != 0)
+            hasChanges = true;
+        recordDiffs(hasRemovals, hasAdditions, hasChanges);
+        Iterator iter = apiDiff.packagesRemoved.iterator();
+        while ((indexType == 3 || indexType == 0) && iter.hasNext()) {
+            PackageAPI pkg = (PackageAPI)(iter.next());
+            packageNames.add(new Index(pkg.name_, 0));
+        }
+        iter = apiDiff.packagesAdded.iterator();
+        while ((indexType == 3 || indexType == 1) && iter.hasNext()) {
+            PackageAPI pkg = (PackageAPI)(iter.next());
+            packageNames.add(new Index(pkg.name_, 1));
+        }
+        iter = apiDiff.packagesChanged.iterator();
+        while ((indexType == 3 || indexType == 2) && iter.hasNext()) {
+            PackageDiff pkg = (PackageDiff)(iter.next());
+            packageNames.add(new Index(pkg.name_, 2));
+        }
+        Collections.sort(packageNames);
+
+        // No letter index needed for packages
+
+        // Now emit all the package names and links to their respective files
+        emitIndexHeader("Packages", indexType, hasRemovals, hasAdditions, hasChanges);
+
+        // Extra line because no index is emitted
+        h_.writeText("<br>");
+
+        // Package names are unique, so no need to check for duplicates.
+        iter = packageNames.iterator();
+        char oldsw = '\0';
+        while (iter.hasNext()) {
+            Index pkg = (Index)(iter.next());
+            oldsw = emitPackageIndexEntry(pkg, oldsw);
+        }
+    }
+
+    /** 
+     * Emit an index entry for a package. 
+     * Package names are unique, so no need to check for duplicates.
+     */
+    public char emitPackageIndexEntry(Index pkg, char oldsw) {
+        char res = oldsw;
+        // See if we are in a new section of the alphabet
+        char sw = pkg.name_.charAt(0);
+        if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
+            // No need to emit section letters for packages
+            res = sw;
+            // Add the named anchor for this new letter
+            h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
+        }
+        // Package names are unique, so no need to check for duplicates.
+        if (pkg.changeType_ == 0) {
+            h_.writeText("<A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "#" + pkg.name_  + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + pkg.name_ + "</strike></A><br>");
+        } else if (pkg.changeType_ == 1) {
+            h_.writeText("<A HREF=\"" + h_.reportFileName + "-summary" + h_.reportFileExt + "#" + pkg.name_  + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + pkg.name_ + "</b></A><br>");
+        } else if (pkg.changeType_ == 2) {
+            h_.writeText("<A HREF=\"pkg_" + pkg.name_ + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\">" + pkg.name_ + "</A><br>");
+        }
+        return res;
+    }
+
+    /** 
+     * Emit all the entries and links for the given iterator
+     * to their respective files. 
+     */
+    public void emitIndexEntries(Iterator iter) {
+        char oldsw = '\0';
+        int multipleMarker = 0;
+        Index currIndex = null; // The entry which is emitted
+        while (iter.hasNext()) {
+            // The next entry after the current one
+            Index nextIndex = (Index)(iter.next()); 
+            if (currIndex == null) {
+                currIndex = nextIndex; // Prime the pump
+            } else {
+                if (nextIndex.name_.compareTo(currIndex.name_) == 0) {
+                    // It's a duplicate index, so emit the name and then
+                    // the indented entries
+                    if (multipleMarker == 0)
+                        multipleMarker = 1; // Start of a duplicate index
+                    else if (multipleMarker == 1)
+                        multipleMarker = 2; // Inside a duplicate index
+                    oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
+                } else {
+                    if (multipleMarker == 1)
+                        multipleMarker = 2; // Inside a duplicate index
+                    oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
+                    multipleMarker = 0; // Not in a duplicate index any more
+                }
+                currIndex = nextIndex;
+            }
+        }
+        // Emit the last entry left in currIndex
+        if (multipleMarker == 1)
+            multipleMarker = 2; // Inside a duplicate index
+        if (currIndex != null)
+            oldsw = emitIndexEntry(currIndex, oldsw, multipleMarker);
+    }
+    
+    /** 
+     * Whether to log all missing @since tags to a file or not. 
+     * If false, just warn the user.
+     */
+    public static boolean logMissingSinces = true;
+
+    /** The file used to output details of missing @since tags. */
+    public static PrintWriter missingSincesFile = null;
+
+    /** 
+     * Emit elements in the given iterator which were added and 
+     * missing @since tags. 
+     */
+    public void emitMissingSinces(Iterator iter) {
+//        if (!logMissingSinces)
+//            return;
+        if (missingSincesFile == null) {
+            String sinceFileName = h_.outputDir + JDiff.DIR_SEP + "missingSinces.txt";
+            try {
+                FileOutputStream fos = new FileOutputStream(sinceFileName);
+                missingSincesFile = new PrintWriter(fos);
+            } catch (IOException e) {
+                System.out.println("IO Error while attempting to create " + sinceFileName);
+                System.out.println("Error: " + e.getMessage());
+                System.exit(1);
+            }
+        }
+        while (iter.hasNext()) {
+            Index currIndex = (Index)(iter.next()); 
+            // Only display information about added elements
+            if (currIndex.changeType_ != 1) 
+                continue;
+            String programElementType = currIndex.ename_;
+            String details = null;
+            if (programElementType.compareTo("class") == 0) {
+                details = currIndex.pkgName_ + "." + currIndex.name_;
+                if (currIndex.isInterface_)
+                    details = details + " Interface";
+                else
+                    details = details + " Class";
+            } else if (programElementType.compareTo("constructor") == 0) {
+                details = currIndex.pkgName_ + "." + currIndex.name_ + " Constructor (" + currIndex.type_ + ")";
+            } else if (programElementType.compareTo("method") == 0) {
+                details = currIndex.pkgName_ + "." + currIndex.className_ + " " + "Method " + currIndex.name_ + "(" + currIndex.type_ + ")";
+            } else if (programElementType.compareTo("field") == 0) {
+                details = currIndex.pkgName_ + "." + currIndex.className_ + " " + "Field " + currIndex.name_;
+            } else {
+                System.out.println("Error: unknown program element type");
+                System.exit(3);
+            }
+            if (currIndex.doc_ == null) {
+                if (logMissingSinces)
+                    missingSincesFile.println("NO DOC BLOCK: " + details);
+                else
+                    System.out.println("Warning: the doc block for the new element: " + details + " is missing, so there is no @since tag");
+            } else if (currIndex.doc_.indexOf("@since") != -1) {
+                if (logMissingSinces)
+                    missingSincesFile.println("OK: " + details);
+            } else {
+                if (logMissingSinces)
+                    missingSincesFile.println("MISSING @SINCE TAG: " + details);
+                else
+                    System.out.println("Warning: the doc block for the new element: " + details + " is missing an @since tag");
+            }
+        }
+    }
+    
+    /** 
+     * Emit a single entry and the link to its file.
+     *
+     * @param programElementType "Class", "Constructor",
+     *                           "Method", or "Field".
+     */
+    public char emitIndexEntry(Index currIndex, char oldsw, int multipleMarker) {
+        String programElementType = currIndex.ename_;
+        if (programElementType.compareTo("class") == 0) {
+            return emitClassIndexEntry(currIndex, oldsw, multipleMarker);
+        } else if (programElementType.compareTo("constructor") == 0) {
+            return emitCtorIndexEntry(currIndex, oldsw, multipleMarker);
+        } else if (programElementType.compareTo("method") == 0) {
+            return emitMethodIndexEntry(currIndex, oldsw, multipleMarker);
+        } else if (programElementType.compareTo("field") == 0) {
+            return emitFieldIndexEntry(currIndex, oldsw, multipleMarker);
+        } else {
+            System.out.println("Error: unknown program element type");
+            System.exit(3);
+        }
+        return '\0';
+    }
+
+    /** Emit the index of classes, which appears in the bottom left frame. */
+    public void emitClassesIndex(APIDiff apiDiff, int indexType) {
+        // Add all the names of classes to a new list, to be sorted later
+        classNames = new ArrayList(); // Index[]
+        boolean hasRemovals = false;
+        boolean hasAdditions = false;
+        boolean hasChanges = false;
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkgDiff = (PackageDiff)(iter.next());
+            if (pkgDiff.classesRemoved.size() != 0)
+                hasRemovals = true;
+            if (pkgDiff.classesAdded.size() != 0)
+                hasAdditions = true;
+            if (pkgDiff.classesChanged.size() != 0)
+                hasChanges = true;
+            recordDiffs(hasRemovals, hasAdditions, hasChanges);
+            String pkgName = pkgDiff.name_;
+            Iterator iterClass = pkgDiff.classesRemoved.iterator();
+            while ((indexType == 3 || indexType == 0) && iterClass.hasNext()) {
+                ClassAPI cls = (ClassAPI)(iterClass.next());
+                classNames.add(new Index(cls.name_, 0, pkgName, cls.isInterface_));
+            }
+            iterClass = pkgDiff.classesAdded.iterator();
+            while ((indexType == 3 || indexType == 1) && iterClass.hasNext()) {
+                ClassAPI cls = (ClassAPI)(iterClass.next());
+                Index idx = new Index(cls.name_, 1, pkgName, cls.isInterface_);
+                idx.doc_ = cls.doc_; // Used for checking @since
+                classNames.add(idx);
+            }
+            iterClass = pkgDiff.classesChanged.iterator();
+            while ((indexType == 3 || indexType == 2) && iterClass.hasNext()) {
+                ClassDiff cls = (ClassDiff)(iterClass.next());
+                classNames.add(new Index(cls.name_, 2, pkgName, cls.isInterface_));
+            }
+        }
+        Collections.sort(classNames);
+        emitIndexHeader("Classes", indexType, hasRemovals, hasAdditions, hasChanges);
+        emitIndexEntries(classNames.iterator());
+        if (indexType == 1)
+            emitMissingSinces(classNames.iterator());
+    }
+
+    /** Emit an index entry for a class. */
+    public char emitClassIndexEntry(Index cls, char oldsw, 
+                                    int multipleMarker) {
+        char res = oldsw;
+        String className = cls.pkgName_ + "." + cls.name_;
+        String classRef = cls.pkgName_ + "." + cls.name_;
+        boolean isInterface = cls.isInterface_;
+        // See if we are in a new section of the alphabet
+        char sw = cls.name_.charAt(0);
+        if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
+            res = sw;
+            // Add the named anchor for this new letter
+            h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
+            if (sw == '_')
+                h_.writeText("<br><b>underscore</b>&nbsp;");
+            else
+                h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
+            generateLetterIndex(classNames, sw, false);
+        }
+        // Deal with displaying duplicate indexes
+        if (multipleMarker == 1) {
+            h_.writeText("<i>" + cls.name_ + "</i><br>");
+        }
+        if (multipleMarker != 0)
+            h_.indent(INDENT_SIZE);
+        if (cls.changeType_ == 0) {
+            // Emit a reference to the correct place for the class in the 
+            // JDiff page for the package
+            h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + 
+                         "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + cls.name_ + "</strike></A><br>");
+        } else if (cls.changeType_ == 1) {
+            String cn = cls.name_;
+            if (multipleMarker != 0)
+                cn = cls.pkgName_;
+            if (isInterface)
+                h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b><i>" + cn + "</i></b></A><br>");
+            else
+                h_.writeText("<A HREF=\"pkg_" + cls.pkgName_ + h_.reportFileExt + "#" + cls.name_ + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + cn + "</b></A><br>");
+        } else if (cls.changeType_ == 2) {
+            String cn = cls.name_;
+            if (multipleMarker != 0)
+                cn = cls.pkgName_;
+            if (isInterface)
+                h_.writeText("<A HREF=\"" + classRef + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\"><i>" + cn + "</i></A><br>");
+            else
+                h_.writeText("<A HREF=\"" + classRef + h_.reportFileExt + "\" class=\"hiddenlink\" target=\"rightframe\">" + cn + "</A><br>");
+        }
+        return res;
+    }
+    
+    /** 
+     * Emit the index of all constructors, which appears in the bottom left 
+     * frame. 
+     */
+    public void emitConstructorsIndex(APIDiff apiDiff, int indexType) {
+        // Add all the names of constructors to a new list, to be sorted later
+        ctorNames = new ArrayList(); // Index[]
+        boolean hasRemovals = false;
+        boolean hasAdditions = false;
+        boolean hasChanges = false;
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkgDiff = (PackageDiff)(iter.next());
+            String pkgName = pkgDiff.name_;
+            Iterator iterClass = pkgDiff.classesChanged.iterator();
+            while (iterClass.hasNext()) {
+                ClassDiff classDiff = (ClassDiff)(iterClass.next());
+                if (classDiff.ctorsRemoved.size() != 0)
+                    hasRemovals = true;
+                if (classDiff.ctorsAdded.size() != 0)
+                    hasAdditions = true;
+                if (classDiff.ctorsChanged.size() != 0)
+                    hasChanges = true;
+                recordDiffs(hasRemovals, hasAdditions, hasChanges);
+                String className = classDiff.name_;
+                Iterator iterCtor = classDiff.ctorsRemoved.iterator();
+                while ((indexType == 3 || indexType == 0) && iterCtor.hasNext()) {
+                    ConstructorAPI ctor = (ConstructorAPI)(iterCtor.next());
+                    ctorNames.add(new Index(className, 0, pkgName, ctor.type_));
+                }
+                iterCtor = classDiff.ctorsAdded.iterator();
+                while ((indexType == 3 || indexType == 1) && iterCtor.hasNext()) {
+                    ConstructorAPI ctor = (ConstructorAPI)(iterCtor.next());
+                    Index idx = new Index(className, 1, pkgName, ctor.type_);
+                    idx.doc_ = ctor.doc_; // Used for checking @since
+                    ctorNames.add(idx);
+                }
+                iterCtor = classDiff.ctorsChanged.iterator();
+                while ((indexType == 3 || indexType == 2) && iterCtor.hasNext()) {
+                    MemberDiff ctor = (MemberDiff)(iterCtor.next());
+                    ctorNames.add(new Index(className, 2, pkgName, ctor.newType_));
+                }
+            }
+        }
+        Collections.sort(ctorNames);
+        emitIndexHeader("Constructors", indexType, hasRemovals, hasAdditions, hasChanges);
+        emitIndexEntries(ctorNames.iterator());
+        if (indexType == 1)
+            emitMissingSinces(ctorNames.iterator());
+    }
+
+    /** Emit an index entry for a constructor. */
+    public char emitCtorIndexEntry(Index ctor, char oldsw, int multipleMarker) {
+        char res = oldsw;
+        String className = ctor.pkgName_ + "." + ctor.name_;
+        String memberRef = ctor.pkgName_ + "." + ctor.name_;
+        String type = ctor.type_;
+        if (type.compareTo("void") == 0)
+            type = "";
+        String shownType = HTMLReportGenerator.simpleName(type);
+        // See if we are in a new section of the alphabet
+        char sw = ctor.name_.charAt(0);
+        if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
+            res = sw;
+            // Add the named anchor for this new letter
+            h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
+            if (sw == '_')
+                h_.writeText("<br><b>underscore</b>&nbsp;");
+            else
+                h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
+            generateLetterIndex(ctorNames, sw, false);
+        }
+        // Deal with displaying duplicate indexes
+        if (multipleMarker == 1) {
+            h_.writeText("<i>" + ctor.name_ + "</i><br>");
+        }
+        if (multipleMarker != 0)
+            h_.indent(INDENT_SIZE);
+        // Deal with each type of difference
+        // The output displayed for unique or duplicate entries is the same
+        // for constructors.
+        if (ctor.changeType_ == 0) {
+            String commentID = className + ".ctor_removed(" + type + ")";
+            h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + ctor.name_ + "</strike>");
+            h_.emitTypeWithParens(shownType, false);
+            h_.writeText("</A></nobr>&nbsp;constructor<br>");
+        } else if (ctor.changeType_ == 1) {
+            String commentID = className + ".ctor_added(" + type + ")";
+            h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + ctor.name_ + "</b>");
+            h_.emitTypeWithParens(shownType, false);
+            h_.writeText("</A></nobr>&nbsp;constructor<br>");
+        } else if (ctor.changeType_ == 2) {
+            String commentID = className + ".ctor_changed(" + type + ")";
+            h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + ctor.name_);
+            h_.emitTypeWithParens(shownType, false);
+            h_.writeText("</A></nobr>&nbsp;constructor<br>");
+        }
+        return res;
+    }
+
+    /** 
+     * Emit the index of all methods, which appears in the bottom left frame. 
+     */
+    public void emitMethodsIndex(APIDiff apiDiff, int indexType) {
+        // Add all the names of methods to a new list, to be sorted later
+        methNames = new ArrayList(); // Index[]
+        boolean hasRemovals = false;
+        boolean hasAdditions = false;
+        boolean hasChanges = false;
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkgDiff = (PackageDiff)(iter.next());
+            String pkgName = pkgDiff.name_;
+            Iterator iterClass = pkgDiff.classesChanged.iterator();
+            while (iterClass.hasNext()) {
+                ClassDiff classDiff = (ClassDiff)(iterClass.next());
+                if (classDiff.methodsRemoved.size() != 0)
+                    hasRemovals = true;
+                if (classDiff.methodsAdded.size() != 0)
+                    hasAdditions = true;
+                if (classDiff.methodsChanged.size() != 0)
+                    hasChanges = true;
+                recordDiffs(hasRemovals, hasAdditions, hasChanges);
+                String className = classDiff.name_;
+                Iterator iterMeth = classDiff.methodsRemoved.iterator();
+                while ((indexType == 3 || indexType == 0) && iterMeth.hasNext()) {
+                    MethodAPI meth = (MethodAPI)(iterMeth.next());
+                    methNames.add(new Index(meth.name_, 0, pkgName, className, meth.getSignature()));
+                }
+                iterMeth = classDiff.methodsAdded.iterator();
+                while ((indexType == 3 || indexType == 1) && iterMeth.hasNext()) {
+                    MethodAPI meth = (MethodAPI)(iterMeth.next());
+                    Index idx = new Index(meth.name_, 1, pkgName, className, meth.getSignature());
+                    idx.doc_ = meth.doc_; // Used for checking @since
+                    methNames.add(idx);
+                }
+                iterMeth = classDiff.methodsChanged.iterator();
+                while ((indexType == 3 || indexType == 2) && iterMeth.hasNext()) {
+                    MemberDiff meth = (MemberDiff)(iterMeth.next());
+                    methNames.add(new Index(meth.name_, 2, pkgName, className, meth.newSignature_));
+                }
+            }
+        }
+        Collections.sort(methNames);
+        emitIndexHeader("Methods", indexType, hasRemovals, hasAdditions, hasChanges);
+        emitIndexEntries(methNames.iterator());
+        if (indexType == 1)
+            emitMissingSinces(methNames.iterator());
+    }
+
+    /** Emit an index entry for a method. */
+    public char emitMethodIndexEntry(Index meth, char oldsw, 
+                                     int multipleMarker) {
+        char res = oldsw;
+        String className = meth.pkgName_ + "." + meth.className_;
+        String memberRef = meth.pkgName_ + "." + meth.className_;
+        String type = meth.type_;
+        if (type.compareTo("void") == 0)
+            type = "";
+        String shownType = HTMLReportGenerator.simpleName(type);
+        // See if we are in a new section of the alphabet
+        char sw = meth.name_.charAt(0);
+        if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
+            res = sw;
+            // Add the named anchor for this new letter
+            h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
+            if (sw == '_')
+                h_.writeText("<br><b>underscore</b>&nbsp;");
+            else
+                h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
+            generateLetterIndex(methNames, sw, false);
+        }
+        // Deal with displaying duplicate indexes
+        if (multipleMarker == 1) {
+            h_.writeText("<i>" + meth.name_ + "</i><br>");
+        }
+        if (multipleMarker != 0)
+            h_.indent(INDENT_SIZE);
+        // Deal with each type of difference
+        if (meth.changeType_ == 0) {
+            String commentID = className + "." + meth.name_ + "_removed(" + type + ")";                    
+            if (multipleMarker == 0) {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + meth.name_ + "</strike>");
+                h_.emitTypeWithParens(shownType, false);
+            } else {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;<strike>");
+                h_.emitTypeWithParens(shownType, false);
+                h_.writeText("</strike>&nbsp;in&nbsp;" + className);
+            }
+            h_.writeText("</A></nobr><br>");
+        } else if (meth.changeType_ == 1) {
+            String commentID = className + "." + meth.name_ + "_added(" + type + ")";                    
+            if (multipleMarker == 0) {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><b>" + meth.name_ + "</b>");
+                h_.emitTypeWithParens(shownType, false);
+            } else {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;<b>");
+                h_.emitTypeWithParens(shownType, false);
+                h_.writeText("</b>&nbsp;in&nbsp;" + className);
+            }
+            h_.writeText("</A></nobr><br>");
+        } else if (meth.changeType_ == 2) {
+            String commentID = className + "." + meth.name_ + "_changed(" + type + ")";                    
+            if (multipleMarker == 0) {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + meth.name_);
+                h_.emitTypeWithParens(shownType, false);
+            } else {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">type&nbsp;");
+                h_.emitTypeWithParens(shownType, false);
+                h_.writeText("&nbsp;in&nbsp;" + className);
+            }
+            h_.writeText("</A></nobr><br>");
+        }
+        return res;
+    }
+
+    /** 
+     * Emit the index of all fields, which appears in the bottom left frame. 
+     */
+    public void emitFieldsIndex(APIDiff apiDiff, int indexType) {
+        // Add all the names of fields to a new list, to be sorted later
+        fieldNames = new ArrayList(); // Index[]
+        boolean hasRemovals = false;
+        boolean hasAdditions = false;
+        boolean hasChanges = false;
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkgDiff = (PackageDiff)(iter.next());
+            String pkgName = pkgDiff.name_;
+            Iterator iterClass = pkgDiff.classesChanged.iterator();
+            while (iterClass.hasNext()) {
+                ClassDiff classDiff = (ClassDiff)(iterClass.next());
+                if (classDiff.fieldsRemoved.size() != 0)
+                    hasRemovals = true;
+                if (classDiff.fieldsAdded.size() != 0)
+                    hasAdditions = true;
+                if (classDiff.fieldsChanged.size() != 0)
+                    hasChanges = true;
+                recordDiffs(hasRemovals, hasAdditions, hasChanges);
+                String className = classDiff.name_;
+                Iterator iterField = classDiff.fieldsRemoved.iterator();
+                while ((indexType == 3 || indexType == 0) && iterField.hasNext()) {
+                    FieldAPI fld = (FieldAPI)(iterField.next());
+                    fieldNames.add(new Index(fld.name_, 0, pkgName, className, fld.type_, true));
+                }
+                iterField = classDiff.fieldsAdded.iterator();
+                while ((indexType == 3 || indexType == 1) && iterField.hasNext()) {
+                    FieldAPI fld = (FieldAPI)(iterField.next());
+                    Index idx = new Index(fld.name_, 1, pkgName, className, fld.type_, true);
+                    idx.doc_ = fld.doc_; // Used for checking @since
+                    fieldNames.add(idx);
+                }
+                iterField = classDiff.fieldsChanged.iterator();
+                while ((indexType == 3 || indexType == 2) && iterField.hasNext()) {
+                    MemberDiff fld = (MemberDiff)(iterField.next());
+                    fieldNames.add(new Index(fld.name_, 2, pkgName, className, fld.newType_, true));
+                }
+            }
+        }
+        Collections.sort(fieldNames);
+        emitIndexHeader("Fields", indexType, hasRemovals, hasAdditions, hasChanges);
+        emitIndexEntries(fieldNames.iterator());
+        if (indexType == 1)
+            emitMissingSinces(fieldNames.iterator());
+    }
+
+    /** Emit an index entry for a field. */
+    public char emitFieldIndexEntry(Index fld, char oldsw, 
+                                    int multipleMarker) {
+        char res = oldsw;
+        String className = fld.pkgName_ + "." + fld.className_;
+        String memberRef = fld.pkgName_ + "." + fld.className_;
+        String type = fld.type_;
+        if (type.compareTo("void") == 0)
+            type = "";
+        String shownType = HTMLReportGenerator.simpleName(type);
+        // See if we are in a new section of the alphabet
+        char sw = fld.name_.charAt(0);
+        if (Character.toUpperCase(sw) != Character.toUpperCase(oldsw)) {
+            res = sw;
+            // Add the named anchor for this new letter
+            h_.writeText("<A NAME=\"" + Character.toUpperCase(res) + "\"></A>");
+            if (sw == '_')
+                h_.writeText("<br><b>underscore</b>&nbsp;");
+            else
+                h_.writeText("<br><font size=\"+2\">" + Character.toUpperCase(sw) + "</font>&nbsp;");
+            generateLetterIndex(fieldNames, sw, false);
+        }
+        // Deal with displaying duplicate indexes
+        if (multipleMarker == 1) {
+            h_.writeText("<i>" + fld.name_ + "</i><br>");
+        }
+        if (multipleMarker != 0) {
+// More context than this is helpful here: h_.indent(INDENT_SIZE);
+            h_.writeText("&nbsp;in&nbsp;");
+        }
+        // Deal with each type of difference
+        if (fld.changeType_ == 0) {
+            String commentID = className + "." + fld.name_;                    
+            if (multipleMarker == 0) {            
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + fld.name_ + "</strike></A>");
+                h_.writeText("</nobr><br>");
+            } else {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\"><strike>" + className + "</strike></A>");
+                h_.writeText("</nobr><br>");
+            }
+        } else if (fld.changeType_ == 1) {
+            String commentID = className + "." + fld.name_;                    
+            if (multipleMarker == 0) {            
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + fld.name_ + "</A>");
+                h_.writeText("</nobr><br>");
+            } else {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + className + "</A>");
+                h_.writeText("</nobr><br>");
+            }
+        } else if (fld.changeType_ == 2) {
+            String commentID = className + "." + fld.name_;                    
+            if (multipleMarker == 0) {            
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + fld.name_ + "</A>");
+                h_.writeText("</nobr><br>");
+            } else {
+                h_.writeText("<nobr><A HREF=\"" + memberRef + h_.reportFileExt + "#" + commentID + "\" class=\"hiddenlink\" target=\"rightframe\">" + className + "</A>");
+                h_.writeText("</nobr><br>");
+            }
+        }
+        return res;
+    }
+
+    /** 
+     * Emit the index of all changes, which appears in the bottom left frame.
+     * Has to be run after all the other indexes have been written, since it
+     * uses data from when they are generated.
+     */
+    public void emitAllDiffsIndex(APIDiff apiDiff, int indexType) {
+        allNames = new ArrayList(); // Index[]
+        // Add all the changes into one big list, and sort it by name,
+        // ignoring case
+        allNames.addAll(packageNames);
+        allNames.addAll(classNames);
+        allNames.addAll(ctorNames);
+        allNames.addAll(methNames);
+        allNames.addAll(fieldNames);
+        // Compares two Index objects' names, ignoring case differences.
+        Collections.sort(allNames);
+
+        emitIndexHeader("All Differences", indexType, atLeastOneRemoval, 
+                        atLeastOneAddition, atLeastOneChange);
+
+        // Tell generateLetterIndex to use allNames as the list when 
+        // using the other methods to generate the indexes.
+        isAllNames = true; 
+        
+        // Now emit a line for each entry in the list in the appropriate 
+        // format for each program element
+        Iterator iter = allNames.iterator();
+        char oldsw = '\0';
+        int multipleMarker = 0;
+        Index currIndex = null; // The entry which is emitted
+        while (iter.hasNext()) {
+            // The next entry after the current one
+            Index nextIndex = (Index)(iter.next()); 
+            if (currIndex == null) {
+                currIndex = nextIndex; // Prime the pump
+            } else {
+                if (nextIndex.name_.compareTo(currIndex.name_) == 0) {
+                    // It's a duplicate index, so emit the name and then
+                    // the indented entries
+                    if (multipleMarker == 0)
+                        multipleMarker = 1; // Start of a duplicate index
+                    else if (multipleMarker == 1)
+                        multipleMarker = 2; // Inside a duplicate index
+                    oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
+                } else {
+                    if (multipleMarker == 1)
+                        multipleMarker = 2; // Inside a duplicate index
+                    oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
+                    multipleMarker = 0; // Not in a duplicate index any more
+                }
+                currIndex = nextIndex;
+            }
+        }
+        // Emit the last entry left in currIndex
+        if (multipleMarker == 1)
+            multipleMarker = 2; // Inside a duplicate index
+        if (currIndex != null)
+            oldsw = emitIndexEntryForAny(currIndex, oldsw, multipleMarker);
+
+        // Tell generateLetterIndex to stop using allNames as the list when 
+        // using the other methods to generate the indexes.
+        isAllNames = false; 
+    }
+
+    /** Call the appropriate *IndexEntry method for each entry. */
+    public char emitIndexEntryForAny(Index currIndex, char oldsw, 
+                                     int multipleMarker) {
+        if (currIndex.ename_.compareTo("package") == 0) {
+            h_.writeText("<!-- Package " + currIndex.name_ + " -->");
+            return emitPackageIndexEntry(currIndex, oldsw);
+        } else if (currIndex.ename_.compareTo("class") == 0) {
+            h_.writeText("<!-- Class " + currIndex.name_ + " -->");
+            return emitClassIndexEntry(currIndex, oldsw, multipleMarker);
+        } else if (currIndex.ename_.compareTo("constructor") == 0) {
+            h_.writeText("<!-- Constructor " + currIndex.name_ + " -->");
+            return emitCtorIndexEntry(currIndex, oldsw, multipleMarker);
+        } else if (currIndex.ename_.compareTo("method") == 0) {
+            h_.writeText("<!-- Method " + currIndex.name_ + " -->");
+            return emitMethodIndexEntry(currIndex, oldsw, multipleMarker);
+        } else if (currIndex.ename_.compareTo("field") == 0) {
+            h_.writeText("<!-- Field " + currIndex.name_ + " -->");
+            return emitFieldIndexEntry(currIndex, oldsw, multipleMarker);
+        }
+        return '\0';
+    }
+
+    /** The list of all changes for all program elements. */
+    private List allNames = null; // Index[]
+
+    /** The list of all package changes. */
+    private List packageNames = null; // Index[]
+
+    /** The list of all class changes. */
+    private List classNames = null; // Index[]
+
+    /** The list of all constructor changes. */
+    private List ctorNames = null; // Index[]
+
+    /** The list of all method changes. */
+    private List methNames = null; // Index[]
+
+    /** The list of all field changes. */
+    private List fieldNames = null; // Index[]
+
+    /** If set, then use allNames to generate the letter indexes. */
+    private boolean isAllNames = false;
+
+    /** 
+     * If any of the parameters are set, then set the respective atLeastOne
+     * variable, used to generate the links at the top of the allDiffs index. 
+     * Never unset an atLeastOne variable.
+     */
+    private void recordDiffs(boolean hasRemovals, boolean hasAdditions, 
+                        boolean hasChanges) {
+        if (hasRemovals)
+            atLeastOneRemoval = true;
+        if (hasAdditions)
+            atLeastOneAddition = true;
+        if (hasChanges)
+            atLeastOneChange = true;
+    }
+
+    /** Set if there was at least one removal in the entire API. */
+    private boolean atLeastOneRemoval = false;
+
+    /** Set if there was at least one addition in the entire API. */
+    private boolean atLeastOneAddition = false;
+
+    /** Set if there was at least one change in the entire API. */
+    private boolean atLeastOneChange = false;
+
+    /** 
+     * The number of non-breaking spaces to indent a duplicate indexes'
+     * entries by. 
+     */
+    private final int INDENT_SIZE = 2;
+}
+
+/**
+ * Class used to produce indexes of packages and classes. 
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class Index implements Comparable {
+
+    /** The name of the program element this Index object represents. */
+    public String ename_ = null;
+
+    /** Name of the changed package, class or member. */
+    public String name_ = null;
+    
+    /** Type of change. 0 = remove, 1 = add, 2 = change. */
+    public int changeType_;
+    
+    /** Name of the changed package if name_ is a class name. */
+    public String pkgName_ = null;
+    
+    /** Set if this class is an interface. */
+    public boolean isInterface_= false;
+    
+    /** The doc block of added elements, default is null. */
+    public String doc_ = null;
+    
+    /** 
+     * The new member type. For methods, this is the signature.
+     */
+    public String type_ = null;
+
+    /** 
+     * The class name. Only used by methods.
+     */
+    public String className_ = null;
+
+    /** Constructor for packages. */
+    public Index(String name, int changeType) {
+        ename_ = "package";
+        name_ = name;
+        changeType_ = changeType;
+    }
+    
+    /** Constructor for classes. */
+    public Index(String name, int changeType, String pkgName, boolean isInterface) {
+        ename_ = "class";
+        name_ = name;
+        changeType_ = changeType;
+        pkgName_ = pkgName;
+        isInterface_ = isInterface;
+    }
+        
+    /** Constructor for constructors. */
+    public Index(String name, int changeType, String pkgName, String type) {
+        ename_ = "constructor";
+        name_ = name;
+        changeType_ = changeType;
+        pkgName_ = pkgName;
+        type_  = type;
+    }
+        
+    /** Constructor for methods. */
+    public Index(String name, int changeType, String pkgName, 
+                 String className, String type) {
+        ename_ = "method";
+        name_ = name;
+        changeType_ = changeType;
+        pkgName_ = pkgName;
+        className_ = className;
+        type_  = type;
+    }
+        
+    /** 
+     * Constructor for fields. 
+     *
+     * The boolean <code>fld</code> is simply there to differentiate this
+     * constructor from the one for methods.
+     */
+    public Index(String name, int changeType, String pkgName, 
+                 String className, String type, boolean fld) {
+        ename_ = "field";
+        name_ = name;
+        changeType_ = changeType;
+        pkgName_ = pkgName;
+        className_ = className;
+        type_  = type;
+    }
+        
+        
+    /** Compare two Index objects by their simple names, ignoring case. */
+    public int compareTo(Object o) {
+        return name_.compareToIgnoreCase(((Index)o).name_);
+    }  
+    
+}
+
diff --git a/src/jdiff/HTMLReportGenerator.java b/src/jdiff/HTMLReportGenerator.java
new file mode 100755
index 0000000..f6cf861
--- /dev/null
+++ b/src/jdiff/HTMLReportGenerator.java
@@ -0,0 +1,2163 @@
+package jdiff;
+
+import java.util.*;
+import java.io.*;
+import java.text.*;
+
+/**
+ * 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
+ */
+public class HTMLReportGenerator {
+
+    /** Default constructor. */
+    public HTMLReportGenerator() {
+    }   
+
+    /** The Comments object for existing comments. */
+    private Comments existingComments_ = null;
+
+    /** 
+     * 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.
+     */
+    private Comments newComments_ = null;
+
+    /** 
+     * Accessor method for the freshly generated Comments object. 
+     * The list of comments is sorted before the object is returned.
+     */
+    public Comments getNewComments() {
+        Collections.sort(newComments_.commentsList_);
+        return newComments_;
+    }
+
+    /** Generate the report. */
+    public void generate(APIComparator comp, Comments existingComments) {
+        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&nbsp;Differences&nbsp;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);
+        }
+    }   
+
+    /** 
+     * 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.
+     */
+    public void writeReport(APIDiff apiDiff) {
+
+        // 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>&copy;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 -->");
+}
+    
+    /** 
+     * Write out the details of a changed package in a separate file. 
+     */
+    public void reportChangedPackage(PackageDiff[] pkgDiffs, int pkgIndex) {
+        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;
+    }
+
+    /** 
+     * Write out the details of a changed class in a separate file. 
+     */
+    public void reportChangedClass(String pkgName, ClassDiff[] classDiffs, int classIndex) {
+        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;
+    }
+
+    /** 
+     * Write out the details of constructors in a class. 
+     */
+    public void reportAllCtors(String pkgName, ClassDiff classDiff) {
+        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();
+        }
+    }
+
+    /** 
+     * Write out the details of methods in a class. 
+     */
+    public void reportAllMethods(String pkgName, ClassDiff classDiff) {
+        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();
+        }
+    }
+
+    /** 
+     * Write out the details of fields in a class. 
+     */
+    public void reportAllFields(String pkgName, ClassDiff classDiff) {
+        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();
+        }
+        
+    }
+
+    /** 
+     * Write the start of the HTML header, together with the current
+     * date and time in an HTML comment. 
+     */
+    public void writeStartHTMLHeaderWithDate() {
+        writeStartHTMLHeader(true);
+    }
+
+    /** Write the start of the HTML header. */
+    public void writeStartHTMLHeader() {
+        writeStartHTMLHeader(false);
+    }
+
+    /** Write the start of the HTML header. */
+    public void writeStartHTMLHeader(boolean addDate) {
+        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 + "\">");
+    }
+
+    /** Write the HTML title */
+    public void writeHTMLTitle(String title) {
+        writeText("<TITLE>");
+        writeText(title);
+        writeText("</TITLE>");
+    }
+
+    /** 
+     * Write the HTML style sheet reference for files in the subdirectory.
+     */
+    public void writeStyleSheetRef() {
+        writeStyleSheetRef(false);
+    }
+
+    /** 
+     * Write the HTML style sheet reference. If inSameDir is set, don't add
+     * "../" to the location.
+     */
+
+    public void writeStyleSheetRef(boolean inSameDir) {
+        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>");
+    }
+
+    /** Write the HTML footer. */
+    public void writeHTMLFooter() {
+    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>");
+    }
+
+    /** 
+     * 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
+     */
+    public void writeSectionHeader(String title, String packageName, 
+                                   String prevElemLink, String nextElemLink,
+                                   String className, int level, 
+                                   boolean hasRemovals, 
+                                   boolean hasAdditions, 
+                                   boolean hasChanges) {
+        writeNavigationBar(packageName, prevElemLink, nextElemLink,
+                           className, level, true,
+                           hasRemovals, hasAdditions, hasChanges);
+        if (level != 0) {
+            reportFile.println("<H2>");
+            reportFile.println(title);
+            reportFile.println("</H2>");
+        }
+    }
+    
+    /** 
+     * 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
+     */
+    public void writeSectionFooter(String packageName, 
+                                   String prevElemLink, String nextElemLink,
+                                   String className, int level) {
+            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>&copy;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);
+*/
+    }
+   
+    /** 
+     * 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
+     */
+
+    public void writeNavigationBar(String pkgName,
+                                   String prevElemLink, String nextElemLink,
+                                   String className, int level,
+                                   boolean upperNavigationBar,
+                                   boolean hasRemovals, boolean hasAdditions, 
+                                   boolean hasChanges) {
+
+            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>&nbsp;</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>&nbsp;</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>&nbsp;</TD>");
+        }
+
+        if (atOverview) {
+            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Overview</B></FONT>&nbsp;</TD>");
+            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Package</FONT>&nbsp;</TD>");
+            reportFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</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>&nbsp;</TD>");
+            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Package</B></FONT>&nbsp;</TD>");
+            reportFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1\"> &nbsp;<FONT CLASS=\"NavBarFont1\">Class</FONT>&nbsp;</TD>");
+        }
+        if (atClass) {
+            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + changesSummaryName + "\"><FONT CLASS=\"NavBarFont1\"><B>Overview</B></FONT></A>&nbsp;</TD>");
+            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"pkg_" + pkgName + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Package</B></FONT></A>&nbsp;</TD>");
+            reportFile.println("      <TD BGCOLOR=\"#FFFFFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Class</B></FONT>&nbsp;</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>&nbsp;</TD>");
+                else
+                    reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <FONT CLASS=\"NavBarFont1\">Text Changes</FONT>&nbsp;</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>&nbsp;</TD>");
+                else
+                    reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <FONT CLASS=\"NavBarFont1\">Text Changes</FONT>&nbsp;</TD>");
+            } else {
+                reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"" + Diff.diffFileName + "index" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Text Changes</B></FONT></A>&nbsp;</TD>");
+            }
+        }
+
+        if (doStats) {
+            reportFile.println("      <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_statistics" + reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Statistics</B></FONT></A>&nbsp;</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>&nbsp;</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("&nbsp;<B>PREV " + elemName + "</B>&nbsp;");
+            } else {
+                reportFile.println("&nbsp;<A HREF=\"" + prevElemLink + "\"><B>PREV " + elemName + "</B></A>");
+            }
+            if (nextElemLink == null) {
+                reportFile.println("&nbsp;<B>NEXT " + elemName + "</B>&nbsp;");
+            } else {
+                reportFile.println("&nbsp;<A HREF=\"" + nextElemLink + "\"><B>NEXT " + elemName + "</B></A>");
+            }
+            reportFile.println("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;");
+        } else {
+            reportFile.println("  &nbsp;&nbsp;");        
+        }
+        // Links for frames and non-frames.
+        reportFile.println("  <A HREF=\"" + "../" + reportFileName + reportFileExt + "\" TARGET=\"_top\"><B>FRAMES</B></A>  &nbsp;");
+        if (className == null) {
+            if (level == 0) {
+                reportFile.println("  &nbsp;<A HREF=\"" + pkgName + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
+            } else {
+                reportFile.println("  &nbsp;<A HREF=\"pkg_" + pkgName + reportFileExt + "\" TARGET=\"_top\"><B>NO FRAMES</B></A></FONT></TD>");
+            }
+        } else {
+            reportFile.println("  &nbsp;<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: &nbsp;");
+                if (hasCtors) {
+                    reportFile.println("<a href=\"#constructors\">CONSTRUCTORS</a>&nbsp;|&nbsp;");
+                } else {
+                    reportFile.println("CONSTRUCTORS&nbsp;|&nbsp;");
+                }
+                if (hasMethods) {
+                    reportFile.println("<a href=\"#methods\">METHODS</a>&nbsp;|&nbsp;");
+                } else {
+                    reportFile.println("METHODS&nbsp;|&nbsp;");
+                }
+                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: &nbsp;");
+                if (hasRemovals) {
+                    reportFile.println("<a href=\"#Removed\">REMOVED</a>&nbsp;|&nbsp;");
+                } else {
+                    reportFile.println("REMOVED&nbsp;|&nbsp;");
+                }
+                if (hasAdditions) {
+                    reportFile.println("<a href=\"#Added\">ADDED</a>&nbsp;|&nbsp;");
+                } else {
+                    reportFile.println("ADDED&nbsp;|&nbsp;");
+                }
+                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 -->");
+*/
+    }
+    
+    /** Write the start of a table. */
+    public void writeTableStart(String title, int colSpan) {
+        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>");
+    }
+
+    /** 
+     * If a class or package name is considered to be too long for convenient
+     * display, insert <br> in the middle of it at a period.
+     */
+    public String makeTwoRows(String name) {
+        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;
+    }
+
+    /** 
+     * 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
+     */
+    public void writePackageTableEntry(String pkgName, int linkType, 
+                                       String possibleComment, boolean useOld) {
+        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>");
+        }
+    }
+
+    /** 
+     * 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
+     */
+    public void writeClassTableEntry(String pkgName, String className, 
+                                     int linkType, boolean isInterface, 
+                                     String possibleComment, boolean useOld) {
+        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>");
+        }
+    }
+
+    /** 
+     * Write a table entry for a constructor. 
+     *
+     * linkType: 0 - no link by default, 1 = link to Javadoc HTML file
+     */
+    public void writeCtorTableEntry(String pkgName, String className, 
+                                    String type, int linkType, 
+                                    String possibleComment, boolean useOld) {
+        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>");
+        }
+    }
+
+    /** 
+     * Write a table entry for a changed constructor.
+     */
+    public void writeCtorChangedTableEntry(String pkgName, String className, 
+                                           MemberDiff memberDiff) {
+        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>");
+    }
+
+    /** 
+     * Write a table entry for a method. 
+     *
+     * linkType: 0 - no link by default, 1 = link to Javadoc HTML file
+     */
+    public void writeMethodTableEntry(String pkgName, String className, 
+                                      MethodAPI methodAPI, int linkType, 
+                                      String possibleComment, boolean useOld) {
+        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("&nbsp;" + 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("&nbsp;<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>");
+        }
+    }
+
+    /**
+     * Write a table entry for a changed method.
+     */
+    public void writeMethodChangedTableEntry(String pkgName, String className, 
+                                      MemberDiff memberDiff) {
+        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("&nbsp;<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>");
+    }
+
+    /** 
+     * Write a table entry for a field. 
+     *
+     * linkType: 0 - no link by default, 1 = link to Javadoc HTML file
+     */
+    public void writeFieldTableEntry(String pkgName, String className, 
+                                     FieldAPI fieldAPI, int linkType, 
+                                     String possibleComment, boolean useOld) {
+        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("&nbsp;" + 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("&nbsp;<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>");
+        }
+        }
+
+    /** 
+     * Write a table entry for a changed field.
+     */
+    public void writeFieldChangedTableEntry(String pkgName, String className, 
+                                            MemberDiff memberDiff) {
+        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("&nbsp;<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>");
+    }
+
+    /**
+     * Emit all changes associated with a MemberDiff as an entry in a table.
+     *
+     * @param memberType 0 = ctor, 1 = method, 2 = field
+     */
+    public void emitChanges(MemberDiff memberDiff, int memberType){
+        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>");
+    }
+
+    /** 
+     * Emit a string which is an exception by surrounding it with 
+     * &lt;code&gt; tags.
+     * If there is a space in the type, e.g. &quot;String, File&quot;, then
+     * surround it with parentheses too. Do not add &lt;code&gt; tags or 
+     * parentheses if the String is "no exceptions".
+     */
+    public void emitException(String ex) {
+        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>");
+            }
+        }
+    }
+
+    /** 
+     * Emit a string which is a type by surrounding it with &lt;code&gt; tags.
+     * If there is a space in the type, e.g. &quot;String, File&quot;, then
+     * surround it with parentheses too.
+     */
+    public void emitType(String type) {
+        if (type.compareTo("") == 0)
+            return;
+        if (type.indexOf(' ') != -1) {
+            reportFile.print("(<code>" + type + "</code>)");
+        } else {
+            reportFile.print("<code>" + type + "</code>");
+        }
+    }
+
+    /** 
+     * Emit a string which is a type by surrounding it with &lt;code&gt; tags.
+     * Also surround it with parentheses too. Used to display methods' 
+     * parameters.
+     * Suggestions for where a browser should break the 
+     * text are provided with &lt;br> and &ltnobr> tags.
+     */
+    public static void emitTypeWithParens(String type) {
+        emitTypeWithParens(type, true);
+    }
+
+    /** 
+     * Emit a string which is a type by surrounding it with &lt;code&gt; tags.
+     * Also surround it with parentheses too. Used to display methods' 
+     * parameters.
+     */
+    public static void emitTypeWithParens(String type, boolean addBreaks) {
+        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>)");
+            }
+        }
+    }
+
+    /** 
+     * Emit a string which is a type by surrounding it with &lt;code&gt; tags.
+     * Do not surround it with parentheses. Used to display methods' return
+     * types and field types.
+     */
+    public static void emitTypeWithNoParens(String type) {
+        if (type.compareTo("") != 0)
+            reportFile.print("<code>" + type + "</code>");
+    }
+
+    /** 
+     * Return a String with the simple names of the classes in fqName.
+     * &quot;java.lang.String&quot; becomes &quot;String&quot;, 
+     * &quotjava.lang.String, java.io.File&quot becomes &quotString, File&quot;
+     * and so on. If fqName is null, return null. If fqName is &quot;&quot;, 
+     * return &quot;&quot;. 
+     */
+    public static String simpleName(String fqNames) {
+        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 += "&lt;" + genericParameter + "&gt;";            
+        }
+        return res;
+    }
+
+    /** 
+     * 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
+     */
+    public void emitComment(String commentID, String possibleComment, 
+                            int linkType) {
+        if (noCommentsOnRemovals && linkType == 0) {
+            reportFile.println("  <TD>&nbsp;</TD>");
+            return;
+        }
+        if (noCommentsOnAdditions && linkType == 1) {
+            reportFile.println("  <TD>&nbsp;</TD>");
+            return;
+        }
+        if (noCommentsOnChanges && linkType == 2) {
+            reportFile.println("  <TD>&nbsp;</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>&nbsp;</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);
+    }
+    
+    /** Write the end of a table. */
+    public void writeTableEnd() {
+        reportFile.println("</TABLE>");
+        reportFile.println("&nbsp;");
+    } 
+
+    /** Write a newline out. */
+    public void writeText() {
+        reportFile.println();
+    } 
+
+    /** Write some text out. */
+    public void writeText(String text) {
+        reportFile.println(text);
+    } 
+
+    /** Emit some non-breaking space for indentation. */
+    public void indent(int indent) {
+        for (int i = 0; i < indent; i++)
+            reportFile.print("&nbsp;");
+    } 
+
+    /** 
+     * 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 reportFileName = "changes";
+    
+    /** 
+     * The suffix of the file to which the HTML output is currently being 
+     * written. 
+     */
+    static String reportFileExt = ".html";
+    
+    /** 
+     * The file to which the HTML output is currently being written. 
+     */
+    static PrintWriter reportFile = null;
+
+    /** 
+     * The object which represents the top of the tree of differences
+     * between two APIs. It is only used indirectly when emitting a
+     * navigation bar.
+     */
+    static APIDiff apiDiff = null;
+
+    /** 
+     * If set, then do not suggest comments for removals from the first 
+     * sentence of the doc block of the old API. 
+     */
+    public static boolean noCommentsOnRemovals = false;
+
+    /** 
+     * If set, then do not suggest comments for additions from the first 
+     * sentence of the doc block of the new API. 
+     */
+    public static boolean noCommentsOnAdditions = false;
+
+    /** 
+     * If set, then do not suggest comments for changes from the first 
+     * sentence of the doc block of the new API. 
+     */
+    public static boolean noCommentsOnChanges = false;
+
+    /** 
+     * 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 boolean reportDocChanges = false;
+
+    /** 
+     * 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 newDocPrefix = "../";
+
+    /** 
+     * Define the prefix for HTML links to the existing set of Javadoc-
+     * generated documentation for the old API.
+     */
+    public static String oldDocPrefix = null;
+
+    /** To generate statistical output, set this to true. */
+    public static boolean doStats = false;
+
+    /** 
+     * The destination directory for output files.
+     */
+    public static String outputDir = null;
+    
+    /** 
+     * The destination directory for comments files (if not specified, uses outputDir)
+     */
+    public static String commentsDir = null;
+
+    /** 
+     * The title used on the first page of the report. By default, this is 
+     * &quot;API Differences Between &lt;name of old API&gt; and 
+     * &lt;name of new API&gt;&quot;. It can be
+     * set by using the -doctitle option.
+     */
+    public static String docTitle = null;
+
+    /** 
+     * The browser window title for the report. By default, this is 
+     * &quot;API Differences Between &lt;name of old API&gt; and 
+     * &lt;name of new API&gt;&quot;. It can be
+     * set by using the -windowtitle option.
+     */
+    public static String windowTitle = null;
+
+    /** The desired background color for JDiff tables. */
+    static final String bgcolor = "#FFFFFF";
+
+    /** Set to enable debugging output. */
+    private static final boolean trace = false;
+
+}
diff --git a/src/jdiff/HTMLStatistics.java b/src/jdiff/HTMLStatistics.java
new file mode 100755
index 0000000..6dada5c
--- /dev/null
+++ b/src/jdiff/HTMLStatistics.java
@@ -0,0 +1,491 @@
+package jdiff;
+
+import java.util.*;
+import java.io.*;
+import java.text.*;
+
+/**
+ * Emit an HTML file containing statistics about the differences.
+ * Statistical information only appears if the -stats argument is used.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+
+public class HTMLStatistics {
+
+    /** Constructor. */
+    public HTMLStatistics(HTMLReportGenerator h) {
+        h_ = h;
+    }   
+
+    /** The HTMLReportGenerator instance used to write HTML. */
+    private HTMLReportGenerator h_ = null;
+
+    /** 
+     * Emit the statistics HTML file.
+     */
+
+    public void emitStatistics(String filename, APIDiff apiDiff) {
+        try {
+            FileOutputStream fos = new FileOutputStream(filename);
+            h_.reportFile = new PrintWriter(fos);
+            // Write out the HTML header
+            h_.writeStartHTMLHeader();
+            String oldAPIName = "Old API";
+            if (apiDiff.oldAPIName_ != null)
+                oldAPIName = apiDiff.oldAPIName_;
+            String newAPIName = "New API";
+            if (apiDiff.newAPIName_ != null)
+                newAPIName = apiDiff.newAPIName_;
+            // Write out the title
+            h_.writeHTMLTitle("API Change Statistics");
+            h_.writeStyleSheetRef();
+            h_.writeText("</HEAD>");
+            h_.writeText("<body class=\"gc-documentation\">");
+
+           // writeText("<div class=\"g-section g-tpl-180\">");
+           // Add the nav bar for the summary page
+            
+            
+            // Write a customized navigation bar for the statistics page
+            h_.writeText("<!-- Start of nav bar -->");
+
+            SimpleDateFormat formatter
+              = new SimpleDateFormat ("yyyy.MM.dd HH:mm");
+            Date day = new Date();
+
+	    h_.writeText("<div id=\"gc-container\" style=\"padding-left:1em;padding-right:1em;\">");
+	    h_.writeText("<a name=\"top\"></a>");
+	    h_.writeText("<div id=\"gc-header\">");
+	    h_.writeText("  <div id=\"logo\" style=\"padding-left:1em;\">");
+	    h_.writeText("    <a href=\"../../../documentation.html\" target=\"_top\"><img style=\"border: 0;\" src=\"../../../assets-google/android-logo-sm.gif\" \"/></a>");
+	    h_.writeText("  </div> <!-- End logo -->");
+
+            h_.writeText("<div class=\"and-diff-id\">");
+            h_.writeText("<table class=\"diffspectable\">");
+            h_.writeText("<tr>");
+            h_.writeText("  <td colspan=\"2\" class=\"diffspechead\">API Diff Specification</td>");
+            h_.writeText("</tr>");
+	    h_.writeText("      <tr>");
+	    h_.writeText("        <td class=\"diffspec\">To Version:</td>");
+	    h_.writeText("        <td class=\"diffvaluenew\">" + newAPIName + "</td>");
+	    h_.writeText("      </tr>");
+	    h_.writeText("      <tr>");
+	    h_.writeText("        <td class=\"diffspec\">From Version:</td>");
+	    h_.writeText("        <td class=\"diffvalueold\">" + oldAPIName + "</td>");
+	    h_.writeText("      </tr>");
+            h_.writeText("<tr>");
+            h_.writeText("  <td class=\"diffspec\">Generated</td>");
+            h_.writeText("  <td class=\"diffvalue\">" + formatter.format( day ) + "</td>");
+            h_.writeText("</tr>");
+            h_.writeText("</table>");
+	    h_.writeText("  </div> <!-- End and-diff-id -->");
+
+	    h_.writeText("  <div class=\"and-diff-id\">");
+	    h_.writeText("    <table class=\"diffspectable\">");
+	    h_.writeText("      <tr>");
+	    h_.writeText("        <td class=\"diffspec\" colspan=\"2\"><a href=\"../changes.html\" target=\"_top\">Top of Report</a></div>");
+	    h_.writeText("      </tr>");
+ 	    h_.writeText("    </table>");
+	    h_.writeText("  </div> <!-- End and-diff-id -->");
+
+	    h_.writeText("</div> <!-- End gc-header -->");
+	    h_.writeText("<div id=\"codesiteContent\" style=\"margin-top: 70px;margin-bottom:80px;\">");
+
+            // Write the title in the body with some formatting
+            h_.writeText("<div style=\"xborder:1px solid yellow;vertical-align:top;padding:1em;margin-left:0;text-align:left;\">");
+            h_.writeText(" <H1 class=\"pagecontenth1\">API&nbsp;Change&nbsp;Statistics</H1>");
+            h_.writeText("</div>");
+
+
+            h_.writeText("<p>");
+            h_.writeText("The percent change statistic reported for all elements in each API is defined recursively as follows:</p>");
+            h_.writeText("<pre>"); 
+            h_.writeText("Percentage difference = 100 * (added + removed + 2*changed)");
+            h_.writeText("                        -----------------------------------");
+            h_.writeText("                        sum of public elements in BOTH APIs");
+            h_.writeText("</pre>"); 
+            h_.writeText("<p>where <code>added</code> is the number of packages added, <code>removed</code> is the number of packages removed, and <code>changed</code> is the number of packages changed.");
+            h_.writeText("This definition is applied recursively for the classes and their program elements, so the value for a changed package will be less than 1, unless every class in that package has changed.");
+            h_.writeText("The definition ensures that if all packages are removed and all new packages are");
+            h_.writeText("added, the change will be 100%. Values are rounded here, so a value of 0% indicates a percentage difference of less than 0.5%.</p>");
+
+            h_.writeText("<p>The overall difference between the two APIs is approximately <span style=\"color:222;font-weight:bold;\">" + (int)(apiDiff.pdiff) + "%</span>.");
+            h_.writeText("</p>");
+
+            h_.writeText("<br><h2 class=\"pagecontenth2\">Contents</h2>");
+            h_.writeText("<dl><dt><a href=\"#packages\">Changed Packages</a></dt> <dd>Sorted by percentage difference</dd>");
+            h_.writeText("<dt><a href=\"#classes\">Changed Classes and <i>Interfaces</i></a></dt><dd>Sorted by percentage difference</dd>");
+            h_.writeText("<dt><a href=\"#numbers\">Total of Differences</a></dt><dd>Listed by number and type</dd></dl>");
+
+            h_.writeText("<br>");
+            h_.writeText("<a name=\"packages\"></a>");
+            h_.writeText("<h2 class=\"pagecontenth2\">Changed Packages, Sorted by Percentage Difference</h2>");
+            emitPackagesByDiff(apiDiff);
+
+            h_.writeText("<br>");
+            h_.writeText("<a name=\"classes\"></a>");
+            h_.writeText("<h2 class=\"pagecontenth2\">Changed Classes and <i>Interfaces</i>, Sorted by Percentage Difference</h2>");
+            emitClassesByDiff(apiDiff);
+
+            h_.writeText("<br>");
+            h_.writeText("<a name=\"numbers\"></a>");
+            h_.writeText("<h2 class=\"pagecontenth2\">Total of Differences, by Number and Type</h2>");
+            h_.writeText("<p>");
+            h_.writeText("The table below lists the numbers of program elements (packages, classes, constructors, methods, and fields) that were removed, added or changed. The table includes only the highest-level program elements &mdash; that is, if a class with two methods was added, the number of methods added does not include those two methods, but the number of classes added does include that class.");
+            h_.writeText("</p>");
+
+            emitNumbersByElement(apiDiff);
+            
+	    h_.writeText("</div><!-- end codesitecontent -->");
+            h_.writeText("<div style=\"padding-left: 10px; padding-right: 10px; margin-top: 0; padding-bottom: 15px;\">");
+            h_.writeText("  <table style=\"width: 100%; border: none;\"><tr>");
+            h_.writeText("    <td style=\"text-align:center;font-size: 10pt; border: none; color: ccc;\"> ");
+            h_.writeText("      <span>&copy;2008 Google - ");
+            h_.writeText("            <a href=\"http://code.google.com\">Code Home</a> - ");
+            h_.writeText("            <a href=\"http://www.google.com/accounts/TOS\">Site Terms of Sservice</a> - "); 
+            h_.writeText("            <a href=\"http://www.google.com/privacy.html\">Privacy Policy</a> ");
+            h_.writeText("      </span>");
+            h_.writeText("      <div style=\"xborder 1px solid red;position:relative;margin-top:-2em;" );
+            h_.writeText("        font-size:8pt;color:aaa;text-align:right;\">");
+            h_.writeText("        <em>Generated by <a href=\"http://www.jdiff.org/\">JDiff</a></em><br><img ");
+            h_.writeText("        align=\"right\" src=\"../../../assets/jdiff_logo.gif\">");
+            h_.writeText("      </span>");
+            h_.writeText("    </td>");
+            h_.writeText(" </tr></table>");
+            h_.writeText("</div>");
+            h_.writeText("</div><!-- end gc-containter -->");
+
+            h_.writeText("<script src=\"http://www.google-analytics.com/ga.js\" type=\"text/javascript\">");
+            h_.writeText("</script>");
+            h_.writeText("<script type=\"text/javascript\">");
+            h_.writeText("  try {");
+            h_.writeText("    var pageTracker = _gat._getTracker(\"UA-18071-1\");");
+            h_.writeText("    pageTracker._setAllowAnchor(true);");
+            h_.writeText("    pageTracker._initData();");
+            h_.writeText("    pageTracker._trackPageview();");
+            h_.writeText("  } catch(e) {}");
+            h_.writeText("</script>");
+
+            h_.writeText("</BODY></HTML>");
+            h_.reportFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + filename);
+            System.out.println("Error: " + e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Emit all packages sorted by percentage difference, and a histogram
+     * of the values.
+     */
+    public void emitPackagesByDiff(APIDiff apiDiff) {
+        
+        Collections.sort(apiDiff.packagesChanged, new ComparePkgPdiffs());
+
+        // Write out the table start
+        h_.writeText("<TABLE summary=\"Packages sorted by percentage difference\" BORDER=\"1\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
+        h_.writeText("<TR WIDTH=\"20%\">");
+        h_.writeText("  <TH>Percentage<br>Difference</TH>");
+        h_.writeText("  <TH>Package</TH>");
+        h_.writeText("</TR>");
+
+        int[] hist = new int[101];
+        for (int i = 0; i < 101; i++) {
+            hist[i] = 0;
+        }
+
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkg = (PackageDiff)(iter.next());
+            int bucket = (int)(pkg.pdiff);
+            hist[bucket]++;
+            h_.writeText("<TR>");
+            if (bucket != 0)
+                h_.writeText("  <TD ALIGN=\"center\">" + bucket + "</TD>");
+            else
+                h_.writeText("  <TD ALIGN=\"center\">&lt;1</TD>");
+            h_.writeText("  <TD><A HREF=\"pkg_" + pkg.name_ + h_.reportFileExt + "\">" + pkg.name_ + "</A></TD>");
+            h_.writeText("</TR>");
+        }
+
+        h_.writeText("</TABLE>");
+        
+        /* Emit the histogram of the results
+        h_.writeText("<hr>");
+        h_.writeText("<p><a name=\"packages_hist\"></a>");
+        h_.writeText("<TABLE summary=\"Histogram of the package percentage differences\" BORDER=\"1\" cellspacing=\"0\" cellpadding=\"0\">");
+        h_.writeText("<TR>");
+        h_.writeText("  <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage<br>Difference</b></FONT></TD>");
+        h_.writeText("  <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Frequency</b></FONT></TD>");
+        h_.writeText("  <TD width=\"300\" ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage Frequency</b></FONT></TD>");
+        h_.writeText("</TR>");
+
+        double total = 0;
+        for (int i = 0; i < 101; i++) {
+            total += hist[i];
+        }
+        for (int i = 0; i < 101; i++) {
+            if (hist[i] != 0) {
+                h_.writeText("<TR>");
+                h_.writeText("  <TD ALIGN=\"center\">" + i + "</TD>");
+                h_.writeText("  <TD>" + (hist[i]/total) + "</TD>");
+                h_.writeText("  <TD><img alt=\"|\" src=\"../black.gif\" height=20 width=" + (hist[i]*300/total) + "></TD>");
+                h_.writeText("</TR>");
+            }
+        }
+        // Repeat the data in a format which is easier for spreadsheets
+        h_.writeText("<!-- START_PACKAGE_HISTOGRAM");
+        for (int i = 0; i < 101; i++) {
+            if (hist[i] != 0) {
+                h_.writeText(i + "," + (hist[i]/total));
+            }
+        }
+        h_.writeText("END_PACKAGE_HISTOGRAM -->");
+        
+        h_.writeText("</TABLE>");
+	*/
+    }
+
+    /**
+     * Emit all classes sorted by percentage difference, and a histogram
+     * of the values..
+     */
+    public void emitClassesByDiff(APIDiff apiDiff) {
+        // Add all the changed classes to a list
+        List allChangedClasses = new ArrayList();
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkg = (PackageDiff)(iter.next());
+            if (pkg.classesChanged != null) {
+                // Add the package name to the class name
+                List cc = new ArrayList(pkg.classesChanged);
+                Iterator iter2 = cc.iterator();
+                while (iter2.hasNext()) {
+                    ClassDiff classDiff = (ClassDiff)(iter2.next());
+                    classDiff.name_ = pkg.name_ + "." + classDiff.name_;
+                }
+                allChangedClasses.addAll(cc);
+            }
+        }
+        Collections.sort(allChangedClasses, new CompareClassPdiffs());
+
+        // Write out the table start
+        h_.writeText("<TABLE summary=\"Classes sorted by percentage difference\" BORDER=\"1\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
+        h_.writeText("<TR WIDTH=\"20%\">");
+        h_.writeText("  <TH><b>Percentage<br>Difference</b></TH>");
+        h_.writeText("  <TH><b>Class or <i>Interface</i></b></TH>");
+        h_.writeText("</TR>");
+
+        int[] hist = new int[101];
+        for (int i = 0; i < 101; i++) {
+            hist[i] = 0;
+        }
+
+        iter = allChangedClasses.iterator();
+        while (iter.hasNext()) {
+            ClassDiff classDiff = (ClassDiff)(iter.next());
+            int bucket = (int)(classDiff.pdiff);
+            hist[bucket]++;
+            h_.writeText("<TR>");
+            if (bucket != 0)
+                h_.writeText("  <TD ALIGN=\"center\">" + bucket + "</TD>");
+            else
+                h_.writeText("  <TD ALIGN=\"center\">&lt;1</TD>");
+            h_.writeText("  <TD><A HREF=\"" + classDiff.name_ + h_.reportFileExt + "\">");
+            if (classDiff.isInterface_)
+                h_.writeText("<i>" + classDiff.name_ + "</i></A></TD>");
+            else
+                h_.writeText(classDiff.name_ + "</A></TD>");
+            h_.writeText("</TR>");
+        }
+
+        h_.writeText("</TABLE>");
+
+        /* Emit the histogram of the results
+        h_.writeText("<hr>");
+        h_.writeText("<p><a name=\"classes_hist\"></a>");
+        h_.writeText("<TABLE summary=\"Histogram of the class percentage differences\" BORDER=\"1\" cellspacing=\"0\" cellpadding=\"0\">");
+        h_.writeText("<TR>");
+        h_.writeText("  <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage<br>Difference</b></FONT></TD>");
+        h_.writeText("  <TD ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Frequency</b></FONT></TD>");
+        h_.writeText("  <TD width=\"300\" ALIGN=\"center\" bgcolor=\"#EEEEFF\"><FONT size=\"+1\"><b>Percentage Frequency</b></FONT></TD>");
+        h_.writeText("</TR>");
+
+        double total = 0;
+        for (int i = 0; i < 101; i++) {
+            total += hist[i];
+        }
+        for (int i = 0; i < 101; i++) {
+            if (hist[i] != 0) {
+                h_.writeText("<TR>");
+                h_.writeText("  <TD ALIGN=\"center\">" + i + "</TD>");
+                h_.writeText("  <TD>" + (hist[i]/total) + "</TD>");
+                h_.writeText("  <TD><img alt=\"|\" src=\"../black.gif\" height=20 width=" + (hist[i]*300/total) + "></TD>");
+                h_.writeText("</TR>");
+            }
+        }
+        // Repeat the data in a format which is easier for spreadsheets
+        h_.writeText("<!-- START_CLASS_HISTOGRAM");
+        for (int i = 0; i < 101; i++) {
+            if (hist[i] != 0) {
+                h_.writeText(i + "," + (hist[i]/total));
+            }
+        }
+        h_.writeText("END_CLASS_HISTOGRAM -->");
+        
+        h_.writeText("</TABLE>");
+	*/
+    }
+
+    /**
+     * Emit a table of numbers of removals, additions and changes by
+     * package, class, constructor, method and field.
+     */
+    public void emitNumbersByElement(APIDiff apiDiff) {
+
+        // Local variables to hold the values
+        int numPackagesRemoved = apiDiff.packagesRemoved.size();
+        int numPackagesAdded = apiDiff.packagesAdded.size();
+        int numPackagesChanged = apiDiff.packagesChanged.size();
+
+        int numClassesRemoved = 0;
+        int numClassesAdded = 0;
+        int numClassesChanged = 0;
+
+        int numCtorsRemoved = 0;
+        int numCtorsAdded = 0;
+        int numCtorsChanged = 0;
+
+        int numMethodsRemoved = 0;
+        int numMethodsAdded = 0;
+        int numMethodsChanged = 0;
+
+        int numFieldsRemoved = 0;
+        int numFieldsAdded = 0;
+        int numFieldsChanged = 0;
+
+        int numRemoved = 0;
+        int numAdded = 0;
+        int numChanged = 0;
+
+        // Calculate the values
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkg = (PackageDiff)(iter.next());
+            numClassesRemoved += pkg.classesRemoved.size();
+            numClassesAdded += pkg.classesAdded.size();
+            numClassesChanged += pkg.classesChanged.size();
+
+            Iterator iter2 = pkg.classesChanged.iterator();
+            while (iter2.hasNext()) {
+                 ClassDiff classDiff = (ClassDiff)(iter2.next());
+                 numCtorsRemoved += classDiff.ctorsRemoved.size();
+                 numCtorsAdded += classDiff.ctorsAdded.size();
+                 numCtorsChanged += classDiff.ctorsChanged.size();
+                 
+                 numMethodsRemoved += classDiff.methodsRemoved.size();
+                 numMethodsAdded += classDiff.methodsAdded.size();
+                 numMethodsChanged += classDiff.methodsChanged.size();
+                 
+                 numFieldsRemoved += classDiff.fieldsRemoved.size();
+                 numFieldsAdded += classDiff.fieldsAdded.size();
+                 numFieldsChanged += classDiff.fieldsChanged.size();
+            }
+        }
+        
+        // Write out the table
+        h_.writeText("<TABLE summary=\"Number of differences\" BORDER=\"1\" WIDTH=\"100%\" cellspacing=\"0\" cellpadding=\"0\">");
+        h_.writeText("<TR>");
+        h_.writeText("  <TH COLSPAN=5 NOWRAP>");
+        h_.writeText("  Number of Differences</TH>");
+        h_.writeText("</TR>");
+        h_.writeText("<TR>");
+        h_.writeText("  <TH>&nbsp;</TD>");
+        h_.writeText("  <TH ALIGN=\"center\"><b>Removals</b></TH>");
+        h_.writeText("  <TH ALIGN=\"center\"><b>Additions</b></TH>");
+        h_.writeText("  <TH ALIGN=\"center\"><b>Changes</b></TH>");
+        h_.writeText("  <TH ALIGN=\"center\"><b>Total</b></TH>");
+        h_.writeText("</TR>");
+
+        h_.writeText("<TR>");
+        h_.writeText("  <TD>Packages</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numPackagesRemoved + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numPackagesAdded + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numPackagesChanged + "</TD>");
+        int numPackages = numPackagesRemoved + numPackagesAdded + numPackagesChanged;
+        h_.writeText("  <TD ALIGN=\"right\">" + numPackages + "</TD>");
+        h_.writeText("</TR>");
+
+        numRemoved += numPackagesRemoved;
+        numAdded += numPackagesAdded;
+        numChanged += numPackagesChanged;
+
+        h_.writeText("<TR>");
+        h_.writeText("  <TD>Classes and <i>Interfaces</i></TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numClassesRemoved + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numClassesAdded + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numClassesChanged + "</TD>");
+        int numClasses = numClassesRemoved + numClassesAdded + numClassesChanged;
+        h_.writeText("  <TD ALIGN=\"right\">" + numClasses + "</TD>");
+        h_.writeText("</TR>");
+
+        numRemoved += numClassesRemoved;
+        numAdded += numClassesAdded;
+        numChanged += numClassesChanged;
+
+        h_.writeText("<TR>");
+        h_.writeText("  <TD>Constructors</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numCtorsRemoved + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numCtorsAdded + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numCtorsChanged + "</TD>");
+        int numCtors = numCtorsRemoved + numCtorsAdded + numCtorsChanged;
+        h_.writeText("  <TD ALIGN=\"right\">" + numCtors + "</TD>");
+        h_.writeText("</TR>");
+
+        numRemoved += numCtorsRemoved;
+        numAdded += numCtorsAdded;
+        numChanged += numCtorsChanged;
+
+        h_.writeText("<TR>");
+        h_.writeText("  <TD>Methods</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numMethodsRemoved + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numMethodsAdded + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numMethodsChanged + "</TD>");
+        int numMethods = numMethodsRemoved + numMethodsAdded + numMethodsChanged;
+        h_.writeText("  <TD ALIGN=\"right\">" + numMethods + "</TD>");
+        h_.writeText("</TR>");
+
+        numRemoved += numMethodsRemoved;
+        numAdded += numMethodsAdded;
+        numChanged += numMethodsChanged;
+
+        h_.writeText("<TR>");
+        h_.writeText("  <TD>Fields</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numFieldsRemoved + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numFieldsAdded + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numFieldsChanged + "</TD>");
+        int numFields = numFieldsRemoved + numFieldsAdded + numFieldsChanged;
+        h_.writeText("  <TD ALIGN=\"right\">" + numFields + "</TD>");
+        h_.writeText("</TR>");
+
+        numRemoved += numFieldsRemoved;
+        numAdded += numFieldsAdded;
+        numChanged += numFieldsChanged;
+
+        h_.writeText("<TR>");
+        h_.writeText("  <TD><b>Total</b></TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numRemoved + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numAdded + "</TD>");
+        h_.writeText("  <TD ALIGN=\"right\">" + numChanged + "</TD>");
+        int total = numRemoved + numAdded + numChanged;
+        h_.writeText("  <TD ALIGN=\"right\">" + total + "</TD>");
+        h_.writeText("</TR>");
+
+        h_.writeText("</TABLE>");
+    }
+
+}
+
diff --git a/src/jdiff/JDiff.java b/src/jdiff/JDiff.java
new file mode 100755
index 0000000..14a7a97
--- /dev/null
+++ b/src/jdiff/JDiff.java
@@ -0,0 +1,297 @@
+package jdiff;
+
+import com.sun.javadoc.*;
+
+import java.util.*;
+import java.io.*;
+import java.lang.reflect.*; // Used for invoking Javadoc indirectly
+import java.lang.Runtime;
+import java.lang.Process;
+
+/**
+ * Generates HTML describing the changes between two sets of Java source code.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com.
+ */
+public class JDiff extends Doclet {
+
+    public static LanguageVersion languageVersion(){
+      return LanguageVersion.JAVA_1_5;
+    } 
+    /**
+     * Doclet-mandated start method. Everything begins here.
+     *
+     * @param root  a RootDoc object passed by Javadoc
+     * @return true if document generation succeeds
+     */
+    public static boolean start(RootDoc root) {
+        if (root != null)
+            System.out.println("JDiff: doclet started ...");
+        JDiff jd = new JDiff();
+        return jd.startGeneration(root);
+    }
+
+    /**
+     * Generate the summary of the APIs.
+     *
+     * @param root  the RootDoc object passed by Javadoc
+     * @return true if no problems encountered within JDiff
+     */
+    protected boolean startGeneration(RootDoc newRoot) {
+        long startTime = System.currentTimeMillis();
+
+        // Open the file where the XML representing the API will be stored.
+        // and generate the XML for the API into it.
+        if (writeXML) {
+            RootDocToXML.writeXML(newRoot);           
+        }
+
+        if (compareAPIs) {
+	    String tempOldFileName = oldFileName;
+	    if (oldDirectory != null) {
+		tempOldFileName = oldDirectory;
+		if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) {
+		    tempOldFileName += JDiff.DIR_SEP;
+		}
+		tempOldFileName += oldFileName;
+	    }
+
+            // Check the file for the old API exists
+            File f = new File(tempOldFileName);
+            if (!f.exists()) {
+                System.out.println("Error: file '" + tempOldFileName + "' does not exist for the old API");
+                return false;
+            }
+            // Check the file for the new API exists
+
+	    String tempNewFileName = newFileName;
+            if (newDirectory != null) {
+		tempNewFileName = newDirectory;
+		if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) {
+		    tempNewFileName += JDiff.DIR_SEP;
+		}
+		tempNewFileName += newFileName;
+            }
+            f = new File(tempNewFileName);
+            if (!f.exists()) {
+                System.out.println("Error: file '" + tempNewFileName + "' does not exist for the new API");
+                return false;
+            }
+
+            // Read the file where the XML representing the old API is stored
+            // and create an API object for it.
+            System.out.print("JDiff: reading the old API in from file '" + tempOldFileName + "'...");
+            // Read the file in, but do not add any text to the global comments
+            API oldAPI = XMLToAPI.readFile(tempOldFileName, false, oldFileName);
+            
+            // Read the file where the XML representing the new API is stored
+            // and create an API object for it.
+            System.out.print("JDiff: reading the new API in from file '" + tempNewFileName + "'...");
+            // Read the file in, and do add any text to the global comments
+            API newAPI = XMLToAPI.readFile(tempNewFileName, true, newFileName);
+            
+            // Compare the old and new APIs.
+            APIComparator comp = new APIComparator();
+            
+            comp.compareAPIs(oldAPI, newAPI);
+            
+            // Read the file where the XML for comments about the changes between
+            // the old API and new API is stored and create a Comments object for 
+            // it. The Comments object may be null if no file exists.
+            int suffix = oldFileName.lastIndexOf('.');
+            String commentsFileName = "user_comments_for_" + oldFileName.substring(0, suffix);
+            suffix = newFileName.lastIndexOf('.');
+            commentsFileName += "_to_" + newFileName.substring(0, suffix) + ".xml";
+            commentsFileName = commentsFileName.replace(' ', '_');
+                if (HTMLReportGenerator.commentsDir !=null) {
+                  commentsFileName = HTMLReportGenerator.commentsDir + DIR_SEP + commentsFileName;
+                } else if (HTMLReportGenerator.outputDir != null) {
+                  commentsFileName = HTMLReportGenerator.outputDir + DIR_SEP + commentsFileName;
+                }
+            System.out.println("JDiff: reading the comments in from file '" + commentsFileName + "'...");
+            Comments existingComments = Comments.readFile(commentsFileName);
+            if (existingComments == null)
+                System.out.println(" (the comments file will be created)");
+            
+            // Generate an HTML report which summarises all the API differences.
+            HTMLReportGenerator reporter = new HTMLReportGenerator();
+            reporter.generate(comp, existingComments);
+            
+            // Emit messages about which comments are now unused and
+            // which are new.
+            Comments newComments = reporter.getNewComments();
+            Comments.noteDifferences(existingComments, newComments);
+            
+            // Write the new comments out to the same file, with unused comments
+            // now commented out.
+            System.out.println("JDiff: writing the comments out to file '" + commentsFileName + "'...");
+            Comments.writeFile(commentsFileName, newComments);
+        }
+
+        System.out.print("JDiff: finished (took " + (System.currentTimeMillis() - startTime)/1000 + "s");
+        if (writeXML)
+            System.out.println(", not including scanning the source files)."); 
+        else if (compareAPIs)
+            System.out.println(").");
+       return true;
+    }
+
+//
+// Option processing
+// 
+
+    /**
+     * This method is called by Javadoc to
+     * parse the options it does not recognize. It then calls
+     * {@link #validOptions} to validate them.
+     *
+     * @param option  a String containing an option
+     * @return an int telling how many components that option has
+     */
+    public static int optionLength(String option) {
+        return Options.optionLength(option);
+    }
+
+    /**
+     * After parsing the available options using {@link #optionLength},
+     * Javadoc invokes this method with an array of options-arrays.
+     *
+     * @param options   an array of String arrays, one per option
+     * @param reporter  a DocErrorReporter for generating error messages
+     * @return true if no errors were found, and all options are
+     *         valid
+     */
+    public static boolean validOptions(String[][] options, 
+                                       DocErrorReporter reporter) {
+        return Options.validOptions(options, reporter);
+    }
+    
+    /** 
+     * This method is only called when running JDiff as a standalone
+     * application, and uses ANT to execute the build configuration in the 
+     * XML configuration file passed in.
+     */
+    public static void main(String[] args) {
+        if (args.length == 0) {
+            //showUsage();
+            System.out.println("Looking for a local 'build.xml' configuration file");
+        } else if (args.length == 1) {
+            if (args[0].compareTo("-help") == 0 ||
+                args[0].compareTo("-h") == 0 ||
+                args[0].compareTo("?") == 0) {
+                showUsage();
+            } else if (args[0].compareTo("-version") == 0) {
+                System.out.println("JDiff version: " + JDiff.version);
+            }
+            return;
+        }
+        int rc = runAnt(args);
+        return;
+    }
+
+    /** 
+     * Display usage information for JDiff.
+     */
+    public static void showUsage() {
+        System.out.println("usage: java jdiff.JDiff [-version] [-buildfile <XML configuration file>]");
+        System.out.println("If no build file is specified, the local build.xml file is used.");
+    }
+
+    /** 
+     * Invoke ANT by reflection. 
+     *
+     * @return The integer return code from running ANT.
+     */
+    public static int runAnt(String[] args) {
+        String className = null;
+        Class c = null;
+        try {
+            className = "org.apache.tools.ant.Main";
+            c = Class.forName(className);
+        } catch (ClassNotFoundException e1) {
+            System.err.println("Error: ant.jar not found on the classpath");
+            return -1;
+        }
+        try {
+            Class[] methodArgTypes = new Class[1];
+            methodArgTypes[0] = args.getClass();
+            Method mainMethod = c.getMethod("main", methodArgTypes);
+            Object[] methodArgs = new Object[1];
+            methodArgs[0] = args;
+            // The object can be null because the method is static
+            Integer res = (Integer)mainMethod.invoke(null, methodArgs);
+            System.gc(); // Clean up after running ANT
+            return res.intValue();
+        } catch (NoSuchMethodException e2) {
+            System.err.println("Error: method \"main\" not found");
+            e2.printStackTrace();
+        } catch (IllegalAccessException e4) {
+            System.err.println("Error: class not permitted to be instantiated");
+            e4.printStackTrace();
+        } catch (InvocationTargetException e5) {
+            System.err.println("Error: method \"main\" could not be invoked");
+            e5.printStackTrace();
+        } catch (Exception e6) {
+            System.err.println("Error: ");
+            e6.printStackTrace();
+        }
+        System.gc(); // Clean up after running ANT
+        return -1;
+    }
+
+    /** 
+     * The name of the file where the XML representing the old API is
+     * stored. 
+     */
+    static String oldFileName = "old_java.xml";
+
+    /** 
+     * The name of the directory where the XML representing the old API is
+     * stored. 
+     */
+    static String oldDirectory = null;
+
+    /** 
+     * The name of the file where the XML representing the new API is 
+     * stored. 
+     */
+    static String newFileName = "new_java.xml";
+
+    /** 
+     * The name of the directory where the XML representing the new API is 
+     * stored. 
+     */
+    static String newDirectory = null;
+
+    /** If set, then generate the XML for an API and exit. */
+    static boolean writeXML = false;
+
+    /** If set, then read in two XML files and compare their APIs. */
+    static boolean compareAPIs = false;
+
+    /** 
+     * The file separator for the local filesystem, forward or backward slash. 
+     */
+    static String DIR_SEP = System.getProperty("file.separator");
+
+    /** Details for where to find JDiff. */
+    static final String jDiffLocation = "http://www.jdiff.org";
+    /** Contact email address for the primary JDiff maintainer. */
+    static final String authorEmail = "mdoar@pobox.com";
+
+    /** A description for HTML META tags. */
+    static final String jDiffDescription = "JDiff is a Javadoc doclet which generates an HTML report of all the packages, classes, constructors, methods, and fields which have been removed, added or changed in any way, including their documentation, when two APIs are compared.";
+    /** Keywords for HTML META tags. */
+    static final String jDiffKeywords = "diff, jdiff, javadiff, java diff, java difference, API difference, difference between two APIs, API diff, Javadoc, doclet";
+
+    /** The current JDiff version. */
+    static final String version = "1.1.0";
+
+    /** The current virtual machine version. */
+    static String javaVersion = System.getProperty("java.version");
+
+    /** Set to enable increased logging verbosity for debugging. */
+    private static boolean trace = false;
+
+} //JDiff
diff --git a/src/jdiff/JDiffAntTask.java b/src/jdiff/JDiffAntTask.java
new file mode 100755
index 0000000..e5b14ac
--- /dev/null
+++ b/src/jdiff/JDiffAntTask.java
@@ -0,0 +1,536 @@
+package jdiff;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Vector;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Javadoc;
+import org.apache.tools.ant.taskdefs.Javadoc.DocletInfo;
+import org.apache.tools.ant.taskdefs.Javadoc.DocletParam;
+import org.apache.tools.ant.types.FileSet;
+import org.apache.tools.ant.types.DirSet;
+import org.apache.tools.ant.types.Path;
+
+/**
+ * An Ant task to produce a simple JDiff report. More complex reports still
+ * need parameters that are controlled by the Ant Javadoc task.
+ */
+public class JDiffAntTask {
+
+    public void execute() throws BuildException {
+	jdiffHome = project.getProperty("JDIFF_HOME");
+	if (jdiffHome == null || jdiffHome.compareTo("") == 0 | 
+	    jdiffHome.compareTo("(not set)") == 0) {
+	    throw new BuildException("Error: invalid JDIFF_HOME property. Set it in the build file to the directory where jdiff is installed");
+	}
+        project.log(" JDiff home: " + jdiffHome, Project.MSG_INFO);
+
+	jdiffClassPath = jdiffHome + DIR_SEP + "jdiff.jar" +
+	    System.getProperty("path.separator") +
+	    jdiffHome + DIR_SEP + "xerces.jar";
+
+	// TODO detect and set verboseAnt
+
+	// Create, if necessary, the directory for the JDiff HTML report
+        if (!destdir.mkdir() && !destdir.exists()) {
+	    throw new BuildException(getDestdir() + " is not a valid directory");
+	} else {
+	    project.log(" Report location: " + getDestdir() + DIR_SEP 
+			+ "changes.html", Project.MSG_INFO);
+	}
+	// Could also output the other parameters used for JDiff here
+ 	
+	// Check that there are indeed two projects to compare. If there
+	// are no directories in the project, let Javadoc do the complaining
+	if (oldProject == null || newProject == null) {
+	    throw new BuildException("Error: two projects are needed, one <old> and one <new>");
+	}
+
+	/*
+	// Display the directories being compared, and some name information
+	if (getVerbose()) {
+	    project.log("Older version: " + oldProject.getName(), 
+			Project.MSG_INFO);
+	    project.log("Included directories for older version:", 
+			Project.MSG_INFO);
+	    DirectoryScanner ds = 
+		oldProject.getDirset().getDirectoryScanner(project);
+	    String[] files = ds.getIncludedDirectories();
+	    for (int i = 0; i < files.length; i++) {
+		project.log(" " + files[i], Project.MSG_INFO);
+	    }
+	    ds = null;
+	    
+	    project.log("Newer version: " + newProject.getName(), 
+			Project.MSG_INFO);
+	    project.log("Included directories for newer version:", 
+			Project.MSG_INFO);
+	    ds = newProject.getDirset().getDirectoryScanner(project);
+	    files = ds.getIncludedDirectories();
+	    for (int i = 0; i < files.length; i++) {
+		project.log(" " + files[i], Project.MSG_INFO);
+	    }
+	}
+	*/
+
+	// Call Javadoc twice to generate Javadoc for each project
+	generateJavadoc(oldProject);
+	generateJavadoc(newProject);
+
+	// Call Javadoc three times for JDiff.
+	generateXML(oldProject);
+	generateXML(newProject);
+	compareXML(oldProject.getName(), newProject.getName());
+
+	// Repeat some useful information
+	project.log(" Report location: " + getDestdir() + DIR_SEP 
+		    + "changes.html", Project.MSG_INFO);
+    }
+
+    /**
+     * Convenient method to create a Javadoc task, configure it and run it
+     * to generate the XML representation of a project's source files.
+     *
+     * @param proj The current Project
+     */
+    protected void generateXML(ProjectInfo proj) {
+	String apiname = proj.getName();
+	Javadoc jd = initJavadoc("Analyzing " + apiname);
+	jd.setDestdir(getDestdir());
+	addSourcePaths(jd, proj);
+
+	// Tell Javadoc which packages we want to scan. 
+	// JDiff works with packagenames, not sourcefiles.
+	jd.setPackagenames(getPackageList(proj));
+	
+	// Create the DocletInfo first so we have a way to use it to add params
+	DocletInfo dInfo = jd.createDoclet();
+	jd.setDoclet("jdiff.JDiff");
+	jd.setDocletPath(new Path(project, jdiffClassPath));
+	
+	// Now set up some parameters for the JDiff doclet.
+	DocletParam dp1 = dInfo.createParam();
+	dp1.setName("-apiname");
+	dp1.setValue(apiname);
+	DocletParam dp2 = dInfo.createParam();
+	dp2.setName("-baseURI");
+	dp2.setValue("http://www.w3.org");
+	// Put the generated file in the same directory as the report
+	DocletParam dp3 = dInfo.createParam();
+	dp3.setName("-apidir");
+	dp3.setValue(getDestdir().toString());
+	
+	// Execute the Javadoc command to generate the XML file.
+	jd.perform();
+    }
+
+    /**
+     * Convenient method to create a Javadoc task, configure it and run it
+     * to compare the XML representations of two instances of a project's 
+     * source files, and generate an HTML report summarizing the differences.
+     *
+     * @param oldapiname The name of the older version of the project
+     * @param newapiname The name of the newer version of the project
+     */
+    protected void compareXML(String oldapiname, String newapiname) {
+	Javadoc jd = initJavadoc("Comparing versions");
+	jd.setDestdir(getDestdir());
+	jd.setPrivate(true);
+
+	// Tell Javadoc which files we want to scan - a dummy file in this case
+	jd.setSourcefiles(jdiffHome + DIR_SEP + "Null.java");
+	
+	// Create the DocletInfo first so we have a way to use it to add params
+	DocletInfo dInfo = jd.createDoclet();
+	jd.setDoclet("jdiff.JDiff");
+	jd.setDocletPath(new Path(project, jdiffClassPath));
+	
+	// Now set up some parameters for the JDiff doclet.
+	DocletParam dp1 = dInfo.createParam();
+	dp1.setName("-oldapi");
+	dp1.setValue(oldapiname);
+	DocletParam dp2 = dInfo.createParam();
+	dp2.setName("-newapi");
+	dp2.setValue(newapiname);
+	// Get the generated XML files from the same directory as the report
+	DocletParam dp3 = dInfo.createParam();
+	dp3.setName("-oldapidir");
+	dp3.setValue(getDestdir().toString());
+	DocletParam dp4 = dInfo.createParam();
+	dp4.setName("-newapidir");
+	dp4.setValue(getDestdir().toString());
+
+	// Assume that Javadoc reports already exist in ../"apiname"
+	DocletParam dp5 = dInfo.createParam();
+	dp5.setName("-javadocold");
+	dp5.setValue(".." + DIR_SEP + oldapiname + DIR_SEP);
+	DocletParam dp6 = dInfo.createParam();
+	dp6.setName("-javadocnew");
+	dp6.setValue(".." + DIR_SEP + newapiname + DIR_SEP);
+	
+	if (getStats()) {
+	    // There are no arguments to this argument
+	    dInfo.createParam().setName("-stats");
+	    // We also have to copy two image files for the stats pages
+	    copyFile(jdiffHome + DIR_SEP + "black.gif",
+		     getDestdir().toString() + DIR_SEP + "black.gif");
+	    copyFile(jdiffHome + DIR_SEP + "background.gif",
+		     getDestdir().toString() + DIR_SEP + "background.gif");
+	}
+	
+	if (getDocchanges()) {
+	    // There are no arguments to this argument
+	    dInfo.createParam().setName("-docchanges");
+	}
+
+	// Execute the Javadoc command to compare the two XML files
+	jd.perform();
+    }
+
+    /**
+     * Generate the Javadoc for the project. If you want to generate
+     * the Javadoc report for the project with different parameters from the
+     * simple ones used here, then use the Javadoc Ant task directly, and 
+     * set the javadoc attribute to the "old" or "new" element.
+     *
+     * @param proj The current Project
+     */
+    protected void generateJavadoc(ProjectInfo proj) {	
+	String javadoc = proj.getJavadoc();
+	if (javadoc != null && javadoc.compareTo("generated") != 0) {
+	    project.log("Configured to use existing Javadoc located in " +  
+			javadoc, Project.MSG_INFO);
+	    return;
+	}
+
+	String apiname = proj.getName();
+	Javadoc jd = initJavadoc("Javadoc for " + apiname);
+	jd.setDestdir(new File(getDestdir().toString() + DIR_SEP + apiname));
+	addSourcePaths(jd, proj);
+
+	jd.setPrivate(true);
+	jd.setPackagenames(getPackageList(proj));
+
+	// Execute the Javadoc command to generate a regular Javadoc report
+	jd.perform();
+    }
+
+    /**
+     * Create a fresh new Javadoc task object and initialize it.
+     *
+     * @param logMsg String which appears as a prefix in the Ant log
+     * @return The new task.Javadoc object
+     */
+    protected Javadoc initJavadoc(String logMsg) {
+	Javadoc jd = new Javadoc();
+	jd.setProject(project); // Vital, otherwise Ant crashes
+	jd.setTaskName(logMsg);
+	jd.setSource(getSource()); // So we can set the language version
+	jd.init();
+
+	// Set up some common parameters for the Javadoc task
+	if (verboseAnt) {
+	    jd.setVerbose(true);
+	}
+	return jd;
+    }
+
+    /**
+     * Add the root directories for the given project to the Javadoc 
+     * sourcepath.
+     */
+    protected void addSourcePaths(Javadoc jd, ProjectInfo proj) {
+	Vector dirSets = proj.getDirsets();
+	int numDirSets = dirSets.size();
+	for (int i = 0; i < numDirSets; i++) {
+	    DirSet dirSet = (DirSet)dirSets.elementAt(i);
+	    jd.setSourcepath(new Path(project, dirSet.getDir(project).toString()));
+	}
+    }
+
+    /**
+     * Return the comma-separated list of packages. The list is
+     * generated from Ant DirSet tasks, and includes all directories
+     * in a hierarchy, e.g. com, com/acme. com/acme/foo. Duplicates are 
+     * ignored.
+     */
+    protected String getPackageList(ProjectInfo proj) throws BuildException {
+	String packageList = ""; 
+	java.lang.StringBuffer sb = new StringBuffer();
+	Vector dirSets = proj.getDirsets();
+	int numDirSets = dirSets.size();
+	boolean addComma = false;
+	for (int i = 0; i < numDirSets; i++) {
+	    DirSet dirSet = (DirSet)dirSets.elementAt(i);
+	    DirectoryScanner dirScanner = dirSet.getDirectoryScanner(project);
+	    String[] files = dirScanner.getIncludedDirectories();
+	    for (int j = 0; j < files.length; j++) {
+		if (!addComma){
+		    addComma = true;
+		} else {
+		    sb.append(",");
+		}
+		sb.append(files[j]);
+	    }
+	}
+	packageList = sb.toString();
+	if (packageList.compareTo("") == 0) {
+	    throw new BuildException("Error: no packages found to scan");
+	}
+	project.log(" Package list: " + packageList, Project.MSG_INFO);
+	
+	return packageList;
+    }
+
+    /**
+     * Copy a file from src to dst. Also checks that "destdir/changes" exists
+     */
+    protected void copyFile(String src, String dst){
+	File srcFile = new File(src);
+	File dstFile = new File(dst);
+	try {
+	    File reportSubdir = new File(getDestdir().toString() + 
+					 DIR_SEP + "changes");
+	    if (!reportSubdir.mkdir() && !reportSubdir.exists()) {
+		project.log("Warning: unable to create " + reportSubdir,
+			    Project.MSG_WARN);
+	    }
+
+	    InputStream in = new FileInputStream(src);
+	    OutputStream out = new FileOutputStream(dst);
+		
+	    // Transfer bytes from in to out
+	    byte[] buf = new byte[1024];
+	    int len;
+	    while ((len = in.read(buf)) > 0) {
+		out.write(buf, 0, len);
+	    }
+	    in.close();
+	    out.close();
+	} catch (java.io.FileNotFoundException fnfe) {
+	    project.log("Warning: unable to copy " + src.toString() + 
+			" to " + dst.toString(), Project.MSG_WARN);
+	    // Discard the exception
+	} catch (java.io.IOException ioe) {
+	    project.log("Warning: unable to copy " + src.toString() + 
+			" to " + dst.toString(), Project.MSG_WARN);
+	    // Discard the exception
+	}
+    }
+
+    /**
+     * The JDiff Ant task does not inherit from an Ant task, such as the 
+     * Javadoc task, though this is usually how most Tasks are
+     * written. This is because JDiff needs to run Javadoc three times
+     * (twice for generating XML, once for generating HTML). The
+     * Javadoc task has not easy way to reset its list of packages, so
+     * we needed to be able to crate new Javadoc task objects.
+     *
+     * Note: Don't confuse this class with the ProjectInfo used by JDiff. 
+     * This Project class is from Ant.
+     */
+    private Project project;
+
+    /**
+     * Used as part of Ant's startup.
+     */
+    public void setProject(Project proj) {
+        project = proj;
+    }
+
+    /** 
+     * Ferward or backward slash, as appropriate.
+     */
+    static String DIR_SEP = System.getProperty("file.separator");
+
+    /**
+     * JDIFF_HOME must be set as a property in the Ant build file.
+     * It should be set to the root JDiff directory, ie. the one where 
+     * jdiff.jar is found.
+     */
+    private String jdiffHome = "(not set)";
+
+    /**
+     * The classpath used by Javadoc to find jdiff.jar and xerces.jar.
+     */
+    private String jdiffClassPath = "(not set)";
+
+    /* ***************************************************************** */
+    /* * Objects and methods which are related to attributes           * */
+    /* ***************************************************************** */
+
+    /** 
+     * The destination directory for the generated report.
+     * The default is "./jdiff_report".
+     */
+    private File destdir = new File("jdiff_report");
+
+    /**
+     * Used to store the destdir attribute of the JDiff task XML element.
+     */
+    public void setDestdir(File value) {
+	this.destdir = value;
+    }
+
+    public File getDestdir() {
+	return this.destdir;
+    }
+
+    /** 
+     * Increases the JDiff Ant task logging verbosity if set with "yes", "on" 
+     * or true". Default has to be false.
+     * To increase verbosity of Javadoc, start Ant with -v or -verbose.
+     */ 
+    private boolean verbose = false;
+
+    public void setVerbose(boolean value) {
+	this.verbose = value;
+    }
+
+    public boolean getVerbose() {
+	return this.verbose;
+    }
+
+    /** 
+     * Set if ant was started with -v or -verbose 
+     */
+    private boolean verboseAnt = false;
+
+    /** 
+     * Add the -docchanges argument, to track changes in Javadoc documentation
+     * as well as changes in classes etc.
+     */ 
+    private boolean docchanges = false;
+
+    public void setDocchanges(boolean value) {
+	this.docchanges = value;
+    }
+
+    public boolean getDocchanges() {
+	return this.docchanges;
+    }
+
+    /** 
+     * Add statistics to the report if set. Default can only be false.
+     */ 
+    private boolean stats = false;
+
+    public void setStats(boolean value) {
+	this.stats = value;
+    }
+
+    public boolean getStats() {
+	return this.stats;
+    }
+
+    /**
+     * Allow the source language version to be specified.
+     */
+    private String source = "1.5"; // Default is 1.5, so generics will work
+    
+    public void setSource(String source) {
+        this.source = source;
+    }
+    
+    public String getSource() {
+        return source;
+    }
+
+    /* ***************************************************************** */
+    /* * Classes and objects which are related to elements             * */
+    /* ***************************************************************** */
+
+    /**
+     * A ProjectInfo-derived object for the older version of the project
+     */
+    private ProjectInfo oldProject = null;
+
+    /**
+     * Used to store the child element named "old", which is under the 
+     * JDiff task XML element.
+     */
+    public void addConfiguredOld(ProjectInfo projInfo) {
+	oldProject = projInfo;
+    }
+
+    /**
+     * A ProjectInfo-derived object for the newer version of the project
+     */
+    private ProjectInfo newProject = null;
+
+    /**
+     * Used to store the child element named "new", which is under the 
+     * JDiff task XML element.
+     */
+    public void addConfiguredNew(ProjectInfo projInfo) {
+	newProject = projInfo;
+    }
+
+    /**
+     * This class handles the information about a project, whether it is
+     * the older or newer version.
+     *
+     * Note: Don't confuse this class with the Project used by Ant.
+     * This ProjectInfo class is from local to this task.
+     */
+    public static class ProjectInfo {
+	/** 
+	 * The name of the project. This is used (without spaces) as the 
+	 * base of the name of the file which contains the XML representing 
+	 * the project.
+	 */
+	private String name;
+
+	public void setName(String value) {
+	    name = value;
+	}
+
+	public String getName() {
+	    return name;
+	}
+
+	/** 
+	 * The location of the Javadoc HTML for this project. Default value
+	 * is "generate", which will cause the Javadoc to be generated in 
+	 * a subdirectory named "name" in the task's destdir directory.
+	 */
+	private String javadoc;
+
+	public void setJavadoc(String value) {
+	    javadoc = value;
+	}
+
+	public String getJavadoc() {
+	    return javadoc;
+	}
+
+ 	/**
+	 * These are the directories which contain the packages which make 
+	 * up the project. Filesets are not supported by JDiff.
+	 */
+	private Vector dirsets = new Vector();
+
+	public void setDirset(DirSet value) {
+	    dirsets.add(value);
+	}
+
+	public Vector getDirsets() {
+	    return dirsets;
+	}
+
+	/** 
+	 * Used to store the child element named "dirset", which is under the 
+	 * "old" or "new" XML elements.
+	 */
+	public void addDirset(DirSet aDirset) {
+	    setDirset(aDirset);
+	}
+	
+    }
+}
diff --git a/src/jdiff/MemberDiff.java b/src/jdiff/MemberDiff.java
new file mode 100755
index 0000000..28e3258
--- /dev/null
+++ b/src/jdiff/MemberDiff.java
@@ -0,0 +1,73 @@
+package jdiff;
+
+import java.util.*;
+import com.sun.javadoc.*;
+
+/**
+ * The changes between two class constructor, method or field members.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class MemberDiff {
+
+    /** The name of the member. */
+    public String name_;
+
+    /** 
+     * The old member type. For methods, this is the return type. 
+     */
+    public String oldType_ = null;
+    /** 
+     * The new member type. For methods, this is the return type.
+     */
+    public String newType_ = null;
+
+    /** The old signature. Null except for methods. */
+    public String oldSignature_ = null;
+    /** The new signature. Null except for methods. */
+    public String newSignature_ = null;
+    
+    /** 
+     * The old list of exceptions. Null except for methods and constructors. 
+     */
+    public String oldExceptions_ = null;
+    /** 
+     * The new list of exceptions. Null except for methods and constructors. 
+     */
+    public String newExceptions_ = null;
+    
+    /** 
+     * A string describing the changes in documentation. 
+     */
+    public String documentationChange_ = null;
+
+    /**
+     * A string describing the changes in modifiers.      
+     * Changes can be in whether this is abstract, static, final, and in 
+     * its visibility.
+     * Null if no change. 
+     */
+    public String modifiersChange_ = null;
+
+    /**
+     * The class name where the new member is defined.
+     * Null if no change in inheritance. 
+     */
+    public String inheritedFrom_ = null;
+
+    /** Default constructor. */
+    public MemberDiff(String name) {
+        name_ = name;
+    }   
+
+    /** Add a change in the modifiers. */
+    public void addModifiersChange(String commonModifierChanges) {
+        if (commonModifierChanges != null) {
+            if (modifiersChange_ == null)
+                modifiersChange_ = commonModifierChanges;
+            else
+                modifiersChange_ += " " + commonModifierChanges;
+        }
+    }
+}
diff --git a/src/jdiff/MergeChanges.java b/src/jdiff/MergeChanges.java
new file mode 100755
index 0000000..a8a4c85
--- /dev/null
+++ b/src/jdiff/MergeChanges.java
@@ -0,0 +1,342 @@
+package jdiff;
+
+import java.util.*;
+
+/**
+ * Convert some remove and add operations into change operations.
+ *
+ * Once the numbers of members removed and added are known
+ * we can deduce more information about changes. For instance, if there are
+ * two methods with the same name, and one or more of them has a 
+ * parameter type change, then this can only be reported as removing 
+ * the old version(s) and adding the new version(s), because there are 
+ * multiple methods with the same name. 
+ *
+ * However, if only <i>one</i> method with a given name is removed, and  
+ * only <i>one</i> method with the same name is added, we can convert these
+ * operations to a change operation. For constructors, this is true if 
+ * the types are the same. For fields, the field names have to be the same, 
+ * though this should never occur, since field names are unique.
+ *
+ * Another merge which can be made is if two or more methods with the same name
+ * were marked as removed and added because of changes other than signature.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class MergeChanges {
+
+    /**
+     * Convert some remove and add operations into change operations.
+     *
+     * Note that if a single thread modifies a collection directly while it is 
+     * iterating over the collection with a fail-fast iterator, the iterator 
+     * will throw java.util.ConcurrentModificationException   
+     */
+    public static void mergeRemoveAdd(APIDiff apiDiff) {
+        // Go through all the ClassDiff objects searching for the above cases.
+        Iterator iter = apiDiff.packagesChanged.iterator();
+        while (iter.hasNext()) {
+            PackageDiff pkgDiff = (PackageDiff)(iter.next());
+            Iterator iter2 = pkgDiff.classesChanged.iterator();
+            while (iter2.hasNext()) {
+                ClassDiff classDiff = (ClassDiff)(iter2.next());
+                // Note: using iterators to step through the members gives a
+                // ConcurrentModificationException exception with large files.
+                // Constructors
+                ConstructorAPI[] ctorArr = new ConstructorAPI[classDiff.ctorsRemoved.size()];
+                ctorArr = (ConstructorAPI[])classDiff.ctorsRemoved.toArray(ctorArr);
+                for (int ctorIdx = 0; ctorIdx < ctorArr.length; ctorIdx++) {
+                    ConstructorAPI removedCtor = ctorArr[ctorIdx];
+                    mergeRemoveAddCtor(removedCtor, classDiff, pkgDiff);
+                }
+                // Methods
+                MethodAPI[] methodArr = new MethodAPI[classDiff.methodsRemoved.size()];
+                methodArr = (MethodAPI[])classDiff.methodsRemoved.toArray(methodArr);
+                for (int methodIdx = 0; methodIdx < methodArr.length; methodIdx++) {
+                    MethodAPI removedMethod = methodArr[methodIdx];
+                    // Only merge locally defined methods
+                    if (removedMethod.inheritedFrom_ == null)
+                        mergeRemoveAddMethod(removedMethod, classDiff, pkgDiff);
+                }
+                // Fields
+                FieldAPI[] fieldArr = new FieldAPI[classDiff.fieldsRemoved.size()];
+                fieldArr = (FieldAPI[])classDiff.fieldsRemoved.toArray(fieldArr);
+                for (int fieldIdx = 0; fieldIdx < fieldArr.length; fieldIdx++) {
+                    FieldAPI removedField = fieldArr[fieldIdx]; 
+                    // Only merge locally defined fields
+                    if (removedField.inheritedFrom_ == null)
+                        mergeRemoveAddField(removedField, classDiff, pkgDiff);
+                }
+            }
+        }        
+    }
+
+    /**
+     * Convert some removed and added constructors into changed constructors.
+     */
+    public static void mergeRemoveAddCtor(ConstructorAPI removedCtor, ClassDiff classDiff, PackageDiff pkgDiff) {
+        // Search on the type of the constructor
+        int startRemoved = classDiff.ctorsRemoved.indexOf(removedCtor);
+        int endRemoved = classDiff.ctorsRemoved.lastIndexOf(removedCtor);
+        int startAdded = classDiff.ctorsAdded.indexOf(removedCtor);
+        int endAdded = classDiff.ctorsAdded.lastIndexOf(removedCtor);
+        if (startRemoved != -1 && startRemoved == endRemoved && 
+            startAdded != -1 && startAdded == endAdded) {
+            // There is only one constructor with the type of the
+            // removedCtor in both the removed and added constructors.
+            ConstructorAPI addedCtor = (ConstructorAPI)(classDiff.ctorsAdded.get(startAdded));
+            // Create a MemberDiff for this change
+            MemberDiff ctorDiff = new MemberDiff(classDiff.name_);
+            ctorDiff.oldType_ = removedCtor.type_;
+            ctorDiff.newType_ = addedCtor.type_; // Should be the same as removedCtor.type
+            ctorDiff.oldExceptions_ = removedCtor.exceptions_;
+            ctorDiff.newExceptions_ = addedCtor.exceptions_;
+            ctorDiff.addModifiersChange(removedCtor.modifiers_.diff(addedCtor.modifiers_));
+            // Track changes in documentation
+            if (APIComparator.docChanged(removedCtor.doc_, addedCtor.doc_)) {
+                String type = ctorDiff.newType_;
+                if (type.compareTo("void") == 0)
+                    type = "";
+                String fqName = pkgDiff.name_ + "." + classDiff.name_;
+                String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+                String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + ".ctor_changed(" + type + ")\" class=\"hiddenlink\">";
+                String id = pkgDiff.name_ + "." + classDiff.name_ + ".ctor(" + HTMLReportGenerator.simpleName(type) + ")";
+                String title = link1 + "Class <b>" + classDiff.name_ + 
+                    "</b></a>, " + link2 + "constructor <b>" + classDiff.name_ + "(" + HTMLReportGenerator.simpleName(type) + ")</b></a>";
+                ctorDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedCtor.doc_, addedCtor.doc_, id, title);
+            }
+            classDiff.ctorsChanged.add(ctorDiff);
+            // Now remove the entries from the remove and add lists
+            classDiff.ctorsRemoved.remove(startRemoved);
+            classDiff.ctorsAdded.remove(startAdded);
+            if (trace && ctorDiff.modifiersChange_ != null)
+                System.out.println("Merged the removal and addition of constructor into one change: " + ctorDiff.modifiersChange_);
+        }
+    }
+
+    /**
+     * Convert some removed and added methods into changed methods.
+     */
+    public static void mergeRemoveAddMethod(MethodAPI removedMethod, 
+                                            ClassDiff classDiff, 
+                                            PackageDiff pkgDiff) {
+        mergeSingleMethods(removedMethod, classDiff, pkgDiff);
+        mergeMultipleMethods(removedMethod, classDiff, pkgDiff);
+    }
+
+    /**
+     * Convert single removed and added methods into a changed method.
+     */
+    public static void mergeSingleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
+        // Search on the name of the method
+        int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
+        int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
+        int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
+        int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
+        if (startRemoved != -1 && startRemoved == endRemoved && 
+            startAdded != -1 && startAdded == endAdded) {
+            // There is only one method with the name of the
+            // removedMethod in both the removed and added methods.
+            MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(startAdded));
+            if (addedMethod.inheritedFrom_ == null) {
+                // Create a MemberDiff for this change
+                MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
+                methodDiff.oldType_ = removedMethod.returnType_;
+                methodDiff.newType_ = addedMethod.returnType_;
+                methodDiff.oldSignature_ = removedMethod.getSignature();
+                methodDiff.newSignature_ = addedMethod.getSignature();
+                methodDiff.oldExceptions_ = removedMethod.exceptions_;
+                methodDiff.newExceptions_ = addedMethod.exceptions_;
+                // The addModifiersChange field may not have been
+                // initialized yet if there were multiple methods of the same
+                // name.
+                diffMethods(methodDiff, removedMethod, addedMethod);
+                methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
+                // Track changes in documentation
+                if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
+                    String sig = methodDiff.newSignature_;
+                    if (sig.compareTo("void") == 0)
+                        sig = "";
+                    String fqName = pkgDiff.name_ + "." + classDiff.name_;
+                    String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+                    String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
+                    String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
+                    String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
+                        link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
+                    methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
+                }
+                classDiff.methodsChanged.add(methodDiff);
+                // Now remove the entries from the remove and add lists
+                classDiff.methodsRemoved.remove(startRemoved);
+                classDiff.methodsAdded.remove(startAdded);
+                if (trace) {
+                    System.out.println("Merged the removal and addition of method " + 
+                                       removedMethod.name_ + 
+                                       " into one change");
+                }
+            } //if (addedMethod.inheritedFrom_ == null)
+        }
+    }
+
+    /**
+     * Convert multiple removed and added methods into changed methods.
+     * This handles the case where the methods' signatures are unchanged, but
+     * something else changed.
+     */
+    public static void mergeMultipleMethods(MethodAPI removedMethod, ClassDiff classDiff, PackageDiff pkgDiff) {
+        // Search on the name and signature of the method
+        int startRemoved = classDiff.methodsRemoved.indexOf(removedMethod);
+        int endRemoved = classDiff.methodsRemoved.lastIndexOf(removedMethod);
+        int startAdded = classDiff.methodsAdded.indexOf(removedMethod);
+        int endAdded = classDiff.methodsAdded.lastIndexOf(removedMethod);
+        if (startRemoved != -1 && endRemoved != -1 && 
+            startAdded != -1 && endAdded != -1) {
+            // Find the index of the current removed method
+            int removedIdx = -1;
+            for (int i = startRemoved; i <= endRemoved; i++) {                
+                if (removedMethod.equalSignatures(classDiff.methodsRemoved.get(i))) {
+                    removedIdx = i;
+                    break;
+                }
+            }
+            if (removedIdx == -1) {
+                System.out.println("Error: removed method index not found");
+                System.exit(5);
+            }
+            // Find the index of the added method with the same signature, if 
+            // it exists, and make sure it is defined locally.
+            int addedIdx = -1;
+            for (int i = startAdded; i <= endAdded; i++) {
+                MethodAPI addedMethod2 = (MethodAPI)(classDiff.methodsAdded.get(i));
+                if (addedMethod2.inheritedFrom_ == null &&
+                    removedMethod.equalSignatures(addedMethod2))
+                    addedIdx = i;
+                    break;
+            }
+            if (addedIdx == -1)
+                return;
+            MethodAPI addedMethod = (MethodAPI)(classDiff.methodsAdded.get(addedIdx));
+            // Create a MemberDiff for this change
+            MemberDiff methodDiff = new MemberDiff(removedMethod.name_);
+            methodDiff.oldType_ = removedMethod.returnType_;
+            methodDiff.newType_ = addedMethod.returnType_;
+            methodDiff.oldSignature_ = removedMethod.getSignature();
+            methodDiff.newSignature_ = addedMethod.getSignature();
+            methodDiff.oldExceptions_ = removedMethod.exceptions_;
+            methodDiff.newExceptions_ = addedMethod.exceptions_;
+                // The addModifiersChange field may not have been
+                // initialized yet if there were multiple methods of the same
+                // name.
+                diffMethods(methodDiff, removedMethod, addedMethod);
+            methodDiff.addModifiersChange(removedMethod.modifiers_.diff(addedMethod.modifiers_));
+            // Track changes in documentation
+            if (APIComparator.docChanged(removedMethod.doc_, addedMethod.doc_)) {
+                String sig = methodDiff.newSignature_;
+                if (sig.compareTo("void") == 0)
+                    sig = "";
+                String fqName = pkgDiff.name_ + "." + classDiff.name_;
+                String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+                String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedMethod.name_ + "_changed(" + sig + ")\" class=\"hiddenlink\">";
+                String id = pkgDiff.name_ + "." + classDiff.name_ + ".dmethod." + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")";
+                String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
+                    link2 +  HTMLReportGenerator.simpleName(methodDiff.newType_) + " <b>" + addedMethod.name_ + "(" + HTMLReportGenerator.simpleName(sig) + ")</b></a>";
+                methodDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedMethod.doc_, addedMethod.doc_, id, title);
+            }
+            classDiff.methodsChanged.add(methodDiff);
+            // Now remove the entries from the remove and add lists
+            classDiff.methodsRemoved.remove(removedIdx);
+            classDiff.methodsAdded.remove(addedIdx);
+            if (trace) {
+                System.out.println("Merged the removal and addition of method " + 
+                                   removedMethod.name_ + 
+                                   " into one change. There were multiple methods of this name.");
+            }
+        }
+    }
+
+    /**
+     * Track changes in methods related to abstract, native, and 
+     * synchronized modifiers here.
+     */
+    public static void diffMethods(MemberDiff methodDiff, 
+                                   MethodAPI oldMethod, 
+                                   MethodAPI newMethod) {
+        // Abstract or not
+        if (oldMethod.isAbstract_ != newMethod.isAbstract_) {
+            String changeText = "";
+            if (oldMethod.isAbstract_)
+                changeText += "Changed from abstract to non-abstract.";
+            else
+                changeText += "Changed from non-abstract to abstract.";
+            methodDiff.addModifiersChange(changeText);
+        }
+        // Native or not
+        if (Diff.showAllChanges && 
+	    oldMethod.isNative_ != newMethod.isNative_) {
+            String changeText = "";
+            if (oldMethod.isNative_)
+                changeText += "Changed from native to non-native.";
+            else
+                changeText += "Changed from non-native to native.";
+            methodDiff.addModifiersChange(changeText);
+        }
+        // Synchronized or not
+        if (Diff.showAllChanges && 
+	    oldMethod.isSynchronized_ != newMethod.isSynchronized_) {
+            String changeText = "";
+            if (oldMethod.isSynchronized_)
+                changeText += "Changed from synchronized to non-synchronized.";
+            else
+                changeText += "Changed from non-synchronized to synchronized.";
+            methodDiff.addModifiersChange(changeText);
+        }
+    }
+
+    /**
+     * Convert some removed and added fields into changed fields.
+     */
+    public static void mergeRemoveAddField(FieldAPI removedField, ClassDiff classDiff, PackageDiff pkgDiff) {
+        // Search on the name of the field
+        int startRemoved = classDiff.fieldsRemoved.indexOf(removedField);
+        int endRemoved = classDiff.fieldsRemoved.lastIndexOf(removedField);
+        int startAdded = classDiff.fieldsAdded.indexOf(removedField);
+        int endAdded = classDiff.fieldsAdded.lastIndexOf(removedField);
+        if (startRemoved != -1 && startRemoved == endRemoved && 
+            startAdded != -1 && startAdded == endAdded) {
+            // There is only one field with the name of the
+            // removedField in both the removed and added fields.
+            FieldAPI addedField = (FieldAPI)(classDiff.fieldsAdded.get(startAdded));
+            if (addedField.inheritedFrom_ == null) {
+                // Create a MemberDiff for this change
+                MemberDiff fieldDiff = new MemberDiff(removedField.name_);
+                fieldDiff.oldType_ = removedField.type_;
+                fieldDiff.newType_ = addedField.type_;
+                fieldDiff.addModifiersChange(removedField.modifiers_.diff(addedField.modifiers_));
+                // Track changes in documentation
+                if (APIComparator.docChanged(removedField.doc_, addedField.doc_)) {
+                    String fqName = pkgDiff.name_ + "." + classDiff.name_;
+                    String link1 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "\" class=\"hiddenlink\">";
+                    String link2 = "<a href=\"" + fqName + HTMLReportGenerator.reportFileExt + "#" + fqName + "." + addedField.name_ + "\" class=\"hiddenlink\">";
+                    String id = pkgDiff.name_ + "." + classDiff.name_ + ".field." + addedField.name_;
+                    String title = link1 + "Class <b>" + classDiff.name_ + "</b></a>, " +
+                        link2 + HTMLReportGenerator.simpleName(fieldDiff.newType_) + " <b>" + addedField.name_ + "</b></a>";
+                    fieldDiff.documentationChange_ = Diff.saveDocDiffs(pkgDiff.name_, classDiff.name_, removedField.doc_, addedField.doc_, id, title);
+                }
+                classDiff.fieldsChanged.add(fieldDiff);
+                // Now remove the entries from the remove and add lists
+                classDiff.fieldsRemoved.remove(startRemoved);
+                classDiff.fieldsAdded.remove(startAdded);
+                if (trace) {
+                    System.out.println("Merged the removal and addition of field " + 
+                                       removedField.name_ + 
+                                       " into one change");
+                }
+            } //if (addedField.inheritedFrom == null) 
+        }
+    }
+
+    /** Set to enable increased logging verbosity for debugging. */
+    private static boolean trace = false;
+
+}
diff --git a/src/jdiff/MethodAPI.java b/src/jdiff/MethodAPI.java
new file mode 100755
index 0000000..2afdc80
--- /dev/null
+++ b/src/jdiff/MethodAPI.java
@@ -0,0 +1,159 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent a method, analogous to MethodDoc in the 
+ * Javadoc doclet API. 
+ * 
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this method.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class MethodAPI implements Comparable {
+
+    /** Name of the method. */
+    public String name_ = null;
+
+    /** Return type of the method. */
+    public String returnType_ = null;
+
+    /** 
+     * The fully qualified name of the class or interface this method is
+     * inherited from. If this is null, then the method is defined locally
+     * in this class or interface.
+     */
+    public String inheritedFrom_ = null;
+
+    /** 
+     * The exceptions thrown by this method, being all the exception types
+     * separated by commas. "no exceptions" if no exceptions are thrown.
+     */
+    public String exceptions_ = "no exceptions";
+
+    /** Set if this method is abstract. */
+    public boolean isAbstract_ = false;
+
+    /** Set if this method is native. */
+    public boolean isNative_ = false;
+
+    /** Set if this method is synchronized. */
+    public boolean isSynchronized_ = false;
+
+    /** Modifiers for this class. */
+    public Modifiers modifiers_;
+
+    public List params_; // ParamAPI[]
+
+    /** The doc block, default is null. */
+    public String doc_ = null;
+
+    /** Constructor. */
+    public MethodAPI(String name, String returnType, boolean isAbstract, 
+                     boolean isNative, boolean isSynchronized,
+                     Modifiers modifiers) {
+        name_ = name;
+        returnType_ = returnType;
+        isAbstract_ = isAbstract;
+        isNative_ = isNative;
+        isSynchronized_ = isSynchronized;
+        modifiers_ = modifiers;
+        params_ = new ArrayList(); // ParamAPI[]
+    }
+
+    /** Copy constructor. */
+    public MethodAPI(MethodAPI m) {
+        name_ = m.name_;
+        returnType_ = m.returnType_;
+        inheritedFrom_ = m.inheritedFrom_;
+        exceptions_ = m.exceptions_;
+        isAbstract_ = m.isAbstract_;
+        isNative_ = m.isNative_;
+        isSynchronized_ = m.isSynchronized_;
+        modifiers_ = m.modifiers_; // Note: shallow copy
+        params_ = m.params_; // Note: shallow copy
+        doc_ = m.doc_;
+        signature_ = m.signature_; // Cached
+    }
+
+    /** 
+     * Compare two methods, including the return type, and parameter 
+     * names and types, and modifiers. 
+     */
+    public int compareTo(Object o) {
+        MethodAPI oMethod = (MethodAPI)o;
+        int comp = name_.compareTo(oMethod.name_);
+        if (comp != 0)
+            return comp;
+        comp = returnType_.compareTo(oMethod.returnType_);
+        if (comp != 0)
+            return comp;
+        if (APIComparator.changedInheritance(inheritedFrom_, oMethod.inheritedFrom_) != 0)
+            return -1;
+        if (isAbstract_ != oMethod.isAbstract_) {
+            return -1;
+        }
+        if (Diff.showAllChanges && 
+	    isNative_ != oMethod.isNative_) {
+            return -1;
+        }
+        if (Diff.showAllChanges && 
+	    isSynchronized_ != oMethod.isSynchronized_) {
+            return -1;
+        }
+        comp = exceptions_.compareTo(oMethod.exceptions_);
+        if (comp != 0)
+            return comp;
+        comp = modifiers_.compareTo(oMethod.modifiers_);
+        if (comp != 0)
+            return comp;
+        comp = getSignature().compareTo(oMethod.getSignature());
+        if (comp != 0)
+            return comp;
+        if (APIComparator.docChanged(doc_, oMethod.doc_))
+            return -1;
+        return 0;
+    }
+  
+    /** 
+     * Tests two methods, using just the method name, used by indexOf(). 
+     */
+    public boolean equals(Object o) {
+        if (name_.compareTo(((MethodAPI)o).name_) == 0)
+            return true;
+        return false;
+    }
+    
+    /** 
+     * Tests two methods for equality, using just the signature.
+     */
+    public boolean equalSignatures(Object o) {
+        if (getSignature().compareTo(((MethodAPI)o).getSignature()) == 0)
+            return true;
+        return false;
+    }
+    
+    /** Cached result of getSignature(). */
+    public String signature_ = null;
+
+    /** Return the signature of the method. */
+    public String getSignature() {
+        if (signature_ != null)
+            return signature_;
+        String res = "";
+        boolean first = true;
+        Iterator iter = params_.iterator();
+        while (iter.hasNext()) {
+            if (!first)
+                res += ", ";
+            ParamAPI param = (ParamAPI)(iter.next());
+            res += param.toString();
+            first = false;
+        }
+        signature_ = res;
+        return res; 
+    }
+}
diff --git a/src/jdiff/Modifiers.java b/src/jdiff/Modifiers.java
new file mode 100755
index 0000000..70f58e8
--- /dev/null
+++ b/src/jdiff/Modifiers.java
@@ -0,0 +1,105 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Track the various modifiers for a program element.
+ *
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this set of modifiers.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class Modifiers implements Comparable {
+
+    /** Set if the program element is static. */
+    public boolean isStatic = false;
+
+    /** Set if the program element is final. */
+    public boolean isFinal = false;
+
+    /** Set if the program element is deprecated. */
+    public boolean isDeprecated = false;
+
+    /** 
+     * The visibility level; "public", "protected", "package" or 
+     * "private" 
+     */
+    public String visibility = null;
+
+    /** Default constructor. */
+    public Modifiers() {
+    }
+
+    /** Compare two Modifiers objects by their contents. */
+    public int compareTo(Object o) {
+        Modifiers oModifiers = (Modifiers)o;
+        if (isStatic != oModifiers.isStatic)
+            return -1;
+        if (isFinal != oModifiers.isFinal)
+            return -1;
+        if (isDeprecated != oModifiers.isDeprecated)
+            return -1;
+        if (visibility != null) {
+            int comp = visibility.compareTo(oModifiers.visibility);
+            if (comp != 0)
+                return comp;
+        }
+        return 0;
+    }
+
+    /** 
+     * Generate a String describing the differences between the current
+     * (old) Modifiers object and a new Modifiers object. The string has 
+     * no leading space, but does end in a period.
+     *
+     * @param newModifiers The new Modifiers object.
+     * @return The description of the differences, null if there is no change.
+     */
+    public String diff(Modifiers newModifiers) {
+        String res = "";
+        boolean hasContent = false;
+        if (isStatic != newModifiers.isStatic) {
+            res += "Change from ";
+            if (isStatic) 
+                res += "static to non-static.<br>";
+            else
+                res += "non-static to static.<br>";
+            hasContent = true;
+        }
+        if (isFinal != newModifiers.isFinal) {
+            if (hasContent)
+                res += " ";
+            res += "Change from ";
+            if (isFinal) 
+                res += "final to non-final.<br>";
+            else
+                res += "non-final to final.<br>";
+            hasContent = true;
+        }
+        if (isDeprecated != newModifiers.isDeprecated) {
+            if (hasContent)
+                res += " ";
+            if (isDeprecated)
+                res += "Change from deprecated to undeprecated.<br>";
+            else
+                res += "<b>Now deprecated</b>.<br>";
+            hasContent = true;
+        }
+        if (visibility != null) {
+            int comp = visibility.compareTo(newModifiers.visibility);
+            if (comp != 0) {
+                if (hasContent)
+                    res += " ";
+                res += "Change of visibility from " + visibility + " to " + 
+                    newModifiers.visibility + ".<br>";
+                hasContent = true;
+            }
+        }
+        if (res.compareTo("") == 0)
+            return null;
+        return res;
+    }
+}
diff --git a/src/jdiff/Options.java b/src/jdiff/Options.java
new file mode 100755
index 0000000..748da7c
--- /dev/null
+++ b/src/jdiff/Options.java
@@ -0,0 +1,443 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+import com.sun.javadoc.*;
+
+/** 
+ * Class to handle options for JDiff.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class Options {
+
+    /** Default constructor. */
+    public Options() {
+    }
+
+    /**
+     * Returns the "length" of a given option. If an option takes no
+     * arguments, its length is one. If it takes one argument, its
+     * length is two, and so on. This method is called by Javadoc to
+     * parse the options it does not recognize. It then calls
+     * {@link #validOptions} to validate them.
+     * <blockquote>
+     * <b>Note:</b><br>
+     * The options arrive as case-sensitive strings. For options that
+     * are not case-sensitive, use toLowerCase() on the option string
+     * before comparing it.
+     * </blockquote>
+     *
+     * @param option  a String containing an option
+     * @return an int telling how many components that option has
+     */
+    public static int optionLength(String option) {
+        String opt = option.toLowerCase();
+        
+        // Standard options
+        if (opt.equals("-authorid"))  return 2;
+        if (opt.equals("-versionid")) return 2;
+        if (opt.equals("-d"))         return 2;
+        if (opt.equals("-classlist")) return 1;
+        if (opt.equals("-title"))     return 2;
+        if (opt.equals("-docletid"))  return 1;
+        if (opt.equals("-evident"))   return 2;
+        if (opt.equals("-skippkg"))   return 2;
+        if (opt.equals("-skipclass")) return 2;
+        if (opt.equals("-execdepth")) return 2;
+        if (opt.equals("-help"))      return 1;
+        if (opt.equals("-version"))      return 1;
+        if (opt.equals("-package"))   return 1;
+        if (opt.equals("-protected")) return 1;
+        if (opt.equals("-public"))    return 1;
+        if (opt.equals("-private"))   return 1;
+        if (opt.equals("-sourcepath")) return 2;
+        
+        // Options to control JDiff
+        if (opt.equals("-apiname"))    return 2;
+        if (opt.equals("-oldapi"))    return 2;
+        if (opt.equals("-newapi"))    return 2;
+
+        // Options to control the location of the XML files
+        if (opt.equals("-apidir"))       return 2;
+        if (opt.equals("-oldapidir"))    return 2;
+        if (opt.equals("-newapidir"))    return 2;
+        if (opt.equals("-usercommentsdir"))    return 2;
+
+
+        // Options for the exclusion level for classes and members
+        if (opt.equals("-excludeclass"))    return 2;
+        if (opt.equals("-excludemember"))    return 2;
+
+        if (opt.equals("-firstsentence"))    return 1;
+        if (opt.equals("-docchanges"))    return 1;
+        if (opt.equals("-packagesonly"))    return 1;
+        if (opt.equals("-showallchanges"))    return 1;
+
+        // Option to change the location for the existing Javadoc
+        // documentation for the new API. Default is "../"
+        if (opt.equals("-javadocnew"))    return 2;
+        // Option to change the location for the existing Javadoc
+        // documentation for the old API. Default is null.
+        if (opt.equals("-javadocold"))    return 2;
+
+        if (opt.equals("-baseuri"))    return 2;
+
+        // Option not to suggest comments at all
+        if (opt.equals("-nosuggest"))    return 2;
+
+        // Option to enable checking that the comments end with a period.
+        if (opt.equals("-checkcomments"))    return 1;
+        // Option to retain non-printing characters in comments.
+        if (opt.equals("-retainnonprinting"))    return 1;
+        // Option for the name of the exclude tag
+        if (opt.equals("-excludetag"))    return 2;
+        // Generate statistical output
+        if (opt.equals("-stats"))    return 1;
+
+        // Set the browser window title
+        if (opt.equals("-windowtitle"))    return 2;
+        // Set the report title
+        if (opt.equals("-doctitle"))    return 2;
+
+        return 0;
+    }//optionLength()
+
+   /**
+    * After parsing the available options using {@link #optionLength},
+    * Javadoc invokes this method with an array of options-arrays, where
+    * the first item in any array is the option, and subsequent items in
+    * that array are its arguments. So, if -print is an option that takes
+    * no arguments, and -copies is an option that takes 1 argument, then
+    * <pre>
+    *     -print -copies 3
+    * </pre>
+    * produces an array of arrays that looks like:
+    * <pre>
+    *      option[0][0] = -print
+    *      option[1][0] = -copies
+    *      option[1][1] = 3
+    * </pre>
+    * (By convention, command line switches start with a "-", but
+    * they don't have to.)
+    * <p>
+    * <b>Note:</b><br>
+    * Javadoc passes <i>all</i>parameters to this method, not just
+    * those that Javadoc doesn't recognize. The only way to
+    * identify unexpected arguments is therefore to check for every
+    * Javadoc parameter as well as doclet parameters.
+    *
+    * @param options   an array of String arrays, one per option
+    * @param reporter  a DocErrorReporter for generating error messages
+    * @return true if no errors were found, and all options are
+    *         valid
+    */
+    public static boolean validOptions(String[][] options,
+                                       DocErrorReporter reporter) {
+        final DocErrorReporter errOut = reporter;
+        
+        // A nice object-oriented way of handling errors. An instance of this
+        // class puts out an error message and keeps track of whether or not
+        // an error was found.
+        class ErrorHandler {
+            boolean noErrorsFound = true;
+            void msg(String msg) {
+                noErrorsFound = false;
+                errOut.printError(msg);
+            }
+        }
+        
+        ErrorHandler err = new ErrorHandler();
+        if (trace)
+            System.out.println("Command line arguments: "); 
+        for (int i = 0; i < options.length; i++) {
+            for (int j = 0; j < options[i].length; j++) {
+                Options.cmdOptions += " " + options[i][j];
+                if (trace)
+                    System.out.print(" " + options[i][j]); 
+            }            
+        }
+        if (trace)
+            System.out.println(); 
+
+        for (int i = 0; i < options.length; i++) {
+            if (options[i][0].toLowerCase().equals("-apiname")) {
+                if (options[i].length < 2) {
+                    err.msg("No version identifier specified after -apiname option.");
+                } else if (JDiff.compareAPIs) {
+                    err.msg("Use the -apiname option, or the -oldapi and -newapi options, but not both.");
+                } else { 
+                    String filename = options[i][1];
+                    RootDocToXML.apiIdentifier = filename;
+                    filename = filename.replace(' ', '_');
+                    RootDocToXML.outputFileName =  filename + ".xml";
+                    JDiff.writeXML = true;
+                    JDiff.compareAPIs = false;
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-apidir")) {
+                if (options[i].length < 2) {
+                    err.msg("No directory specified after -apidir option.");
+                } else {
+		    RootDocToXML.outputDirectory = options[i][1];
+                }
+                continue;
+            }
+	    if (options[i][0].toLowerCase().equals("-oldapi")) {
+                if (options[i].length < 2) {
+                    err.msg("No version identifier specified after -oldapi option.");
+                } else if (JDiff.writeXML) {
+                    err.msg("Use the -apiname or -oldapi option, but not both.");
+                } else { 
+                    String filename = options[i][1];
+                    filename = filename.replace(' ', '_');
+                    JDiff.oldFileName =  filename + ".xml";
+                    JDiff.writeXML = false;
+                    JDiff.compareAPIs = true;
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-oldapidir")) {
+                if (options[i].length < 2) {
+                    err.msg("No directory specified after -oldapidir option.");
+                } else { 
+                	JDiff.oldDirectory = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-newapi")) {
+                if (options[i].length < 2) {
+                    err.msg("No version identifier specified after -newapi option.");
+                } else if (JDiff.writeXML) {
+                    err.msg("Use the -apiname or -newapi option, but not both.");
+                } else { 
+                    String filename = options[i][1];
+                    filename = filename.replace(' ', '_');
+                    JDiff.newFileName =  filename + ".xml";
+                    JDiff.writeXML = false;
+                    JDiff.compareAPIs = true;
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-newapidir")) {
+                if (options[i].length < 2) {
+                    err.msg("No directory specified after -newapidir option.");
+                } else { 
+                	JDiff.newDirectory = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-usercommentsdir")) {
+                if (options[i].length < 2) {
+                    err.msg("Android: No directory specified after -usercommentsdir option.");
+                } else { 
+                    HTMLReportGenerator.commentsDir = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-d")) {
+                if (options[i].length < 2) {
+                    err.msg("No directory specified after -d option.");
+                } else {
+                    HTMLReportGenerator.outputDir = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-javadocnew")) {
+                if (options[i].length < 2) {
+                    err.msg("No location specified after -javadocnew option.");
+                } else {
+                    HTMLReportGenerator.newDocPrefix = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-javadocold")) {
+                if (options[i].length < 2) {
+                    err.msg("No location specified after -javadocold option.");
+                } else {
+                    HTMLReportGenerator.oldDocPrefix = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-baseuri")) {
+                if (options[i].length < 2) {
+                    err.msg("No base location specified after -baseURI option.");
+                } else {
+                    RootDocToXML.baseURI = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-excludeclass")) {
+                if (options[i].length < 2) {
+                    err.msg("No level (public|protected|package|private) specified after -excludeclass option.");
+                } else { 
+                    String level = options[i][1];
+                    if (level.compareTo("public") != 0 &&
+                        level.compareTo("protected") != 0 &&
+                        level.compareTo("package") != 0 &&
+                        level.compareTo("private") != 0) {
+                        err.msg("Level specified after -excludeclass option must be one of (public|protected|package|private).");
+                    } else {
+                        RootDocToXML.classVisibilityLevel = level;
+                    }
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-excludemember")) {
+                if (options[i].length < 2) {
+                    err.msg("No level (public|protected|package|private) specified after -excludemember option.");
+                } else { 
+                    String level = options[i][1];
+                    if (level.compareTo("public") != 0 &&
+                        level.compareTo("protected") != 0 &&
+                        level.compareTo("package") != 0 &&
+                        level.compareTo("private") != 0) {
+                        err.msg("Level specified after -excludemember option must be one of (public|protected|package|private).");
+                    } else {
+                        RootDocToXML.memberVisibilityLevel = level;
+                    }
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-firstsentence")) {
+                RootDocToXML.saveAllDocs = false;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-docchanges")) {
+                HTMLReportGenerator.reportDocChanges = true;
+                Diff.noDocDiffs = false;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-packagesonly")) {
+                RootDocToXML.packagesOnly = true;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-showallchanges")) {
+                Diff.showAllChanges = true;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-nosuggest")) {
+                if (options[i].length < 2) {
+                    err.msg("No level (all|remove|add|change) specified after -nosuggest option.");
+                } else { 
+                    String level = options[i][1];
+                    if (level.compareTo("all") != 0 &&
+                        level.compareTo("remove") != 0 &&
+                        level.compareTo("add") != 0 &&
+                        level.compareTo("change") != 0) {
+                        err.msg("Level specified after -nosuggest option must be one of (all|remove|add|change).");
+                    } else {
+                        if (level.compareTo("removal") == 0)
+                            HTMLReportGenerator.noCommentsOnRemovals = true;
+                        else if (level.compareTo("add") == 0)
+                            HTMLReportGenerator.noCommentsOnAdditions = true;
+                        else if (level.compareTo("change") == 0)
+                            HTMLReportGenerator.noCommentsOnChanges = true;
+                        else if (level.compareTo("all") == 0) {
+                            HTMLReportGenerator.noCommentsOnRemovals = true;
+                            HTMLReportGenerator.noCommentsOnAdditions = true;
+                            HTMLReportGenerator.noCommentsOnChanges = true;
+                        }
+                    }
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-checkcomments")) {
+                APIHandler.checkIsSentence = true;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-retainnonprinting")) {
+                RootDocToXML.stripNonPrintables = false;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-excludetag")) {
+                if (options[i].length < 2) {
+                    err.msg("No exclude tag specified after -excludetag option.");
+                } else { 
+                    RootDocToXML.excludeTag = options[i][1];
+                    RootDocToXML.excludeTag = RootDocToXML.excludeTag.trim();
+                    RootDocToXML.doExclude = true;
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-stats")) {
+                HTMLReportGenerator.doStats = true;
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-doctitle")) {
+                if (options[i].length < 2) {
+                    err.msg("No HTML text specified after -doctitle option.");
+                } else { 
+                    HTMLReportGenerator.docTitle = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-windowtitle")) {
+                if (options[i].length < 2) {
+                    err.msg("No text specified after -windowtitle option.");
+                } else { 
+                    HTMLReportGenerator.windowTitle = options[i][1];
+                }
+                continue;
+            }
+            if (options[i][0].toLowerCase().equals("-version")) {
+                System.out.println("JDiff version: " + JDiff.version);
+                System.exit(0);
+            }
+            if (options[i][0].toLowerCase().equals("-help")) {
+                usage();
+                System.exit(0);
+            }
+        }//for
+        if (!JDiff.writeXML && !JDiff.compareAPIs) {
+            err.msg("First use the -apiname option to generate an XML file for one API.");
+            err.msg("Then use the -apiname option again to generate another XML file for a different version of the API.");
+            err.msg("Finally use the -oldapi option and -newapi option to generate a report about how the APIs differ.");
+        }
+        return err.noErrorsFound;
+    }// validOptions()
+
+    /** Display the arguments for JDiff. */
+    public static void usage() {
+        System.err.println("JDiff version: " + JDiff.version);
+        System.err.println("");
+        System.err.println("Valid JDiff arguments:");
+        System.err.println("");
+        System.err.println("  -apiname <Name of a version>");
+        System.err.println("  -oldapi <Name of a version>");
+        System.err.println("  -newapi <Name of a version>");
+        
+        System.err.println("  Optional Arguments");
+        System.err.println();
+        System.err.println("  -d <directory> Destination directory for output HTML files");
+        System.err.println("  -oldapidir <directory> Location of the XML file for the old API");
+        System.err.println("  -newapidir <directory> Location of the XML file for the new API");
+        System.err.println("  -sourcepath <location of Java source files>");
+        System.err.println("  -javadocnew <location of existing Javadoc files for the new API>");
+        System.err.println("  -javadocold <location of existing Javadoc files for the old API>");
+        System.err.println("  -usercommentsdir <directory> Path to dir containing the user_comments* file(s)");
+        
+        System.err.println("  -baseURI <base> Use \"base\" as the base location of the various DTDs and Schemas used by JDiff");
+        System.err.println("  -excludeclass [public|protected|package|private] Exclude classes which are not public, protected etc");
+        System.err.println("  -excludemember [public|protected|package|private] Exclude members which are not public, protected etc");
+        
+        System.err.println("  -firstsentence Save only the first sentence of each comment block with the API.");
+        System.err.println("  -docchanges Report changes in Javadoc comments between the APIs");
+        System.err.println("  -nosuggest [all|remove|add|change] Do not add suggested comments to all, or the removed, added or chabged sections");
+        System.err.println("  -checkcomments Check that comments are sentences");
+        System.err.println("  -stripnonprinting Remove non-printable characters from comments.");
+        System.err.println("  -excludetag <tag> Define the Javadoc tag which implies exclusion");
+        System.err.println("  -stats Generate statistical output");
+        System.err.println("  -help       (generates this output)");
+        System.err.println("");
+        System.err.println("For more help, see jdiff.html");
+    }
+    
+    /** All the options passed on the command line. Logged to XML. */
+    public static String cmdOptions = "";
+
+    /** Set to enable increased logging verbosity for debugging. */
+    private static boolean trace = false;
+}
diff --git a/src/jdiff/PackageAPI.java b/src/jdiff/PackageAPI.java
new file mode 100755
index 0000000..885752f
--- /dev/null
+++ b/src/jdiff/PackageAPI.java
@@ -0,0 +1,49 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent a package, analogous to PackageDoc in the 
+ * Javadoc doclet API. 
+ *
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this package.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class PackageAPI implements Comparable {
+
+    /** Full qualified name of the package. */
+    public String name_;
+
+    /** Classes within this package. */
+    public List classes_;  // ClassAPI[]
+
+    /** The doc block, default is null. */
+    public String doc_ = null;
+
+    /** Constructor. */
+    public PackageAPI(String name) {
+        name_ = name;
+        classes_ = new ArrayList(); // ClassAPI[]
+    }
+
+    /** Compare two PackageAPI objects by name. */
+    public int compareTo(Object o) {
+        PackageAPI oPackageAPI = (PackageAPI)o;
+        if (APIComparator.docChanged(doc_, oPackageAPI.doc_))
+            return -1;
+        return name_.compareTo(oPackageAPI.name_);
+    }
+
+    /** 
+     * Tests two packages, using just the package name, used by indexOf().
+     */
+    public boolean equals(Object o) {
+        if (name_.compareTo(((PackageAPI)o).name_) == 0)
+            return true;
+        return false;
+    }
+}
diff --git a/src/jdiff/PackageDiff.java b/src/jdiff/PackageDiff.java
new file mode 100755
index 0000000..ba94f6e
--- /dev/null
+++ b/src/jdiff/PackageDiff.java
@@ -0,0 +1,38 @@
+package jdiff;
+
+import java.util.*;
+import com.sun.javadoc.*;
+
+/**
+ * Changes between two packages.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class PackageDiff {
+
+    public String name_;
+
+    /** Classes added in the new API. */
+    public List classesAdded = null;
+    /** Classes removed in the new API. */
+    public List classesRemoved = null;
+    /** Classes changed in the new API. */
+    public List classesChanged = null;
+
+    /** 
+     * A string describing the changes in documentation. 
+     */
+    public String documentationChange_ = null;
+
+    /* The percentage difference for this package. */
+    public double pdiff = 0.0;
+
+    /** Default constructor. */
+    public PackageDiff(String name) {
+        name_ = name;
+        classesAdded = new ArrayList(); // ClassAPI[]
+        classesRemoved = new ArrayList(); // ClassAPI[]
+        classesChanged = new ArrayList(); // ClassDiff[]
+    }   
+}
diff --git a/src/jdiff/ParamAPI.java b/src/jdiff/ParamAPI.java
new file mode 100755
index 0000000..196a4b5
--- /dev/null
+++ b/src/jdiff/ParamAPI.java
@@ -0,0 +1,55 @@
+package jdiff;
+
+import java.io.*;
+import java.util.*;
+
+/** 
+ * Class to represent any (name, type) pair such as a parameter. 
+ * Analogous to ParamType in the Javadoc doclet API. 
+ *
+ * The method used for Collection comparison (compareTo) must make its
+ * comparison based upon everything that is known about this parameter.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+class ParamAPI implements Comparable {
+    /** Name of the (name, type) pair. */
+    public String name_;
+
+    /** Type of the (name, type) pair. */
+    public String type_;
+
+    public ParamAPI(String name, String type) {
+        name_ = name;
+        type_ = type;
+    }
+
+    /** Compare two ParamAPI objects using both name and type. */
+    public int compareTo(Object o) {
+        ParamAPI oParamAPI = (ParamAPI)o;
+        int comp = name_.compareTo(oParamAPI.name_);
+        if (comp != 0)
+            return comp;
+        comp = type_.compareTo(oParamAPI.type_);
+        if (comp != 0)
+            return comp;
+        return 0;
+    }
+  
+    /** 
+     * Tests two ParamAPI objects using just the name, used by indexOf().
+     */
+    public boolean equals(Object o) {
+        if (name_.compareTo(((ParamAPI)o).name_) == 0)
+            return true;
+        return false;
+    }
+    
+    /** Used to create signatures. */
+    public String toString() {
+        if (type_.compareTo("void") == 0)
+            return "";
+        return type_;
+    }
+}  
diff --git a/src/jdiff/RootDocToXML.java b/src/jdiff/RootDocToXML.java
new file mode 100755
index 0000000..f68a60b
--- /dev/null
+++ b/src/jdiff/RootDocToXML.java
@@ -0,0 +1,1147 @@
+package jdiff;
+
+import com.sun.javadoc.*;
+import com.sun.javadoc.ParameterizedType;
+import com.sun.javadoc.Type;
+
+import java.util.*;
+import java.io.*;
+import java.lang.reflect.*;
+
+/**
+ * Converts a Javadoc RootDoc object into a representation in an 
+ * XML file.
+ *
+ * See the file LICENSE.txt for copyright details.
+ * @author Matthew Doar, mdoar@pobox.com
+ */
+public class RootDocToXML {
+
+    /** Default constructor. */
+    public RootDocToXML() {
+    }
+
+    /**
+     * Write the XML representation of the API to a file.
+     *
+     * @param root  the RootDoc object passed by Javadoc
+     * @return true if no problems encountered
+     */
+    public static boolean writeXML(RootDoc root) {
+    	String tempFileName = outputFileName;
+    	if (outputDirectory != null) {
+	    tempFileName = outputDirectory;
+	    if (!tempFileName.endsWith(JDiff.DIR_SEP)) 
+		tempFileName += JDiff.DIR_SEP;
+	    tempFileName += outputFileName;
+    	}
+
+        try {
+            FileOutputStream fos = new FileOutputStream(tempFileName);
+            outputFile = new PrintWriter(fos);
+            System.out.println("JDiff: writing the API to file '" + tempFileName + "'...");
+            if (root.specifiedPackages().length != 0 || root.specifiedClasses().length != 0) {
+                RootDocToXML apiWriter = new RootDocToXML();
+                apiWriter.emitXMLHeader();
+                apiWriter.logOptions();
+                apiWriter.processPackages(root);
+                apiWriter.emitXMLFooter();
+            }
+            outputFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + tempFileName);
+            System.out.println("Error: " +  e.getMessage());
+            System.exit(1);
+        }
+        // If validation is desired, write out the appropriate api.xsd file
+        // in the same directory as the XML file.
+        if (XMLToAPI.validateXML) {
+            writeXSD();
+        }
+        return true;
+    }
+
+    /**
+     * Write the XML Schema file used for validation.
+     */
+    public static void writeXSD() {
+        String xsdFileName = outputFileName;
+        if (outputDirectory == null) {
+	    int idx = xsdFileName.lastIndexOf('\\');
+	    int idx2 = xsdFileName.lastIndexOf('/');
+	    if (idx == -1 && idx2 == -1) {
+		xsdFileName = "";
+	    } else if (idx == -1 && idx2 != -1) {
+		xsdFileName = xsdFileName.substring(0, idx2);
+	    } else if (idx != -1  && idx2 == -1) {
+		xsdFileName = xsdFileName.substring(0, idx);
+	    } else if (idx != -1  && idx2 != -1) {
+		int max = idx2 > idx ? idx2 : idx;
+		xsdFileName = xsdFileName.substring(0, max);
+	    }
+	} else {
+	    xsdFileName = outputDirectory;
+	    if (!xsdFileName.endsWith(JDiff.DIR_SEP)) 
+		 xsdFileName += JDiff.DIR_SEP;
+	}
+        xsdFileName += "api.xsd";
+        try {
+            FileOutputStream fos = new FileOutputStream(xsdFileName);
+            PrintWriter xsdFile = new PrintWriter(fos);
+            // The contents of the api.xsd file
+            xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
+            xsdFile.println("<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">");
+            xsdFile.println("");
+            xsdFile.println("<xsd:annotation>");
+            xsdFile.println("  <xsd:documentation>");
+            xsdFile.println("  Schema for JDiff API representation.");
+            xsdFile.println("  </xsd:documentation>");
+            xsdFile.println("</xsd:annotation>");
+            xsdFile.println();
+            xsdFile.println("<xsd:element name=\"api\" type=\"apiType\"/>");
+            xsdFile.println("");
+            xsdFile.println("<xsd:complexType name=\"apiType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"package\" type=\"packageType\" minOccurs='1' maxOccurs='unbounded'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"packageType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:choice maxOccurs='unbounded'>");
+            xsdFile.println("      <xsd:element name=\"class\" type=\"classType\"/>");
+            xsdFile.println("      <xsd:element name=\"interface\" type=\"classType\"/>");
+            xsdFile.println("    </xsd:choice>");
+            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"classType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"implements\" type=\"interfaceTypeName\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"constructor\" type=\"constructorType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"method\" type=\"methodType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"field\" type=\"fieldType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"extends\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"interfaceTypeName\">");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"constructorType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"paramsType\">");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"exceptionType\">");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"methodType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"param\" type=\"paramsType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>");
+            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"return\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"native\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"synchronized\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("<xsd:complexType name=\"fieldType\">");
+            xsdFile.println("  <xsd:sequence>");
+            xsdFile.println("    <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>");
+            xsdFile.println("  </xsd:sequence>");
+            xsdFile.println("  <xsd:attribute name=\"name\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"type\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"transient\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"volatile\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"value\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>");
+            xsdFile.println("  <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>");
+            xsdFile.println("  <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>");
+            xsdFile.println("  <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>");
+            xsdFile.println("</xsd:complexType>");
+            xsdFile.println();
+            xsdFile.println("</xsd:schema>");
+            xsdFile.close();
+        } catch(IOException e) {
+            System.out.println("IO Error while attempting to create " + xsdFileName);
+            System.out.println("Error: " +  e.getMessage());
+            System.exit(1);
+        }
+    }
+
+    /**
+     * Write the options which were used to generate this XML file
+     * out as XML comments.
+     */
+    public void logOptions() {
+        outputFile.print("<!-- ");
+        outputFile.print(" Command line arguments = " + Options.cmdOptions);
+        outputFile.println(" -->");
+    }
+
+    /**
+     * Process each package and the classes/interfaces within it.
+     *
+     * @param pd  an array of PackageDoc objects
+     */
+    public void processPackages(RootDoc root) {
+        PackageDoc[] specified_pd = root.specifiedPackages();
+	Map pdl = new TreeMap();
+        for (int i = 0; specified_pd != null && i < specified_pd.length; i++) {
+	    pdl.put(specified_pd[i].name(), specified_pd[i]);
+	}
+
+	// Classes may be specified separately, so merge their packages into the
+	// list of specified packages.
+        ClassDoc[] cd = root.specifiedClasses();
+	// This is lists of the specific classes to document
+	Map classesToUse = new HashMap();
+        for (int i = 0; cd != null && i < cd.length; i++) {
+	    PackageDoc cpd = cd[i].containingPackage();
+	    if (cpd == null && !packagesOnly) {
+		// If the RootDoc object has been created from a jar file
+		// this duplicates classes, so we have to be able to disable it.
+		// TODO this is still null?
+		cpd = root.packageNamed("anonymous");
+	    }
+            String pkgName = cpd.name();
+            String className = cd[i].name();
+	    if (trace) System.out.println("Found package " + pkgName + " for class " + className);
+	    if (!pdl.containsKey(pkgName)) {
+		if (trace) System.out.println("Adding new package " + pkgName);
+		pdl.put(pkgName, cpd);
+	    }
+
+	    // Keep track of the specific classes to be used for this package
+	    List classes;
+	    if (classesToUse.containsKey(pkgName)) {
+		classes = (ArrayList) classesToUse.get(pkgName);
+	    } else {
+		classes = new ArrayList();
+	    }
+	    classes.add(cd[i]);
+	    classesToUse.put(pkgName, classes);
+	}
+
+	PackageDoc[] pd = (PackageDoc[]) pdl.values().toArray(new PackageDoc[0]);
+        for (int i = 0; pd != null && i < pd.length; i++) {
+            String pkgName = pd[i].name();
+            
+            // Check for an exclude tag in the package doc block, but not
+	    // in the package.htm[l] file.
+            if (!shownElement(pd[i], null))
+                continue;
+
+            if (trace) System.out.println("PROCESSING PACKAGE: " + pkgName);
+            outputFile.println("<package name=\"" + pkgName + "\">");
+
+            int tagCount = pd[i].tags().length;
+            if (trace) System.out.println("#tags: " + tagCount);
+            
+            List classList;
+	    if (classesToUse.containsKey(pkgName)) {
+		// Use only the specified classes in the package
+		System.out.println("Using the specified classes");
+		classList = (ArrayList) classesToUse.get(pkgName);
+	    } else {
+		// Use all classes in the package
+		classList = new LinkedList(Arrays.asList(pd[i].allClasses()));
+	    }
+            Collections.sort(classList);
+            ClassDoc[] classes = new ClassDoc[classList.size()];
+            classes = (ClassDoc[])classList.toArray(classes);
+            processClasses(classes, pkgName);
+
+            addPkgDocumentation(root, pd[i], 2);
+
+            outputFile.println("</package>");
+        }
+    } // processPackages
+    
+    /**
+     * Process classes and interfaces.
+     *
+     * @param cd An array of ClassDoc objects.
+     */
+    public void processClasses(ClassDoc[] cd, String pkgName) {
+        if (cd.length == 0)
+            return;
+        if (trace) System.out.println("PROCESSING CLASSES, number=" + cd.length);
+        for (int i = 0; i < cd.length; i++) {
+            String className = cd[i].name();
+            if (trace) System.out.println("PROCESSING CLASS/IFC: " + className);
+            // Only save the shown elements
+            if (!shownElement(cd[i], classVisibilityLevel))
+                continue;
+            boolean isInterface = false;
+            if (cd[i].isInterface())
+                isInterface = true;
+            if (isInterface) {
+                outputFile.println("  <!-- start interface " + pkgName + "." + className + " -->");
+                outputFile.print("  <interface name=\"" + className + "\"");
+            } else {
+                outputFile.println("  <!-- start class " + pkgName + "." + className + " -->");
+                outputFile.print("  <class name=\"" + className + "\"");
+            }
+            // Add attributes to the class element
+            Type parent = cd[i].superclassType();
+            if (parent != null)
+                outputFile.println(" extends=\"" + buildEmittableTypeString(parent) + "\"");
+            outputFile.println("    abstract=\"" + cd[i].isAbstract() + "\"");
+            addCommonModifiers(cd[i], 4);
+            outputFile.println(">");
+            // Process class members. (Treat inner classes as members.)
+            processInterfaces(cd[i].interfaceTypes());
+            processConstructors(cd[i].constructors());
+            processMethods(cd[i], cd[i].methods());
+            processFields(cd[i].fields());
+
+            addDocumentation(cd[i], 4);
+
+            if (isInterface) {
+                outputFile.println("  </interface>");
+                outputFile.println("  <!-- end interface " + pkgName + "." + className + " -->");
+            } else {
+                outputFile.println("  </class>");
+                outputFile.println("  <!-- end class " + pkgName + "." + className + " -->");
+            }
+            // Inner classes have already been added.
+            /*
+              ClassDoc[] ic = cd[i].innerClasses();
+              for (int k = 0; k < ic.length; k++) {
+              System.out.println("Inner class " + k + ", name = " + ic[k].name());
+              } 
+            */
+        }//for
+    }//processClasses()
+    
+    /**
+     * Add qualifiers for the program element as attributes.
+     *
+     * @param ped The given program element.
+     */
+    public void addCommonModifiers(ProgramElementDoc ped, int indent) {
+        addSourcePosition(ped, indent);
+        // Static and final and visibility on one line
+        for (int i = 0; i < indent; i++) outputFile.print(" ");
+        outputFile.print("static=\"" + ped.isStatic() + "\"");
+        outputFile.print(" final=\"" + ped.isFinal() + "\"");
+        // Visibility
+        String visibility = null;
+        if (ped.isPublic())
+            visibility = "public";
+        else if (ped.isProtected())
+            visibility = "protected";
+        else if (ped.isPackagePrivate())
+            visibility = "package";
+        else if (ped.isPrivate())
+            visibility = "private";
+        outputFile.println(" visibility=\"" + visibility + "\"");
+
+        // Deprecation on its own line
+        for (int i = 0; i < indent; i++) outputFile.print(" ");
+        boolean isDeprecated = false;
+        Tag[] ta = ((Doc)ped).tags("deprecated");
+        if (ta.length != 0) {
+            isDeprecated = true;
+        }
+        if (ta.length > 1) {
+            System.out.println("JDiff: warning: multiple @deprecated tags found in comments for " + ped.name() + ". Using the first one only.");
+            System.out.println("Text is: " + ((Doc)ped).getRawCommentText());
+        }
+        if (isDeprecated) {
+            String text = ta[0].text(); // Use only one @deprecated tag
+            if (text != null && text.compareTo("") != 0) {
+                int idx = endOfFirstSentence(text);
+                if (idx == 0) {
+                    // No useful comment
+                    outputFile.print("deprecated=\"deprecated, no comment\"");
+                } else {
+                    String fs = null;
+                    if (idx == -1)
+                        fs = text;
+                    else
+                        fs = text.substring(0, idx+1);
+                    String st = API.hideHTMLTags(fs);
+                    outputFile.print("deprecated=\"" + st + "\"");
+                }
+            } else {
+                outputFile.print("deprecated=\"deprecated, no comment\"");
+            }
+        } else {
+            outputFile.print("deprecated=\"not deprecated\"");
+        }
+
+    } //addQualifiers()
+
+    /**
+     * Insert the source code details, if available.
+     *
+     * @param ped The given program element.
+     */
+    public void addSourcePosition(ProgramElementDoc ped, int indent) {
+        if (!addSrcInfo)
+            return;
+        if (JDiff.javaVersion.startsWith("1.1") || 
+            JDiff.javaVersion.startsWith("1.2") || 
+            JDiff.javaVersion.startsWith("1.3")) {
+            return; // position() only appeared in J2SE1.4
+        }
+        try {
+            // Could cache the method for improved performance
+            Class c = ProgramElementDoc.class;
+            Method m = c.getMethod("position", (Class[]) null);
+            Object sp = m.invoke(ped, (Object[]) null);
+            if (sp != null) {
+                for (int i = 0; i < indent; i++) outputFile.print(" ");
+                outputFile.println("src=\"" + sp + "\"");
+            }
+        } catch (NoSuchMethodException e2) {
+            System.err.println("Error: method \"position\" not found");
+            e2.printStackTrace();
+        } catch (IllegalAccessException e4) {
+            System.err.println("Error: class not permitted to be instantiated");
+            e4.printStackTrace();
+        } catch (InvocationTargetException e5) {
+            System.err.println("Error: method \"position\" could not be invoked");
+            e5.printStackTrace();
+        } catch (Exception e6) {
+            System.err.println("Error: ");
+            e6.printStackTrace();
+        }
+    }
+
+    /**
+     * Process the interfaces implemented by the class.
+     *
+     * @param ifaces An array of ClassDoc objects
+     */
+    public void processInterfaces(Type[] ifaces) {
+        if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.length);
+        for (int i = 0; i < ifaces.length; i++) {
+            String ifaceName = buildEmittableTypeString(ifaces[i]);
+            if (trace) System.out.println("PROCESSING INTERFACE: " + ifaceName);
+            outputFile.println("    <implements name=\"" + ifaceName + "\"/>");
+        }//for
+    }//processInterfaces()
+    
+    /**
+     * Process the constructors in the class.
+     *
+     * @param ct An array of ConstructorDoc objects
+     */
+    public void processConstructors(ConstructorDoc[] ct) {
+        if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ct.length);
+        for (int i = 0; i < ct.length; i++) {
+            String ctorName = ct[i].name();
+            if (trace) System.out.println("PROCESSING CONSTRUCTOR: " + ctorName);
+            // Only save the shown elements
+            if (!shownElement(ct[i], memberVisibilityLevel))
+                continue;
+            outputFile.print("    <constructor name=\"" + ctorName + "\"");
+
+            Parameter[] params = ct[i].parameters();
+            boolean first = true;
+            if (params.length != 0) {
+                outputFile.print(" type=\"");
+                for (int j = 0; j < params.length; j++) {
+                    if (!first)
+                        outputFile.print(", ");
+                    emitType(params[j].type());
+                    first = false;
+                }
+                outputFile.println("\"");
+            } else
+                outputFile.println();
+            addCommonModifiers(ct[i], 6);
+            outputFile.println(">");
+            
+            // Generate the exception elements if any exceptions are thrown
+            processExceptions(ct[i].thrownExceptions());
+
+            addDocumentation(ct[i], 6);
+
+            outputFile.println("    </constructor>");
+        }//for
+    }//processConstructors()
+    
+    /**
+     * Process all exceptions thrown by a constructor or method.
+     *
+     * @param cd An array of ClassDoc objects
+     */
+    public void processExceptions(ClassDoc[] cd) {
+        if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + cd.length);
+        for (int i = 0; i < cd.length; i++) {
+            String exceptionName = cd[i].name();
+            if (trace) System.out.println("PROCESSING EXCEPTION: " + exceptionName);
+            outputFile.print("      <exception name=\"" + exceptionName + "\" type=\"");
+            emitType(cd[i]);
+            outputFile.println("\"/>");
+        }//for
+    }//processExceptions()
+    
+    /**
+     * Process the methods in the class.
+     *
+     * @param md An array of MethodDoc objects
+     */
+    public void processMethods(ClassDoc cd, MethodDoc[] md) {
+        if (trace) System.out.println("PROCESSING " +cd.name()+" METHODS, number = " + md.length);
+        for (int i = 0; i < md.length; i++) {
+            String methodName = md[i].name();
+            if (trace) System.out.println("PROCESSING METHOD: " + methodName);
+            // Skip <init> and <clinit>
+            if (methodName.startsWith("<"))
+                continue;
+            // Only save the shown elements
+            if (!shownElement(md[i], memberVisibilityLevel))
+                continue;
+            outputFile.print("    <method name=\"" + methodName + "\"");
+            com.sun.javadoc.Type retType = md[i].returnType();
+            if (retType.qualifiedTypeName().compareTo("void") == 0) {
+                // Don't add a return attribute if the return type is void
+                outputFile.println();
+            } else {
+                outputFile.print(" return=\"");
+                emitType(retType);
+                outputFile.println("\"");
+            }
+            outputFile.print("      abstract=\"" + md[i].isAbstract() + "\"");
+            outputFile.print(" native=\"" + md[i].isNative() + "\"");
+            outputFile.println(" synchronized=\"" + md[i].isSynchronized() + "\"");
+            addCommonModifiers(md[i], 6);
+            outputFile.println(">");
+            // Generate the parameter elements, if any
+            Parameter[] params = md[i].parameters();
+            for (int j = 0; j < params.length; j++) {
+                outputFile.print("      <param name=\"" + params[j].name() + "\"");
+                outputFile.print(" type=\"");
+                emitType(params[j].type());
+                outputFile.println("\"/>");
+            }
+
+            // Generate the exception elements if any exceptions are thrown
+            processExceptions(md[i].thrownExceptions());
+
+            addDocumentation(md[i], 6);
+
+            outputFile.println("    </method>");
+        }//for
+    }//processMethods()
+
+    /**
+     * Process the fields in the class.
+     *
+     * @param fd An array of FieldDoc objects
+     */
+    public void processFields(FieldDoc[] fd) {
+        if (trace) System.out.println("PROCESSING FIELDS, number=" + fd.length);
+        for (int i = 0; i < fd.length; i++) {
+            String fieldName = fd[i].name();
+            if (trace) System.out.println("PROCESSING FIELD: " + fieldName);
+            // Only save the shown elements
+            if (!shownElement(fd[i], memberVisibilityLevel))
+                continue;
+            outputFile.print("    <field name=\"" + fieldName + "\"");
+            outputFile.print(" type=\"");
+            emitType(fd[i].type());
+            outputFile.println("\"");
+            outputFile.print("      transient=\"" + fd[i].isTransient() + "\"");
+            outputFile.println(" volatile=\"" + fd[i].isVolatile() + "\"");
+/* JDK 1.4 and later */
+/*
+            String value = fd[i].constantValueExpression();
+            if (value != null)
+                outputFile.println(" value=\"" + value + "\"");
+*/
+            addCommonModifiers(fd[i], 6);
+            outputFile.println(">");
+
+            addDocumentation(fd[i], 6);
+
+            outputFile.println("    </field>");
+
+        }//for
+    }//processFields()
+    
+    /**
+     * Emit the type name. Removed any prefixed warnings about ambiguity.
+     * The type maybe an array.
+     *
+     * @param type A Type object.
+     */
+    public void emitType(com.sun.javadoc.Type type) {
+        String name = buildEmittableTypeString(type);
+        if (name == null)
+            return;
+        outputFile.print(name);
+    }
+
+    /**
+     * Build the emittable type name. The type may be an array and/or
+     * a generic type.
+     *
+     * @param type A Type object
+     * @return The emittable type name
+     */
+    private String buildEmittableTypeString(com.sun.javadoc.Type type) {
+        if (type == null) {
+    	    return null;
+        }
+      // type.toString() returns the fully qualified name of the type
+      // including the dimension and the parameters we just need to
+      // escape the generic parameters brackets so that the XML
+      // generated is correct
+      String name = type.toString().replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+      if (name.startsWith("<<ambiguous>>")) {
+          name = name.substring(13);
+      }
+      return name;
+    }    
+
+    /**
+     * Emit the XML header.
+     */
+    public void emitXMLHeader() {
+        outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>");
+        outputFile.println("<!-- Generated by the JDiff Javadoc doclet -->");
+        outputFile.println("<!-- (" + JDiff.jDiffLocation + ") -->");
+        outputFile.println("<!-- on " + new Date() + " -->");
+        outputFile.println();
+/* No need for this any longer, since doc block text is in an CDATA element
+        outputFile.println("<!-- XML Schema is used, but XHTML transitional DTD is needed for nbsp -->");
+        outputFile.println("<!-- entity definitions etc.-->");
+        outputFile.println("<!DOCTYPE api");
+        outputFile.println("     PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"");
+        outputFile.println("     \"" + baseURI + "/TR/xhtml1/DTD/xhtml1-transitional.dtd\">");
+*/
+        outputFile.println("<api");
+        outputFile.println("  xmlns:xsi='" + baseURI + "/2001/XMLSchema-instance'");
+        outputFile.println("  xsi:noNamespaceSchemaLocation='api.xsd'");
+        outputFile.println("  name=\"" + apiIdentifier + "\"");
+        outputFile.println("  jdversion=\"" + JDiff.version + "\">");
+        outputFile.println();
+    }
+
+    /**
+     * Emit the XML footer.
+     */
+    public void emitXMLFooter() {
+        outputFile.println();
+        outputFile.println("</api>");
+    }
+
+    /** 
+     * Determine if the program element is shown, according to the given 
+     * level of visibility. 
+     *
+     * @param ped The given program element.
+     * @param visLevel The desired visibility level; "public", "protected",
+     *   "package" or "private". If null, only check for an exclude tag.
+     * @return boolean Set if this element is shown.
+     */
+    public boolean shownElement(Doc doc, String visLevel) {
+        // If a doc block contains @exclude or a similar such tag, 
+        // then don't display it.
+	if (doExclude && excludeTag != null && doc != null) {
+            String rct = doc.getRawCommentText();
+            if (rct != null && rct.indexOf(excludeTag) != -1) {
+                return false;
+	    }
+	}  
+	if (visLevel == null) {
+	    return true;
+	}
+	ProgramElementDoc ped = null;
+	if (doc instanceof ProgramElementDoc) {
+	    ped = (ProgramElementDoc)doc;
+	}
+        if (visLevel.compareTo("private") == 0)
+            return true;
+        // Show all that is not private 
+        if (visLevel.compareTo("package") == 0)
+            return !ped.isPrivate();
+        // Show all that is not private or package
+        if (visLevel.compareTo("protected") == 0)
+            return !(ped.isPrivate() || ped.isPackagePrivate());
+        // Show all that is not private or package or protected,
+        // i.e. all that is public
+        if (visLevel.compareTo("public") == 0)
+            return ped.isPublic();
+        return false;
+    } //shownElement()
+    
+    /** 
+     * Strip out non-printing characters, replacing them with a character 
+     * which will not change where the end of the first sentence is found.
+     * This character is the hash mark, '&#035;'.
+     */
+    public String stripNonPrintingChars(String s, Doc doc) {
+        if (!stripNonPrintables)
+            return s;
+        char[] sa = s.toCharArray();
+        for (int i = 0; i < sa.length; i++) {
+            char c = sa[i];
+            // TODO still have an issue with Unicode: 0xfc in java.lang.String.toUpperCase comments
+//            if (Character.isDefined(c))
+            if (Character.isLetterOrDigit(c))
+                continue;
+            // There must be a better way that is still platform independent!
+            if (c == ' ' ||
+                c == '.' ||
+                c == ',' ||
+                c == '\r' ||
+                c == '\t' ||
+                c == '\n' ||
+                c == '!' ||
+                c == '?' ||
+                c == ';' ||
+                c == ':' ||
+                c == '[' ||
+                c == ']' ||
+                c == '(' ||
+                c == ')' ||
+                c == '~' ||
+                c == '@' ||
+                c == '#' ||
+                c == '$' ||
+                c == '%' ||
+                c == '^' ||
+                c == '&' ||
+                c == '*' ||
+                c == '-' ||
+                c == '=' ||
+                c == '+' ||
+                c == '_' ||
+                c == '|' ||
+                c == '\\' ||
+                c == '/' ||
+                c == '\'' ||
+                c == '}' ||
+                c == '{' ||
+                c == '"' ||
+                c == '<' ||
+                c == '>' ||
+                c == '`'
+                )
+                continue;
+/* Doesn't seem to return the expected values?
+            int val = Character.getNumericValue(c);
+//            if (s.indexOf("which is also a test for non-printable") != -1)
+//                System.out.println("** Char " + i + "[" + c + "], val =" + val); //DEBUG
+            // Ranges from http://www.unicode.org/unicode/reports/tr20/
+            // Should really replace 0x2028 and  0x2029 with <br/>
+            if (val == 0x0 ||
+                inRange(val, 0x2028, 0x2029) || 
+                inRange(val, 0x202A, 0x202E) || 
+                inRange(val, 0x206A, 0x206F) || 
+                inRange(val, 0xFFF9, 0xFFFC) || 
+                inRange(val, 0xE0000, 0xE007F)) {
+                if (trace) {
+                    System.out.println("Warning: changed non-printing character  " + sa[i] + " in " + doc.name()); 
+                }
+                sa[i] = '#';
+            }
+*/
+            // Replace the non-printable character with a printable character
+            // which does not change the end of the first sentence
+            sa[i] = '#';
+        }
+        return new String(sa);
+    }
+
+    /** Return true if val is in the range [min|max], inclusive. */
+    public boolean inRange(int val, int min, int max) {
+        if (val < min)
+            return false;
+        if (val > max)
+            return false;
+        return true;
+    }
+
+    /** 
+     * Add at least the first sentence from a doc block to the API. This is
+     * used by the report generator if no comment is provided.
+     * Need to make sure that HTML tags are not confused with XML tags.
+     * This could be done by stuffing the &lt; character to another string
+     * or by handling HTML in the parser. This second option seems neater. Note that
+     * XML expects all element tags to have either a closing "/>" or a matching
+     * end element tag. Due to the difficulties of converting incorrect HTML
+     * to XHTML, the first option is used.
+     */
+    public void addDocumentation(ProgramElementDoc ped, int indent) {
+        String rct = ((Doc)ped).getRawCommentText();
+        if (rct != null) {
+            rct = stripNonPrintingChars(rct, (Doc)ped);
+            rct = rct.trim();
+            if (rct.compareTo("") != 0 && 
+                rct.indexOf(Comments.placeHolderText) == -1 &&
+                rct.indexOf("InsertOtherCommentsHere") == -1) {
+                int idx = endOfFirstSentence(rct);
+                if (idx == 0)
+                    return;
+                for (int i = 0; i < indent; i++) outputFile.print(" ");
+                outputFile.println("<doc>");
+                for (int i = 0; i < indent; i++) outputFile.print(" ");
+                String firstSentence = null;
+                if (idx == -1)
+                    firstSentence = rct;
+                else
+                    firstSentence = rct.substring(0, idx+1);
+                boolean checkForAts = false;
+                if (checkForAts && firstSentence.indexOf("@") != -1 && 
+                    firstSentence.indexOf("@link") == -1) {
+                    System.out.println("Warning: @ tag seen in comment: " + 
+                                       firstSentence);
+                }
+                String firstSentenceNoTags = API.stuffHTMLTags(firstSentence);
+                outputFile.println(firstSentenceNoTags);
+                for (int i = 0; i < indent; i++) outputFile.print(" ");
+                outputFile.println("</doc>");
+            }
+        }
+    }
+
+    /** 
+     * Add at least the first sentence from a doc block for a package to the API. This is
+     * used by the report generator if no comment is provided.
+     * The default source tree may not include the package.html files, so
+     * this may be unavailable in many cases.
+     * Need to make sure that HTML tags are not confused with XML tags.
+     * This could be done by stuffing the &lt; character to another string
+     * or by handling HTML in the parser. This second option is neater. Note that
+     * XML expects all element tags to have either a closing "/>" or a matching
+     * end element tag.  Due to the difficulties of converting incorrect HTML
+     * to XHTML, the first option is used.
+     */
+    public void addPkgDocumentation(RootDoc root, PackageDoc pd, int indent) {
+        String rct = null;
+        String filename = pd.name();
+        try {
+            // See if the source path was specified as part of the
+            // options and prepend it if it was.
+            String srcLocation = null;
+            String[][] options = root.options();
+            for (int opt = 0; opt < options.length; opt++) {
+                if ((options[opt][0]).compareTo("-sourcepath") == 0) {
+                    srcLocation = options[opt][1];
+                    break;
+                }
+            }
+            filename = filename.replace('.', JDiff.DIR_SEP.charAt(0));
+            if (srcLocation != null) {
+                // Make a relative location absolute 
+                if (srcLocation.startsWith("..")) {
+                    String curDir = System.getProperty("user.dir");
+                    while (srcLocation.startsWith("..")) {
+                        srcLocation = srcLocation.substring(3);
+                        int idx = curDir.lastIndexOf(JDiff.DIR_SEP);
+                        curDir = curDir.substring(0, idx+1);
+                    }
+                    srcLocation = curDir + srcLocation;
+                }
+                filename = srcLocation + JDiff.DIR_SEP + filename;
+            }
+            // Try both ".htm" and ".html"
+            filename += JDiff.DIR_SEP + "package.htm";
+            File f2 = new File(filename);
+            if (!f2.exists()) {
+                filename += "l";
+            }
+            FileInputStream f = new FileInputStream(filename);
+            BufferedReader d = new BufferedReader(new InputStreamReader(f));
+            String str = d.readLine();
+ 	    // Ignore everything except the lines between <body> elements
+	    boolean inBody = false;
+	    while(str != null) {
+                if (!inBody) {
+		    if (str.toLowerCase().trim().startsWith("<body")) {
+			inBody = true;
+		    }
+		    str = d.readLine(); // Get the next line
+		    continue; // Ignore the line
+		} else {
+		    if (str.toLowerCase().trim().startsWith("</body")) {
+			inBody = false;
+			continue; // Ignore the line
+		    }
+		}
+                if (rct == null)
+                    rct = str + "\n";
+                else
+                    rct += str + "\n";
+                str = d.readLine();
+            }
+        }  catch(java.io.FileNotFoundException e) {
+            // If it doesn't exist, that's fine
+            if (trace)
+                System.out.println("No package level documentation file at '" + filename + "'");
+        } catch(java.io.IOException e) {
+            System.out.println("Error reading file \"" + filename + "\": " + e.getMessage());
+            System.exit(5);
+        }     
+        if (rct != null) {
+            rct = stripNonPrintingChars(rct, (Doc)pd);
+            rct = rct.trim();
+            if (rct.compareTo("") != 0 &&
+                rct.indexOf(Comments.placeHolderText) == -1 &&
+                rct.indexOf("InsertOtherCommentsHere") == -1) {
+                int idx = endOfFirstSentence(rct);
+                if (idx == 0)
+                    return;
+                for (int i = 0; i < indent; i++) outputFile.print(" ");
+                outputFile.println("<doc>");
+                for (int i = 0; i < indent; i++) outputFile.print(" ");
+                String firstSentence = null;
+                if (idx == -1)
+                    firstSentence = rct;
+                else
+                    firstSentence = rct.substring(0, idx+1);
+                String firstSentenceNoTags = API.stuffHTMLTags(firstSentence);
+                outputFile.println(firstSentenceNoTags);
+                for (int i = 0; i < indent; i++) outputFile.print("