remove jdiff and scripts; add documentation

This commit is contained in:
Greg DiCristofaro 2023-09-01 14:20:23 -04:00
parent 0abbbd473c
commit 40b9dc991b
68 changed files with 58 additions and 20649 deletions

2
.gitignore vendored
View File

@ -78,8 +78,6 @@ genfiles.properties
!/netbeans-plat/15
/docs/doxygen-user/user-docs
/docs/doxygen-dev/build-docs
/jdiff-javadocs/*
/jdiff-logs/*
/gen_version.txt
hs_err_pid*.log

View File

@ -1,295 +0,0 @@
"""
Generates an api diff from one commit to another. This script relies on gitpython and similarly require git
installed on the system. This script also requires python 3.
This script can be called as follows:
python apidiff.py <previous tag id> <latest tag id> -r <repo path> -o <output path>
If the '-o' flag is not specified, this script will create a folder at apidiff_output in the same directory as the
script. For full list of options call:
python apidiff.py -h
"""
import os
import subprocess
import sys
import time
from pathlib import Path
from typing import Tuple, Iterator, List
import argparse as argparse
from git import Repo, Blob, Tree
"""
These are exit codes for jdiff:
return code 1 = error in jdiff
return code 100 = no changes
return code 101 = compatible changes
return code 102 = incompatible changes
"""
NO_CHANGES = 100
COMPATIBLE = 101
NON_COMPATIBLE = 102
ERROR = 1
def compare_xml(jdiff_path: str, root_dir: str, output_folder: str, oldapi_folder: str,
newapi_folder: str, api_file_name: str, log_path: str) -> int:
"""
Compares xml generated by jdiff using jdiff.
:param jdiff_path: Path to jdiff jar.
:param root_dir: directory for output .
:param output_folder: Folder for diff output.
:param oldapi_folder: Folder name of old api (i.e. release-4.10.2).
:param newapi_folder: Folder name of new api (i.e. release-4.10.2).
:param api_file_name: Name of xml file name (i.e. if output.xml, just 'output')
:param log_path: Path to log file.
:return: jdiff exit code.
"""
jdiff_parent = os.path.dirname(jdiff_path)
null_file = fix_path(os.path.join(jdiff_parent, "lib", "Null.java"))
# comments are expected in a specific place
make_dir(os.path.join(root_dir,
output_folder,
f"user_comments_for_{oldapi_folder}",
f"{api_file_name}_to_{newapi_folder}"))
log = open(log_path, "w")
cmd = ["javadoc",
"-doclet", "jdiff.JDiff",
"-docletpath", fix_path(jdiff_path),
"-d", fix_path(output_folder),
"-oldapi", fix_path(os.path.join(oldapi_folder, api_file_name)),
"-newapi", fix_path(os.path.join(newapi_folder, api_file_name)),
"-script",
null_file]
code = None
try:
jdiff = subprocess.Popen(cmd, stdout=log, stderr=log, cwd=root_dir)
jdiff.wait()
code = jdiff.returncode
except Exception as e:
log_and_print(log, f"Error executing javadoc: {str(e)}\nExiting...")
exit(1)
log.close()
print(f"Compared XML for {oldapi_folder} {newapi_folder}")
if code == NO_CHANGES:
print(" No API changes")
elif code == COMPATIBLE:
print(" API Changes are backwards compatible")
elif code == NON_COMPATIBLE:
print(" API Changes are not backwards compatible")
else:
print(" *Error in XML, most likely an empty module")
sys.stdout.flush()
return code
def gen_xml(jdiff_path: str, output_path: str, log_output_path: str, src: str, packages: List[str]):
"""
Uses jdiff to generate an xml representation of the source code.
:param jdiff_path: Path to jdiff jar.
:param output_path: Path to output path of diff.
:param log_output_path: The log output path.
:param src: The path to the source code.
:param packages: The packages to process.
"""
make_dir(output_path)
log = open_log_file(log_output_path)
log_and_print(log, f"Generating XML for: {src} outputting to: {output_path}")
cmd = ["javadoc",
"-doclet", "jdiff.JDiff",
"-docletpath", fix_path(jdiff_path),
"-apiname", fix_path(output_path),
"-sourcepath", fix_path(src)]
cmd = cmd + packages
try:
jdiff = subprocess.Popen(cmd, stdout=log, stderr=log)
jdiff.wait()
except Exception as e:
log_and_print(log, f"Error executing javadoc {str(e)}\nExiting...")
exit(1)
log_and_print(log, f"Generated XML for: " + str(packages))
log.close()
sys.stdout.flush()
def _list_paths(root_tree: Tree, src_folder, path: Path = None) -> Iterator[Tuple[str, Blob]]:
"""
Given the root path to serve as a prefix, walks the tree of a git commit returning all files and blobs.
Repurposed from: https://www.enricozini.org/blog/2019/debian/gitpython-list-all-files-in-a-git-commit/
Args:
root_tree: The tree of the commit to walk.
src_folder: relative path in repo to source folder that will be copied.
path: The path to use as a prefix.
Returns: A tuple iterator where each tuple consists of the path as a string and a blob of the file.
"""
for blob in root_tree.blobs:
next_path = Path(path) / blob.name if path else blob.name
if Path(src_folder) in Path(next_path).parents:
ret_item = (next_path, blob)
yield ret_item
for tree in root_tree.trees:
next_path = Path(path) / tree.name if path else tree.name
yield from _list_paths(tree, src_folder, next_path)
def _get_tree(repo_path: str, commit_id: str) -> Tree:
"""
Retrieves the git tree that can be walked for files and file content at the specified commit.
Args:
repo_path: The path to the repo or a child directory of the repo.
commit_id: The commit id.
Returns: The tree.
"""
repo = Repo(repo_path, search_parent_directories=True)
commit = repo.commit(commit_id.strip())
return commit.tree
def copy_commit_paths(repo_path, commit_id, src_folder, output_folder):
"""
Copies all files located within a repo in the folder 'src_folder' to 'output_folder'.
:param repo_path: The path to the repo.
:param commit_id: The commit id.
:param src_folder: The relative path in the repo to the source folder.
:param output_folder: The output folder where the source will be copied.
"""
tree = _get_tree(repo_path, commit_id)
for rel_path, blob in _list_paths(tree, src_folder):
output_path = os.path.join(output_folder, os.path.relpath(rel_path, src_folder))
parent_folder = os.path.dirname(output_path)
make_dir(parent_folder)
output_file = open(output_path, 'w')
output_file.write(blob.data_stream.read().decode('utf-8'))
output_file.close()
def open_log_file(log_path):
"""
Opens a path to a lof file for appending. Creating directories and log file as necessary.
:param log_path: The path to the log file.
:return: The log file opened for writing.
"""
if not os.path.exists(log_path):
make_dir(os.path.dirname(log_path))
Path(log_path).touch()
return open(log_path, 'a+')
def fix_path(path):
"""
Generates a path that is escaped from cygwin paths if present.
:param path: Path (possibly including cygdrive).
:return: The normalized path.
"""
if "cygdrive" in path:
new_path = path[11:]
return "C:/" + new_path
else:
return path
def log_and_print(log, message):
"""
Creates a log entry and prints to stdout.
:param log: The log file object.
:param message: The string to be printed.
"""
time_stamp = time.strftime('%Y-%m-%d %H:%M:%S')
print(f"{time_stamp}: {message}")
log.write(f"{time_stamp}: {message}\n")
def make_dir(dir_path: str):
"""
Create the given directory, if it doesn't already exist.
:param dir_path: The path to the directory.
:return: True if created.
"""
try:
if not os.path.isdir(dir_path):
os.makedirs(dir_path)
if os.path.isdir(dir_path):
return True
return False
except IOError:
print("Exception thrown when creating directory: " + dir_path)
return False
def run_compare(output_path: str, jdiff_path: str, repo_path: str, src_rel_path: str, prev_commit_id: str,
latest_commit_id: str, packages: List[str]):
"""
Runs a comparison of the api between two different commits/branches/tags of the same repo generating a jdiff diff.
:param output_path: The output path for artifacts.
:param jdiff_path: The path to the jdiff jar.
:param repo_path: The path to the repo.
:param src_rel_path: The relative path in the repo to the source directory.
:param prev_commit_id: The previous commit/branch/tag id.
:param latest_commit_id: The latest commit/branch/tag id.
:param packages: The packages to be considered for the api diff.
"""
log_path = os.path.join(output_path, "messages.log")
output_file_name = "output"
diff_dir = "diff"
src_folder = "src"
for commit_id in [prev_commit_id, latest_commit_id]:
src_copy = os.path.join(output_path, src_folder, commit_id)
copy_commit_paths(repo_path, commit_id, src_rel_path, src_copy)
gen_xml(jdiff_path, os.path.join(output_path, commit_id, output_file_name), log_path, src_copy, packages)
# compare the two
compare_xml(jdiff_path, output_path, os.path.join(output_path, diff_dir),
prev_commit_id, latest_commit_id, output_file_name, log_path)
def main():
parser = argparse.ArgumentParser(description="Generates a jdiff diff of the java api between two commits in a "
"repo.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument(dest='prev_commit', type=str, help=r'The git commit id/branch/tag to be used for the first '
r'commit')
parser.add_argument(dest='latest_commit', type=str, help=r'The git commit id/branch/tag to be used for the latest '
r'commit')
parser.add_argument('-r', '--repo', dest='repo_path', type=str, required=True,
help='The path to the repo. If not specified, path of script is used.')
parser.add_argument('-o', '--output', dest='output_path', type=str, required=False,
help='The location for output of all artifacts. Defaults to an output folder in same directory'
'as script')
parser.add_argument('-s', '--src', dest='src_rel_folder', type=str, required=False, default="bindings/java/src",
help='The relative path within the repo of the src folder.')
# list of packages can be specified like this:
# https://stackoverflow.com/questions/15753701/how-can-i-pass-a-list-as-a-command-line-argument-with-argparse
parser.add_argument('-p', '--packages', dest='packages', nargs='+', required=False,
default=["org.sleuthkit.datamodel"], help='The packages to consider in api diff.')
parser.add_argument('-j', '--jdiff', dest='jdiff_path', type=str, required=False,
help='The packages to consider in api diff.')
args = parser.parse_args()
script_path = os.path.dirname(os.path.realpath(__file__))
repo_path = args.repo_path if args.repo_path else script_path
output_path = args.output_path if args.output_path else os.path.join(script_path, "apidiff_output")
jdiff_path = args.jdiff_path if args.jdiff_path else os.path.join(script_path,
"thirdparty/jdiff/v-custom/jdiff.jar")
run_compare(output_path=output_path,
jdiff_path=jdiff_path,
repo_path=repo_path,
packages=args.packages,
src_rel_path=args.src_rel_folder,
prev_commit_id=args.prev_commit,
latest_commit_id=args.latest_commit)
main()

View File

@ -0,0 +1,25 @@
# APIDiff
## Overview
This code can be used to determine the public API changes between the previous version of Autopsy jars to current version of Autopsy jars. Based on those changes, this code can update version numbers (release, implementation, specification, dependency version).
## Sample Usage & Procedure
1. Before starting download the nbm jar files from the [previous release](https://github.com/sleuthkit/autopsy/releases/). The jar files will be located in the zip file or the installation directory (i.e. `C:\Program Files\Autopsy-x.xx.x`) at `autopsy/modules`.
2. Make sure you build the current source directory in order for the program to find the new compiled jars.
3. This code can be called from this directory with a command like: `java -jar APIUpdate-1.0-jar-with-dependencies.jar -p C:\path\to\prev\vers\jars\` to get api updates.
- You can specify the `-u` flag to make updates in source code making the command: `java -jar APIUpdate-1.0-jar-with-dependencies.jar -p C:\path\to\prev\vers\jars\ -u`.
- You can also add ` >C:\Users\gregd\Desktop\outputdiff.txt 2>&1` to output to a file.
## Arguments
```
usage: APIUpdate
-c,--curr-path <path> The path to the current version jar files
-cv,--curr-version <version> The current version number
-p,--prev-path <path> The path to the previous version jar files
-pv,--prev-version <version> The previous version number
-s,--src-path <path> The path to the root of the autopsy report
-u,--update Update source code versions
```

View File

@ -46,6 +46,8 @@
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<!--output jar file to project root-->
<outputDirectory>${project.basedir}</outputDirectory>
</configuration>
<executions>
<execution>

View File

@ -19,10 +19,14 @@
package org.sleuthkit.autopsy.apiupdate;
import java.io.File;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
@ -74,11 +78,11 @@ public class CLIProcessor {
private static final Option SRC_LOC_OPT = Option.builder()
.argName("path")
.desc("The path to the root of the autopsy repor")
.desc("The path to the root of the autopsy report")
.hasArg(true)
.longOpt("src-path")
.option("s")
.required(true)
.required(false)
.build();
private static final Option UPDATE_OPT = Option.builder()
@ -102,9 +106,11 @@ public class CLIProcessor {
private static final String DEFAULT_CURR_VERSION = "Current Version";
private static final String DEFAULT_PREV_VERSION = "Previous Version";
private static final String BUILD_REL_PATH = "build/cluster/modules";
private static final String JAR_SRC_REL_PATH = "../../../";
/**
* Creates an Options object from a list of options.
*
* @param opts The list of options.
* @return The options object.
*/
@ -133,6 +139,7 @@ public class CLIProcessor {
/**
* Prints help message.
*
* @param ex The exception or null if no exception.
*/
static void printHelp(Exception ex) {
@ -145,9 +152,10 @@ public class CLIProcessor {
/**
* Parses the CLI args.
*
* @param args The arguments.
* @return The CLIArgs object.
* @throws ParseException
* @throws ParseException
*/
static CLIArgs parseCli(String[] args) throws ParseException {
CommandLine helpCmd = parser.parse(HELP_OPTIONS, args, true);
@ -159,12 +167,23 @@ public class CLIProcessor {
CommandLine cmd = parser.parse(CLI_OPTIONS, args);
String curVers = cmd.hasOption(CUR_VERS_OPT) ? cmd.getOptionValue(CUR_VERS_OPT) : DEFAULT_CURR_VERSION;
String prevVers = cmd.hasOption(PREV_VERS_OPT) ? cmd.getOptionValue(PREV_VERS_OPT) : DEFAULT_PREV_VERSION;
String curVersPath = cmd.hasOption(CUR_VERS_PATH_OPT)
? cmd.getOptionValue(CUR_VERS_PATH_OPT)
: Paths.get(cmd.getOptionValue(SRC_LOC_OPT), BUILD_REL_PATH).toString();
String srcPath;
try {
srcPath = cmd.hasOption(SRC_LOC_OPT)
? cmd.getOptionValue(SRC_LOC_OPT)
: new File(CLIProcessor.class.getProtectionDomain().getCodeSource().getLocation()
.toURI()).toPath().resolve(JAR_SRC_REL_PATH).toAbsolutePath().toString();
} catch (URISyntaxException ex) {
throw new ParseException("Unable to determine source path from current location: " + ex.getMessage());
}
String curVersPath = cmd.hasOption(CUR_VERS_PATH_OPT)
? cmd.getOptionValue(CUR_VERS_PATH_OPT)
: Paths.get(srcPath, BUILD_REL_PATH).toString();
String prevVersPath = cmd.getOptionValue(PREV_VERS_PATH_OPT);
String srcPath = cmd.getOptionValue(SRC_LOC_OPT);
boolean makeUpdate = cmd.hasOption(UPDATE_OPT);
File curVersFile = new File(curVersPath);
File prevVersFile = new File(prevVersPath);
@ -223,15 +242,16 @@ public class CLIProcessor {
}
/**
* @return The path to the directory containing the jars for current version.
* @return The path to the directory containing the jars for current
* version.
*/
public File getCurrentVersPath() {
return currentVersPath;
}
/**
* @return The path to the directory containing the jars for previous version.
* @return The path to the directory containing the jars for previous
* version.
*/
public File getPreviousVersPath() {
return previousVersPath;

View File

@ -1,2 +0,0 @@
build
bin

View File

@ -1,506 +0,0 @@
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!

View File

@ -1,59 +0,0 @@
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/

View File

@ -1,182 +0,0 @@
Released
--------
1.1.1 - 3rd October 2008
Patch for 1990278 (the xml report doesn't escape & in type names) from
owenomalley included.
1.1.0 - 3rd September 2007
Support for generics (new with J2SE1.5) added. Still need to add
support for enums, varargs and annotations.
#1604749 fixed - Scanning only specified files is broken
Merged patch from Stefan Aust so you can pass a "source" argument to the Ant
task to specify the language version, e.g. for dealing with asserts.
1.0.10 - 31st May 2004
#954228 Feature request from Kohsuke Kawaguchi (and 2 years ago from
Steve Loughran!) to add a custom Ant Task for JDiff.
#875516 Some HTML elements in user comments were being processed incorrectly.
Thanks to Jojo Dijamco for pointing this bug out.
#875470 @link tags were not expanded in user comments.
#877437 Other issues with @link in user comments.
Many thanks to Brian Duff for these bugs and their fixes.
#815039 If the only changes in a class are in native or sync method changes,
and showAllChanges is not used, then no change has occurred. Thanks to
C. Le Goff for finding this bug and providing the fix.
#872344 JDiff was using a relative URI for XMLReader.parse(). This
made it hard to use an XML parser other than Xerces. Thanks to Brian
Duff for finding this bug and providing the fix.
Tested with j2sdk1.5.0 beta1, released with examples built by JDK1.4.2.
Updated copyright notices to 2004.
1.0.9 - 19th June 2003
Darren Carlton <darren at bea.com> pointed out that Javadoc
package.html files may, in fact, be full HTML documents.
This fix only solves the issue for package.html
HTML, not the general issue of HTML comments.
Jan Rueten-Budde <jan at rueten-budde.de> noted that the -excludetag
option was not working for package-level documentation block,
and that its documentation needed improvement.
Jan also observed that only the simple names, not the fully-qualified names of
exceptions were being saved and compared. This meant that if an exception
changed class, but kept the same name, the change was not detected. The
qualified name is now used. See the troubleshooting section for how to use the
simple names if the entries in reports are too large.
Tested with J2SE1.4.1.
Minor HTML formatting fixes.
Changed the default behaviour to not show changes in native and sychronized
modifiers. Added the -showallchanges to show changes in these modifiers. Thanks
go to Jesse Glick for this.
Bug 757376. Now copes with body elements with attributes in package.htm[l]
1.0.8 - 31st July 2002
Completed feature request 543260 (add an option to emit XML in another
directory) - thanks to Martin Bosak for these changes.
Fixed bug 587072 (InvocationTargetException doing diff) by adding extra string
length checks when determining the first sentence of a comment.
Added a top-level ANT build file.
1.0.7 - June 24th 2002
Completed feature request 561922 to make JDiff JAXP compliant. This
change required xerces.jar to be added to the -docletpath values in
build scripts.
Fixed feature request 510307 and bug 517383: duplicate comment
ids. This only occurred when package-level documentation existed
and also the last file scanned was an interface.
Fixed bug where comments for interfaces did not have @link tags
expanded.
Fixed bug 547422 by adding a note about other ANT Javadoc task
properties to the example build.xml file.
Removed some left-over Windows variable notation from csh files (patch
565921) and made the ANT build.xml files less platform-dependent.
Added more comments in the ANT build.xml files about the -javadocold
and -javadocnew arguments, and how to make sure that links to shipped Javadoc
documentation are relative, and not absolute.
Created KNOWN_LIMITATIONS.txt file to document issues which are
unlikely to be fixed soon.
Renamed JDIFF_INSTALL local variable to JDIFF_HOME in Jdiff build scripts,
since this seems to be more like ANT (patch 566022).
1.0.6 - January 9th 2002
Fixed bug 494135 (Compare classes which have no package).
Fixed bug 494970 (HTML strike element in the index was not closed when
fields of the same name were deleted from different classes), and made
a minor change to the index entries for fields with the same names.
Fixed a bug where not specifying -javadocold could lead to a broken link
in removed constructor text, and in the links to old package descriptions.
Added feature request 494058, an ANT 1.4.1 build file for the JDiff
examples, using the ANT Javadoc task - thanks to Douglas Bullard.
Added feature request 493367 (Ability to use other XML parsers) -
thanks to Vladislav Protasov for suggesting this.
Changed the default to be to *not* report changes in documentation. Use the
-docchanges to enable this option.
Added the ability to run jdiff.JDiff as a Java application called from a
batch file or shell script, which makes the necessary calls to Javadoc
using a single XML configuration file.
Added better detection of a method or field moving to or from a parent
class.
Added checking for changes in field values (the @value tag in J2SE1.4).
Changed the link to existing Javadoc link to the specific package or class.
Improved the algorithm used to find the summary sentence in documentation.
Added the -windowtitle and -doctitle options to make it easier to
uniquely identify different reports about the same APIs.
Added ability to validate the XML generated by JDiff.
Changed the XML representation of <implements> to be a single element.
Options to display the version and usage of JDiff added.
More cleaning up of the generated HTML.
1.0.5 - November 17th 2001
Fixed bug 482207 (Unable to run javadiff behind a firewall) by adding
the -baseURI option.
Fixed bug 482194 (Missing support for iso-8859-1 charset), which also
caused the option -allsentences to be renamed -firstsentence and
reversed in sense.
Added links to previous and next packages and classes.
Added top-level index file for all documentation differences.
Added links from documentation difference pages to classes, in
addition to the existing links to constructors, methods and fields.
Added META tags to generated HTML.
Removed unnecessary use of xhtml1-transitional.dtd, since all comment
text is in CDATA elements.
Used http://www.w3.org/People/Raggett/tidy/ to clean up the generated HTML.
Corrected somes uses of JDK/J2SE and the capitalization of Javadoc.
1.0.4 - November 8th 2001
Added feature request 472605 with the documentation differences page,
which shows specific changes in Javadoc documentation as inserts and deletes.
Improved the end of sentence checking in doc blocks.
Added graphics of the statistics histograms.
1.0.3 - November 2nd 2001
Fixed bugs in tracking package and class-level documentation changes.
Fixed bugs in tracking documentation changes at the constructor,
method and field level.
Fixed a bug in indexes links to default constructors which had changed
Fixed a bug that -nodocchanges did not behave as expected
Fixed a bug in converting @link tags to HTML links, but some broken
links remain.
Fixed bug 472529 (updated documentation for new options)
Fixed bug 472703 (background color is weird)
Fixed bug 476930 (HTML tables need &nbsp; in some places)
Added feature request 476946 (location of user comments file)
Added feature request 476201 (Need more statistics) by creating the
-stats option which generates a statistics page in the report.
Added feature request 476202 (Port to J2SE1.4)
Improved merging of additions and removals into changes for methods.
Better display of elements which start with underscores in indexes
1.0.2 - October 2001
Added scripts for non-Windows platforms
Added support for emitting all documentation for an API to the XML, and then
tracking changes in the documentation, with links to the old and new comments.
Added the option -allsentences to emit all documentation for an API.
Added an option -nodocchanges to disable the comparison of documentation.
1.0.1 - October 2001
Fixed bug 469794.
Added script to generate differences between jar files.
Added documentation about use behind firewalls.
Added notes for future work.
1.0 - October 9th 2001
First release of JDiff.

Binary file not shown.

View File

@ -1,20 +0,0 @@
JDiff 1.1.1
Known limitations, features and bugs which aren't likely to get fixed
soon are listed here. There is another list of smaller gripes in src/TODO.
If you need one of these items to be fixed, submit a patch to the
patches page at javadiff.sourceforge.net, or contact Matt Doar at
mdoar@pobox.com.
The number which follows each entry is the SourceForge bug identifier.
1) HTML comments are also included in the diff between
documentation. (510309).
2) JDiff doesn't expand @docroot doc tags in Javadoc1.4
3) JDiff does not support locales other than English (530063).
4) Handling of @link tags in the first sentence of comments is not as
general as it should be. See convertAtLinks() comments.
5) strictfp and strict modifiers are not checked for between
APIs (547422).
6) Changes in parameter names are not tracked.

View File

@ -1,236 +0,0 @@
********************
* JDiff Work Items *
********************
KNOWN BUGS
==========
BUG: JDiff doesn't expand @docroot doc tags in Javadoc1.4
BUG: Handling of @link tags in the first sentence of comments is not as
general as it should be. See convertAtLinks() comments.
MINOR BUG: @links to methods may not be expanded properly if we don't have the
signature in the @link. This only happens once (at invoke) in J2SE1.2/1.3,
and the link goes to the right page, just not the right named anchor.
Pretty rare.
BUG? The MergeChanges code may not be transferring all change information into
the MemberDiff for methods and fields?
DESIGN
======
Short Term:
----------
Add an option for verbose output?
Add an option to redirect output to a log file?
Does not check when there is a change in the class in which a locally defined
method is overriding another method.
Statistics - is a change in inheritance for a method or field really equivalent to one add and one delete in all cases?
org.omg.CORBA.AnySeqHelper is generated by javac or javadoc using J2SE1.3 on
in J2SE1.2,
so has no documentation at the Sun website. Same for J2SE1.4 with org.omg.CosNaming.BindingIteratorPOA.html. This is probably due to the issue now noted in the "Limitations" section in jdiff.html.
No longer need the lib/TR directory - files can be deleted
Generating EJB1.2 to EJB1.2 there is an added constructor with multiple
parameters whose Javadoc named anchor has no space in it, but other such
constructors do. This produces a link to the top of the page rather than the
correct place. Same with the Servlet report.
538102. A developer may not want to show changes from
native to non-native, etc, since this is really
an implementation detail. An easy way to select
what is shown would be useful.
548382. Separate class and interface information might be useful,
since Javadoc does so.
476310. There are some cases where a doc change is the only change.
Should this really count towards the API % change?
Create an ANT script instead of prepare_release.bat
Create a top-level ANT script.
Long Term:
---------
549926 - add ability to emit XML representation of the changes, which can then
be transformed into HTML.
Ship .gz and .tar?
Better end of sentence detection for annotations - use the J2SE14 approach?
Support inherited exclusion in RootDocToXML.java
Refactor code in Diff.java to avoid duplication
Better setting of trace per module
Add a color square to indicate how much a package or class changed
Add interactive ability with jdiff.JDiff for easiest use?
Add progress bars for the longer parts of the program?
Does Javadoc add "()" for links to methods with no arguments? It does
now, but perhaps it did not with J2SE1.2 javadoc?
ant.bat is a complex script for all windows platforms - could be useful
icontrol and ant page at has helpful info for a JavaUI and ANT task
http://icplus.sourceforge.net/dbc_example.html
How to find which classes are documented in a J2SE release, since it is
a subset of of the source classes shipped.
[All files marked as 404 (not found) by Xenu should have their packages
removed from the list scanned when generating the XML. E.g.
http://java.sun.com/j2se/1.3/docs/api/java/awt/dnd/peer/DragSourceContextPeer.html
http://java.sun.com/j2se/1.3/docs/api/java/text/resources/DateFormatZoneData.html
These packages are not documented in the J2SE1.3.]
Reduce the memory usage with large APIs
Use -linksource in tests with J2SE1.4. (Source info is now in the XML)
Break up the XML file into smaller files?
Sometimes "changed" is not linked but documentation is noted as having
changed because the documentation was inherited from a superclass.
Break out subclasses in DiffMyers.java for jar files?
In Javadoc generated HTML, the methods and fields which are inherited
from a parent do not have named anchors for the JDiff to link to them. This
means that the links go to the correct child class, but the user has to look
in the inherited methods and fields sections to find the right element.
This was fixed by checking for the case in HTMLReportGenerator, but the tricky
case, which is not checked for, is the case with inner classes and methods or
fields moving to their parent class. In this case, the class will be
correct, but the link will take you to the top of the page, rather than the
actual method.
Add support for differences for other languages - create base classes,
generalize XML.
The comment data in the deprecated attribute should really be in a CDATA
section to avoid having to hide the HTML tags. But his would mean that the
attribute deprecated would become an element and be harder to parse
the text out.
Add ability to specify the location of the generated XML file?
Add a "jdiff_" prefix to all generated HTML files to clearly distinguish them
from Javadoc generated files?
Good to add support for Notes - force altered classes etc recorded in user_comments.xml to appear
Break HTMLReportGenerator up into at least two files
Should have added new class to ClassDiff etc? Tidy up where possible.
Constructor params should be elements?
Define accessor methods for _ vars
Use a modifiers field instead of separate modifiers in XML to save space?
Add a name_ field to the ConstructorAPI class? Or a common superclass?
The final comparison call to Javadoc could be a separate Java program, but it
seems more obvious to use Javadoc there too. Also provides the future ability
to scan the current product at the same time as comparing it, reducing three
steps to two.
The index files are quite large with J2SE14 - 1.7MB for an HTML file.
Provide an option to include the sub-totals with the statistics table?
TESTS
=====
More checking of excludeTag with "@docset Internal" works, or however the tag
value is defined in J2SE1.4
Test change from a class to an interface
Test interfaces more
Test very long names
Test changes in deprecation at a class level
Test @first again
Test @link with all different formats
Test comments, multiple ids and the warning of multiple identical ids
Test that identifiers with spaces work
Test classes with no packages
Check that the comments file tests correctly for the correct APIs
Test case of a package and a class having the same name
Test case of moving a method into a superclass and vice versa
Test nested class definitions more closely
Test moving methods and fields from one superclass to another
Test @value changes
Compare new test results to old test results to check parent/child work
DOCUMENTATION, BUILD AND EXAMPLES
=================================
Examples:
---------
Add more changes
Documentation:
-------------
Example of writing your own comments for the report
Developers' notes: TODO is must, OPTION is maybe
REPORT PRESENTATION
===================
Add the ability to use the API names in the -doctitle and -windowtitle options
Add the ability to add a watermark, default "Internal", to the generated pages
Minor: when only one kind of change exists in an index, then that one
choice should be prechosen and highlighted.
Update all the error and warning messages in jdiff.html
Better text demo of showing all private changes in APIs
Fix the small size of old and new links
Add ability to have no newjavadoc links either
Better presentation of all the documentation changes? Perhaps sorted?
Run HTML checker on all generated HTML files - no errors
- some warnings about <span> elements mixed with <blockquote>, due to
a combination of diffing HTML with using HTML output
and others still to fix, using tidy and the tests:
Warning: html doctype doesn't match content
Warning: <nobr> is probably intended as </nobr>
1 ChangedPackage.ChangedMethods.html:92:156: Warning: replacing unexpected </nobr> by </code>
1 ChangedPackage.ChangedMethods.html:71:187: Warning: discarding unexpected </nobr>
1 ChangedPackage.ChangedMethods.html:71:179: Warning: trimming empty <nobr>
Avoid single letter indexes with just one entry on a new line
If a return type has a [] in it, browser may break the line before the []
Maybe the table should be name and a row under it for the description , to stop cramped names and descriptions?
Add note that links in docdiffs may not necessarily work, since some of them
are written expecting to be in documents in the Java API tree.
Add a "having problems?" page?
MISCELLANEOUS
=============

View File

@ -1,42 +0,0 @@
Handy commands for SourceForge:
export CVS_RSH=ssh
cvs -z3 -d:ext:mdoar@javadiff.cvs.sourceforge.net:/cvsroot/javadiff co -P jdiff
ssh -l mdoar javadiff.sourceforge.net
scp jdkchanges.zip mdoar@javadiff.sourceforge.net:/home/groups/j/ja/javadiff/htdocs/
scp jdkchanges.zip mdoar@jdiff.sourceforge.net:/home/groups/j/jd/jdiff/htdocs/
scp index.html mdoar@javadiff.sourceforge.net:/home/users/m/md/mdoar/jdiff/htdocs
crontab when logged in as mdoar in /home/users/m/md/mdoar:
0 1 * * * /home/users/m/md/mdoar/nightly.sh > /dev/null
/home/users/m/md/mdoar/nightly.sh contains:
cd tarballs
cvs -Q -d:pserver:anonymous@cvs1:/cvsroot/javadiff export -Dtomorrow jdiff
if [ -d jdiff ]
then
echo "Tarball created on: " > jdiff/CREATED_ON
echo `date` >> jdiff/CREATED_ON
tar czf /home/groups/j/ja/javadiff/htdocs/jdiff/jdiff_latest.tar.gz jdiff
rm -rf jdiff
fi
These are the CVS repository backups, only changed when something has changed:
http://cvs.sourceforge.net/cvstarballs/javadiff-cvsroot.tar.bz2
ftp upload.sourceforge.net
anonymous
cd incoming
put jdiff-1.1.0.zip
put jdiff-1.1.0-src.zip
cvs tag JDIFF_1_1_0 .
Creating a src package:
mv jdiff jdiff-1.1.0-src
find jdiff-1.1.0-src | zip source -@
mv source.zip jdiff-1.1.0-src.zip
(Probably neater to do it in Ant and exclude CVS directories)

View File

@ -1,153 +0,0 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML>
<HEAD>
<meta name="verify-v1" content="1pJFfshdW8CD9kVpWKs/HpekVbolROysqxdjRxKSh+E=" /><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<meta name="description" content="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.">
<meta name="keywords" content="diff, jdiff, javadiff, java diff, java difference, API difference, difference between two APIs, API diff, Javadoc, doclet">
<TITLE>JDiff - An HTML Report of API Differences</TITLE>
<LINK REL="stylesheet" TYPE="text/css"
HREF="http://javadiff.cvs.sourceforge.net/*checkout*/javadiff/jdiff/doc/stylesheet.css
TITLE="Style"></HEAD>
<BODY BGCOLOR="#ffffff">
<table width="100%">
<tr>
<td align="left"><A href="http://sourceforge.net/projects/javadiff/">
<IMG src="http://javadiff.cvs.sourceforge.net/*checkout*/javadiff/jdiff/lib/jdiff_logo.gif"
width="88" height="31" border="0" alt="JDiff Logo"><br>JDiff Project</A></td>
<td width="40%">
<center>
<span style="background: #FFFF00">JDiff will always remain free, but <a href="http://www.amazon.com/o/registry/2HEXGDBO2S63Y">donations</a> are gratefully
accepted.</span>
</center>
</td>
<td align="right"><A href="http://sourceforge.net"> <IMG src="http://sourceforge.net/sflogo.php?group_id=37160" width="88" height="31" border="0" alt="SourceForge Logo"></A></td>
</tr>
</table>
<center>
<H1>JDiff - An HTML Report of API Differences</H1>
</center>
<BLOCKQUOTE>
<b>JDiff</b> is a Javadoc <a
href="http://java.sun.com/j2se/javadoc">doclet</a> 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. This is very useful
for describing exactly what has changed between two releases of a
product. Only the API (Application Programming Interface) of each
version is compared. It does not compare what the source code does
when executed.
</BLOCKQUOTE>
<BLOCKQUOTE>
It's great for reporting what has changed between two releases of your
product!
You can download the <a href="http://sourceforge.net/projects/javadiff/">latest
version from here</a>, with a module name of <code>jdiff</code>.
There is also an article originally published <a href="http://javadiff.cvs.sourceforge.net/*checkout*/javadiff/jdiff/doc/JDiffArticle.pdf"><i>Java Developers Journal</i></a>.
</BLOCKQUOTE>
<HR>
<BLOCKQUOTE>
<b>
Note: to eliminate the annoying <code>register.com</code> banner,
after you choose a report from below, simply click on &quot;No
Frames&quot; or &quot;Frames&quot;.
</b>
</BLOCKQUOTE>
<h2>
<a href="http://javadiff.cvs.sourceforge.net/*checkout*/javadiff/jdiff/doc/jdiff.html">Documentation</a>
</h2>
<A NAME="sampleoutput">
<h2>Sample Output</h2>
<BLOCKQUOTE>
<table>
<tr>
<td>
<a href="http://guava-libraries.googlecode.com/svn/trunk/javadoc/jdiff/changes.html">
<b>Guava API</a></b> changes. Guava is a Google project that contains several of Google's core Java libraries.
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se142_j2se150b1/changes.html">
<b>Comparing J2SE1.4.2 and J2SE1.5.0b1</a>.</b>
Report (KB, gzip'd tar)
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se142_j2se150b1/j2se142_j2se150b1.tar.gz">Download</a>
<--
<img border="0" src="http://javadiff.cvs.sourceforge.net/*checkout*/javadiff/jdiff/lib/new.gif" alt="NEW!">
-->
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se140_j2se141/changes.html">
<b>Comparing J2SE1.4.0 and J2SE1.4.1</a>.</b>
Report (13KB, gzip'd tar)
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se140_j2se141/j2se140_j2se141.tar.gz">
<b>Download</a></b>
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se140_j2se141_docs/changes.html">
<b>Comparing J2SE1.4.0 and J2SE1.4.1</a> including documentation and
statistics.</b>
Report (528KB, gzip'd tar)
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se140_j2se141_docs/j2se140_j2se141_docs.tar.gz">
<b>Download</a></b>
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se131_j2se14/changes.html">
<b>Comparing J2SE1.3.1 and J2SE1.4</a>.</b>
Report (456KB, gzip'd tar)
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se131_j2se14/j2se131_j2se14.tar.gz">
<b>Download</a></b>
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se131_j2se14_docs/changes.html">
<b>Comparing J2SE1.3.1 and J2SE1.4</a> including documentation and
statistics.</b>
Report (2.9MB, gzip'd tar)
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se131_j2se14_docs/j2se131_j2se14_docs.tar.gz">
<b>Download</a></b>
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se13_j2se131_docs/changes.html">
<b>Comparing J2SE1.3 and J2SE1.3.1</a> including documentation and
statistics</b>
</td>
</tr>
<tr>
<td>
<a href="http://javadiff.sourceforge.net/jdiff/reports/j2se12_j2se13/changes.html">
<b>Comparing J2SE1.2 and J2SE1.3</b></a>
</td>
</tr>
<tr>
<td>
<a href="http://jdiff.sourceforge.net/jdiff/reports/j2se12_j2se13_docs/changes.html">
<b>Comparing J2SE1.2 and J2SE1.3</a> including documentation and statistics</b>
</td>
</tr>
</table>
</BLOCKQUOTE>
<HR>
<p align="center">
<font size="-1">
Copyright &copy; 2001-2010 <a href="mailto:mdoar@pobox.com">Matthew B. Doar</a><br> JDiff is licensed under the <a href="http://cvs.sourceforge.net/viewcvs.py/*checkout*/javadiff/jdiff/LICENSE.txt">LGPL</a>.
</font>
</p>
</BODY>
</HTML>

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +0,0 @@
/* Page background color */
body { font-family: arial; }

View File

@ -1,9 +0,0 @@
<html>
<head>
<title>Example page</title>
</head>
<body>
<p>Moved to <a href="http://example.org/">example.org</a>
or <a href="http://example.com/">example.com</a>.</p>
</body>
</html>

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
<?xml version='1.0'?>
<!DOCTYPE xs:schema SYSTEM "XMLSchema.dtd" [
<!ENTITY % p 'xs:'>
<!ENTITY % s ':xs'>
<!ATTLIST xs:documentation xmlns CDATA #IMPLIED>
]>
<xs:schema targetNamespace="http://www.w3.org/2000/10/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2000/10/XMLSchema">
<xs:annotation>
<xs:documentation xmlns="">
<h1>XML Schema instance namespace</h1>
<p>See <a href="http://www.w3.org/TR/xmlschema-1/">The XML Schema draft recommendation</a> for an introduction</p>
<hr />
<address><a href="mailto:ht@tux.w3.org">Henry S. Thompson</a></address>
$Date: 2001/11/16 06:19:40 $<br />
$Id: XMLSchema-instance,v 1.1 2001/11/16 06:19:40 mdoar Exp $
</xs:documentation>
</xs:annotation>
<xs:attribute name="type">
<xs:annotation>
<xs:documentation>No definitions are provided here, as
this schema is never used as such</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="null"/>
<xs:attribute name="schemaLocation"/>
<xs:attribute name="noNamespaceSchemaLocation"/>
</xs:schema>

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +0,0 @@
<?xml version='1.0'?>
<!DOCTYPE xs:schema SYSTEM "XMLSchema.dtd" [
<!ATTLIST xs:documentation xmlns CDATA #IMPLIED>
<!ELEMENT p ANY>
<!ELEMENT a ANY>
<!ATTLIST a href CDATA #IMPLIED>
<!ELEMENT hr ANY>
<!ELEMENT h1 ANY>
<!ELEMENT br ANY>
]>
<xs:schema targetNamespace="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.w3.org/1999/xhtml">
<xs:annotation>
<xs:documentation>
<h1>XML Schema instance namespace</h1>
<p>See <a href="http://www.w3.org/TR/xmlschema-1/">the XML Schema
Recommendation</a> for an introduction</p>
<hr />
$Date: 2002/01/09 20:32:53 $<br />
$Id: XMLSchema-instance,v 1.1 2002/01/09 20:32:53 mdoar Exp $
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation><p>This schema should never be used as such:
<a href="http://www.w3.org/TR/xmlschema-1/#no-xsi">the XML
Schema Recommendation</a> forbids the declaration of
attributes in this namespace</p>
</xs:documentation>
</xs:annotation>
<xs:attribute name="nil"/>
<xs:attribute name="type"/>
<xs:attribute name="schemaLocation"/>
<xs:attribute name="noNamespaceSchemaLocation"/>
</xs:schema>

View File

@ -1,9 +0,0 @@
/**
* This class is used only as a "null" argument for Javadoc when comparing
* two API files. Javadoc has to have a package, .java or .class file as an
* argument, even though JDiff doesn't use it.
*/
public class Null {
public Null() {
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

View File

@ -1,111 +0,0 @@
<?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>

View File

@ -1,31 +0,0 @@
<?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>

View File

@ -1 +0,0 @@
*.class

View File

@ -1,429 +0,0 @@
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();
}
}

View File

@ -1,934 +0,0 @@
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;
}

View File

@ -1,40 +0,0 @@
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[]
}
}

View File

@ -1,362 +0,0 @@
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;
}

View File

@ -1,91 +0,0 @@
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;
}
}

View File

@ -1,154 +0,0 @@
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;
}
}
}

View File

@ -1,537 +0,0 @@
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 >= 6) {
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.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("<!-- This file contains comments for a JDiff report. -->");
outputFile.println("<!-- It is used only in generating the report, and does not need to ship 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 text. -->");
outputFile.println("<!-- A comment element can have multiple identifier elements, which will -->");
outputFile.println("<!-- will cause the same text to appear at each place in the report, but -->");
outputFile.println("<!-- will be converted to separate comments when the comments file is used. -->");
outputFile.println("<!-- HTML tags in the text field will appear in the report. -->");
outputFile.println("<!-- You also need to close p HTML elements, used for paragraphs - see the top-level documentation. -->");
}
/**
* 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;
}

View File

@ -1,210 +0,0 @@
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;
}

View File

@ -1,25 +0,0 @@
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_);
}
}

View File

@ -1,25 +0,0 @@
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_);
}
}

View File

@ -1,66 +0,0 @@
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;
}
}

View File

@ -1,654 +0,0 @@
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;
}

View File

@ -1,850 +0,0 @@
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;
}
}

View File

@ -1,54 +0,0 @@
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_);
}
}

View File

@ -1,107 +0,0 @@
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;
}
}

View File

@ -1,336 +0,0 @@
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=\"25%,75%\">");
// 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("JDiff");
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(" <TD NOWRAP bgcolor=\"#FFFFCC\"><FONT size=\"+1\">");
h_.writeText(" <B>JDiff&nbsp;Indexes</B></FONT><br></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD NOWRAP bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><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 bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><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 bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><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 bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><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 bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><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 bgcolor=\"#FFFFFF\"><FONT CLASS=\"FrameItemFont\"><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("/* The JDiff style sheet, derived from the Javadoc style sheet. */");
h_.writeText("/* Generated by the JDiff Javadoc doclet */");
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("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_.reportFile.close();
} catch(IOException e) {
System.out.println("IO Error while attempting to create " + stylesheetFileName);
System.out.println("Error: " + e.getMessage());
System.exit(1);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,436 +0,0 @@
package jdiff;
import java.util.*;
import java.io.*;
/**
* 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();
// Write out the title
h_.writeHTMLTitle("JDiff Statistics");
h_.writeStyleSheetRef();
h_.writeText("</HEAD>");
h_.writeText("<BODY>");
// Write a customized navigation bar for the statistics 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>");
}
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1Rev\"> &nbsp;<FONT CLASS=\"NavBarFont1Rev\"><B>Statistics</B></FONT>&nbsp;</TD>");
h_.writeText(" <TD BGCOLOR=\"#EEEEFF\" CLASS=\"NavBarCell1\"> <A HREF=\"jdiff_help" + h_.reportFileExt + "\"><FONT CLASS=\"NavBarFont1\"><B>Help</B></FONT></A>&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_statistics" + 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 Statistics</H1>");
h_.writeText("</center>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("The percent change statistic reported for all elements in each API is defined recursively as follows:<br>");
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("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%.");
h_.writeText("<p>The overall difference between the two APIs is approximately " + (int)(apiDiff.pdiff) + "%.");
h_.writeText("</BLOCKQUOTE>");
h_.writeText("<h3>Sections</h3>");
h_.writeText("<a href=\"#packages\">Packages</a> sorted by percentage difference<br>");
h_.writeText("<a href=\"#classes\">Classes and <i>Interfaces</i></a> sorted by percentage difference<br>");
h_.writeText("<a href=\"#numbers\">Differences</a> by number and type<br>");
h_.writeText("<hr>");
h_.writeText("<a name=\"packages\"></a>");
h_.writeText("<h2>Packages Sorted By Percentage Difference</h2>");
emitPackagesByDiff(apiDiff);
h_.writeText("<hr>");
h_.writeText("<a name=\"classes\"></a>");
h_.writeText("<h2>Classes and <i>Interfaces</i> Sorted By Percentage Difference</h2>");
emitClassesByDiff(apiDiff);
h_.writeText("<hr>");
h_.writeText("<a name=\"numbers\"></a>");
h_.writeText("<h2>Differences By Number and Type</h2>");
h_.writeText("<BLOCKQUOTE>");
h_.writeText("The numbers of program elements (packages, classes. constructors, methods and fields) which are recorded as removed, added or changed includes only the highest-level program elements. 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("</BLOCKQUOTE>");
emitNumbersByElement(apiDiff);
h_.writeText("</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(" <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>Package</b></FONT></TD>");
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(" <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>Class or <i>Interface</i></b></FONT></TD>");
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(" <TD COLSPAN=5 ALIGN=\"center\" NOWRAP bgcolor=\"#EEEEFF\"><FONT size=\"+1\">");
h_.writeText(" <B>Number of Differences</B></FONT></TD>");
h_.writeText("</TR>");
h_.writeText("<TR>");
h_.writeText(" <TD>&nbsp;</TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Removals</b></TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Additions</b></TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Changes</b></TD>");
h_.writeText(" <TD ALIGN=\"center\"><b>Total</b></TD>");
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>");
}
}

View File

@ -1,307 +0,0 @@
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 boolean runningScript = false;
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.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(").");
if(runningScript) {
// Run the script reporter to see if this module is backwards compatible
ScriptReport sr = new ScriptReport();
int i = sr.run(comp);
System.out.println("Exiting with status code: " + i);
System.exit(i);
}
return true;
}
return false;
}
//
// 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.1";
/** The current JVM version. */
static String javaVersion = System.getProperty("java.version");
/** Set to enable increased logging verbosity for debugging. */
private static boolean trace = false;
} //JDiff

View File

@ -1,571 +0,0 @@
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");
}
if (getIncompatible()) {
// There are no arguments to this argument
dInfo.createParam().setName("-incompatible");
}
if(getScript()) {
dInfo.createParam().setName("-script");
}
// 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 the -incompatible argument, to only report incompatible changes.
*/
private boolean incompatible = false;
public void setIncompatible(boolean value) {
this.incompatible = value;
}
public boolean getIncompatible() {
return this.incompatible;
}
/**
* Add the -script argument
*/
private boolean script = false;
public void setScript(boolean value) {
this.script = value;
}
public boolean getScript() {
return this.script;
}
/**
* 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);
}
}
}

View File

@ -1,73 +0,0 @@
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;
}
}
}

View File

@ -1,342 +0,0 @@
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;
}

View File

@ -1,159 +0,0 @@
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;
}
}

View File

@ -1,106 +0,0 @@
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 (!HTMLReportGenerator.incompatibleChangesOnly &&
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;
}
}

View File

@ -1,448 +0,0 @@
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;
// 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("-incompatible")) 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;
// Tells JDiff we are running in 'script mode' so it must
// return a specific return code based on the comparison
// of the source files
if (opt.equals("-script")) return 1;
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("-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("-incompatible")) {
HTMLReportGenerator.incompatibleChangesOnly = true;
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("-script")) {
JDiff.runningScript = true;
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(" -apidir <directory> Destination directory for the XML file generated with the '-apiname' argument.");
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(" -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(" -incompatible Only report incompatible changes");
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;
}

View File

@ -1,49 +0,0 @@
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;
}
}

View File

@ -1,38 +0,0 @@
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[]
}
}

View File

@ -1,55 +0,0 @@
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_;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +0,0 @@
package jdiff;
import java.util.*;
/**
* Emit a standard text report with only the names
* of all packages which need a major version number change.
*/
public class ScriptReport {
/** Default constructor. */
public ScriptReport() { }
/**
* Checks to see if the tested module is backwards compatible.
*
* @return 100 if no changes
* 101 if compatible changes
* 102 if not compatible
*/
public int run(APIComparator comp) {
// Get the APIDiff
APIDiff apiDiff = comp.apiDiff;
if(apiDiff.packagesRemoved.size() > 0) {
return 102;
}
Iterator piter = apiDiff.packagesChanged.iterator();
while (piter.hasNext()) {
PackageDiff pkgDiff = (PackageDiff)(piter.next());
if(pkgDiff.classesRemoved.size() > 0) {
return 102;
}
Iterator citer = pkgDiff.classesChanged.iterator();
while(citer.hasNext()) {
ClassDiff classDiff = (ClassDiff)(citer.next());
if(classDiff.methodsRemoved.size() > 0) {
return 102;
}
Iterator miter = classDiff.methodsChanged.iterator();
while (miter.hasNext()) {
// Check if method has different return type
MemberDiff memberDiff = (MemberDiff)(miter.next());
if(!memberDiff.oldType_ .equals(memberDiff.newType_)) {
return 102;
}
}
}
}
// If there were any changes, but we haven't returned yet
// they must all be backwards compatible changes
if(apiDiff.packagesChanged.size() > 0) {
return 101;
}
// If we've reached here there must be no changes at all
return 100;
}
}

View File

@ -1,34 +0,0 @@
package jdiff;
import java.io.*;
import java.util.*;
/**
* Represents a single comment element. Has an identifier and some text.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
class SingleComment implements Comparable {
/** The identifier for this comment. */
public String id_ = null;
/** The text of this comment. */
public String text_ = null;
/** If false, then this comment is inactive. */
public boolean isUsed_ = true;
public SingleComment(String id, String text) {
// Escape the commentID in case it contains "<" or ">"
// characters (generics)
id_ = id.replaceAll("<", "&lt;").replaceAll(">", "&gt;");;
text_ = text;
}
/** Compare two SingleComment objects using just the id. */
public int compareTo(Object o) {
return id_.compareTo(((SingleComment)o).id_);
}
}

View File

@ -1,36 +0,0 @@
package jdiff;
import java.util.*;
import java.io.*;
/**
* Reads in lines from an input stream and displays them.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com.
*/
class StreamReader extends Thread {
/** The input stream. */
InputStream is_;
/** Constructor which takes an InputStream. */
StreamReader(InputStream is) {
is_ = is;
}
/** Method which is called when this thread is started. */
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is_);
BufferedReader br = new BufferedReader(isr);
String line = null;
while((line = br.readLine()) != null)
System.out.println(line);
} catch (IOException ioe) {
System.out.println("IO Error invoking Javadoc");
ioe.printStackTrace();
} catch (Exception e) {
// Ignore read errors which indicate that the process is complete
}
}
}

View File

@ -1,377 +0,0 @@
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.InputSource;
import org.xml.sax.helpers.*;
/**
* Creates an API object from an XML file. The API object is the internal
* representation of an API.
* All methods in this class for populating an API object are static.
*
* See the file LICENSE.txt for copyright details.
* @author Matthew Doar, mdoar@pobox.com
*/
public class XMLToAPI {
/** The instance of the API object which is populated from the file. */
private static API api_ = null;
/** Default constructor. */
private XMLToAPI() {
}
/**
* Read the file where the XML representing the API is stored.
*
* @param filename The full name of the file containing the XML
* representing the API
* @param createGlobalComments If set, then store possible comments
* @param apiName The simple name of the API file. If -oldapidir and
* -newapidir are not used, then this is the same as
* the filename parameter
*/
public static API readFile(String filename, boolean createGlobalComments,
String apiName) {
// The instance of the API object which is populated from the file.
api_ = new API();
api_.name_ = apiName; // Checked later
try {
XMLReader parser = null;
DefaultHandler handler = new APIHandler(api_, createGlobalComments);
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 (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);
}
// Add the inherited methods and fields to each class
addInheritedElements();
return api_;
} //readFile()
/**
* Add the inherited methods and fields to each class in turn.
*/
public static void addInheritedElements() {
Iterator iter = api_.packages_.iterator();
while (iter.hasNext()) {
PackageAPI pkg = (PackageAPI)(iter.next());
Iterator iter2 = pkg.classes_.iterator();
while (iter2.hasNext()) {
ClassAPI cls = (ClassAPI)(iter2.next());
// Look up any inherited classes or interfaces
if (cls.extends_ != null) {
ClassAPI parent = (ClassAPI)api_.classes_.get(cls.extends_);
if (parent != null)
addInheritedElements(cls, parent, cls.extends_);
}
if (cls.implements_.size() != 0) {
Iterator iter3 = cls.implements_.iterator();
while (iter3.hasNext()) {
String implName = (String)(iter3.next());
ClassAPI parent = (ClassAPI)api_.classes_.get(implName);
if (parent != null)
addInheritedElements(cls, parent, implName);
}
}
} //while (iter2.hasNext())
} //while (iter.hasNext())
}
/**
* Add all the inherited methods and fields in the second class to
* the first class, marking them as inherited from the second class.
* Do not add a method or a field if it is already defined locally.
*
* Only elements at the specified visibility level or
* higher appear in the XML file. All that remains to be tested for
* a private element, which is never inherited.
*
* If the parent class inherits any classes or interfaces, call this
* method recursively with those parents.
*/
public static void addInheritedElements(ClassAPI child, ClassAPI parent,
String fqParentName) {
if (parent.methods_.size() != 0) {
Iterator iter = parent.methods_.iterator();
while (iter.hasNext()) {
MethodAPI m = (MethodAPI)(iter.next());
// See if it the method is overridden locally
boolean overridden = false;
Iterator iter2 = child.methods_.iterator();
while (iter2.hasNext()) {
MethodAPI localM = (MethodAPI)(iter2.next());
if (localM.name_.compareTo(m.name_) == 0 &&
localM.getSignature().compareTo(m.getSignature()) == 0)
overridden = true;
}
if (!overridden && m.inheritedFrom_ == null &&
m.modifiers_.visibility != null &&
m.modifiers_.visibility.compareTo("private") != 0) {
MethodAPI m2 = new MethodAPI(m);
m2.inheritedFrom_ = fqParentName;
child.methods_.add(m2);
}
}
}
if (parent.fields_.size() != 0) {
Iterator iter = parent.fields_.iterator();
while (iter.hasNext()) {
FieldAPI f = (FieldAPI)(iter.next());
if (child.fields_.indexOf(f) == -1 &&
f.inheritedFrom_ == null &&
f.modifiers_.visibility != null &&
f.modifiers_.visibility.compareTo("private") != 0) {
FieldAPI f2 = new FieldAPI(f);
f2.inheritedFrom_ = fqParentName;
child.fields_.add(f2);
}
}
}
// Look up any inherited classes or interfaces
if (parent.extends_ != null) {
ClassAPI parent2 = (ClassAPI)api_.classes_.get(parent.extends_);
if (parent2 != null)
addInheritedElements(child, parent2, parent.extends_);
}
if (parent.implements_.size() != 0) {
Iterator iter3 = parent.implements_.iterator();
while (iter3.hasNext()) {
String implName = (String)(iter3.next());
ClassAPI parent2 = (ClassAPI)api_.classes_.get(implName);
if (parent2 != null)
addInheritedElements(child, parent2, implName);
}
}
}
//
// Methods to add data to an API object. Called by the XML parser.
//
/**
* Set the name of the API object.
*
* @param name The name of the package.
*/
public static void nameAPI(String name) {
if (name == null) {
System.out.println("Error: no API identifier found in the XML file '" + api_.name_ + "'");
System.exit(3);
}
// Check the given name against the filename currently stored in
// the name_ field
String filename2 = name.replace(' ','_');
filename2 += ".xml";
if (filename2.compareTo(api_.name_) != 0) {
System.out.println("Warning: API identifier in the XML file (" +
name + ") differs from the name of the file '" +
api_.name_ + "'");
}
api_.name_ = name;
}
/**
* Create a new package and add it to the API. Called by the XML parser.
*
* @param name The name of the package.
*/
public static void addPackage(String name) {
api_.currPkg_ = new PackageAPI(name);
api_.packages_.add(api_.currPkg_);
}
/**
* Create a new class and add it to the current package. Called by the XML parser.
*
* @param name The name of the class.
* @param parent The name of the parent class, null if no class is extended.
* @param modifiers Modifiers for this class.
*/
public static void addClass(String name, String parent,
boolean isAbstract,
Modifiers modifiers) {
api_.currClass_ = new ClassAPI(name, parent, false, isAbstract, modifiers);
api_.currPkg_.classes_.add(api_.currClass_);
String fqName = api_.currPkg_.name_ + "." + name;
ClassAPI caOld = (ClassAPI)api_.classes_.put(fqName, api_.currClass_);
if (caOld != null) {
System.out.println("Warning: duplicate class : " + fqName + " found. Using the first instance only.");
}
}
/**
* Add an new interface and add it to the current package. Called by the
* XML parser.
*
* @param name The name of the interface.
* @param parent The name of the parent interface, null if no
* interface is extended.
*/
public static void addInterface(String name, String parent,
boolean isAbstract,
Modifiers modifiers) {
api_.currClass_ = new ClassAPI(name, parent, true, isAbstract, modifiers);
api_.currPkg_.classes_.add(api_.currClass_);
}
/**
* Add an inherited interface to the current class. Called by the XML
* parser.
*
* @param name The name of the inherited interface.
*/
public static void addImplements(String name) {
api_.currClass_.implements_.add(name);
}
/**
* Add a constructor to the current class. Called by the XML parser.
*
* @param name The name of the constructor.
* @param type The type of the constructor.
* @param modifiers Modifiers for this constructor.
*/
public static void addCtor(String type, Modifiers modifiers) {
String t = type;
if (t == null)
t = "void";
api_.currCtor_ = new ConstructorAPI(t, modifiers);
api_.currClass_.ctors_.add(api_.currCtor_);
}
/**
* Add a method to the current class. Called by the XML parser.
*
* @param name The name of the method.
* @param returnType The return type of the method, null if it is void.
* @param modifiers Modifiers for this method.
*/
public static void addMethod(String name, String returnType,
boolean isAbstract, boolean isNative,
boolean isSynchronized, Modifiers modifiers) {
String rt = returnType;
if (rt == null)
rt = "void";
api_.currMethod_ = new MethodAPI(name, rt, isAbstract, isNative,
isSynchronized, modifiers);
api_.currClass_.methods_.add(api_.currMethod_);
}
/**
* Add a field to the current class. Called by the XML parser.
*
* @param name The name of the field.
* @param type The type of the field, null if it is void.
* @param modifiers Modifiers for this field.
*/
public static void addField(String name, String type, boolean isTransient,
boolean isVolatile, String value, Modifiers modifiers) {
String t = type;
if (t == null)
t = "void";
api_.currField_ = new FieldAPI(name, t, isTransient, isVolatile, value, modifiers);
api_.currClass_.fields_.add(api_.currField_);
}
/**
* Add a parameter to the current method. Called by the XML parser.
* Constuctors have their type (signature) in an attribute, since it
* is often shorter and makes parsing a little easier.
*
* @param name The name of the parameter.
* @param type The type of the parameter, null if it is void.
*/
public static void addParam(String name, String type) {
String t = type;
if (t == null)
t = "void";
ParamAPI paramAPI = new ParamAPI(name, t);
api_.currMethod_.params_.add(paramAPI);
}
/**
* Add an exception to the current method or constructor.
* Called by the XML parser.
*
* @param name The name of the parameter.
* @param type The type of the parameter.
* May be null in JDiff1.0.8 and earlier versions.
* @param currElement Name of the current element.
*/
public static void addException(String name, String type, String currElement) {
String exceptionId = type;
if (type == null || !showExceptionTypes)
exceptionId = name;
if (currElement.compareTo("method") == 0) {
if (api_.currMethod_.exceptions_.compareTo("no exceptions") == 0)
api_.currMethod_.exceptions_ = exceptionId;
else
api_.currMethod_.exceptions_ += ", " + exceptionId;
} else {
if (api_.currCtor_.exceptions_.compareTo("no exceptions") == 0)
api_.currCtor_.exceptions_ = exceptionId;
else
api_.currCtor_.exceptions_ += ", " + exceptionId;
}
}
/**
* If set, validate the XML which represents an API. By default, this is
* not set for reasons of efficiency, and also because if JDiff generated
* the XML, it should not need validating.
*/
public static boolean validateXML = false;
/**
* If set, then store and display the whole qualified name of exceptions.
* If not set, then store and display just the name of the exception,
* which is shorter, but may not detect when an exception changes class,
* but retains the same name.
*/
private static boolean showExceptionTypes = true;
}

View File

@ -1,971 +0,0 @@
#
# Autopsy Forensic Browser
#
# Copyright 2012-2013 Basis Technology Corp.
# Contact: carrier <at> sleuthkit <dot> org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#######################
# This script exists to help us determine update the library
# versions appropriately. See this page for version details.
#
# http://wiki.sleuthkit.org/index.php?title=Autopsy_3_Module_Versions
#
# The basic idea is that this script uses javadoc/jdiff to
# compare the current state of the source code to the last
# tag and identifies if APIs were removed, added, etc.
#
# When run from the Autopsy build script, this script will:
# - Clone Autopsy and checkout to the previous release tag
# as found in the NEWS.txt file
# - Auto-discover all modules and packages
# - Run jdiff, comparing the current and previous modules
# - Use jdiff's output to determine if each module
# a) has no changes
# b) has backwards compatible changes
# c) has backwards incompatible changes
# - Based off it's compatibility, updates each module's
# a) Major version
# b) Specification version
# c) Implementation version
# - Updates the dependencies on each module depending on the
# updated version numbers
#
# Optionally, when run from the command line, one can provide the
# desired tag to compare the current version to, the directory for
# the current version of Autopsy, and whether to automatically
# update the version numbers and dependencies.
# ------------------------------------------------------------
import errno
import os
import shutil
import stat
import subprocess
import sys
import traceback
from os import remove, close
from shutil import move
from tempfile import mkstemp
from xml.dom.minidom import parse, parseString
# Jdiff return codes. Described in more detail further on
NO_CHANGES = 100
COMPATIBLE = 101
NON_COMPATIBLE = 102
ERROR = 1
# Set this to true when developing the script. It does not delete
# the cloned repo each time - making it much faster to run.
TESTING = False
# An Autopsy module object
class Module:
# Initialize it with a name, return code, and version numbers
def __init__(self, name=None, ret=None, versions=None):
self.name = name
self.ret = ret
self.versions = versions
# As a string, the module should be it's name
def __str__(self):
return self.name
def __repr__(self):
return self.name
# When compared to another module, the two are equal if the names are the same
def __cmp__(self, other):
if isinstance(other, Module):
if self.name == other.name:
return 0
elif self.name < other.name:
return -1
else:
return 1
return 1
def __eq__(self, other):
if isinstance(other, Module):
if self.name == other.name:
return True
return False
def set_name(self, name):
self.name = name
def set_ret(self, ret):
self.ret = ret
def set_versions(self, versions):
self.versions = versions
def spec(self):
return self.versions[0]
def impl(self):
return self.versions[1]
def release(self):
return self.versions[2]
# Representation of the Specification version number
class Spec:
# Initialize specification number, where num is a string like x.y
def __init__(self, num):
self.third = None
spec_nums = num.split(".")
if len(spec_nums) == 3:
self.final = spec_nums[2]
self.third = int(self.final)
l, r = spec_nums[0], spec_nums[1]
self.left = int(l)
self.right = int(r)
def __str__(self):
return self.get()
def __cmp__(self, other):
if isinstance(other, Spec):
if self.left == other.left:
if self.right == other.right:
return 0
if self.right < other.right:
return -1
return 1
if self.left < other.left:
return -1
return 1
elif isinstance(other, str):
l, r = other.split(".")
if self.left == int(l):
if self.right == int(r):
return 0
if self.right < int(r):
return -1
return 1
if self.left < int(l):
return -1
return 1
return -1
def incrementIncompat(self):
return str(self.left + 1) + ".0"
def incrementCompat(self):
return str(self.left) + "." + str(self.right + 1)
def get(self):
spec_str = str(self.left) + "." + str(self.right)
if self.third is not None:
spec_str += "." + str(self.final)
return spec_str
def set(self, num):
if isinstance(num, str):
l, r = num.split(".")
self.left = int(l)
self.right = int(r)
elif isinstance(num, Spec):
self.left = num.left
self.right = num.right
return self
# ================================ #
# Core Functions #
# ================================ #
# Given a list of modules and the names for each version, compare
# the generated jdiff XML for each module and output the jdiff
# JavaDocs.
#
# modules: the list of all modules both versions have in common
# apiname_tag: the api name of the previous version, most likely the tag
# apiname_cur: the api name of the current version, most likely "Current"
#
# returns the exit code from the modified jdiff.jar
# return code 1 = error in jdiff
# return code 100 = no changes
# return code 101 = compatible changes
# return code 102 = incompatible changes
def compare_xml(module, apiname_tag, apiname_cur):
global docdir
make_dir(docdir)
null_file = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/lib/Null.java"))
jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar"))
oldapi = fix_path("build/jdiff-xml/" + apiname_tag + "-" + module.name)
newapi = fix_path("build/jdiff-xml/" + apiname_cur + "-" + module.name)
docs = fix_path(docdir + "/" + module.name)
# Comments are strange. They look for a file with additional user comments in a
# directory like docs/user_comments_for_xyz. The problem being that xyz is the
# path to the new/old api. So xyz turns into multiple directories for us.
# i.e. user_comments_for_build/jdiff-xml/[tag name]-[module name]_to_build/jdiff-xml
comments = fix_path(docs + "/user_comments_for_build")
jdiff_com = fix_path(comments + "/jdiff-xml")
tag_comments = fix_path(jdiff_com + "/" + apiname_tag + "-" + module.name + "_to_build")
jdiff_tag_com = fix_path(tag_comments + "/jdiff-xml")
if not os.path.exists(jdiff):
print("JDIFF doesn't exist.")
make_dir(docs)
make_dir(comments)
make_dir(jdiff_com)
make_dir(tag_comments)
make_dir(jdiff_tag_com)
make_dir("jdiff-logs")
log = open("jdiff-logs/COMPARE-" + module.name + ".log", "w")
cmd = ["javadoc",
"-doclet", "jdiff.JDiff",
"-docletpath", jdiff,
"-d", docs,
"-oldapi", oldapi,
"-newapi", newapi,
"-script",
null_file]
try:
jdiff = subprocess.Popen(cmd, stdout=log, stderr=log)
jdiff.wait()
code = jdiff.returncode
except Exception:
printt("Error executing javadoc. Exiting...")
exit(1)
log.close()
print("Compared XML for " + module.name)
if code == NO_CHANGES:
print(" No API changes")
elif code == COMPATIBLE:
print(" API Changes are backwards compatible")
elif code == NON_COMPATIBLE:
print(" API Changes are not backwards compatible")
else:
print(" *Error in XML, most likely an empty module")
sys.stdout.flush()
return code
# Generate the jdiff xml for the given module
# path: path to the autopsy source
# module: Module object
# name: api name for jdiff
def gen_xml(path, modules, name):
for module in modules:
# If its the regression test, the source is in the "test" dir
if module.name == "Testing":
src = os.path.join(path, module.name, "test", "qa-functional", "src")
else:
src = os.path.join(path, module.name, "src")
# xerces = os.path.abspath("./lib/xerces.jar")
xml_out = fix_path(os.path.abspath("./build/jdiff-xml/" + name + "-" + module.name))
jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar"))
make_dir("build/jdiff-xml")
make_dir("jdiff-logs")
log = open("jdiff-logs/GEN_XML-" + name + "-" + module.name + ".log", "w")
cmd = ["javadoc",
"-doclet", "jdiff.JDiff",
"-docletpath", jdiff, # ;" + xerces, <-- previous problems required this
"-apiname", xml_out, # leaving it in just in case it's needed once again
"-sourcepath", fix_path(src)]
cmd = cmd + get_packages(src)
try:
jdiff = subprocess.Popen(cmd, stdout=log, stderr=log)
jdiff.wait()
except Exception:
printt("Error executing javadoc. Exiting...")
exit(1)
log.close()
print("Generated XML for " + name + " " + module.name)
sys.stdout.flush()
# Find all the modules in the given path
def find_modules(path):
modules = []
# Step into each folder in the given path and
# see if it has manifest.mf - if so, it's a module
for dir in os.listdir(path):
directory = os.path.join(path, dir)
if os.path.isdir(directory):
for file in os.listdir(directory):
if file == "manifest.mf":
modules.append(Module(dir, None, None))
return modules
# Detects the differences between the source and tag modules
def module_diff(source_modules, tag_modules):
added_modules = [x for x in source_modules if x not in tag_modules]
removed_modules = [x for x in tag_modules if x not in source_modules]
similar_modules = [x for x in source_modules if x in tag_modules]
added_modules = (added_modules if added_modules else [])
removed_modules = (removed_modules if removed_modules else [])
similar_modules = (similar_modules if similar_modules else [])
return similar_modules, added_modules, removed_modules
# Reads the previous tag from NEWS.txt
def get_tag(sourcepath):
news = open(sourcepath + "/NEWS.txt", "r")
second_instance = False
for line in news:
if "----------------" in line:
if second_instance:
ver = line.split("VERSION ")[1]
ver = ver.split(" -")[0]
return ("autopsy-" + ver).strip()
else:
second_instance = True
continue
news.close()
# ========================================== #
# Dependency Functions #
# ========================================== #
# Write a new XML file, copying all the lines from projectxml
# and replacing the specification version for the code-name-base base
# with the supplied specification version spec
def set_dep_spec(projectxml, base, spec):
print(" Updating Specification version..")
orig = open(projectxml, "r")
f, abs_path = mkstemp()
new_file = open(abs_path, "w")
found_base = False
spacing = " "
sopen = "<specification-version>"
sclose = "</specification-version>\n"
for line in orig:
if base in line:
found_base = True
if found_base and sopen in line:
update = spacing + sopen + str(spec) + sclose
new_file.write(update)
else:
new_file.write(line)
new_file.close()
close(f)
orig.close()
remove(projectxml)
move(abs_path, projectxml)
# Write a new XML file, copying all the lines from projectxml
# and replacing the release version for the code-name-base base
# with the supplied release version
def set_dep_release(projectxml, base, release):
print(" Updating Release version..")
orig = open(projectxml, "r")
f, abs_path = mkstemp()
new_file = open(abs_path, "w")
found_base = False
spacing = " "
ropen = "<release-version>"
rclose = "</release-version>\n"
for line in orig:
if base in line:
found_base = True
if found_base and ropen in line:
update = spacing + ropen + str(release) + rclose
new_file.write(update)
else:
new_file.write(line)
new_file.close()
close(f)
orig.close()
remove(projectxml)
move(abs_path, projectxml)
# Return the dependency versions in the XML dependency node
def get_dep_versions(dep):
run_dependency = dep.getElementsByTagName("run-dependency")[0]
release_version = run_dependency.getElementsByTagName("release-version")
if release_version:
release_version = getTagText(release_version[0].childNodes)
specification_version = run_dependency.getElementsByTagName("specification-version")
if specification_version:
specification_version = getTagText(specification_version[0].childNodes)
return int(release_version), Spec(specification_version)
# Given a code-name-base, see if it corresponds with any of our modules
def get_module_from_base(modules, code_name_base):
for module in modules:
if "org.sleuthkit.autopsy." + module.name.lower() == code_name_base:
return module
return None # If it didn't match one of our modules
# Check the text between two XML tags
def getTagText(nodelist):
for node in nodelist:
if node.nodeType == node.TEXT_NODE:
return node.data
# Check the projectxml for a dependency on any module in modules
def check_for_dependencies(projectxml, modules):
dom = parse(projectxml)
dep_list = dom.getElementsByTagName("dependency")
for dep in dep_list:
code_name_base = dep.getElementsByTagName("code-name-base")[0]
code_name_base = getTagText(code_name_base.childNodes)
module = get_module_from_base(modules, code_name_base)
if module:
print(" Found dependency on " + module.name)
release, spec = get_dep_versions(dep)
if release != module.release() and module.release() is not None:
set_dep_release(projectxml, code_name_base, module.release())
else: print(" Release version is correct")
if spec != module.spec() and module.spec() is not None:
set_dep_spec(projectxml, code_name_base, module.spec())
else: print(" Specification version is correct")
# Given the module and the source directory, return
# the paths to the manifest and project properties files
def get_dependency_file(module, source):
projectxml = os.path.join(source, module.name, "nbproject", "project.xml")
if os.path.isfile(projectxml):
return projectxml
# Verify/Update the dependencies for each module, basing the dependency
# version number off the versions in each module
def update_dependencies(modules, source):
for module in modules:
print("Checking the dependencies for " + module.name + "...")
projectxml = get_dependency_file(module, source)
if projectxml == None:
print(" Error finding project xml file")
else:
other = [x for x in modules]
check_for_dependencies(projectxml, other)
sys.stdout.flush()
# ======================================== #
# Versioning Functions #
# ======================================== #
# Return the specification version in the given project.properties/manifest.mf file
def get_specification(project, manifest):
try:
# Try to find it in the project file
# it will be there if impl version is set to append automatically
f = open(project, 'r')
for line in f:
if "spec.version.base" in line:
return Spec(line.split("=")[1].strip())
f.close()
# If not found there, try the manifest file
f = open(manifest, 'r')
for line in f:
if "OpenIDE-Module-Specification-Version:" in line:
return Spec(line.split(": ")[1].strip())
except Exception as e:
print("Error parsing Specification version for")
print(project)
print(e)
# Set the specification version in the given project properties file
# but if it can't be found there, set it in the manifest file
def set_specification(project, manifest, num):
try:
# First try the project file
f = open(project, 'r')
for line in f:
if "spec.version.base" in line:
f.close()
replace(project, line, "spec.version.base=" + str(num) + "\n")
return
f.close()
# If it's not there, try the manifest file
f = open(manifest, 'r')
for line in f:
if "OpenIDE-Module-Specification-Version:" in line:
f.close()
replace(manifest, line, "OpenIDE-Module-Specification-Version: " + str(num) + "\n")
return
# Otherwise we're out of luck
print(" Error finding the Specification version to update")
print(" " + manifest)
f.close()
except:
print(" Error incrementing Specification version for")
print(" " + project)
# Return the implementation version in the given manifest.mf file
def get_implementation(manifest):
try:
f = open(manifest, 'r')
for line in f:
if "OpenIDE-Module-Implementation-Version" in line:
return int(line.split(": ")[1].strip())
f.close()
except:
print("Error parsing Implementation version for")
print(manifest)
# Set the implementation version in the given manifest file
def set_implementation(manifest, num):
try:
f = open(manifest, 'r')
for line in f:
if "OpenIDE-Module-Implementation-Version" in line:
f.close()
replace(manifest, line, "OpenIDE-Module-Implementation-Version: " + str(num) + "\n")
return
# If it isn't there, add it
f.close()
write_implementation(manifest, num)
except:
print(" Error incrementing Implementation version for")
print(" " + manifest)
# Rewrite the manifest file to include the implementation version
def write_implementation(manifest, num):
f = open(manifest, "r")
contents = f.read()
contents = contents[:-2] + "OpenIDE-Module-Implementation-Version: " + str(num) + "\n\n"
f.close()
f = open(manifest, "w")
f.write(contents)
f.close()
# Return the release version in the given manifest.mf file
def get_release(manifest):
try:
f = open(manifest, 'r')
for line in f:
if "OpenIDE-Module:" in line:
return int(line.split("/")[1].strip())
f.close()
except:
#print("Error parsing Release version for")
#print(manifest)
return 0
# Set the release version in the given manifest file
def set_release(manifest, num):
try:
f = open(manifest, 'r')
for line in f:
if "OpenIDE-Module:" in line:
f.close()
index = line.index('/') - len(line) + 1
newline = line[:index] + str(num)
replace(manifest, line, newline + "\n")
return
print(" Error finding the release version to update")
print(" " + manifest)
f.close()
except:
print(" Error incrementing release version for")
print(" " + manifest)
# Given the module and the source directory, return
# the paths to the manifest and project properties files
def get_version_files(module, source):
manifest = os.path.join(source, module.name, "manifest.mf")
project = os.path.join(source, module.name, "nbproject", "project.properties")
if os.path.isfile(manifest) and os.path.isfile(project):
return manifest, project
# Returns a the current version numbers for the module in source
def get_versions(module, source):
manifest, project = get_version_files(module, source)
if manifest == None or project == None:
print(" Error finding manifeset and project properties files")
return
spec = get_specification(project, manifest)
impl = get_implementation(manifest)
release = get_release(manifest)
return [spec, impl, release]
# Update the version numbers for every module in modules
def update_versions(modules, source):
for module in modules:
versions = module.versions
manifest, project = get_version_files(module, source)
print("Updating " + module.name + "...")
if manifest == None or project == None:
print(" Error finding manifeset and project properties files")
return
if module.ret == COMPATIBLE:
versions = [versions[0].set(versions[0].incrementCompat()), versions[1] + 1, versions[2]]
set_specification(project, manifest, versions[0])
set_implementation(manifest, versions[1])
module.set_versions(versions)
elif module.ret == NON_COMPATIBLE:
versions = [versions[0].set(versions[0].incrementIncompat()), versions[1] + 1, versions[2] + 1]
set_specification(project, manifest, versions[0])
set_implementation(manifest, versions[1])
set_release(manifest, versions[2])
module.set_versions(versions)
elif module.ret == NO_CHANGES:
versions = [versions[0], versions[1] + 1, versions[2]]
set_implementation(manifest, versions[1])
module.set_versions(versions)
elif module.ret == None:
versions = [Spec("1.0"), 1, 1]
set_specification(project, manifest, versions[0])
set_implementation(manifest, versions[1])
set_release(manifest, versions[2])
module.set_versions(versions)
sys.stdout.flush()
# Given a list of the added modules, remove the modules
# which have the correct 'new module default' version number
def remove_correct_added(modules):
correct = [x for x in modules]
for module in modules:
if module.spec() == "1.0" or module.spec() == "0.0":
if module.impl() == 1:
if module.release() == 1 or module.release() == 0:
correct.remove(module)
return correct
# ==================================== #
# Helper Functions #
# ==================================== #
# Replace pattern with subst in given file
def replace(file, pattern, subst):
#Create temp file
fh, abs_path = mkstemp()
new_file = open(abs_path,'w')
old_file = open(file)
for line in old_file:
new_file.write(line.replace(pattern, subst))
#close temp file
new_file.close()
close(fh)
old_file.close()
#Remove original file
remove(file)
#Move new file
move(abs_path, file)
# Given a list of modules print the version numbers that need changing
def print_version_updates(modules):
f = open("gen_version.txt", "a")
for module in modules:
versions = module.versions
if module.ret == COMPATIBLE:
output = (module.name + ":\n")
output += ("\tMajor Release:\tNo Change.\n")
output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].incrementCompat()) + "\n")
if versions[1] is None:
output += ("\tImplementation: Not defined\n")
else:
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
output += ("\n")
print(output)
sys.stdout.flush()
f.write(output)
elif module.ret == NON_COMPATIBLE:
output = (module.name + ":\n")
output += ("\tMajor Release:\t" + str(versions[2]) + "\t->\t" + str(versions[2] + 1) + "\n")
output += ("\tSpecification:\t" + str(versions[0]) + "\t->\t" + str(versions[0].incrementIncompat()) + "\n")
if versions[1] is None:
output += ("\tImplementation: Not defined\n")
else:
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
output += ("\n")
print(output)
sys.stdout.flush()
f.write(output)
elif module.ret == ERROR:
output = (module.name + ":\n")
output += ("\t*Unable to detect necessary changes\n")
output += ("\tMajor Release:\t\t" + str(versions[2]) + "\n")
output += ("\tSpecification:\t" + str(versions[0]) + "\n")
if versions[1] is None:
output += ("\tImplementation: Not defined\n")
else:
output += ("\tImplementation:\t" + str(versions[1]) + "\n")
output += ("\n")
print(output)
f.write(output)
sys.stdout.flush()
elif module.ret == NO_CHANGES:
output = (module.name + ":\n")
output += ("\tMajor Release:\tNo Change.\n")
output += ("\tSpecification:\tNo Change.\n")
if versions[1] is None:
output += ("\tImplementation: Not defined\n")
else:
output += ("\tImplementation:\t" + str(versions[1]) + "\t->\t" + str(versions[1] + 1) + "\n")
output += ("\n")
print(output)
sys.stdout.flush()
f.write(output)
elif module.ret is None:
output = ("Added " + module.name + ":\n")
if module.release() != 1 and module.release() != 0:
output += ("\tMajor Release:\t\t" + str(module.release()) + "\t->\t" + "1\n")
output += ("\n")
if module.spec() != "1.0" and module.spec() != "0.0":
output += ("\tSpecification:\t" + str(module.spec()) + "\t->\t" + "1.0\n")
output += ("\n")
if module.impl() != 1:
output += ("\tImplementation:\t" + str(module.impl()) + "\t->\t" + "1\n")
output += ("\n")
print(output)
sys.stdout.flush()
f.write(output)
sys.stdout.flush()
f.close()
# Changes cygwin paths to Windows
def fix_path(path):
if "cygdrive" in path:
new_path = path[11:]
return "C:/" + new_path
else:
return path
# Print a 'title'
def printt(title):
print("\n" + title)
lines = ""
for letter in title:
lines += "-"
print(lines)
sys.stdout.flush()
# Get a list of package names in the given path
# The path is expected to be of the form {base}/module/src
#
# NOTE: We currently only check for packages of the form
# org.sleuthkit.autopsy.x
# If we add other namespaces for commercial modules we will
# have to add a check here
def get_packages(path):
packages = []
package_path = os.path.join(path, "org", "sleuthkit", "autopsy")
for folder in os.listdir(package_path):
package_string = "org.sleuthkit.autopsy."
packages.append(package_string + folder)
return packages
# Create the given directory, if it doesn't already exist
def make_dir(dir):
try:
if not os.path.isdir(dir):
os.mkdir(dir)
if os.path.isdir(dir):
return True
return False
except:
print("Exception thrown when creating directory")
return False
# Delete the given directory, and make sure it is deleted
def del_dir(dir):
try:
if os.path.isdir(dir):
shutil.rmtree(dir, ignore_errors=False, onerror=handleRemoveReadonly)
if os.path.isdir(dir):
return False
else:
return True
return True
except:
print("Exception thrown when deleting directory")
traceback.print_exc()
return False
# Handle any permisson errors thrown by shutil.rmtree
def handleRemoveReadonly(func, path, exc):
excvalue = exc[1]
if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
func(path)
else:
raise
# Run git clone and git checkout for the tag
def do_git(tag, tag_dir):
try:
printt("Cloning Autopsy tag " + tag + " into dir " + tag_dir + " (this could take a while)...")
subprocess.call(["git", "clone", "https://github.com/sleuthkit/autopsy.git", tag_dir],
stdout=subprocess.PIPE)
printt("Checking out tag " + tag + "...")
subprocess.call(["git", "checkout", tag],
stdout=subprocess.PIPE,
cwd=tag_dir)
return True
except Exception as ex:
print("Error cloning and checking out Autopsy: ", sys.exc_info()[0])
print(str(ex))
print("The terminal you are using most likely does not recognize git commands.")
return False
# Get the flags from argv
def args():
try:
sys.argv.pop(0)
while sys.argv:
arg = sys.argv.pop(0)
if arg == "-h" or arg == "--help":
return 1
elif arg == "-t" or arg == "--tag":
global tag
tag = sys.argv.pop(0)
elif arg == "-s" or arg == "--source":
global source
source = sys.argv.pop(0)
elif arg == "-d" or arg == "--dir":
global docdir
docdir = sys.argv.pop(0)
elif arg == "-a" or arg == "--auto":
global dry
dry = False
else:
raise Exception()
except:
pass
# Print script run info
def printinfo():
global tag
global source
global docdir
global dry
printt("Release script information:")
if source is None:
source = fix_path(os.path.abspath("."))
print("Using source directory:\n " + source)
if tag is None:
tag = get_tag(source)
print("Checking out to tag:\n " + tag)
if docdir is None:
docdir = fix_path(os.path.abspath("./jdiff-javadocs"))
print("Generating jdiff JavaDocs in:\n " + docdir)
if dry is True:
print("Dry run: will not auto-update version numbers")
sys.stdout.flush()
# Print the script's usage/help
def usage():
return \
"""
USAGE:
Compares the API of the current Autopsy source code with a previous
tagged version. By default, it will detect the previous tag from
the NEWS file and will not update the versions in the source code.
OPTIONAL FLAGS:
-t --tag Specify a previous tag to compare to.
Otherwise the NEWS file will be used.
-d --dir The output directory for the jdiff JavaDocs. If no
directory is given, the default is jdiff-javadocs/{module}.
-s --source The directory containing Autopsy's source code.
-a --auto Automatically update version numbers (not dry).
-h --help Prints this usage.
"""
# ==================================== #
# Main Functionality #
# ==================================== #
# Where the magic happens
def main():
global tag; global source; global docdir; global dry
tag = None; source = None; docdir = None; dry = True
ret = args()
if ret:
print(usage())
return 0
printinfo()
# Check if javadoc and jdiff are present.
jdiff = fix_path(os.path.abspath("./thirdparty/jdiff/v-custom/jdiff.jar"))
if(not os.path.isfile(jdiff)):
printt("jdiff not found. Exiting...")
return 1
try:
subprocess.call(["javadoc"], stdout=open(os.devnull, "w"), stderr=subprocess.STDOUT)
except Exception:
printt("javadoc not found in path. Exiting...")
return 1
# -----------------------------------------------
# 1) Clone Autopsy, checkout to given tag/commit
# 2) Get the modules in the clone and the source
# 3) Generate the xml comparison
# -----------------------------------------------
if (not TESTING) and (not del_dir("./build/" + tag)):
print("\n\n=========================================")
print(" Failed to delete previous Autopsy clone.")
print(" Unable to continue...")
print("=========================================")
return 1
tag_dir = os.path.abspath("./build/" + tag)
if not do_git(tag, tag_dir):
return 1
sys.stdout.flush()
tag_modules = find_modules(tag_dir)
source_modules = find_modules(source)
printt("Generating jdiff XML reports...")
apiname_tag = tag
apiname_cur = "current"
gen_xml(tag_dir, tag_modules, apiname_tag)
gen_xml(source, source_modules, apiname_cur)
if not TESTING:
printt("Deleting cloned Autopsy directory...")
print("Clone successfully deleted" if del_dir(tag_dir) else "Failed to delete clone")
sys.stdout.flush()
# -----------------------------------------------------
# 1) Seperate modules into added, similar, and removed
# 2) Compare XML for each module
# -----------------------------------------------------
printt("Comparing modules found...")
similar_modules, added_modules, removed_modules = module_diff(source_modules, tag_modules)
if added_modules or removed_modules:
for m in added_modules:
print("+ Added " + m.name)
sys.stdout.flush()
for m in removed_modules:
print("- Removed " + m.name)
sys.stdout.flush()
else:
print("No added or removed modules")
sys.stdout.flush()
printt("Comparing jdiff outputs...")
for module in similar_modules:
module.set_ret(compare_xml(module, apiname_tag, apiname_cur))
print("Refer to the jdiff-javadocs folder for more details")
# ------------------------------------------------------------
# 1) Do versioning
# 2) Auto-update version numbers in files and the_modules list
# 3) Auto-update dependencies
# ------------------------------------------------------------
printt("Auto-detecting version numbers and changes...")
for module in added_modules:
module.set_versions(get_versions(module, source))
for module in similar_modules:
module.set_versions(get_versions(module, source))
added_modules = remove_correct_added(added_modules)
the_modules = similar_modules + added_modules
print_version_updates(the_modules)
if not dry:
printt("Auto-updating version numbers...")
update_versions(the_modules, source)
print("All auto-updates complete")
printt("Detecting and auto-updating dependencies...")
update_dependencies(the_modules, source)
printt("Deleting jdiff XML...")
xml_dir = os.path.abspath("./build/jdiff-xml")
print("XML successfully deleted" if del_dir(xml_dir) else "Failed to delete XML")
print("\n--- Script completed successfully ---")
return 0
# Start off the script
if __name__ == "__main__":
sys.exit(main())