• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package jdiff;
2 
3 import java.io.File;
4 import java.io.InputStream;
5 import java.io.OutputStream;
6 import java.io.FileInputStream;
7 import java.io.FileOutputStream;
8 import java.util.Vector;
9 
10 import org.apache.tools.ant.BuildException;
11 import org.apache.tools.ant.DirectoryScanner;
12 import org.apache.tools.ant.Project;
13 import org.apache.tools.ant.taskdefs.Javadoc;
14 import org.apache.tools.ant.taskdefs.Javadoc.DocletInfo;
15 import org.apache.tools.ant.taskdefs.Javadoc.DocletParam;
16 import org.apache.tools.ant.types.FileSet;
17 import org.apache.tools.ant.types.DirSet;
18 import org.apache.tools.ant.types.Path;
19 
20 /**
21  * An Ant task to produce a simple JDiff report. More complex reports still
22  * need parameters that are controlled by the Ant Javadoc task.
23  */
24 public class JDiffAntTask {
25 
execute()26     public void execute() throws BuildException {
27 	jdiffHome = project.getProperty("JDIFF_HOME");
28 	if (jdiffHome == null || jdiffHome.compareTo("") == 0 |
29 	    jdiffHome.compareTo("(not set)") == 0) {
30 	    throw new BuildException("Error: invalid JDIFF_HOME property. Set it in the build file to the directory where jdiff is installed");
31 	}
32         project.log(" JDiff home: " + jdiffHome, Project.MSG_INFO);
33 
34 	jdiffClassPath = jdiffHome + DIR_SEP + "jdiff.jar" +
35 	    System.getProperty("path.separator") +
36 	    jdiffHome + DIR_SEP + "xerces.jar";
37 
38 	// TODO detect and set verboseAnt
39 
40 	// Create, if necessary, the directory for the JDiff HTML report
41         if (!destdir.mkdir() && !destdir.exists()) {
42 	    throw new BuildException(getDestdir() + " is not a valid directory");
43 	} else {
44 	    project.log(" Report location: " + getDestdir() + DIR_SEP
45 			+ "changes.html", Project.MSG_INFO);
46 	}
47 	// Could also output the other parameters used for JDiff here
48 
49 	// Check that there are indeed two projects to compare. If there
50 	// are no directories in the project, let Javadoc do the complaining
51 	if (oldProject == null || newProject == null) {
52 	    throw new BuildException("Error: two projects are needed, one <old> and one <new>");
53 	}
54 
55 	/*
56 	// Display the directories being compared, and some name information
57 	if (getVerbose()) {
58 	    project.log("Older version: " + oldProject.getName(),
59 			Project.MSG_INFO);
60 	    project.log("Included directories for older version:",
61 			Project.MSG_INFO);
62 	    DirectoryScanner ds =
63 		oldProject.getDirset().getDirectoryScanner(project);
64 	    String[] files = ds.getIncludedDirectories();
65 	    for (int i = 0; i < files.length; i++) {
66 		project.log(" " + files[i], Project.MSG_INFO);
67 	    }
68 	    ds = null;
69 
70 	    project.log("Newer version: " + newProject.getName(),
71 			Project.MSG_INFO);
72 	    project.log("Included directories for newer version:",
73 			Project.MSG_INFO);
74 	    ds = newProject.getDirset().getDirectoryScanner(project);
75 	    files = ds.getIncludedDirectories();
76 	    for (int i = 0; i < files.length; i++) {
77 		project.log(" " + files[i], Project.MSG_INFO);
78 	    }
79 	}
80 	*/
81 
82 	// Call Javadoc twice to generate Javadoc for each project
83 	generateJavadoc(oldProject);
84 	generateJavadoc(newProject);
85 
86 	// Call Javadoc three times for JDiff.
87 	generateXML(oldProject);
88 	generateXML(newProject);
89 	compareXML(oldProject.getName(), newProject.getName());
90 
91 	// Repeat some useful information
92 	project.log(" Report location: " + getDestdir() + DIR_SEP
93 		    + "changes.html", Project.MSG_INFO);
94     }
95 
96     /**
97      * Convenient method to create a Javadoc task, configure it and run it
98      * to generate the XML representation of a project's source files.
99      *
100      * @param proj The current Project
101      */
generateXML(ProjectInfo proj)102     protected void generateXML(ProjectInfo proj) {
103 	String apiname = proj.getName();
104 	Javadoc jd = initJavadoc("Analyzing " + apiname);
105 	jd.setDestdir(getDestdir());
106 	addSourcePaths(jd, proj);
107 
108 	// Tell Javadoc which packages we want to scan.
109 	// JDiff works with packagenames, not sourcefiles.
110 	jd.setPackagenames(getPackageList(proj));
111 
112 	// Create the DocletInfo first so we have a way to use it to add params
113 	DocletInfo dInfo = jd.createDoclet();
114 	jd.setDoclet("jdiff.JDiff");
115 	jd.setDocletPath(new Path(project, jdiffClassPath));
116 
117 	// Now set up some parameters for the JDiff doclet.
118 	DocletParam dp1 = dInfo.createParam();
119 	dp1.setName("-apiname");
120 	dp1.setValue(apiname);
121 	DocletParam dp2 = dInfo.createParam();
122 	dp2.setName("-baseURI");
123 	dp2.setValue("http://www.w3.org");
124 	// Put the generated file in the same directory as the report
125 	DocletParam dp3 = dInfo.createParam();
126 	dp3.setName("-apidir");
127 	dp3.setValue(getDestdir().toString());
128 
129 	// Execute the Javadoc command to generate the XML file.
130 	jd.perform();
131     }
132 
133     /**
134      * Convenient method to create a Javadoc task, configure it and run it
135      * to compare the XML representations of two instances of a project's
136      * source files, and generate an HTML report summarizing the differences.
137      *
138      * @param oldapiname The name of the older version of the project
139      * @param newapiname The name of the newer version of the project
140      */
compareXML(String oldapiname, String newapiname)141     protected void compareXML(String oldapiname, String newapiname) {
142 	Javadoc jd = initJavadoc("Comparing versions");
143 	jd.setDestdir(getDestdir());
144 	jd.setPrivate(true);
145 
146 	// Tell Javadoc which files we want to scan - a dummy file in this case
147 	jd.setSourcefiles(jdiffHome + DIR_SEP + "Null.java");
148 
149 	// Create the DocletInfo first so we have a way to use it to add params
150 	DocletInfo dInfo = jd.createDoclet();
151 	jd.setDoclet("jdiff.JDiff");
152 	jd.setDocletPath(new Path(project, jdiffClassPath));
153 
154 	// Now set up some parameters for the JDiff doclet.
155 	DocletParam dp1 = dInfo.createParam();
156 	dp1.setName("-oldapi");
157 	dp1.setValue(oldapiname);
158 	DocletParam dp2 = dInfo.createParam();
159 	dp2.setName("-newapi");
160 	dp2.setValue(newapiname);
161 	// Get the generated XML files from the same directory as the report
162 	DocletParam dp3 = dInfo.createParam();
163 	dp3.setName("-oldapidir");
164 	dp3.setValue(getDestdir().toString());
165 	DocletParam dp4 = dInfo.createParam();
166 	dp4.setName("-newapidir");
167 	dp4.setValue(getDestdir().toString());
168 
169 	// Assume that Javadoc reports already exist in ../"apiname"
170 	DocletParam dp5 = dInfo.createParam();
171 	dp5.setName("-javadocold");
172 	dp5.setValue(".." + DIR_SEP + oldapiname + DIR_SEP);
173 	DocletParam dp6 = dInfo.createParam();
174 	dp6.setName("-javadocnew");
175 	dp6.setValue(".." + DIR_SEP + newapiname + DIR_SEP);
176 
177 	if (getStats()) {
178 	    // There are no arguments to this argument
179 	    dInfo.createParam().setName("-stats");
180 	    // We also have to copy two image files for the stats pages
181 	    copyFile(jdiffHome + DIR_SEP + "black.gif",
182 		     getDestdir().toString() + DIR_SEP + "black.gif");
183 	    copyFile(jdiffHome + DIR_SEP + "background.gif",
184 		     getDestdir().toString() + DIR_SEP + "background.gif");
185 	}
186 
187 	if (getDocchanges()) {
188 	    // There are no arguments to this argument
189 	    dInfo.createParam().setName("-docchanges");
190 	}
191 
192 	// Execute the Javadoc command to compare the two XML files
193 	jd.perform();
194     }
195 
196     /**
197      * Generate the Javadoc for the project. If you want to generate
198      * the Javadoc report for the project with different parameters from the
199      * simple ones used here, then use the Javadoc Ant task directly, and
200      * set the javadoc attribute to the "old" or "new" element.
201      *
202      * @param proj The current Project
203      */
generateJavadoc(ProjectInfo proj)204     protected void generateJavadoc(ProjectInfo proj) {
205 	String javadoc = proj.getJavadoc();
206 	if (javadoc != null && javadoc.compareTo("generated") != 0) {
207 	    project.log("Configured to use existing Javadoc located in " +
208 			javadoc, Project.MSG_INFO);
209 	    return;
210 	}
211 
212 	String apiname = proj.getName();
213 	Javadoc jd = initJavadoc("Javadoc for " + apiname);
214 	jd.setDestdir(new File(getDestdir().toString() + DIR_SEP + apiname));
215 	addSourcePaths(jd, proj);
216 
217 	jd.setPrivate(true);
218 	jd.setPackagenames(getPackageList(proj));
219 
220 	// Execute the Javadoc command to generate a regular Javadoc report
221 	jd.perform();
222     }
223 
224     /**
225      * Create a fresh new Javadoc task object and initialize it.
226      *
227      * @param logMsg String which appears as a prefix in the Ant log
228      * @return The new task.Javadoc object
229      */
initJavadoc(String logMsg)230     protected Javadoc initJavadoc(String logMsg) {
231 	Javadoc jd = new Javadoc();
232 	jd.setProject(project); // Vital, otherwise Ant crashes
233 	jd.setTaskName(logMsg);
234 	jd.setSource(getSource()); // So we can set the language version
235 	jd.init();
236 
237 	// Set up some common parameters for the Javadoc task
238 	if (verboseAnt) {
239 	    jd.setVerbose(true);
240 	}
241 	return jd;
242     }
243 
244     /**
245      * Add the root directories for the given project to the Javadoc
246      * sourcepath.
247      */
addSourcePaths(Javadoc jd, ProjectInfo proj)248     protected void addSourcePaths(Javadoc jd, ProjectInfo proj) {
249 	Vector dirSets = proj.getDirsets();
250 	int numDirSets = dirSets.size();
251 	for (int i = 0; i < numDirSets; i++) {
252 	    DirSet dirSet = (DirSet)dirSets.elementAt(i);
253 	    jd.setSourcepath(new Path(project, dirSet.getDir(project).toString()));
254 	}
255     }
256 
257     /**
258      * Return the comma-separated list of packages. The list is
259      * generated from Ant DirSet tasks, and includes all directories
260      * in a hierarchy, e.g. com, com/acme. com/acme/foo. Duplicates are
261      * ignored.
262      */
getPackageList(ProjectInfo proj)263     protected String getPackageList(ProjectInfo proj) throws BuildException {
264 	String packageList = "";
265 	java.lang.StringBuffer sb = new StringBuffer();
266 	Vector dirSets = proj.getDirsets();
267 	int numDirSets = dirSets.size();
268 	boolean addComma = false;
269 	for (int i = 0; i < numDirSets; i++) {
270 	    DirSet dirSet = (DirSet)dirSets.elementAt(i);
271 	    DirectoryScanner dirScanner = dirSet.getDirectoryScanner(project);
272 	    String[] files = dirScanner.getIncludedDirectories();
273 	    for (int j = 0; j < files.length; j++) {
274 		if (!addComma){
275 		    addComma = true;
276 		} else {
277 		    sb.append(",");
278 		}
279 		sb.append(files[j]);
280 	    }
281 	}
282 	packageList = sb.toString();
283 	if (packageList.compareTo("") == 0) {
284 	    throw new BuildException("Error: no packages found to scan");
285 	}
286 	project.log(" Package list: " + packageList, Project.MSG_INFO);
287 
288 	return packageList;
289     }
290 
291     /**
292      * Copy a file from src to dst. Also checks that "destdir/changes" exists
293      */
copyFile(String src, String dst)294     protected void copyFile(String src, String dst){
295 	File srcFile = new File(src);
296 	File dstFile = new File(dst);
297 	try {
298 	    File reportSubdir = new File(getDestdir().toString() +
299 					 DIR_SEP + "changes");
300 	    if (!reportSubdir.mkdir() && !reportSubdir.exists()) {
301 		project.log("Warning: unable to create " + reportSubdir,
302 			    Project.MSG_WARN);
303 	    }
304 
305 	    InputStream in = new FileInputStream(src);
306 	    OutputStream out = new FileOutputStream(dst);
307 
308 	    // Transfer bytes from in to out
309 	    byte[] buf = new byte[1024];
310 	    int len;
311 	    while ((len = in.read(buf)) > 0) {
312 		out.write(buf, 0, len);
313 	    }
314 	    in.close();
315 	    out.close();
316 	} catch (java.io.FileNotFoundException fnfe) {
317 	    project.log("Warning: unable to copy " + src.toString() +
318 			" to " + dst.toString(), Project.MSG_WARN);
319 	    // Discard the exception
320 	} catch (java.io.IOException ioe) {
321 	    project.log("Warning: unable to copy " + src.toString() +
322 			" to " + dst.toString(), Project.MSG_WARN);
323 	    // Discard the exception
324 	}
325     }
326 
327     /**
328      * The JDiff Ant task does not inherit from an Ant task, such as the
329      * Javadoc task, though this is usually how most Tasks are
330      * written. This is because JDiff needs to run Javadoc three times
331      * (twice for generating XML, once for generating HTML). The
332      * Javadoc task has not easy way to reset its list of packages, so
333      * we needed to be able to crate new Javadoc task objects.
334      *
335      * Note: Don't confuse this class with the ProjectInfo used by JDiff.
336      * This Project class is from Ant.
337      */
338     private Project project;
339 
340     /**
341      * Used as part of Ant's startup.
342      */
setProject(Project proj)343     public void setProject(Project proj) {
344         project = proj;
345     }
346 
347     /**
348      * Ferward or backward slash, as appropriate.
349      */
350     static String DIR_SEP = System.getProperty("file.separator");
351 
352     /**
353      * JDIFF_HOME must be set as a property in the Ant build file.
354      * It should be set to the root JDiff directory, ie. the one where
355      * jdiff.jar is found.
356      */
357     private String jdiffHome = "(not set)";
358 
359     /**
360      * The classpath used by Javadoc to find jdiff.jar and xerces.jar.
361      */
362     private String jdiffClassPath = "(not set)";
363 
364     /* ***************************************************************** */
365     /* * Objects and methods which are related to attributes           * */
366     /* ***************************************************************** */
367 
368     /**
369      * The destination directory for the generated report.
370      * The default is "./jdiff_report".
371      */
372     private File destdir = new File("jdiff_report");
373 
374     /**
375      * Used to store the destdir attribute of the JDiff task XML element.
376      */
setDestdir(File value)377     public void setDestdir(File value) {
378 	this.destdir = value;
379     }
380 
getDestdir()381     public File getDestdir() {
382 	return this.destdir;
383     }
384 
385     /**
386      * Increases the JDiff Ant task logging verbosity if set with "yes", "on"
387      * or true". Default has to be false.
388      * To increase verbosity of Javadoc, start Ant with -v or -verbose.
389      */
390     private boolean verbose = false;
391 
setVerbose(boolean value)392     public void setVerbose(boolean value) {
393 	this.verbose = value;
394     }
395 
getVerbose()396     public boolean getVerbose() {
397 	return this.verbose;
398     }
399 
400     /**
401      * Set if ant was started with -v or -verbose
402      */
403     private boolean verboseAnt = false;
404 
405     /**
406      * Add the -docchanges argument, to track changes in Javadoc documentation
407      * as well as changes in classes etc.
408      */
409     private boolean docchanges = false;
410 
setDocchanges(boolean value)411     public void setDocchanges(boolean value) {
412 	this.docchanges = value;
413     }
414 
getDocchanges()415     public boolean getDocchanges() {
416 	return this.docchanges;
417     }
418 
419     /**
420      * Add statistics to the report if set. Default can only be false.
421      */
422     private boolean stats = false;
423 
setStats(boolean value)424     public void setStats(boolean value) {
425 	this.stats = value;
426     }
427 
getStats()428     public boolean getStats() {
429 	return this.stats;
430     }
431 
432     /**
433      * Allow the source language version to be specified.
434      */
435     private String source = "1.5"; // Default is 1.5, so generics will work
436 
setSource(String source)437     public void setSource(String source) {
438         this.source = source;
439     }
440 
getSource()441     public String getSource() {
442         return source;
443     }
444 
445     /* ***************************************************************** */
446     /* * Classes and objects which are related to elements             * */
447     /* ***************************************************************** */
448 
449     /**
450      * A ProjectInfo-derived object for the older version of the project
451      */
452     private ProjectInfo oldProject = null;
453 
454     /**
455      * Used to store the child element named "old", which is under the
456      * JDiff task XML element.
457      */
addConfiguredOld(ProjectInfo projInfo)458     public void addConfiguredOld(ProjectInfo projInfo) {
459 	oldProject = projInfo;
460     }
461 
462     /**
463      * A ProjectInfo-derived object for the newer version of the project
464      */
465     private ProjectInfo newProject = null;
466 
467     /**
468      * Used to store the child element named "new", which is under the
469      * JDiff task XML element.
470      */
addConfiguredNew(ProjectInfo projInfo)471     public void addConfiguredNew(ProjectInfo projInfo) {
472 	newProject = projInfo;
473     }
474 
475     /**
476      * This class handles the information about a project, whether it is
477      * the older or newer version.
478      *
479      * Note: Don't confuse this class with the Project used by Ant.
480      * This ProjectInfo class is from local to this task.
481      */
482     public static class ProjectInfo {
483 	/**
484 	 * The name of the project. This is used (without spaces) as the
485 	 * base of the name of the file which contains the XML representing
486 	 * the project.
487 	 */
488 	private String name;
489 
setName(String value)490 	public void setName(String value) {
491 	    name = value;
492 	}
493 
getName()494 	public String getName() {
495 	    return name;
496 	}
497 
498 	/**
499 	 * The location of the Javadoc HTML for this project. Default value
500 	 * is "generate", which will cause the Javadoc to be generated in
501 	 * a subdirectory named "name" in the task's destdir directory.
502 	 */
503 	private String javadoc;
504 
setJavadoc(String value)505 	public void setJavadoc(String value) {
506 	    javadoc = value;
507 	}
508 
getJavadoc()509 	public String getJavadoc() {
510 	    return javadoc;
511 	}
512 
513  	/**
514 	 * These are the directories which contain the packages which make
515 	 * up the project. Filesets are not supported by JDiff.
516 	 */
517 	private Vector dirsets = new Vector();
518 
setDirset(DirSet value)519 	public void setDirset(DirSet value) {
520 	    dirsets.add(value);
521 	}
522 
getDirsets()523 	public Vector getDirsets() {
524 	    return dirsets;
525 	}
526 
527 	/**
528 	 * Used to store the child element named "dirset", which is under the
529 	 * "old" or "new" XML elements.
530 	 */
addDirset(DirSet aDirset)531 	public void addDirset(DirSet aDirset) {
532 	    setDirset(aDirset);
533 	}
534 
535     }
536 }
537