Home | History | Annotate | Download | only in jdiff
      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 (at) pobox.com.
     16  */
     17 public 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 (at) 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