1package jdiff;
2
3import com.sun.javadoc.*;
4
5import java.util.*;
6import java.io.*;
7import java.lang.reflect.*; // Used for invoking Javadoc indirectly
8import java.lang.Runtime;
9import 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 */
17public class JDiff extends Doclet {
18
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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 = "http://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