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