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