blob: e5b14ac26a763ce989fb3abd63537672c6841f93 [file] [log] [blame]
package jdiff;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Javadoc;
import org.apache.tools.ant.taskdefs.Javadoc.DocletInfo;
import org.apache.tools.ant.taskdefs.Javadoc.DocletParam;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.DirSet;
import org.apache.tools.ant.types.Path;
/**
* An Ant task to produce a simple JDiff report. More complex reports still
* need parameters that are controlled by the Ant Javadoc task.
*/
public class JDiffAntTask {
public void execute() throws BuildException {
jdiffHome = project.getProperty("JDIFF_HOME");
if (jdiffHome == null || jdiffHome.compareTo("") == 0 |
jdiffHome.compareTo("(not set)") == 0) {
throw new BuildException("Error: invalid JDIFF_HOME property. Set it in the build file to the directory where jdiff is installed");
}
project.log(" JDiff home: " + jdiffHome, Project.MSG_INFO);
jdiffClassPath = jdiffHome + DIR_SEP + "jdiff.jar" +
System.getProperty("path.separator") +
jdiffHome + DIR_SEP + "xerces.jar";
// TODO detect and set verboseAnt
// Create, if necessary, the directory for the JDiff HTML report
if (!destdir.mkdir() && !destdir.exists()) {
throw new BuildException(getDestdir() + " is not a valid directory");
} else {
project.log(" Report location: " + getDestdir() + DIR_SEP
+ "changes.html", Project.MSG_INFO);
}
// Could also output the other parameters used for JDiff here
// Check that there are indeed two projects to compare. If there
// are no directories in the project, let Javadoc do the complaining
if (oldProject == null || newProject == null) {
throw new BuildException("Error: two projects are needed, one <old> and one <new>");
}
/*
// Display the directories being compared, and some name information
if (getVerbose()) {
project.log("Older version: " + oldProject.getName(),
Project.MSG_INFO);
project.log("Included directories for older version:",
Project.MSG_INFO);
DirectoryScanner ds =
oldProject.getDirset().getDirectoryScanner(project);
String[] files = ds.getIncludedDirectories();
for (int i = 0; i < files.length; i++) {
project.log(" " + files[i], Project.MSG_INFO);
}
ds = null;
project.log("Newer version: " + newProject.getName(),
Project.MSG_INFO);
project.log("Included directories for newer version:",
Project.MSG_INFO);
ds = newProject.getDirset().getDirectoryScanner(project);
files = ds.getIncludedDirectories();
for (int i = 0; i < files.length; i++) {
project.log(" " + files[i], Project.MSG_INFO);
}
}
*/
// Call Javadoc twice to generate Javadoc for each project
generateJavadoc(oldProject);
generateJavadoc(newProject);
// Call Javadoc three times for JDiff.
generateXML(oldProject);
generateXML(newProject);
compareXML(oldProject.getName(), newProject.getName());
// Repeat some useful information
project.log(" Report location: " + getDestdir() + DIR_SEP
+ "changes.html", Project.MSG_INFO);
}
/**
* Convenient method to create a Javadoc task, configure it and run it
* to generate the XML representation of a project's source files.
*
* @param proj The current Project
*/
protected void generateXML(ProjectInfo proj) {
String apiname = proj.getName();
Javadoc jd = initJavadoc("Analyzing " + apiname);
jd.setDestdir(getDestdir());
addSourcePaths(jd, proj);
// Tell Javadoc which packages we want to scan.
// JDiff works with packagenames, not sourcefiles.
jd.setPackagenames(getPackageList(proj));
// Create the DocletInfo first so we have a way to use it to add params
DocletInfo dInfo = jd.createDoclet();
jd.setDoclet("jdiff.JDiff");
jd.setDocletPath(new Path(project, jdiffClassPath));
// Now set up some parameters for the JDiff doclet.
DocletParam dp1 = dInfo.createParam();
dp1.setName("-apiname");
dp1.setValue(apiname);
DocletParam dp2 = dInfo.createParam();
dp2.setName("-baseURI");
dp2.setValue("http://www.w3.org");
// Put the generated file in the same directory as the report
DocletParam dp3 = dInfo.createParam();
dp3.setName("-apidir");
dp3.setValue(getDestdir().toString());
// Execute the Javadoc command to generate the XML file.
jd.perform();
}
/**
* Convenient method to create a Javadoc task, configure it and run it
* to compare the XML representations of two instances of a project's
* source files, and generate an HTML report summarizing the differences.
*
* @param oldapiname The name of the older version of the project
* @param newapiname The name of the newer version of the project
*/
protected void compareXML(String oldapiname, String newapiname) {
Javadoc jd = initJavadoc("Comparing versions");
jd.setDestdir(getDestdir());
jd.setPrivate(true);
// Tell Javadoc which files we want to scan - a dummy file in this case
jd.setSourcefiles(jdiffHome + DIR_SEP + "Null.java");
// Create the DocletInfo first so we have a way to use it to add params
DocletInfo dInfo = jd.createDoclet();
jd.setDoclet("jdiff.JDiff");
jd.setDocletPath(new Path(project, jdiffClassPath));
// Now set up some parameters for the JDiff doclet.
DocletParam dp1 = dInfo.createParam();
dp1.setName("-oldapi");
dp1.setValue(oldapiname);
DocletParam dp2 = dInfo.createParam();
dp2.setName("-newapi");
dp2.setValue(newapiname);
// Get the generated XML files from the same directory as the report
DocletParam dp3 = dInfo.createParam();
dp3.setName("-oldapidir");
dp3.setValue(getDestdir().toString());
DocletParam dp4 = dInfo.createParam();
dp4.setName("-newapidir");
dp4.setValue(getDestdir().toString());
// Assume that Javadoc reports already exist in ../"apiname"
DocletParam dp5 = dInfo.createParam();
dp5.setName("-javadocold");
dp5.setValue(".." + DIR_SEP + oldapiname + DIR_SEP);
DocletParam dp6 = dInfo.createParam();
dp6.setName("-javadocnew");
dp6.setValue(".." + DIR_SEP + newapiname + DIR_SEP);
if (getStats()) {
// There are no arguments to this argument
dInfo.createParam().setName("-stats");
// We also have to copy two image files for the stats pages
copyFile(jdiffHome + DIR_SEP + "black.gif",
getDestdir().toString() + DIR_SEP + "black.gif");
copyFile(jdiffHome + DIR_SEP + "background.gif",
getDestdir().toString() + DIR_SEP + "background.gif");
}
if (getDocchanges()) {
// There are no arguments to this argument
dInfo.createParam().setName("-docchanges");
}
// Execute the Javadoc command to compare the two XML files
jd.perform();
}
/**
* Generate the Javadoc for the project. If you want to generate
* the Javadoc report for the project with different parameters from the
* simple ones used here, then use the Javadoc Ant task directly, and
* set the javadoc attribute to the "old" or "new" element.
*
* @param proj The current Project
*/
protected void generateJavadoc(ProjectInfo proj) {
String javadoc = proj.getJavadoc();
if (javadoc != null && javadoc.compareTo("generated") != 0) {
project.log("Configured to use existing Javadoc located in " +
javadoc, Project.MSG_INFO);
return;
}
String apiname = proj.getName();
Javadoc jd = initJavadoc("Javadoc for " + apiname);
jd.setDestdir(new File(getDestdir().toString() + DIR_SEP + apiname));
addSourcePaths(jd, proj);
jd.setPrivate(true);
jd.setPackagenames(getPackageList(proj));
// Execute the Javadoc command to generate a regular Javadoc report
jd.perform();
}
/**
* Create a fresh new Javadoc task object and initialize it.
*
* @param logMsg String which appears as a prefix in the Ant log
* @return The new task.Javadoc object
*/
protected Javadoc initJavadoc(String logMsg) {
Javadoc jd = new Javadoc();
jd.setProject(project); // Vital, otherwise Ant crashes
jd.setTaskName(logMsg);
jd.setSource(getSource()); // So we can set the language version
jd.init();
// Set up some common parameters for the Javadoc task
if (verboseAnt) {
jd.setVerbose(true);
}
return jd;
}
/**
* Add the root directories for the given project to the Javadoc
* sourcepath.
*/
protected void addSourcePaths(Javadoc jd, ProjectInfo proj) {
Vector dirSets = proj.getDirsets();
int numDirSets = dirSets.size();
for (int i = 0; i < numDirSets; i++) {
DirSet dirSet = (DirSet)dirSets.elementAt(i);
jd.setSourcepath(new Path(project, dirSet.getDir(project).toString()));
}
}
/**
* Return the comma-separated list of packages. The list is
* generated from Ant DirSet tasks, and includes all directories
* in a hierarchy, e.g. com, com/acme. com/acme/foo. Duplicates are
* ignored.
*/
protected String getPackageList(ProjectInfo proj) throws BuildException {
String packageList = "";
java.lang.StringBuffer sb = new StringBuffer();
Vector dirSets = proj.getDirsets();
int numDirSets = dirSets.size();
boolean addComma = false;
for (int i = 0; i < numDirSets; i++) {
DirSet dirSet = (DirSet)dirSets.elementAt(i);
DirectoryScanner dirScanner = dirSet.getDirectoryScanner(project);
String[] files = dirScanner.getIncludedDirectories();
for (int j = 0; j < files.length; j++) {
if (!addComma){
addComma = true;
} else {
sb.append(",");
}
sb.append(files[j]);
}
}
packageList = sb.toString();
if (packageList.compareTo("") == 0) {
throw new BuildException("Error: no packages found to scan");
}
project.log(" Package list: " + packageList, Project.MSG_INFO);
return packageList;
}
/**
* Copy a file from src to dst. Also checks that "destdir/changes" exists
*/
protected void copyFile(String src, String dst){
File srcFile = new File(src);
File dstFile = new File(dst);
try {
File reportSubdir = new File(getDestdir().toString() +
DIR_SEP + "changes");
if (!reportSubdir.mkdir() && !reportSubdir.exists()) {
project.log("Warning: unable to create " + reportSubdir,
Project.MSG_WARN);
}
InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst);
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (java.io.FileNotFoundException fnfe) {
project.log("Warning: unable to copy " + src.toString() +
" to " + dst.toString(), Project.MSG_WARN);
// Discard the exception
} catch (java.io.IOException ioe) {
project.log("Warning: unable to copy " + src.toString() +
" to " + dst.toString(), Project.MSG_WARN);
// Discard the exception
}
}
/**
* The JDiff Ant task does not inherit from an Ant task, such as the
* Javadoc task, though this is usually how most Tasks are
* written. This is because JDiff needs to run Javadoc three times
* (twice for generating XML, once for generating HTML). The
* Javadoc task has not easy way to reset its list of packages, so
* we needed to be able to crate new Javadoc task objects.
*
* Note: Don't confuse this class with the ProjectInfo used by JDiff.
* This Project class is from Ant.
*/
private Project project;
/**
* Used as part of Ant's startup.
*/
public void setProject(Project proj) {
project = proj;
}
/**
* Ferward or backward slash, as appropriate.
*/
static String DIR_SEP = System.getProperty("file.separator");
/**
* JDIFF_HOME must be set as a property in the Ant build file.
* It should be set to the root JDiff directory, ie. the one where
* jdiff.jar is found.
*/
private String jdiffHome = "(not set)";
/**
* The classpath used by Javadoc to find jdiff.jar and xerces.jar.
*/
private String jdiffClassPath = "(not set)";
/* ***************************************************************** */
/* * Objects and methods which are related to attributes * */
/* ***************************************************************** */
/**
* The destination directory for the generated report.
* The default is "./jdiff_report".
*/
private File destdir = new File("jdiff_report");
/**
* Used to store the destdir attribute of the JDiff task XML element.
*/
public void setDestdir(File value) {
this.destdir = value;
}
public File getDestdir() {
return this.destdir;
}
/**
* Increases the JDiff Ant task logging verbosity if set with "yes", "on"
* or true". Default has to be false.
* To increase verbosity of Javadoc, start Ant with -v or -verbose.
*/
private boolean verbose = false;
public void setVerbose(boolean value) {
this.verbose = value;
}
public boolean getVerbose() {
return this.verbose;
}
/**
* Set if ant was started with -v or -verbose
*/
private boolean verboseAnt = false;
/**
* Add the -docchanges argument, to track changes in Javadoc documentation
* as well as changes in classes etc.
*/
private boolean docchanges = false;
public void setDocchanges(boolean value) {
this.docchanges = value;
}
public boolean getDocchanges() {
return this.docchanges;
}
/**
* Add statistics to the report if set. Default can only be false.
*/
private boolean stats = false;
public void setStats(boolean value) {
this.stats = value;
}
public boolean getStats() {
return this.stats;
}
/**
* Allow the source language version to be specified.
*/
private String source = "1.5"; // Default is 1.5, so generics will work
public void setSource(String source) {
this.source = source;
}
public String getSource() {
return source;
}
/* ***************************************************************** */
/* * Classes and objects which are related to elements * */
/* ***************************************************************** */
/**
* A ProjectInfo-derived object for the older version of the project
*/
private ProjectInfo oldProject = null;
/**
* Used to store the child element named "old", which is under the
* JDiff task XML element.
*/
public void addConfiguredOld(ProjectInfo projInfo) {
oldProject = projInfo;
}
/**
* A ProjectInfo-derived object for the newer version of the project
*/
private ProjectInfo newProject = null;
/**
* Used to store the child element named "new", which is under the
* JDiff task XML element.
*/
public void addConfiguredNew(ProjectInfo projInfo) {
newProject = projInfo;
}
/**
* This class handles the information about a project, whether it is
* the older or newer version.
*
* Note: Don't confuse this class with the Project used by Ant.
* This ProjectInfo class is from local to this task.
*/
public static class ProjectInfo {
/**
* The name of the project. This is used (without spaces) as the
* base of the name of the file which contains the XML representing
* the project.
*/
private String name;
public void setName(String value) {
name = value;
}
public String getName() {
return name;
}
/**
* The location of the Javadoc HTML for this project. Default value
* is "generate", which will cause the Javadoc to be generated in
* a subdirectory named "name" in the task's destdir directory.
*/
private String javadoc;
public void setJavadoc(String value) {
javadoc = value;
}
public String getJavadoc() {
return javadoc;
}
/**
* These are the directories which contain the packages which make
* up the project. Filesets are not supported by JDiff.
*/
private Vector dirsets = new Vector();
public void setDirset(DirSet value) {
dirsets.add(value);
}
public Vector getDirsets() {
return dirsets;
}
/**
* Used to store the child element named "dirset", which is under the
* "old" or "new" XML elements.
*/
public void addDirset(DirSet aDirset) {
setDirset(aDirset);
}
}
}