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