diff --git a/src/jdiff/APIDiff.java b/src/jdiff/APIDiff.java index 4b4fd26..e200dfc 100644 --- a/src/jdiff/APIDiff.java +++ b/src/jdiff/APIDiff.java @@ -1,7 +1,6 @@ package jdiff; import java.util.*; -import com.sun.javadoc.*; /** * The class contains the changes between two API objects; packages added, diff --git a/src/jdiff/ClassDiff.java b/src/jdiff/ClassDiff.java index 8742ab1..6a15afa 100644 --- a/src/jdiff/ClassDiff.java +++ b/src/jdiff/ClassDiff.java @@ -1,7 +1,6 @@ package jdiff; import java.util.*; -import com.sun.javadoc.*; /** * The changes between two classes. diff --git a/src/jdiff/JDiff.java b/src/jdiff/JDiff.java index 66c1189..adfe906 100644 --- a/src/jdiff/JDiff.java +++ b/src/jdiff/JDiff.java @@ -1,31 +1,77 @@ package jdiff; -import com.sun.javadoc.*; - import java.util.*; import java.io.*; import java.lang.reflect.*; // Used for invoking Javadoc indirectly import java.lang.Runtime; import java.lang.Process; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; + +import javax.lang.model.SourceVersion; + /** * Generates HTML describing the changes between two sets of Java source code. * * See the file LICENSE.txt for copyright details. * @author Matthew Doar, mdoar@pobox.com. */ -public class JDiff extends Doclet { +public class JDiff implements Doclet { + + private Reporter reporter; + private Locale locale; + + public enum LanguageVersion { + JAVA_1_5 + } public static LanguageVersion languageVersion(){ return LanguageVersion.JAVA_1_5; - } + } + + @Override + public void init(Locale locale, Reporter reporter) { + this.locale = locale; + this.reporter = reporter; + Options.reset(); + Options.setReporter(reporter); + } + + @Override + public String getName() { + return "JDiff"; + } + + @Override + public Set getSupportedOptions() { + return new LinkedHashSet(Options.getSupportedOptions()); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean run(DocletEnvironment environment) { + if (!Options.processPendingOptions(reporter)) { + return false; + } + if (environment != null) { + System.out.println("JDiff: doclet started ..."); + } + return startGeneration(environment); + } + /** * Doclet-mandated start method. Everything begins here. * * @param root a RootDoc object passed by Javadoc * @return true if document generation succeeds */ - public static boolean start(RootDoc root) { + public static boolean start(DocletEnvironment root) { if (root != null) System.out.println("JDiff: doclet started ..."); JDiff jd = new JDiff(); @@ -35,27 +81,31 @@ public static boolean start(RootDoc root) { /** * Generate the summary of the APIs. * - * @param root the RootDoc object passed by Javadoc + * @param root the DocletEnvironment object passed by Javadoc * @return true if no problems encountered within JDiff */ - protected boolean startGeneration(RootDoc newRoot) { + protected boolean startGeneration(DocletEnvironment root) { long startTime = System.currentTimeMillis(); // Open the file where the XML representing the API will be stored. // and generate the XML for the API into it. if (writeXML) { - RootDocToXML.writeXML(newRoot); + if (root == null) { + System.out.println("Error: no DocletEnvironment available for XML generation"); + return false; + } + RootDocToXML.writeXML(root); } if (compareAPIs) { - String tempOldFileName = oldFileName; - if (oldDirectory != null) { - tempOldFileName = oldDirectory; - if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) { - tempOldFileName += JDiff.DIR_SEP; - } - tempOldFileName += oldFileName; - } + String tempOldFileName = oldFileName; + if (oldDirectory != null) { + tempOldFileName = oldDirectory; + if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) { + tempOldFileName += JDiff.DIR_SEP; + } + tempOldFileName += oldFileName; + } // Check the file for the old API exists File f = new File(tempOldFileName); @@ -65,13 +115,13 @@ protected boolean startGeneration(RootDoc newRoot) { } // Check the file for the new API exists - String tempNewFileName = newFileName; + String tempNewFileName = newFileName; if (newDirectory != null) { - tempNewFileName = newDirectory; - if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) { - tempNewFileName += JDiff.DIR_SEP; - } - tempNewFileName += newFileName; + tempNewFileName = newDirectory; + if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) { + tempNewFileName += JDiff.DIR_SEP; + } + tempNewFileName += newFileName; } f = new File(tempNewFileName); if (!f.exists()) { @@ -84,20 +134,20 @@ protected boolean startGeneration(RootDoc newRoot) { System.out.print("JDiff: reading the old API in from file '" + tempOldFileName + "'..."); // Read the file in, but do not add any text to the global comments API oldAPI = XMLToAPI.readFile(tempOldFileName, false, oldFileName); - + // Read the file where the XML representing the new API is stored // and create an API object for it. System.out.print("JDiff: reading the new API in from file '" + tempNewFileName + "'..."); // Read the file in, and do add any text to the global comments API newAPI = XMLToAPI.readFile(tempNewFileName, true, newFileName); - + // Compare the old and new APIs. APIComparator comp = new APIComparator(); - + comp.compareAPIs(oldAPI, newAPI); - + // Read the file where the XML for comments about the changes between - // the old API and new API is stored and create a Comments object for + // the old API and new API is stored and create a Comments object for // it. The Comments object may be null if no file exists. int suffix = oldFileName.lastIndexOf('.'); String commentsFileName = "user_comments_for_" + oldFileName.substring(0, suffix); @@ -110,16 +160,16 @@ protected boolean startGeneration(RootDoc newRoot) { Comments existingComments = Comments.readFile(commentsFileName); if (existingComments == null) System.out.println(" (the comments file will be created)"); - + // Generate an HTML report which summarises all the API differences. HTMLReportGenerator reporter = new HTMLReportGenerator(); reporter.generate(comp, existingComments); - + // Emit messages about which comments are now unused and // which are new. Comments newComments = reporter.getNewComments(); Comments.noteDifferences(existingComments, newComments); - + // Write the new comments out to the same file, with unused comments // now commented out. System.out.println("JDiff: writing the comments out to file '" + commentsFileName + "'..."); @@ -128,7 +178,7 @@ protected boolean startGeneration(RootDoc newRoot) { System.out.print("JDiff: finished (took " + (System.currentTimeMillis() - startTime)/1000 + "s"); if (writeXML) - System.out.println(", not including scanning the source files)."); + System.out.println(", not including scanning the source files)."); else if (compareAPIs) System.out.println(")."); return true; @@ -136,7 +186,7 @@ else if (compareAPIs) // // Option processing -// +// /** * This method is called by Javadoc to @@ -155,18 +205,18 @@ public static int optionLength(String option) { * Javadoc invokes this method with an array of options-arrays. * * @param options an array of String arrays, one per option - * @param reporter a DocErrorReporter for generating error messages + * @param reporter a Reporter for generating error messages * @return true if no errors were found, and all options are * valid */ - public static boolean validOptions(String[][] options, - DocErrorReporter reporter) { + public static boolean validOptions(String[][] options, + Reporter reporter) { return Options.validOptions(options, reporter); } - - /** + + /** * This method is only called when running JDiff as a standalone - * application, and uses ANT to execute the build configuration in the + * application, and uses ANT to execute the build configuration in the * XML configuration file passed in. */ public static void main(String[] args) { @@ -187,7 +237,7 @@ public static void main(String[] args) { return; } - /** + /** * Display usage information for JDiff. */ public static void showUsage() { @@ -195,8 +245,8 @@ public static void showUsage() { System.out.println("If no build file is specified, the local build.xml file is used."); } - /** - * Invoke ANT by reflection. + /** + * Invoke ANT by reflection. * * @return The integer return code from running ANT. */ @@ -237,27 +287,27 @@ public static int runAnt(String[] args) { return -1; } - /** + /** * The name of the file where the XML representing the old API is - * stored. + * stored. */ static String oldFileName = "old_java.xml"; - /** + /** * The name of the directory where the XML representing the old API is - * stored. + * stored. */ static String oldDirectory = null; - /** - * The name of the file where the XML representing the new API is - * stored. + /** + * The name of the file where the XML representing the new API is + * stored. */ static String newFileName = "new_java.xml"; - /** - * The name of the directory where the XML representing the new API is - * stored. + /** + * The name of the directory where the XML representing the new API is + * stored. */ static String newDirectory = null; @@ -267,8 +317,8 @@ public static int runAnt(String[] args) { /** If set, then read in two XML files and compare their APIs. */ static boolean compareAPIs = false; - /** - * The file separator for the local filesystem, forward or backward slash. + /** + * The file separator for the local filesystem, forward or backward slash. */ static String DIR_SEP = System.getProperty("file.separator"); diff --git a/src/jdiff/MemberDiff.java b/src/jdiff/MemberDiff.java index 28e3258..1c51d46 100644 --- a/src/jdiff/MemberDiff.java +++ b/src/jdiff/MemberDiff.java @@ -1,7 +1,6 @@ package jdiff; import java.util.*; -import com.sun.javadoc.*; /** * The changes between two class constructor, method or field members. diff --git a/src/jdiff/Options.java b/src/jdiff/Options.java index d65793c..a521177 100644 --- a/src/jdiff/Options.java +++ b/src/jdiff/Options.java @@ -2,9 +2,13 @@ import java.io.*; import java.util.*; -import com.sun.javadoc.*; -/** +import javax.tools.Diagnostic; + +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.Reporter; + +/** * Class to handle options for JDiff. * * See the file LICENSE.txt for copyright details. @@ -12,10 +16,66 @@ */ public class Options { + private static final Map OPTION_LENGTHS = createOptionLengths(); + private static final List SUPPORTED_OPTIONS = createSupportedOptions(); + private static final List pendingOptions = new ArrayList(); + + private static Reporter reporter; + + /** All the options passed on the command line. Logged to XML. */ + public static String cmdOptions = ""; + + /** The value of the -sourcepath option, if provided. */ + public static String sourcePath = null; + + /** Set to enable increased logging verbosity for debugging. */ + private static boolean trace = false; + /** Default constructor. */ public Options() { } + public static void setReporter(Reporter reporter) { + Options.reporter = reporter; + } + + public static void reset() { + cmdOptions = ""; + sourcePath = null; + pendingOptions.clear(); + JDiff.writeXML = false; + JDiff.compareAPIs = false; + RootDocToXML.outputFileName = null; + RootDocToXML.apiIdentifier = null; + RootDocToXML.outputDirectory = null; + RootDocToXML.classVisibilityLevel = "protected"; + RootDocToXML.memberVisibilityLevel = "protected"; + RootDocToXML.saveAllDocs = true; + RootDocToXML.doExclude = false; + RootDocToXML.excludeTag = null; + RootDocToXML.baseURI = "http://www.w3.org"; + RootDocToXML.stripNonPrintables = true; + RootDocToXML.addSrcInfo = false; + RootDocToXML.packagesOnly = false; + HTMLReportGenerator.outputDir = null; + HTMLReportGenerator.newDocPrefix = "../"; + HTMLReportGenerator.oldDocPrefix = null; + HTMLReportGenerator.reportDocChanges = false; + HTMLReportGenerator.incompatibleChangesOnly = false; + HTMLReportGenerator.noCommentsOnRemovals = false; + HTMLReportGenerator.noCommentsOnAdditions = false; + HTMLReportGenerator.noCommentsOnChanges = false; + HTMLReportGenerator.doStats = false; + HTMLReportGenerator.docTitle = null; + HTMLReportGenerator.windowTitle = null; + Diff.noDocDiffs = true; + Diff.showAllChanges = false; + } + + public static Collection getSupportedOptions() { + return SUPPORTED_OPTIONS; + } + /** * Returns the "length" of a given option. If an option takes no * arguments, its length is one. If it takes one argument, its @@ -33,75 +93,12 @@ public Options() { * @return an int telling how many components that option has */ public static int optionLength(String option) { - String opt = option.toLowerCase(); - - // Standard options - if (opt.equals("-authorid")) return 2; - if (opt.equals("-versionid")) return 2; - if (opt.equals("-d")) return 2; - if (opt.equals("-classlist")) return 1; - if (opt.equals("-title")) return 2; - if (opt.equals("-docletid")) return 1; - if (opt.equals("-evident")) return 2; - if (opt.equals("-skippkg")) return 2; - if (opt.equals("-skipclass")) return 2; - if (opt.equals("-execdepth")) return 2; - if (opt.equals("-help")) return 1; - if (opt.equals("-version")) return 1; - if (opt.equals("-package")) return 1; - if (opt.equals("-protected")) return 1; - if (opt.equals("-public")) return 1; - if (opt.equals("-private")) return 1; - if (opt.equals("-sourcepath")) return 2; - - // Options to control JDiff - if (opt.equals("-apiname")) return 2; - if (opt.equals("-oldapi")) return 2; - if (opt.equals("-newapi")) return 2; - - // Options to control the location of the XML files - if (opt.equals("-apidir")) return 2; - if (opt.equals("-oldapidir")) return 2; - if (opt.equals("-newapidir")) return 2; - - // Options for the exclusion level for classes and members - if (opt.equals("-excludeclass")) return 2; - if (opt.equals("-excludemember")) return 2; - - if (opt.equals("-firstsentence")) return 1; - if (opt.equals("-docchanges")) return 1; - if (opt.equals("-incompatible")) return 1; - if (opt.equals("-packagesonly")) return 1; - if (opt.equals("-showallchanges")) return 1; - - // Option to change the location for the existing Javadoc - // documentation for the new API. Default is "../" - if (opt.equals("-javadocnew")) return 2; - // Option to change the location for the existing Javadoc - // documentation for the old API. Default is null. - if (opt.equals("-javadocold")) return 2; - - if (opt.equals("-baseuri")) return 2; - - // Option not to suggest comments at all - if (opt.equals("-nosuggest")) return 2; - - // Option to enable checking that the comments end with a period. - if (opt.equals("-checkcomments")) return 1; - // Option to retain non-printing characters in comments. - if (opt.equals("-retainnonprinting")) return 1; - // Option for the name of the exclude tag - if (opt.equals("-excludetag")) return 2; - // Generate statistical output - if (opt.equals("-stats")) return 1; - - // Set the browser window title - if (opt.equals("-windowtitle")) return 2; - // Set the report title - if (opt.equals("-doctitle")) return 2; - - return 0; - }//optionLength() + if (option == null) { + return 0; + } + Integer len = OPTION_LENGTHS.get(option.toLowerCase()); + return len == null ? 0 : len.intValue(); + } /** * After parsing the available options using {@link #optionLength}, @@ -128,46 +125,57 @@ public static int optionLength(String option) { * Javadoc parameter as well as doclet parameters. * * @param options an array of String arrays, one per option - * @param reporter a DocErrorReporter for generating error messages + * @param reporter a Reporter for generating error messages * @return true if no errors were found, and all options are * valid */ public static boolean validOptions(String[][] options, - DocErrorReporter reporter) { - final DocErrorReporter errOut = reporter; - - // A nice object-oriented way of handling errors. An instance of this - // class puts out an error message and keeps track of whether or not - // an error was found. - class ErrorHandler { - boolean noErrorsFound = true; - void msg(String msg) { - noErrorsFound = false; - errOut.printError(msg); + Reporter reporter) { + reset(); + setReporter(reporter); + if (options != null) { + for (int i = 0; i < options.length; i++) { + pendingOptions.add(options[i]); } } - + return handleOptions(); + } + + public static boolean processPendingOptions(Reporter reporter) { + if (reporter != null) { + setReporter(reporter); + } + return handleOptions(); + } + + private static boolean handleOptions() { ErrorHandler err = new ErrorHandler(); if (trace) - System.out.println("Command line arguments: "); - for (int i = 0; i < options.length; i++) { - for (int j = 0; j < options[i].length; j++) { - Options.cmdOptions += " " + options[i][j]; + System.out.println("Command line arguments: "); + for (int i = 0; i < pendingOptions.size(); i++) { + String[] opt = pendingOptions.get(i); + for (int j = 0; j < opt.length; j++) { + cmdOptions += " " + opt[j]; if (trace) - System.out.print(" " + options[i][j]); - } + System.out.print(" " + opt[j]); + } } if (trace) - System.out.println(); + System.out.println(); - for (int i = 0; i < options.length; i++) { - if (options[i][0].toLowerCase().equals("-apiname")) { - if (options[i].length < 2) { + for (int i = 0; i < pendingOptions.size(); i++) { + String[] option = pendingOptions.get(i); + if (option.length == 0) { + continue; + } + String name = option[0].toLowerCase(); + if (name.equals("-apiname")) { + if (option.length < 2) { err.msg("No version identifier specified after -apiname option."); } else if (JDiff.compareAPIs) { err.msg("Use the -apiname option, or the -oldapi and -newapi options, but not both."); - } else { - String filename = options[i][1]; + } else { + String filename = option[1]; RootDocToXML.apiIdentifier = filename; filename = filename.replace(' ', '_'); RootDocToXML.outputFileName = filename + ".xml"; @@ -176,21 +184,21 @@ void msg(String msg) { } continue; } - if (options[i][0].toLowerCase().equals("-apidir")) { - if (options[i].length < 2) { + if (name.equals("-apidir")) { + if (option.length < 2) { err.msg("No directory specified after -apidir option."); } else { - RootDocToXML.outputDirectory = options[i][1]; + RootDocToXML.outputDirectory = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-oldapi")) { - if (options[i].length < 2) { + if (name.equals("-oldapi")) { + if (option.length < 2) { err.msg("No version identifier specified after -oldapi option."); } else if (JDiff.writeXML) { err.msg("Use the -apiname or -oldapi option, but not both."); - } else { - String filename = options[i][1]; + } else { + String filename = option[1]; filename = filename.replace(' ', '_'); JDiff.oldFileName = filename + ".xml"; JDiff.writeXML = false; @@ -198,21 +206,21 @@ void msg(String msg) { } continue; } - if (options[i][0].toLowerCase().equals("-oldapidir")) { - if (options[i].length < 2) { + if (name.equals("-oldapidir")) { + if (option.length < 2) { err.msg("No directory specified after -oldapidir option."); - } else { - JDiff.oldDirectory = options[i][1]; + } else { + JDiff.oldDirectory = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-newapi")) { - if (options[i].length < 2) { + if (name.equals("-newapi")) { + if (option.length < 2) { err.msg("No version identifier specified after -newapi option."); } else if (JDiff.writeXML) { err.msg("Use the -apiname or -newapi option, but not both."); - } else { - String filename = options[i][1]; + } else { + String filename = option[1]; filename = filename.replace(' ', '_'); JDiff.newFileName = filename + ".xml"; JDiff.writeXML = false; @@ -220,51 +228,59 @@ void msg(String msg) { } continue; } - if (options[i][0].toLowerCase().equals("-newapidir")) { - if (options[i].length < 2) { + if (name.equals("-newapidir")) { + if (option.length < 2) { err.msg("No directory specified after -newapidir option."); - } else { - JDiff.newDirectory = options[i][1]; + } else { + JDiff.newDirectory = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-d")) { - if (options[i].length < 2) { + if (name.equals("-d")) { + if (option.length < 2) { err.msg("No directory specified after -d option."); } else { - HTMLReportGenerator.outputDir = options[i][1]; + HTMLReportGenerator.outputDir = option[1]; + } + continue; + } + if (name.equals("-sourcepath")) { + if (option.length < 2) { + err.msg("No location specified after -sourcepath option."); + } else { + sourcePath = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-javadocnew")) { - if (options[i].length < 2) { + if (name.equals("-javadocnew")) { + if (option.length < 2) { err.msg("No location specified after -javadocnew option."); } else { - HTMLReportGenerator.newDocPrefix = options[i][1]; + HTMLReportGenerator.newDocPrefix = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-javadocold")) { - if (options[i].length < 2) { + if (name.equals("-javadocold")) { + if (option.length < 2) { err.msg("No location specified after -javadocold option."); } else { - HTMLReportGenerator.oldDocPrefix = options[i][1]; + HTMLReportGenerator.oldDocPrefix = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-baseuri")) { - if (options[i].length < 2) { + if (name.equals("-baseuri")) { + if (option.length < 2) { err.msg("No base location specified after -baseURI option."); } else { - RootDocToXML.baseURI = options[i][1]; + RootDocToXML.baseURI = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-excludeclass")) { - if (options[i].length < 2) { + if (name.equals("-excludeclass")) { + if (option.length < 2) { err.msg("No level (public|protected|package|private) specified after -excludeclass option."); - } else { - String level = options[i][1]; + } else { + String level = option[1]; if (level.compareTo("public") != 0 && level.compareTo("protected") != 0 && level.compareTo("package") != 0 && @@ -276,11 +292,11 @@ void msg(String msg) { } continue; } - if (options[i][0].toLowerCase().equals("-excludemember")) { - if (options[i].length < 2) { + if (name.equals("-excludemember")) { + if (option.length < 2) { err.msg("No level (public|protected|package|private) specified after -excludemember option."); - } else { - String level = options[i][1]; + } else { + String level = option[1]; if (level.compareTo("public") != 0 && level.compareTo("protected") != 0 && level.compareTo("package") != 0 && @@ -292,32 +308,32 @@ void msg(String msg) { } continue; } - if (options[i][0].toLowerCase().equals("-firstsentence")) { + if (name.equals("-firstsentence")) { RootDocToXML.saveAllDocs = false; continue; } - if (options[i][0].toLowerCase().equals("-docchanges")) { + if (name.equals("-docchanges")) { HTMLReportGenerator.reportDocChanges = true; Diff.noDocDiffs = false; continue; } - if (options[i][0].toLowerCase().equals("-incompatible")) { + if (name.equals("-incompatible")) { HTMLReportGenerator.incompatibleChangesOnly = true; continue; } - if (options[i][0].toLowerCase().equals("-packagesonly")) { + if (name.equals("-packagesonly")) { RootDocToXML.packagesOnly = true; continue; } - if (options[i][0].toLowerCase().equals("-showallchanges")) { + if (name.equals("-showallchanges")) { Diff.showAllChanges = true; continue; } - if (options[i][0].toLowerCase().equals("-nosuggest")) { - if (options[i].length < 2) { + if (name.equals("-nosuggest")) { + if (option.length < 2) { err.msg("No level (all|remove|add|change) specified after -nosuggest option."); - } else { - String level = options[i][1]; + } else { + String level = option[1]; if (level.compareTo("all") != 0 && level.compareTo("remove") != 0 && level.compareTo("add") != 0 && @@ -339,60 +355,168 @@ else if (level.compareTo("all") == 0) { } continue; } - if (options[i][0].toLowerCase().equals("-checkcomments")) { + if (name.equals("-checkcomments")) { APIHandler.checkIsSentence = true; continue; } - if (options[i][0].toLowerCase().equals("-retainnonprinting")) { + if (name.equals("-retainnonprinting")) { RootDocToXML.stripNonPrintables = false; continue; } - if (options[i][0].toLowerCase().equals("-excludetag")) { - if (options[i].length < 2) { + if (name.equals("-excludetag")) { + if (option.length < 2) { err.msg("No exclude tag specified after -excludetag option."); - } else { - RootDocToXML.excludeTag = options[i][1]; + } else { + RootDocToXML.excludeTag = option[1]; RootDocToXML.excludeTag = RootDocToXML.excludeTag.trim(); RootDocToXML.doExclude = true; } continue; } - if (options[i][0].toLowerCase().equals("-stats")) { + if (name.equals("-stats")) { HTMLReportGenerator.doStats = true; continue; } - if (options[i][0].toLowerCase().equals("-doctitle")) { - if (options[i].length < 2) { + if (name.equals("-doctitle")) { + if (option.length < 2) { err.msg("No HTML text specified after -doctitle option."); - } else { - HTMLReportGenerator.docTitle = options[i][1]; + } else { + HTMLReportGenerator.docTitle = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-windowtitle")) { - if (options[i].length < 2) { + if (name.equals("-windowtitle")) { + if (option.length < 2) { err.msg("No text specified after -windowtitle option."); - } else { - HTMLReportGenerator.windowTitle = options[i][1]; + } else { + HTMLReportGenerator.windowTitle = option[1]; } continue; } - if (options[i][0].toLowerCase().equals("-version")) { + if (name.equals("-version")) { System.out.println("JDiff version: " + JDiff.version); System.exit(0); } - if (options[i][0].toLowerCase().equals("-help")) { + if (name.equals("-help")) { usage(); System.exit(0); } - }//for + } if (!JDiff.writeXML && !JDiff.compareAPIs) { err.msg("First use the -apiname option to generate an XML file for one API."); err.msg("Then use the -apiname option again to generate another XML file for a different version of the API."); err.msg("Finally use the -oldapi option and -newapi option to generate a report about how the APIs differ."); } return err.noErrorsFound; - }// validOptions() + } + + private static Map createOptionLengths() { + Map lengths = new LinkedHashMap(); + lengths.put("-authorid", Integer.valueOf(2)); + lengths.put("-versionid", Integer.valueOf(2)); + lengths.put("-d", Integer.valueOf(2)); + lengths.put("-classlist", Integer.valueOf(1)); + lengths.put("-title", Integer.valueOf(2)); + lengths.put("-docletid", Integer.valueOf(1)); + lengths.put("-evident", Integer.valueOf(2)); + lengths.put("-skippkg", Integer.valueOf(2)); + lengths.put("-skipclass", Integer.valueOf(2)); + lengths.put("-execdepth", Integer.valueOf(2)); + lengths.put("-help", Integer.valueOf(1)); + lengths.put("-version", Integer.valueOf(1)); + lengths.put("-package", Integer.valueOf(1)); + lengths.put("-protected", Integer.valueOf(1)); + lengths.put("-public", Integer.valueOf(1)); + lengths.put("-private", Integer.valueOf(1)); + lengths.put("-sourcepath", Integer.valueOf(2)); + lengths.put("-apiname", Integer.valueOf(2)); + lengths.put("-oldapi", Integer.valueOf(2)); + lengths.put("-newapi", Integer.valueOf(2)); + lengths.put("-apidir", Integer.valueOf(2)); + lengths.put("-oldapidir", Integer.valueOf(2)); + lengths.put("-newapidir", Integer.valueOf(2)); + lengths.put("-excludeclass", Integer.valueOf(2)); + lengths.put("-excludemember", Integer.valueOf(2)); + lengths.put("-firstsentence", Integer.valueOf(1)); + lengths.put("-docchanges", Integer.valueOf(1)); + lengths.put("-incompatible", Integer.valueOf(1)); + lengths.put("-packagesonly", Integer.valueOf(1)); + lengths.put("-showallchanges", Integer.valueOf(1)); + lengths.put("-javadocnew", Integer.valueOf(2)); + lengths.put("-javadocold", Integer.valueOf(2)); + lengths.put("-baseuri", Integer.valueOf(2)); + lengths.put("-nosuggest", Integer.valueOf(2)); + lengths.put("-checkcomments", Integer.valueOf(1)); + lengths.put("-retainnonprinting", Integer.valueOf(1)); + lengths.put("-excludetag", Integer.valueOf(2)); + lengths.put("-stats", Integer.valueOf(1)); + lengths.put("-windowtitle", Integer.valueOf(2)); + lengths.put("-doctitle", Integer.valueOf(2)); + return Collections.unmodifiableMap(lengths); + } + + private static List createSupportedOptions() { + List options = new ArrayList(); + for (Map.Entry entry : OPTION_LENGTHS.entrySet()) { + final String name = entry.getKey(); + final int argCount = Math.max(0, entry.getValue().intValue() - 1); + options.add(new LegacyOption(name, argCount)); + } + return Collections.unmodifiableList(options); + } + + private static final class LegacyOption implements Doclet.Option { + private final String name; + private final int argumentCount; + + LegacyOption(String name, int argumentCount) { + this.name = name; + this.argumentCount = argumentCount; + } + + public int getArgumentCount() { + return argumentCount; + } + + public String getDescription() { + return "JDiff legacy option"; + } + + public Kind getKind() { + return Kind.OTHER; + } + + public List getNames() { + return Collections.singletonList(name); + } + + public String getParameters() { + if (argumentCount == 0) { + return ""; + } + if (argumentCount == 1) { + return ""; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < argumentCount; i++) { + if (i > 0) { + sb.append(' '); + } + sb.append(""); + } + return sb.toString(); + } + + public boolean process(String option, List arguments) { + String[] values = new String[arguments.size() + 1]; + values[0] = option; + for (int i = 0; i < arguments.size(); i++) { + values[i + 1] = arguments.get(i); + } + pendingOptions.add(values); + return true; + } + } /** Display the arguments for JDiff. */ public static void usage() { @@ -403,7 +527,7 @@ public static void usage() { System.err.println(" -apiname "); System.err.println(" -oldapi "); System.err.println(" -newapi "); - + System.err.println(" Optional Arguments"); System.err.println(); System.err.println(" -d Destination directory for output HTML files"); @@ -413,11 +537,11 @@ public static void usage() { System.err.println(" -sourcepath "); System.err.println(" -javadocnew "); System.err.println(" -javadocold "); - + System.err.println(" -baseURI Use \"base\" as the base location of the various DTDs and Schemas used by JDiff"); System.err.println(" -excludeclass [public|protected|package|private] Exclude classes which are not public, protected etc"); System.err.println(" -excludemember [public|protected|package|private] Exclude members which are not public, protected etc"); - + System.err.println(" -firstsentence Save only the first sentence of each comment block with the API."); System.err.println(" -docchanges Report changes in Javadoc comments between the APIs"); System.err.println(" -incompatible Only report incompatible changes"); @@ -430,10 +554,19 @@ public static void usage() { System.err.println(""); System.err.println("For more help, see jdiff.html"); } - - /** All the options passed on the command line. Logged to XML. */ - public static String cmdOptions = ""; - /** Set to enable increased logging verbosity for debugging. */ - private static boolean trace = false; + private static class ErrorHandler { + boolean noErrorsFound = true; + void msg(String msg) { + noErrorsFound = false; + print(msg); + } + void print(String msg) { + if (reporter != null) { + reporter.print(Diagnostic.Kind.ERROR, msg); + } else { + System.err.println(msg); + } + } + } } diff --git a/src/jdiff/PackageDiff.java b/src/jdiff/PackageDiff.java index ba94f6e..316d23b 100644 --- a/src/jdiff/PackageDiff.java +++ b/src/jdiff/PackageDiff.java @@ -1,7 +1,6 @@ package jdiff; import java.util.*; -import com.sun.javadoc.*; /** * Changes between two packages. diff --git a/src/jdiff/RootDocToXML.java b/src/jdiff/RootDocToXML.java index 34350ff..8ab2e96 100644 --- a/src/jdiff/RootDocToXML.java +++ b/src/jdiff/RootDocToXML.java @@ -1,15 +1,33 @@ package jdiff; -import com.sun.javadoc.*; -import com.sun.javadoc.ParameterizedType; -import com.sun.javadoc.Type; - import java.util.*; import java.io.*; -import java.lang.reflect.*; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; +import javax.tools.JavaFileObject; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.LineMap; +import com.sun.source.util.DocTrees; +import com.sun.source.util.SourcePositions; +import com.sun.source.util.TreePath; + +import jdk.javadoc.doclet.DocletEnvironment; /** - * Converts a Javadoc RootDoc object into a representation in an + * Converts a Javadoc RootDoc object into a representation in an * XML file. * * See the file LICENSE.txt for copyright details. @@ -17,35 +35,48 @@ */ public class RootDocToXML { + private final DocletEnvironment environment; + private final DocTrees docTrees; + private final Elements elements; + private final Types types; + + private static PrintWriter outputFile = null; + /** Default constructor. */ - public RootDocToXML() { + public RootDocToXML(DocletEnvironment environment) { + this.environment = environment; + this.docTrees = environment.getDocTrees(); + this.elements = environment.getElementUtils(); + this.types = environment.getTypeUtils(); } /** * Write the XML representation of the API to a file. * - * @param root the RootDoc object passed by Javadoc + * @param environment the DocletEnvironment supplied by Javadoc * @return true if no problems encountered */ - public static boolean writeXML(RootDoc root) { - String tempFileName = outputFileName; - if (outputDirectory != null) { - tempFileName = outputDirectory; - if (!tempFileName.endsWith(JDiff.DIR_SEP)) - tempFileName += JDiff.DIR_SEP; - tempFileName += outputFileName; - } + public static boolean writeXML(DocletEnvironment environment) { + String tempFileName = outputFileName; + if (outputDirectory != null) { + tempFileName = outputDirectory; + if (!tempFileName.endsWith(JDiff.DIR_SEP)) + tempFileName += JDiff.DIR_SEP; + tempFileName += outputFileName; + } try { FileOutputStream fos = new FileOutputStream(tempFileName); outputFile = new PrintWriter(fos); System.out.println("JDiff: writing the API to file '" + tempFileName + "'..."); - if (root.specifiedPackages().length != 0 || root.specifiedClasses().length != 0) { - RootDocToXML apiWriter = new RootDocToXML(); - apiWriter.emitXMLHeader(); - apiWriter.logOptions(); - apiWriter.processPackages(root); - apiWriter.emitXMLFooter(); + if (environment != null) { + RootDocToXML apiWriter = new RootDocToXML(environment); + if (apiWriter.hasIncludedTypes()) { + apiWriter.emitXMLHeader(); + apiWriter.logOptions(); + apiWriter.processPackages(); + apiWriter.emitXMLFooter(); + } } outputFile.close(); } catch(IOException e) { @@ -61,29 +92,41 @@ public static boolean writeXML(RootDoc root) { return true; } + private boolean hasIncludedTypes() { + if (environment == null) { + return false; + } + for (Element element : environment.getIncludedElements()) { + if (element instanceof TypeElement) { + return true; + } + } + return false; + } + /** * Write the XML Schema file used for validation. */ public static void writeXSD() { String xsdFileName = outputFileName; if (outputDirectory == null) { - int idx = xsdFileName.lastIndexOf('\\'); - int idx2 = xsdFileName.lastIndexOf('/'); - if (idx == -1 && idx2 == -1) { - xsdFileName = ""; - } else if (idx == -1 && idx2 != -1) { - xsdFileName = xsdFileName.substring(0, idx2); - } else if (idx != -1 && idx2 == -1) { - xsdFileName = xsdFileName.substring(0, idx); - } else if (idx != -1 && idx2 != -1) { - int max = idx2 > idx ? idx2 : idx; - xsdFileName = xsdFileName.substring(0, max); - } - } else { - xsdFileName = outputDirectory; - if (!xsdFileName.endsWith(JDiff.DIR_SEP)) - xsdFileName += JDiff.DIR_SEP; - } + int idx = xsdFileName.lastIndexOf('\\'); + int idx2 = xsdFileName.lastIndexOf('/'); + if (idx == -1 && idx2 == -1) { + xsdFileName = ""; + } else if (idx == -1 && idx2 != -1) { + xsdFileName = xsdFileName.substring(0, idx2); + } else if (idx != -1 && idx2 == -1) { + xsdFileName = xsdFileName.substring(0, idx); + } else if (idx != -1 && idx2 != -1) { + int max = idx2 > idx ? idx2 : idx; + xsdFileName = xsdFileName.substring(0, max); + } + } else { + xsdFileName = outputDirectory; + if (!xsdFileName.endsWith(JDiff.DIR_SEP)) + xsdFileName += JDiff.DIR_SEP; + } xsdFileName += "api.xsd"; try { FileOutputStream fos = new FileOutputStream(xsdFileName); @@ -217,353 +260,314 @@ public void logOptions() { outputFile.print(" Command line arguments = " + Options.cmdOptions); outputFile.println(" -->"); } - /** * Process each package and the classes/interfaces within it. - * - * @param pd an array of PackageDoc objects */ - public void processPackages(RootDoc root) { - PackageDoc[] specified_pd = root.specifiedPackages(); - Map pdl = new TreeMap(); - for (int i = 0; specified_pd != null && i < specified_pd.length; i++) { - pdl.put(specified_pd[i].name(), specified_pd[i]); - } - - // Classes may be specified separately, so merge their packages into the - // list of specified packages. - ClassDoc[] cd = root.specifiedClasses(); - // This is lists of the specific classes to document - Map classesToUse = new HashMap(); - for (int i = 0; cd != null && i < cd.length; i++) { - PackageDoc cpd = cd[i].containingPackage(); - if (cpd == null && !packagesOnly) { - // If the RootDoc object has been created from a jar file - // this duplicates classes, so we have to be able to disable it. - // TODO this is still null? - cpd = root.packageNamed("anonymous"); - } - String pkgName = cpd.name(); - String className = cd[i].name(); - if (trace) System.out.println("Found package " + pkgName + " for class " + className); - if (!pdl.containsKey(pkgName)) { - if (trace) System.out.println("Adding new package " + pkgName); - pdl.put(pkgName, cpd); - } - - // Keep track of the specific classes to be used for this package - List classes; - if (classesToUse.containsKey(pkgName)) { - classes = (ArrayList) classesToUse.get(pkgName); - } else { - classes = new ArrayList(); - } - classes.add(cd[i]); - classesToUse.put(pkgName, classes); - } - - PackageDoc[] pd = (PackageDoc[]) pdl.values().toArray(new PackageDoc[0]); - for (int i = 0; pd != null && i < pd.length; i++) { - String pkgName = pd[i].name(); - - // Check for an exclude tag in the package doc block, but not - // in the package.htm[l] file. - if (!shownElement(pd[i], null)) + public void processPackages() { + Map packageMap = new TreeMap(); + Map> classesByPackage = new HashMap>(); + Map> specifiedClasses = new HashMap>(); + + for (Element element : environment.getSpecifiedElements()) { + if (element instanceof PackageElement) { + PackageElement pkg = (PackageElement) element; + String pkgName = pkg.getQualifiedName().toString(); + packageMap.put(pkgName, pkg); + } else if (element instanceof TypeElement) { + TypeElement type = (TypeElement) element; + PackageElement pkg = safeGetPackageOf(type); + String pkgName = packageNameOf(type, pkg); + if (pkg != null || !packageMap.containsKey(pkgName)) { + packageMap.put(pkgName, pkg); + } + specifiedClasses.computeIfAbsent(pkgName, k -> new ArrayList()).add(type); + } + } + + for (Element element : environment.getIncludedElements()) { + if (element instanceof PackageElement) { + PackageElement pkg = (PackageElement) element; + String pkgName = pkg.getQualifiedName().toString(); + packageMap.put(pkgName, pkg); + } else if (element instanceof TypeElement) { + TypeElement type = (TypeElement) element; + PackageElement pkg = safeGetPackageOf(type); + String pkgName = packageNameOf(type, pkg); + if (pkgName.length() == 0 && packagesOnly) { + continue; + } + if (pkg != null || !packageMap.containsKey(pkgName)) { + packageMap.put(pkgName, pkg); + } + classesByPackage.computeIfAbsent(pkgName, k -> new ArrayList()).add(type); + } + } + + List packageNames = new ArrayList(packageMap.keySet()); + Collections.sort(packageNames); + for (String pkgName : packageNames) { + PackageElement pkg = packageMap.get(pkgName); + if (pkgName.length() == 0 && packagesOnly) { + continue; + } + String pkgComment = safeGetDocComment(pkg); + if (!shownElement(pkg, pkgComment, null)) continue; if (trace) System.out.println("PROCESSING PACKAGE: " + pkgName); outputFile.println(""); - int tagCount = pd[i].tags().length; - if (trace) System.out.println("#tags: " + tagCount); - - List classList; - if (classesToUse.containsKey(pkgName)) { - // Use only the specified classes in the package - System.out.println("Using the specified classes"); - classList = (ArrayList) classesToUse.get(pkgName); - } else { - // Use all classes in the package - classList = new LinkedList(Arrays.asList(pd[i].allClasses())); - } - Collections.sort(classList); - ClassDoc[] classes = new ClassDoc[classList.size()]; - classes = (ClassDoc[])classList.toArray(classes); - processClasses(classes, pkgName); - - addPkgDocumentation(root, pd[i], 2); + List classList; + if (specifiedClasses.containsKey(pkgName)) { + if (trace) System.out.println("Using the specified classes"); + classList = new ArrayList(specifiedClasses.get(pkgName)); + } else { + List included = classesByPackage.get(pkgName); + if (included != null) { + classList = new ArrayList(included); + } else { + classList = new ArrayList(); + } + } + processClasses(classList, pkgName); + + addPkgDocumentation(pkg, pkgComment, 2); outputFile.println(""); } } // processPackages - + + + private PackageElement safeGetPackageOf(TypeElement type) { + try { + return elements.getPackageOf(type); + } catch (IllegalArgumentException e) { + if (trace) { + System.out.println("Unable to resolve package for type '" + type + "': " + e); + } + return null; + } + } + + private String safeGetDocComment(Element element) { + if (element == null) { + return null; + } + + if (element.getKind() == ElementKind.MODULE) { + if (trace) { + System.out.println("Skipping doc comment lookup for module '" + element + "'"); + } + return null; + } + + try { + return elements.getDocComment(element); + } catch (IllegalArgumentException e) { + if (trace) { + System.out.println("Unable to resolve doc comment for element '" + element + "': " + e); + } + return null; + } + } + + private String packageNameOf(TypeElement type, PackageElement pkg) { + if (pkg != null) { + return pkg.getQualifiedName().toString(); + } + Element enclosing = type.getEnclosingElement(); + while (enclosing != null && !(enclosing instanceof PackageElement)) { + enclosing = enclosing.getEnclosingElement(); + } + if (enclosing instanceof PackageElement) { + return ((PackageElement) enclosing).getQualifiedName().toString(); + } + String qualifiedName = type.getQualifiedName().toString(); + int lastDot = qualifiedName.lastIndexOf('.'); + return (lastDot >= 0) ? qualifiedName.substring(0, lastDot) : ""; + } + + /** * Process classes and interfaces. - * - * @param cd An array of ClassDoc objects. */ - public void processClasses(ClassDoc[] cd, String pkgName) { - if (cd.length == 0) + public void processClasses(List classes, String pkgName) { + if (classes == null || classes.isEmpty()) return; - if (trace) System.out.println("PROCESSING CLASSES, number=" + cd.length); - for (int i = 0; i < cd.length; i++) { - String className = cd[i].name(); - if (trace) System.out.println("PROCESSING CLASS/IFC: " + className); - // Only save the shown elements - if (!shownElement(cd[i], classVisibilityLevel)) + classes.sort(Comparator.comparing(type -> type.getSimpleName().toString())); + for (TypeElement type : classes) { + String docComment = safeGetDocComment(type); + if (!shownElement(type, docComment, classVisibilityLevel)) continue; - boolean isInterface = false; - if (cd[i].isInterface()) - isInterface = true; + boolean isInterface = type.getKind().isInterface(); + if (trace) System.out.println("PROCESSING CLASS/IFC: " + type.getSimpleName()); if (isInterface) { - outputFile.println(" "); - outputFile.print(" "); + outputFile.print(" "); - outputFile.print(" "); + outputFile.print(" "); - // Process class members. (Treat inner classes as members.) - processInterfaces(cd[i].interfaceTypes()); - processConstructors(cd[i].constructors()); - processMethods(cd[i], cd[i].methods()); - processFields(cd[i].fields()); - addDocumentation(cd[i], 4); + processInterfaces(type.getInterfaces()); + processConstructors(type); + processMethods(type); + processFields(type); + + addDocumentation(type, docComment, 4); if (isInterface) { outputFile.println(" "); - outputFile.println(" "); + outputFile.println(" "); } else { outputFile.println(" "); - outputFile.println(" "); + outputFile.println(" "); } - // Inner classes have already been added. - /* - ClassDoc[] ic = cd[i].innerClasses(); - for (int k = 0; k < ic.length; k++) { - System.out.println("Inner class " + k + ", name = " + ic[k].name()); - } - */ - }//for - }//processClasses() - - /** - * Add qualifiers for the program element as attributes. - * - * @param ped The given program element. - */ - public void addCommonModifiers(ProgramElementDoc ped, int indent) { - addSourcePosition(ped, indent); - // Static and final and visibility on one line - for (int i = 0; i < indent; i++) outputFile.print(" "); - outputFile.print("static=\"" + ped.isStatic() + "\""); - outputFile.print(" final=\"" + ped.isFinal() + "\""); - // Visibility - String visibility = null; - if (ped.isPublic()) - visibility = "public"; - else if (ped.isProtected()) - visibility = "protected"; - else if (ped.isPackagePrivate()) - visibility = "package"; - else if (ped.isPrivate()) - visibility = "private"; - outputFile.println(" visibility=\"" + visibility + "\""); - - // Deprecation on its own line - for (int i = 0; i < indent; i++) outputFile.print(" "); - boolean isDeprecated = false; - Tag[] ta = ((Doc)ped).tags("deprecated"); - if (ta.length != 0) { - isDeprecated = true; } - if (ta.length > 1) { - System.out.println("JDiff: warning: multiple @deprecated tags found in comments for " + ped.name() + ". Using the first one only."); - System.out.println("Text is: " + ((Doc)ped).getRawCommentText()); - } - if (isDeprecated) { - String text = ta[0].text(); // Use only one @deprecated tag - if (text != null && text.compareTo("") != 0) { - int idx = endOfFirstSentence(text); - if (idx == 0) { - // No useful comment - outputFile.print("deprecated=\"deprecated, no comment\""); - } else { - String fs = null; - if (idx == -1) - fs = text; - else - fs = text.substring(0, idx+1); - String st = API.hideHTMLTags(fs); - outputFile.print("deprecated=\"" + st + "\""); - } - } else { - outputFile.print("deprecated=\"deprecated, no comment\""); - } - } else { - outputFile.print("deprecated=\"not deprecated\""); - } - - } //addQualifiers() - - /** - * Insert the source code details, if available. - * - * @param ped The given program element. - */ - public void addSourcePosition(ProgramElementDoc ped, int indent) { - if (!addSrcInfo) - return; - if (JDiff.javaVersion.startsWith("1.1") || - JDiff.javaVersion.startsWith("1.2") || - JDiff.javaVersion.startsWith("1.3")) { - return; // position() only appeared in J2SE1.4 - } - try { - // Could cache the method for improved performance - Class c = ProgramElementDoc.class; - Method m = c.getMethod("position", null); - Object sp = m.invoke(ped, null); - if (sp != null) { - for (int i = 0; i < indent; i++) outputFile.print(" "); - outputFile.println("src=\"" + sp + "\""); - } - } catch (NoSuchMethodException e2) { - System.err.println("Error: method \"position\" not found"); - e2.printStackTrace(); - } catch (IllegalAccessException e4) { - System.err.println("Error: class not permitted to be instantiated"); - e4.printStackTrace(); - } catch (InvocationTargetException e5) { - System.err.println("Error: method \"position\" could not be invoked"); - e5.printStackTrace(); - } catch (Exception e6) { - System.err.println("Error: "); - e6.printStackTrace(); - } - } + }//processClasses() /** * Process the interfaces implemented by the class. - * - * @param ifaces An array of ClassDoc objects */ - public void processInterfaces(Type[] ifaces) { - if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.length); - for (int i = 0; i < ifaces.length; i++) { - String ifaceName = buildEmittableTypeString(ifaces[i]); + public void processInterfaces(List ifaces) { + if (ifaces == null) + return; + if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.size()); + for (TypeMirror iface : ifaces) { + String ifaceName = buildEmittableTypeString(iface); if (trace) System.out.println("PROCESSING INTERFACE: " + ifaceName); outputFile.println(" "); }//for }//processInterfaces() - + /** * Process the constructors in the class. - * - * @param ct An array of ConstructorDoc objects */ - public void processConstructors(ConstructorDoc[] ct) { - if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ct.length); - for (int i = 0; i < ct.length; i++) { - String ctorName = ct[i].name(); + public void processConstructors(TypeElement type) { + List ctors = new ArrayList(); + for (Element enclosed : type.getEnclosedElements()) { + if (enclosed.getKind() == ElementKind.CONSTRUCTOR) { + ctors.add((ExecutableElement) enclosed); + } + } + if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ctors.size()); + for (ExecutableElement ctor : ctors) { + String ctorName = ctor.getSimpleName().toString(); + if (ctorName.isEmpty() || ctorName.indexOf('<') != -1 || ctorName.indexOf('>') != -1) { + ctorName = type.getSimpleName().toString(); + } + if (ctorName.isEmpty()) { + ctorName = type.toString(); + } + ctorName = ctorName.replace("&", "&").replace("<", "<").replace(">", ">"); if (trace) System.out.println("PROCESSING CONSTRUCTOR: " + ctorName); - // Only save the shown elements - if (!shownElement(ct[i], memberVisibilityLevel)) + String docComment = safeGetDocComment(ctor); + if (!shownElement(ctor, docComment, memberVisibilityLevel)) continue; outputFile.print(" params = ctor.getParameters(); + if (!params.isEmpty()) { outputFile.print(" type=\""); - for (int j = 0; j < params.length; j++) { - if (!first) + for (int j = 0; j < params.size(); j++) { + if (j != 0) outputFile.print(", "); - emitType(params[j].type()); - first = false; + outputFile.print(parameterTypeToString(ctor, params.get(j), j == params.size() - 1)); } outputFile.println("\""); - } else + } else { outputFile.println(); - addCommonModifiers(ct[i], 6); + } + addCommonModifiers(ctor, docComment, 6); outputFile.println(">"); - - // Generate the exception elements if any exceptions are thrown - processExceptions(ct[i].thrownExceptions()); - addDocumentation(ct[i], 6); + processExceptions(ctor.getThrownTypes()); + + addDocumentation(ctor, docComment, 6); outputFile.println(" "); }//for }//processConstructors() - + + private String parameterTypeToString(ExecutableElement method, VariableElement param, boolean lastParam) { + TypeMirror type = param.asType(); + if (method.isVarArgs() && lastParam && type.getKind() == TypeKind.ARRAY) { + TypeMirror component = ((ArrayType) type).getComponentType(); + return buildEmittableTypeString(component) + "..."; + } + return buildEmittableTypeString(type); + } + /** * Process all exceptions thrown by a constructor or method. - * - * @param cd An array of ClassDoc objects */ - public void processExceptions(ClassDoc[] cd) { - if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + cd.length); - for (int i = 0; i < cd.length; i++) { - String exceptionName = cd[i].name(); + public void processExceptions(List thrown) { + if (thrown == null) + return; + if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + thrown.size()); + for (TypeMirror type : thrown) { + String exceptionName = simpleTypeName(type); if (trace) System.out.println("PROCESSING EXCEPTION: " + exceptionName); outputFile.print(" "); }//for }//processExceptions() - /** * Process the methods in the class. - * - * @param md An array of MethodDoc objects */ - public void processMethods(ClassDoc cd, MethodDoc[] md) { - if (trace) System.out.println("PROCESSING " +cd.name()+" METHODS, number = " + md.length); - for (int i = 0; i < md.length; i++) { - String methodName = md[i].name(); + public void processMethods(TypeElement type) { + List methods = new ArrayList(); + for (Element enclosed : type.getEnclosedElements()) { + if (enclosed.getKind() == ElementKind.METHOD) { + methods.add((ExecutableElement) enclosed); + } + } + if (trace) System.out.println("PROCESSING " + type.getSimpleName() + " METHODS, number = " + methods.size()); + for (ExecutableElement method : methods) { + String methodName = method.getSimpleName().toString(); if (trace) System.out.println("PROCESSING METHOD: " + methodName); - // Skip and - if (methodName.startsWith("<")) - continue; - // Only save the shown elements - if (!shownElement(md[i], memberVisibilityLevel)) + String docComment = safeGetDocComment(method); + if (!shownElement(method, docComment, memberVisibilityLevel)) continue; outputFile.print(" "); - // Generate the parameter elements, if any - Parameter[] params = md[i].parameters(); - for (int j = 0; j < params.length; j++) { - outputFile.print(" params = method.getParameters(); + for (int j = 0; j < params.size(); j++) { + VariableElement param = params.get(j); + outputFile.print(" "); } - // Generate the exception elements if any exceptions are thrown - processExceptions(md[i].thrownExceptions()); + processExceptions(method.getThrownTypes()); - addDocumentation(md[i], 6); + addDocumentation(method, docComment, 6); outputFile.println(" "); }//for @@ -571,46 +575,42 @@ public void processMethods(ClassDoc cd, MethodDoc[] md) { /** * Process the fields in the class. - * - * @param fd An array of FieldDoc objects */ - public void processFields(FieldDoc[] fd) { - if (trace) System.out.println("PROCESSING FIELDS, number=" + fd.length); - for (int i = 0; i < fd.length; i++) { - String fieldName = fd[i].name(); + public void processFields(TypeElement type) { + List fields = new ArrayList(); + for (Element enclosed : type.getEnclosedElements()) { + if (enclosed.getKind() == ElementKind.FIELD) { + fields.add((VariableElement) enclosed); + } + } + if (trace) System.out.println("PROCESSING FIELDS, number=" + fields.size()); + for (VariableElement field : fields) { + String fieldName = field.getSimpleName().toString(); if (trace) System.out.println("PROCESSING FIELD: " + fieldName); - // Only save the shown elements - if (!shownElement(fd[i], memberVisibilityLevel)) + String docComment = safeGetDocComment(field); + if (!shownElement(field, docComment, memberVisibilityLevel)) continue; outputFile.print(" "); - addDocumentation(fd[i], 6); + addDocumentation(field, docComment, 6); outputFile.println(" "); }//for }//processFields() - + /** * Emit the type name. Removed any prefixed warnings about ambiguity. * The type maybe an array. - * - * @param type A Type object. */ - public void emitType(com.sun.javadoc.Type type) { + public void emitType(TypeMirror type) { String name = buildEmittableTypeString(type); if (name == null) return; @@ -620,27 +620,33 @@ public void emitType(com.sun.javadoc.Type type) { /** * Build the emittable type name. The type may be an array and/or * a generic type. - * - * @param type A Type object - * @return The emittable type name */ - private String buildEmittableTypeString(com.sun.javadoc.Type type) { + private String buildEmittableTypeString(TypeMirror type) { if (type == null) { - return null; + return null; + } + String name = type.toString() + .replace("&", "&") + .replace("<", "<") + .replace(">", ">"); + if (name.startsWith("<>")) { + name = name.substring(13); + } + return name; + } + + private String simpleTypeName(TypeMirror type) { + Element element = types.asElement(type); + if (element != null) { + return element.getSimpleName().toString(); + } + String name = type.toString(); + int idx = name.lastIndexOf('.') + 1; + if (idx > 0 && idx < name.length()) { + return name.substring(idx); } - // type.toString() returns the fully qualified name of the type - // including the dimension and the parameters we just need to - // escape the generic parameters brackets so that the XML - // generated is correct - String name = type.toString(). - replaceAll("&", "&"). - replaceAll("<", "<"). - replaceAll(">", ">"); - if (name.startsWith("<>")) { - name = name.substring(13); - } - return name; - } + return name; + } /** * Emit the XML header. @@ -651,13 +657,6 @@ public void emitXMLHeader() { outputFile.println(""); outputFile.println(""); outputFile.println(); -/* No need for this any longer, since doc block text is in an CDATA element - outputFile.println(""); - outputFile.println(""); - outputFile.println(""); -*/ outputFile.println(""); } - /** - * Determine if the program element is shown, according to the given - * level of visibility. - * - * @param ped The given program element. - * @param visLevel The desired visibility level; "public", "protected", - * "package" or "private". If null, only check for an exclude tag. - * @return boolean Set if this element is shown. + /** + * Determine if the program element is shown, according to the given + * level of visibility. */ - public boolean shownElement(Doc doc, String visLevel) { - // If a doc block contains @exclude or a similar such tag, - // then don't display it. - if (doExclude && excludeTag != null && doc != null) { - String rct = doc.getRawCommentText(); - if (rct != null && rct.indexOf(excludeTag) != -1) { + public boolean shownElement(Element element, String docComment, String visLevel) { + if (doExclude && excludeTag != null && docComment != null) { + if (docComment.indexOf(excludeTag) != -1) { return false; - } - } - if (visLevel == null) { - return true; - } - ProgramElementDoc ped = null; - if (doc instanceof ProgramElementDoc) { - ped = (ProgramElementDoc)doc; - } + } + } + if (visLevel == null) { + return true; + } + if (element == null) { + return true; + } + Set modifiers = element.getModifiers(); if (visLevel.compareTo("private") == 0) return true; - // Show all that is not private if (visLevel.compareTo("package") == 0) - return !ped.isPrivate(); - // Show all that is not private or package + return !modifiers.contains(Modifier.PRIVATE); if (visLevel.compareTo("protected") == 0) - return !(ped.isPrivate() || ped.isPackagePrivate()); - // Show all that is not private or package or protected, - // i.e. all that is public + return modifiers.contains(Modifier.PUBLIC) || modifiers.contains(Modifier.PROTECTED); if (visLevel.compareTo("public") == 0) - return ped.isPublic(); + return modifiers.contains(Modifier.PUBLIC); return false; } //shownElement() - - /** - * Strip out non-printing characters, replacing them with a character + + /** + * Insert the source code details, if available. + */ + public void addSourcePosition(Element element, int indent) { + if (!addSrcInfo) + return; + if (docTrees == null) + return; + TreePath path = docTrees.getPath(element); + if (path == null) + return; + CompilationUnitTree cu = path.getCompilationUnit(); + if (cu == null) + return; + JavaFileObject fileObject = cu.getSourceFile(); + if (fileObject == null) + return; + SourcePositions positions = docTrees.getSourcePositions(); + long pos = positions.getStartPosition(cu, path.getLeaf()); + if (pos == Diagnostic.NOPOS) + return; + LineMap lineMap = cu.getLineMap(); + if (lineMap == null) + return; + long line = lineMap.getLineNumber(pos); + for (int i = 0; i < indent; i++) outputFile.print(" "); + outputFile.println("src=\"" + fileObject.getName() + ":" + line + "\""); + } + + /** + * Add qualifiers for the program element as attributes. + */ + public void addCommonModifiers(Element element, String docComment, int indent) { + addSourcePosition(element, indent); + for (int i = 0; i < indent; i++) outputFile.print(" "); + Set modifiers = element.getModifiers(); + outputFile.print("static=\"" + modifiers.contains(Modifier.STATIC) + "\""); + outputFile.print(" final=\"" + modifiers.contains(Modifier.FINAL) + "\""); + String visibility = null; + if (modifiers.contains(Modifier.PUBLIC)) + visibility = "public"; + else if (modifiers.contains(Modifier.PROTECTED)) + visibility = "protected"; + else if (modifiers.contains(Modifier.PRIVATE)) + visibility = "private"; + else + visibility = "package"; + outputFile.println(" visibility=\"" + visibility + "\""); + + for (int i = 0; i < indent; i++) outputFile.print(" "); + String deprecatedText = extractDeprecated(docComment); + if (deprecatedText != null) { + if (deprecatedText.length() == 0) { + outputFile.print("deprecated=\"deprecated, no comment\""); + } else { + int idx = endOfFirstSentence(deprecatedText); + String fs = (idx == -1) ? deprecatedText : deprecatedText.substring(0, idx + 1); + String st = API.hideHTMLTags(fs); + outputFile.print("deprecated=\"" + st + "\""); + } + } else { + outputFile.print("deprecated=\"not deprecated\""); + } + } //addQualifiers() + + private String extractDeprecated(String docComment) { + if (docComment == null) + return null; + int idx = docComment.indexOf("@deprecated"); + if (idx == -1) + return null; + int start = idx + "@deprecated".length(); + int len = docComment.length(); + while (start < len) { + char ch = docComment.charAt(start); + if (!Character.isWhitespace(ch)) + break; + start++; + } + int end = start; + while (end < len) { + char ch = docComment.charAt(end); + if (ch == '@' && end > start) { + char prev = docComment.charAt(end - 1); + if (prev == '\n' || prev == '\r') { + break; + } + } + end++; + } + return docComment.substring(start, end).trim(); + } + + /** + * Add at least the first sentence from a doc block to the API. + */ + public void addDocumentation(Element element, String docComment, int indent) { + String rct = docComment; + if (rct != null) { + rct = stripNonPrintingChars(rct, element); + rct = rct.trim(); + if (rct.compareTo("") != 0 && + rct.indexOf(Comments.placeHolderText) == -1 && + rct.indexOf("InsertOtherCommentsHere") == -1) { + int idx = endOfFirstSentence(rct); + if (idx == 0) + return; + for (int i = 0; i < indent; i++) outputFile.print(" "); + outputFile.println(""); + for (int i = 0; i < indent; i++) outputFile.print(" "); + String firstSentence = null; + if (idx == -1) + firstSentence = rct; + else + firstSentence = rct.substring(0, idx+1); + boolean checkForAts = false; + if (checkForAts && firstSentence.indexOf("@") != -1 && + firstSentence.indexOf("@link") == -1) { + System.out.println("Warning: @ tag seen in comment: " + + firstSentence); + } + String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); + outputFile.println(firstSentenceNoTags); + for (int i = 0; i < indent; i++) outputFile.print(" "); + outputFile.println(""); + } + } + } + + /** + * Add at least the first sentence from a doc block for a package to the API. + */ + public void addPkgDocumentation(PackageElement pkg, String pkgComment, int indent) { + String rct = pkgComment; + if (rct == null) { + rct = readPackageFile(pkg); + } + if (rct != null) { + rct = stripNonPrintingChars(rct, pkg); + rct = rct.trim(); + if (rct.compareTo("") != 0 && + rct.indexOf(Comments.placeHolderText) == -1 && + rct.indexOf("InsertOtherCommentsHere") == -1) { + int idx = endOfFirstSentence(rct); + if (idx == 0) + return; + for (int i = 0; i < indent; i++) outputFile.print(" "); + outputFile.println(""); + for (int i = 0; i < indent; i++) outputFile.print(" "); + String firstSentence = null; + if (idx == -1) + firstSentence = rct; + else + firstSentence = rct.substring(0, idx+1); + String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); + outputFile.println(firstSentenceNoTags); + for (int i = 0; i < indent; i++) outputFile.print(" "); + outputFile.println(""); + } + } + } + + private String readPackageFile(PackageElement pkg) { + if (pkg == null || Options.sourcePath == null) + return null; + String pkgName = pkg.getQualifiedName().toString(); + String pkgPath = pkgName.replace('.', JDiff.DIR_SEP.charAt(0)); + String[] roots = Options.sourcePath.split(File.pathSeparator); + for (String root : roots) { + if (root == null || root.length() == 0) + continue; + File base = new File(root); + if (!base.isAbsolute()) { + base = new File(System.getProperty("user.dir"), root); + } + try { + base = base.getCanonicalFile(); + } catch (IOException e) { + base = base.getAbsoluteFile(); + } + File pkgDir = pkgPath.length() == 0 ? base : new File(base, pkgPath); + String content = readPackageContent(new File(pkgDir, "package.html")); + if (content == null) { + content = readPackageContent(new File(pkgDir, "package.htm")); + } + if (content != null) { + return content; + } + } + if (trace) + System.out.println("No package level documentation file for '" + pkgName + "'"); + return null; + } + + private String readPackageContent(File file) { + if (!file.exists()) { + return null; + } + StringBuilder rct = new StringBuilder(); + try { + FileInputStream f = new FileInputStream(file); + BufferedReader d = new BufferedReader(new InputStreamReader(f)); + String str = d.readLine(); + boolean inBody = false; + while(str != null) { + if (!inBody) { + if (str.toLowerCase().trim().startsWith("' || - c == '`' - ) + c == '`') continue; -/* Doesn't seem to return the expected values? - int val = Character.getNumericValue(c); -// if (s.indexOf("which is also a test for non-printable") != -1) -// System.out.println("** Char " + i + "[" + c + "], val =" + val); //DEBUG - // Ranges from http://www.unicode.org/unicode/reports/tr20/ - // Should really replace 0x2028 and 0x2029 with
- if (val == 0x0 || - inRange(val, 0x2028, 0x2029) || - inRange(val, 0x202A, 0x202E) || - inRange(val, 0x206A, 0x206F) || - inRange(val, 0xFFF9, 0xFFFC) || - inRange(val, 0xE0000, 0xE007F)) { - if (trace) { - System.out.println("Warning: changed non-printing character " + sa[i] + " in " + doc.name()); - } - sa[i] = '#'; - } -*/ - // Replace the non-printable character with a printable character - // which does not change the end of the first sentence sa[i] = '#'; } return new String(sa); @@ -802,198 +993,34 @@ public boolean inRange(int val, int min, int max) { return true; } - /** - * Add at least the first sentence from a doc block to the API. This is - * used by the report generator if no comment is provided. - * Need to make sure that HTML tags are not confused with XML tags. - * This could be done by stuffing the < character to another string - * or by handling HTML in the parser. This second option seems neater. Note that - * XML expects all element tags to have either a closing "/>" or a matching - * end element tag. Due to the difficulties of converting incorrect HTML - * to XHTML, the first option is used. - */ - public void addDocumentation(ProgramElementDoc ped, int indent) { - String rct = ((Doc)ped).getRawCommentText(); - if (rct != null) { - rct = stripNonPrintingChars(rct, (Doc)ped); - rct = rct.trim(); - if (rct.compareTo("") != 0 && - rct.indexOf(Comments.placeHolderText) == -1 && - rct.indexOf("InsertOtherCommentsHere") == -1) { - int idx = endOfFirstSentence(rct); - if (idx == 0) - return; - for (int i = 0; i < indent; i++) outputFile.print(" "); - outputFile.println(""); - for (int i = 0; i < indent; i++) outputFile.print(" "); - String firstSentence = null; - if (idx == -1) - firstSentence = rct; - else - firstSentence = rct.substring(0, idx+1); - boolean checkForAts = false; - if (checkForAts && firstSentence.indexOf("@") != -1 && - firstSentence.indexOf("@link") == -1) { - System.out.println("Warning: @ tag seen in comment: " + - firstSentence); - } - String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); - outputFile.println(firstSentenceNoTags); - for (int i = 0; i < indent; i++) outputFile.print(" "); - outputFile.println(""); - } - } - } - - /** - * Add at least the first sentence from a doc block for a package to the API. This is - * used by the report generator if no comment is provided. - * The default source tree may not include the package.html files, so - * this may be unavailable in many cases. - * Need to make sure that HTML tags are not confused with XML tags. - * This could be done by stuffing the < character to another string - * or by handling HTML in the parser. This second option is neater. Note that - * XML expects all element tags to have either a closing "/>" or a matching - * end element tag. Due to the difficulties of converting incorrect HTML - * to XHTML, the first option is used. - */ - public void addPkgDocumentation(RootDoc root, PackageDoc pd, int indent) { - String rct = null; - String filename = pd.name(); - try { - // See if the source path was specified as part of the - // options and prepend it if it was. - String srcLocation = null; - String[][] options = root.options(); - for (int opt = 0; opt < options.length; opt++) { - if ((options[opt][0]).compareTo("-sourcepath") == 0) { - srcLocation = options[opt][1]; - break; - } - } - filename = filename.replace('.', JDiff.DIR_SEP.charAt(0)); - if (srcLocation != null) { - // Make a relative location absolute - if (srcLocation.startsWith("..")) { - String curDir = System.getProperty("user.dir"); - while (srcLocation.startsWith("..")) { - srcLocation = srcLocation.substring(3); - int idx = curDir.lastIndexOf(JDiff.DIR_SEP); - curDir = curDir.substring(0, idx+1); - } - srcLocation = curDir + srcLocation; - } - filename = srcLocation + JDiff.DIR_SEP + filename; - } - // Try both ".htm" and ".html" - filename += JDiff.DIR_SEP + "package.htm"; - File f2 = new File(filename); - if (!f2.exists()) { - filename += "l"; - } - FileInputStream f = new FileInputStream(filename); - BufferedReader d = new BufferedReader(new InputStreamReader(f)); - String str = d.readLine(); - // Ignore everything except the lines between elements - boolean inBody = false; - while(str != null) { - if (!inBody) { - if (str.toLowerCase().trim().startsWith(""); - for (int i = 0; i < indent; i++) outputFile.print(" "); - String firstSentence = null; - if (idx == -1) - firstSentence = rct; - else - firstSentence = rct.substring(0, idx+1); - String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); - outputFile.println(firstSentenceNoTags); - for (int i = 0; i < indent; i++) outputFile.print(" "); - outputFile.println(""); - } - } - } - - /** + /** * Find the index of the end of the first sentence in the given text, * when writing out to an XML file. - * This is an extended version of the algorithm used by the DocCheck - * Javadoc doclet. It checks for @tags too. - * - * @param text The text to be searched. - * @return The index of the end of the first sentence. If there is no - * end, return -1. If there is no useful text, return 0. - * If the whole doc block comment is wanted (default), return -1. */ public static int endOfFirstSentence(String text) { return endOfFirstSentence(text, true); } - /** + /** * Find the index of the end of the first sentence in the given text. - * This is an extended version of the algorithm used by the DocCheck - * Javadoc doclet. It checks for @tags too. - * - * @param text The text to be searched. - * @param writingToXML Set to true when writing out XML. - * @return The index of the end of the first sentence. If there is no - * end, return -1. If there is no useful text, return 0. - * If the whole doc block comment is wanted (default), return -1. */ public static int endOfFirstSentence(String text, boolean writingToXML) { if (saveAllDocs && writingToXML) return -1; - int textLen = text.length(); - if (textLen == 0) - return 0; + int textLen = text.length(); + if (textLen == 0) + return 0; int index = -1; - // Handle some special cases int fromindex = 0; - int ellipsis = text.indexOf(". . ."); // Handles one instance of this + int ellipsis = text.indexOf(". . ."); if (ellipsis != -1) fromindex = ellipsis + 5; - // If the first non-whitespace character is an @, go beyond it int i = 0; while (i < textLen && text.charAt(i) == ' ') { i++; } - if (text.charAt(i) == '@' && fromindex < textLen-1) + if (i < textLen && text.charAt(i) == '@' && fromindex < textLen-1) fromindex = i + 1; - // Use the brute force approach. index = minIndex(index, text.indexOf("? ", fromindex)); index = minIndex(index, text.indexOf("?\t", fromindex)); index = minIndex(index, text.indexOf("?\n", fromindex)); @@ -1023,123 +1050,99 @@ public static int endOfFirstSentence(String text, boolean writingToXML) { index = minIndex(index, text.indexOf(excludeTag)); index = minIndex(index, text.indexOf("@vtexclude", fromindex)); index = minIndex(index, text.indexOf("@vtinclude", fromindex)); - index = minIndex(index, text.indexOf("

", 2)); // Not at start - index = minIndex(index, text.indexOf("

", 2)); // Not at start - index = minIndex(index, text.indexOf("", 2)); + index = minIndex(index, text.indexOf("

", 2)); + index = minIndex(index, text.indexOf(" -1, and return -1 * only if both indexes = -1. - * @param i an int index - * @param j an int index - * @return an int equal to the minimum index > -1, or -1 */ public static int minIndex(int i, int j) { if (i == -1) return j; if (j == -1) return i; return Math.min(i,j); } - - /** - * The name of the file where the XML representing the API will be - * stored. + + /** + * The name of the file where the XML representing the API will be + * stored. */ public static String outputFileName = null; - /** - * The identifier of the API being written out in XML, e.g. - * "SuperProduct 1.3". + /** + * The identifier of the API being written out in XML, e.g. + * "SuperProduct 1.3". */ public static String apiIdentifier = null; - /** - * The file where the XML representing the API will be stored. - */ - private static PrintWriter outputFile = null; - - /** - * The name of the directory where the XML representing the API will be - * stored. + /** + * The name of the directory where the XML representing the API will be + * stored. */ public static String outputDirectory = null; - /** - * Do not display a class with a lower level of visibility than this. + /** + * Do not display a class with a lower level of visibility than this. * Default is to display all public and protected classes. */ public static String classVisibilityLevel = "protected"; - /** - * Do not display a member with a lower level of visibility than this. - * Default is to display all public and protected members + /** + * Do not display a member with a lower level of visibility than this. + * Default is to display all public and protected members * (constructors, methods, fields). */ public static String memberVisibilityLevel = "protected"; - /** - * If set, then save the entire contents of a doc block comment in the - * API file. If not set, then just save the first sentence. Default is + /** + * If set, then save the entire contents of a doc block comment in the + * API file. If not set, then just save the first sentence. Default is * that this is set. */ public static boolean saveAllDocs = true; - /** + /** * If set, exclude program elements marked with whatever the exclude tag * is specified as, e.g. "@exclude". */ public static boolean doExclude = false; - /** + /** * Exclude program elements marked with this String, e.g. "@exclude". */ public static String excludeTag = null; - /** - * The base URI for locating necessary DTDs and Schemas. By default, this - * is "http://www.w3.org". A typical value to use local copies of DTD files - * might be "file:///C:/jdiff/lib" + /** + * The base URI for locating necessary DTDs and Schemas. By default, this + * is "http://www.w3.org". */ public static String baseURI = "http://www.w3.org"; - /** + /** * If set, then strip out non-printing characters from documentation. * Default is that this is set. */ static boolean stripNonPrintables = true; - /** + /** * If set, then add the information about the source file and line number * which is available in J2SE1.4. Default is that this is not set. */ static boolean addSrcInfo = false; - /** - * If set, scan classes with no packages. - * If the source is a jar file this may duplicates classes, so - * disable it using the -packagesonly option. Default is that this is + /** + * If set, scan classes with no packages. + * If the source is a jar file this may duplicates classes, so + * disable it using the -packagesonly option. Default is that this is * not set. */ static boolean packagesOnly = false; diff --git a/src/jdiff/XMLToAPI.java b/src/jdiff/XMLToAPI.java index 8937a66..4f5fb02 100644 --- a/src/jdiff/XMLToAPI.java +++ b/src/jdiff/XMLToAPI.java @@ -11,6 +11,10 @@ import org.xml.sax.InputSource; import org.xml.sax.helpers.*; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + /** * Creates an API object from an XML file. The API object is the internal * representation of an API. @@ -49,16 +53,26 @@ public static API readFile(String filename, boolean createGlobalComments, try { String parserName = System.getProperty("org.xml.sax.driver"); if (parserName == null) { - parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); + parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); } else { // Let the underlying mechanisms try to work out which // class to instantiate parser = org.xml.sax.helpers.XMLReaderFactory.createXMLReader(); } } catch (SAXException saxe) { - System.out.println("SAXException: " + saxe); - saxe.printStackTrace(); - System.exit(1); + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + factory.setValidating(validateXML); + SAXParser saxParser = factory.newSAXParser(); + parser = saxParser.getXMLReader(); + } catch (ParserConfigurationException | SAXException fallbackException) { + System.out.println("SAXException: " + saxe); + saxe.printStackTrace(); + System.out.println("Failed to create XMLReader via SAXParserFactory: " + fallbackException); + fallbackException.printStackTrace(); + System.exit(1); + } } if (validateXML) { parser.setFeature("http://xml.org/sax/features/namespaces", true);