1 package jdiff; 2 3 import com.sun.javadoc.*; 4 5 import java.util.*; 6 import java.io.*; 7 import java.lang.reflect.*; // Used for invoking Javadoc indirectly 8 import java.lang.Runtime; 9 import java.lang.Process; 10 11 /** 12 * Generates HTML describing the changes between two sets of Java source code. 13 * 14 * See the file LICENSE.txt for copyright details. 15 * @author Matthew Doar, mdoar@pobox.com. 16 */ 17 public class JDiff extends Doclet { 18 languageVersion()19 public static LanguageVersion languageVersion(){ 20 return LanguageVersion.JAVA_1_5; 21 } 22 /** 23 * Doclet-mandated start method. Everything begins here. 24 * 25 * @param root a RootDoc object passed by Javadoc 26 * @return true if document generation succeeds 27 */ start(RootDoc root)28 public static boolean start(RootDoc root) { 29 if (root != null) 30 System.out.println("JDiff: doclet started ..."); 31 JDiff jd = new JDiff(); 32 return jd.startGeneration(root); 33 } 34 35 /** 36 * Generate the summary of the APIs. 37 * 38 * @param root the RootDoc object passed by Javadoc 39 * @return true if no problems encountered within JDiff 40 */ startGeneration(RootDoc newRoot)41 protected boolean startGeneration(RootDoc newRoot) { 42 long startTime = System.currentTimeMillis(); 43 44 // Open the file where the XML representing the API will be stored. 45 // and generate the XML for the API into it. 46 if (writeXML) { 47 RootDocToXML.writeXML(newRoot); 48 } 49 50 if (compareAPIs) { 51 String tempOldFileName = oldFileName; 52 if (oldDirectory != null) { 53 tempOldFileName = oldDirectory; 54 if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) { 55 tempOldFileName += JDiff.DIR_SEP; 56 } 57 tempOldFileName += oldFileName; 58 } 59 60 // Check the file for the old API exists 61 File f = new File(tempOldFileName); 62 if (!f.exists()) { 63 System.out.println("Error: file '" + tempOldFileName + "' does not exist for the old API"); 64 return false; 65 } 66 // Check the file for the new API exists 67 68 String tempNewFileName = newFileName; 69 if (newDirectory != null) { 70 tempNewFileName = newDirectory; 71 if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) { 72 tempNewFileName += JDiff.DIR_SEP; 73 } 74 tempNewFileName += newFileName; 75 } 76 f = new File(tempNewFileName); 77 if (!f.exists()) { 78 System.out.println("Error: file '" + tempNewFileName + "' does not exist for the new API"); 79 return false; 80 } 81 82 // Read the file where the XML representing the old API is stored 83 // and create an API object for it. 84 System.out.print("JDiff: reading the old API in from file '" + tempOldFileName + "'..."); 85 // Read the file in, but do not add any text to the global comments 86 API oldAPI = XMLToAPI.readFile(tempOldFileName, false, oldFileName); 87 88 // Read the file where the XML representing the new API is stored 89 // and create an API object for it. 90 System.out.print("JDiff: reading the new API in from file '" + tempNewFileName + "'..."); 91 // Read the file in, and do add any text to the global comments 92 API newAPI = XMLToAPI.readFile(tempNewFileName, true, newFileName); 93 94 // Compare the old and new APIs. 95 APIComparator comp = new APIComparator(); 96 97 comp.compareAPIs(oldAPI, newAPI); 98 99 // Read the file where the XML for comments about the changes between 100 // the old API and new API is stored and create a Comments object for 101 // it. The Comments object may be null if no file exists. 102 int suffix = oldFileName.lastIndexOf('.'); 103 String commentsFileName = "user_comments_for_" + oldFileName.substring(0, suffix); 104 suffix = newFileName.lastIndexOf('.'); 105 commentsFileName += "_to_" + newFileName.substring(0, suffix) + ".xml"; 106 commentsFileName = commentsFileName.replace(' ', '_'); 107 if (HTMLReportGenerator.commentsDir !=null) { 108 commentsFileName = HTMLReportGenerator.commentsDir + DIR_SEP + commentsFileName; 109 } else if (HTMLReportGenerator.outputDir != null) { 110 commentsFileName = HTMLReportGenerator.outputDir + DIR_SEP + commentsFileName; 111 } 112 System.out.println("JDiff: reading the comments in from file '" + commentsFileName + "'..."); 113 Comments existingComments = Comments.readFile(commentsFileName); 114 if (existingComments == null) 115 System.out.println(" (the comments file will be created)"); 116 117 // Generate an HTML report which summarises all the API differences. 118 HTMLReportGenerator reporter = new HTMLReportGenerator(); 119 reporter.generate(comp, existingComments); 120 121 // Emit messages about which comments are now unused and 122 // which are new. 123 Comments newComments = reporter.getNewComments(); 124 Comments.noteDifferences(existingComments, newComments); 125 126 // Write the new comments out to the same file, with unused comments 127 // now commented out. 128 System.out.println("JDiff: writing the comments out to file '" + commentsFileName + "'..."); 129 Comments.writeFile(commentsFileName, newComments); 130 } 131 132 System.out.print("JDiff: finished (took " + (System.currentTimeMillis() - startTime)/1000 + "s"); 133 if (writeXML) 134 System.out.println(", not including scanning the source files)."); 135 else if (compareAPIs) 136 System.out.println(")."); 137 return true; 138 } 139 140 // 141 // Option processing 142 // 143 144 /** 145 * This method is called by Javadoc to 146 * parse the options it does not recognize. It then calls 147 * {@link #validOptions} to validate them. 148 * 149 * @param option a String containing an option 150 * @return an int telling how many components that option has 151 */ optionLength(String option)152 public static int optionLength(String option) { 153 return Options.optionLength(option); 154 } 155 156 /** 157 * After parsing the available options using {@link #optionLength}, 158 * Javadoc invokes this method with an array of options-arrays. 159 * 160 * @param options an array of String arrays, one per option 161 * @param reporter a DocErrorReporter for generating error messages 162 * @return true if no errors were found, and all options are 163 * valid 164 */ validOptions(String[][] options, DocErrorReporter reporter)165 public static boolean validOptions(String[][] options, 166 DocErrorReporter reporter) { 167 return Options.validOptions(options, reporter); 168 } 169 170 /** 171 * This method is only called when running JDiff as a standalone 172 * application, and uses ANT to execute the build configuration in the 173 * XML configuration file passed in. 174 */ main(String[] args)175 public static void main(String[] args) { 176 if (args.length == 0) { 177 //showUsage(); 178 System.out.println("Looking for a local 'build.xml' configuration file"); 179 } else if (args.length == 1) { 180 if (args[0].compareTo("-help") == 0 || 181 args[0].compareTo("-h") == 0 || 182 args[0].compareTo("?") == 0) { 183 showUsage(); 184 } else if (args[0].compareTo("-version") == 0) { 185 System.out.println("JDiff version: " + JDiff.version); 186 } 187 return; 188 } 189 int rc = runAnt(args); 190 return; 191 } 192 193 /** 194 * Display usage information for JDiff. 195 */ showUsage()196 public static void showUsage() { 197 System.out.println("usage: java jdiff.JDiff [-version] [-buildfile <XML configuration file>]"); 198 System.out.println("If no build file is specified, the local build.xml file is used."); 199 } 200 201 /** 202 * Invoke ANT by reflection. 203 * 204 * @return The integer return code from running ANT. 205 */ runAnt(String[] args)206 public static int runAnt(String[] args) { 207 String className = null; 208 Class c = null; 209 try { 210 className = "org.apache.tools.ant.Main"; 211 c = Class.forName(className); 212 } catch (ClassNotFoundException e1) { 213 System.err.println("Error: ant.jar not found on the classpath"); 214 return -1; 215 } 216 try { 217 Class[] methodArgTypes = new Class[1]; 218 methodArgTypes[0] = args.getClass(); 219 Method mainMethod = c.getMethod("main", methodArgTypes); 220 Object[] methodArgs = new Object[1]; 221 methodArgs[0] = args; 222 // The object can be null because the method is static 223 Integer res = (Integer)mainMethod.invoke(null, methodArgs); 224 System.gc(); // Clean up after running ANT 225 return res.intValue(); 226 } catch (NoSuchMethodException e2) { 227 System.err.println("Error: method \"main\" not found"); 228 e2.printStackTrace(); 229 } catch (IllegalAccessException e4) { 230 System.err.println("Error: class not permitted to be instantiated"); 231 e4.printStackTrace(); 232 } catch (InvocationTargetException e5) { 233 System.err.println("Error: method \"main\" could not be invoked"); 234 e5.printStackTrace(); 235 } catch (Exception e6) { 236 System.err.println("Error: "); 237 e6.printStackTrace(); 238 } 239 System.gc(); // Clean up after running ANT 240 return -1; 241 } 242 243 /** 244 * The name of the file where the XML representing the old API is 245 * stored. 246 */ 247 static String oldFileName = "old_java.xml"; 248 249 /** 250 * The name of the directory where the XML representing the old API is 251 * stored. 252 */ 253 static String oldDirectory = null; 254 255 /** 256 * The name of the file where the XML representing the new API is 257 * stored. 258 */ 259 static String newFileName = "new_java.xml"; 260 261 /** 262 * The name of the directory where the XML representing the new API is 263 * stored. 264 */ 265 static String newDirectory = null; 266 267 /** If set, then generate the XML for an API and exit. */ 268 static boolean writeXML = false; 269 270 /** If set, then read in two XML files and compare their APIs. */ 271 static boolean compareAPIs = false; 272 273 /** 274 * The file separator for the local filesystem, forward or backward slash. 275 */ 276 static String DIR_SEP = System.getProperty("file.separator"); 277 278 /** Details for where to find JDiff. */ 279 static final String jDiffLocation = "https://www.jdiff.org"; 280 /** Contact email address for the primary JDiff maintainer. */ 281 static final String authorEmail = "mdoar@pobox.com"; 282 283 /** A description for HTML META tags. */ 284 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."; 285 /** Keywords for HTML META tags. */ 286 static final String jDiffKeywords = "diff, jdiff, javadiff, java diff, java difference, API difference, difference between two APIs, API diff, Javadoc, doclet"; 287 288 /** The current JDiff version. */ 289 static final String version = "1.1.0"; 290 291 /** The current virtual machine version. */ 292 static String javaVersion = System.getProperty("java.version"); 293 294 /** Set to enable increased logging verbosity for debugging. */ 295 private static boolean trace = false; 296 297 } //JDiff 298