• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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