Merge "Migrate JDiff to new Javadoc"
diff --git a/Android.bp b/Android.bp
index f22fe1c..141e3f8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -46,11 +46,12 @@
     ],
 }
 
-// b/246303954: jdiff is excluded from build until it is migrated to Java 17, or
-// replaced by another tool
-// java_library_host {
-//     name: "jdiff",
-//     srcs: ["**/*.java"],
-//
-//     use_tools_jar: true,
-// }
+ java_library_host {
+     name: "jdiff",
+     srcs: ["**/*.java"],
+     static_libs: [
+         "doclava-doclet-adapter",
+     ],
+
+     use_tools_jar: true,
+ }
diff --git a/src/jdiff/CommentsHandler.java b/src/jdiff/CommentsHandler.java
index 9872cb7..3ad9b0a 100755
--- a/src/jdiff/CommentsHandler.java
+++ b/src/jdiff/CommentsHandler.java
@@ -67,10 +67,10 @@
                 System.exit(3);
             }
             // Check the given names against the names of the APIs
-            int idx1 = JDiff.oldFileName.lastIndexOf('.');
-            int idx2 = JDiff.newFileName.lastIndexOf('.');
-            String filename2 = JDiff.oldFileName.substring(0, idx1) + 
-                "_to_" + JDiff.newFileName.substring(0, idx2);
+            int idx1 = Options.oldFileName.lastIndexOf('.');
+            int idx2 = Options.newFileName.lastIndexOf('.');
+            String filename2 = Options.oldFileName.substring(0, idx1) + 
+                "_to_" + Options.newFileName.substring(0, idx2);
             if (filename2.compareTo(commentsName) != 0) {
                 System.out.println("Warning: API identifier in the comments XML file (" + filename2 + ") differs from the name of the file.");
             }
diff --git a/src/jdiff/JDiff.java b/src/jdiff/JDiff.java
index c5cc25a..51d4772 100755
--- a/src/jdiff/JDiff.java
+++ b/src/jdiff/JDiff.java
@@ -1,12 +1,19 @@
 package jdiff;
 
-import com.sun.javadoc.*;
-
-import java.util.*;
+import com.google.doclava.javadoc.RootDocImpl;
+import com.sun.javadoc.DocErrorReporter;
+import com.sun.javadoc.RootDoc;
 import java.io.*;
-import java.lang.reflect.*; // Used for invoking Javadoc indirectly
-import java.lang.Runtime;
 import java.lang.Process;
+import java.lang.Runtime;
+import java.lang.reflect.*; // Used for invoking Javadoc indirectly
+import java.util.*;
+import java.util.HashSet;
+import javax.lang.model.SourceVersion;
+import javax.tools.Diagnostic.Kind;
+import jdk.javadoc.doclet.Doclet;
+import jdk.javadoc.doclet.DocletEnvironment;
+import jdk.javadoc.doclet.Reporter;
 
 /**
  * Generates HTML describing the changes between two sets of Java source code.
@@ -14,11 +21,44 @@
  * See the file LICENSE.txt for copyright details.
  * @author Matthew Doar, mdoar@pobox.com.
  */
-public class JDiff extends Doclet {
+public class JDiff implements Doclet {
 
-    public static LanguageVersion languageVersion(){
-      return LanguageVersion.JAVA_1_5;
-    } 
+    public static Reporter reporter;
+
+    @Override
+    public void init(Locale locale, Reporter reporter) {
+        JDiff.reporter = reporter;
+    }
+
+    @Override
+    public String getName() {
+        return "JDiff";
+    }
+
+    @Override
+    public Set<? extends Option> getSupportedOptions() {
+        return Options.getSupportedOptions();
+    }
+
+    @Override
+    public SourceVersion getSupportedSourceVersion() {
+        return SourceVersion.RELEASE_5;
+    }
+
+    @Override
+    public boolean run(DocletEnvironment environment) {
+        if (!Options.writeXML && !Options.compareAPIs) {
+            JDiff.reporter.print(Kind.ERROR,
+                    "First use the -apiname option to generate an XML file for one API.");
+            JDiff.reporter.print(Kind.ERROR,
+                    "Then use the -apiname option again to generate another XML file for a different version of the API.");
+            JDiff.reporter.print(Kind.ERROR,
+                    "Finally use the -oldapi option and -newapi option to generate a report about how the APIs differ.");
+            return false;
+        }
+        return start(new RootDocImpl(environment));
+    }
+
     /**
      * Doclet-mandated start method. Everything begins here.
      *
@@ -43,19 +83,19 @@
 
         // 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 (Options.writeXML) {
+            RootDocToXML.writeXML(newRoot);
         }
 
-        if (compareAPIs) {
-	    String tempOldFileName = oldFileName;
-	    if (oldDirectory != null) {
-		tempOldFileName = oldDirectory;
-		if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) {
-		    tempOldFileName += JDiff.DIR_SEP;
-		}
-		tempOldFileName += oldFileName;
-	    }
+        if (Options.compareAPIs) {
+        String tempOldFileName = Options.oldFileName;
+        if (Options.oldDirectory != null) {
+        tempOldFileName = Options.oldDirectory;
+        if (!tempOldFileName.endsWith(JDiff.DIR_SEP)) {
+            tempOldFileName += JDiff.DIR_SEP;
+        }
+        tempOldFileName += Options.oldFileName;
+        }
 
             // Check the file for the old API exists
             File f = new File(tempOldFileName);
@@ -65,13 +105,13 @@
             }
             // Check the file for the new API exists
 
-	    String tempNewFileName = newFileName;
-            if (newDirectory != null) {
-		tempNewFileName = newDirectory;
-		if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) {
-		    tempNewFileName += JDiff.DIR_SEP;
-		}
-		tempNewFileName += newFileName;
+        String tempNewFileName = Options.newFileName;
+            if (Options.newDirectory != null) {
+        tempNewFileName = Options.newDirectory;
+        if (!tempNewFileName.endsWith(JDiff.DIR_SEP)) {
+            tempNewFileName += JDiff.DIR_SEP;
+        }
+        tempNewFileName += Options.newFileName;
             }
             f = new File(tempNewFileName);
             if (!f.exists()) {
@@ -83,26 +123,26 @@
             // and create an API object for it.
             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);
-            
+            API oldAPI = XMLToAPI.readFile(tempOldFileName, false, Options.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);
-            
+            API newAPI = XMLToAPI.readFile(tempNewFileName, true, Options.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);
-            suffix = newFileName.lastIndexOf('.');
-            commentsFileName += "_to_" + newFileName.substring(0, suffix) + ".xml";
+            int suffix = Options.oldFileName.lastIndexOf('.');
+            String commentsFileName = "user_comments_for_" + Options.oldFileName.substring(0, suffix);
+            suffix = Options.newFileName.lastIndexOf('.');
+            commentsFileName += "_to_" + Options.newFileName.substring(0, suffix) + ".xml";
             commentsFileName = commentsFileName.replace(' ', '_');
                 if (HTMLReportGenerator.commentsDir !=null) {
                   commentsFileName = HTMLReportGenerator.commentsDir + DIR_SEP + commentsFileName;
@@ -113,16 +153,16 @@
             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 + "'...");
@@ -130,46 +170,16 @@
         }
 
         System.out.print("JDiff: finished (took " + (System.currentTimeMillis() - startTime)/1000 + "s");
-        if (writeXML)
-            System.out.println(", not including scanning the source files)."); 
-        else if (compareAPIs)
+        if (Options.writeXML)
+            System.out.println(", not including scanning the source files).");
+        else if (Options.compareAPIs)
             System.out.println(").");
        return true;
     }
 
-//
-// Option processing
-// 
-
     /**
-     * This method is called by Javadoc to
-     * parse the options it does not recognize. It then calls
-     * {@link #validOptions} to validate them.
-     *
-     * @param option  a String containing an option
-     * @return an int telling how many components that option has
-     */
-    public static int optionLength(String option) {
-        return Options.optionLength(option);
-    }
-
-    /**
-     * After parsing the available options using {@link #optionLength},
-     * 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
-     * @return true if no errors were found, and all options are
-     *         valid
-     */
-    public static boolean validOptions(String[][] options, 
-                                       DocErrorReporter 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) {
@@ -190,7 +200,7 @@
         return;
     }
 
-    /** 
+    /**
      * Display usage information for JDiff.
      */
     public static void showUsage() {
@@ -198,8 +208,8 @@
         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.
      */
@@ -240,36 +250,6 @@
         return -1;
     }
 
-    /** 
-     * The name of the file where the XML representing the old API is
-     * stored. 
-     */
-    static String oldFileName = "old_java.xml";
-
-    /** 
-     * The name of the directory where the XML representing the old API is
-     * stored. 
-     */
-    static String oldDirectory = null;
-
-    /** 
-     * 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. 
-     */
-    static String newDirectory = null;
-
-    /** If set, then generate the XML for an API and exit. */
-    static boolean writeXML = false;
-
-    /** 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.
      */
diff --git a/src/jdiff/Options.java b/src/jdiff/Options.java
index 748da7c..43614df 100755
--- a/src/jdiff/Options.java
+++ b/src/jdiff/Options.java
@@ -3,6 +3,8 @@
 import java.io.*;
 import java.util.*;
 import com.sun.javadoc.*;
+import javax.tools.Diagnostic;
+import jdk.javadoc.doclet.Doclet.Option;
 
 /** 
  * Class to handle options for JDiff.
@@ -12,323 +14,552 @@
  */
 public class Options {
 
-    /** Default constructor. */
-    public Options() {
-    }
+    public static String authorid = "";
+    public static String versionid = "";
+    public static boolean classlist = false;
+    public static String title = "";
+    public static boolean docletid = false;
+    public static String evident = "";
+    public static List<String> skippkg = new ArrayList();
+    public static List<String> skipclass = new ArrayList();
+    public static int execdepth = 0;
 
     /**
-     * Returns the "length" of a given option. If an option takes no
-     * arguments, its length is one. If it takes one argument, its
-     * length is two, and so on. This method is called by Javadoc to
-     * parse the options it does not recognize. It then calls
-     * {@link #validOptions} to validate them.
-     * <blockquote>
-     * <b>Note:</b><br>
-     * The options arrive as case-sensitive strings. For options that
-     * are not case-sensitive, use toLowerCase() on the option string
-     * before comparing it.
-     * </blockquote>
-     *
-     * @param option  a String containing an option
-     * @return an int telling how many components that option has
+     * The name of the file where the XML representing the old API is
+     * stored.
      */
-    public static int optionLength(String option) {
-        String opt = option.toLowerCase();
-        
+    static String oldFileName = "old_java.xml";
+
+    /**
+     * The name of the directory where the XML representing the old API is
+     * stored.
+     */
+    static String oldDirectory = null;
+
+    /**
+     * 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.
+     */
+    static String newDirectory = null;
+
+    /** If set, then generate the XML for an API and exit. */
+    static boolean writeXML = false;
+
+    /** If set, then read in two XML files and compare their APIs. */
+    static boolean compareAPIs = false;
+
+    public static Set<? extends Option> getSupportedOptions() {
+        Set<Option> options = new HashSet<>();
+
         // 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.add(
+                new Option() {
+                    private final List<String> names = List.of("-authorid");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<author>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.authorid = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-versionid");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<version>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.versionid = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-d");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Destination directory for output HTML files";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<directory>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.outputDir = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-classlist");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.classlist = true;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-title");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<title>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.title = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-docletid");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.docletid = true;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-evident");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<evident>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.evident = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-skippkg");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<package>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.skippkg.add(arguments.get(0));
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-skipclass");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<class>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.skipclass.add(arguments.get(0));
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-execdepth");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<depth>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.execdepth = Integer.parseInt(arguments.get(0));
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-version");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        System.out.println("JDiff version: " + JDiff.version);
+                        System.exit(0);
+                        return true;
+                    }
+                }
+        );
+
         // Options to control JDiff
-        if (opt.equals("-apiname"))    return 2;
-        if (opt.equals("-oldapi"))    return 2;
-        if (opt.equals("-newapi"))    return 2;
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-apiname");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<Name of a version>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.skipclass.add(arguments.get(0));
+                        String filename = arguments.get(0);
+                        RootDocToXML.apiIdentifier = filename;
+                        filename = filename.replace(' ', '_');
+                        RootDocToXML.outputFileName =  filename + ".xml";
+                        Options.writeXML = true;
+                        Options.compareAPIs = false;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-oldapi");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<Name of a version>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        String filename = arguments.get(0);
+                        filename = filename.replace(' ', '_');
+                        Options.oldFileName =  filename + ".xml";
+                        Options.writeXML = false;
+                        Options.compareAPIs = true;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-newapi");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<Name of a version>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        String filename = arguments.get(0);
+                        filename = filename.replace(' ', '_');
+                        Options.newFileName =  filename + ".xml";
+                        Options.writeXML = false;
+                        Options.compareAPIs = true;
+                        return true;
+                    }
+                }
+        );
 
         // 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;
-        if (opt.equals("-usercommentsdir"))    return 2;
 
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-apidir");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<directory>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        RootDocToXML.outputDirectory = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-oldapidir");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Location of the XML file for the old API";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<directory>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.oldDirectory = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-newapidir");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Location of the XML file for the new API";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<directory>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Options.newDirectory = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-usercommentsdir");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Path to dir containing the user_comments* file(s)";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<directory>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.commentsDir = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
 
         // 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("-packagesonly"))    return 1;
-        if (opt.equals("-showallchanges"))    return 1;
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-excludeclass");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Exclude classes which are not public, protected etc";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() {
+                        return "[public|protected|package|private]";
+                    }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        String level = arguments.get(0);
+                        if (level.compareTo("public") != 0 &&
+                            level.compareTo("protected") != 0 &&
+                            level.compareTo("package") != 0 &&
+                            level.compareTo("private") != 0) {
+                            JDiff.reporter.print(Diagnostic.Kind.ERROR,
+                                    "Level specified after -excludeclass option must be one of (public|protected|package|private).");
+                            return false;
+                        }
+                        RootDocToXML.classVisibilityLevel = level;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-excludemember");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Exclude members which are not public, protected etc";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() {
+                        return "[public|protected|package|private]";
+                    }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        String level = arguments.get(0);
+                        if (level.compareTo("public") != 0 &&
+                            level.compareTo("protected") != 0 &&
+                            level.compareTo("package") != 0 &&
+                            level.compareTo("private") != 0) {
+                            JDiff.reporter.print(Diagnostic.Kind.ERROR,
+                                    "Level specified after -excludemember option must be one of (public|protected|package|private).");
+                            return false;
+                        }
+                        RootDocToXML.memberVisibilityLevel = level;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-firstsentence");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() {
+                        return "Save only the first sentence of each comment block with the API.";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        RootDocToXML.saveAllDocs = false;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-docchanges");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() {
+                        return "Report changes in Javadoc comments between the APIs";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.reportDocChanges = true;
+                        Diff.noDocDiffs = false;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-packagesonly");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        RootDocToXML.packagesOnly = true;
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-showallchanges");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        Diff.showAllChanges = true;
+                        return true;
+                    }
+                }
+        );
 
         // Option to change the location for the existing Javadoc
         // documentation for the new API. Default is "../"
-        if (opt.equals("-javadocnew"))    return 2;
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-javadocnew");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() {
+                        return "<location of existing Javadoc files for the new API>";
+                    }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.newDocPrefix = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
         // 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;
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-javadocold");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() {
+                        return "<location of existing Javadoc files for the old API>";
+                    }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.oldDocPrefix = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-baseuri");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Use \"base\" as the base location of the various DTDs and Schemas used by JDiff";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<base>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        RootDocToXML.baseURI = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
 
         // 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()
-
-   /**
-    * After parsing the available options using {@link #optionLength},
-    * Javadoc invokes this method with an array of options-arrays, where
-    * the first item in any array is the option, and subsequent items in
-    * that array are its arguments. So, if -print is an option that takes
-    * no arguments, and -copies is an option that takes 1 argument, then
-    * <pre>
-    *     -print -copies 3
-    * </pre>
-    * produces an array of arrays that looks like:
-    * <pre>
-    *      option[0][0] = -print
-    *      option[1][0] = -copies
-    *      option[1][1] = 3
-    * </pre>
-    * (By convention, command line switches start with a "-", but
-    * they don't have to.)
-    * <p>
-    * <b>Note:</b><br>
-    * Javadoc passes <i>all</i>parameters to this method, not just
-    * those that Javadoc doesn't recognize. The only way to
-    * identify unexpected arguments is therefore to check for every
-    * 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
-    * @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);
-            }
-        }
-        
-        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];
-                if (trace)
-                    System.out.print(" " + options[i][j]); 
-            }            
-        }
-        if (trace)
-            System.out.println(); 
-
-        for (int i = 0; i < options.length; i++) {
-            if (options[i][0].toLowerCase().equals("-apiname")) {
-                if (options[i].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];
-                    RootDocToXML.apiIdentifier = filename;
-                    filename = filename.replace(' ', '_');
-                    RootDocToXML.outputFileName =  filename + ".xml";
-                    JDiff.writeXML = true;
-                    JDiff.compareAPIs = false;
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-apidir")) {
-                if (options[i].length < 2) {
-                    err.msg("No directory specified after -apidir option.");
-                } else {
-		    RootDocToXML.outputDirectory = options[i][1];
-                }
-                continue;
-            }
-	    if (options[i][0].toLowerCase().equals("-oldapi")) {
-                if (options[i].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];
-                    filename = filename.replace(' ', '_');
-                    JDiff.oldFileName =  filename + ".xml";
-                    JDiff.writeXML = false;
-                    JDiff.compareAPIs = true;
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-oldapidir")) {
-                if (options[i].length < 2) {
-                    err.msg("No directory specified after -oldapidir option.");
-                } else { 
-                	JDiff.oldDirectory = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-newapi")) {
-                if (options[i].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];
-                    filename = filename.replace(' ', '_');
-                    JDiff.newFileName =  filename + ".xml";
-                    JDiff.writeXML = false;
-                    JDiff.compareAPIs = true;
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-newapidir")) {
-                if (options[i].length < 2) {
-                    err.msg("No directory specified after -newapidir option.");
-                } else { 
-                	JDiff.newDirectory = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-usercommentsdir")) {
-                if (options[i].length < 2) {
-                    err.msg("Android: No directory specified after -usercommentsdir option.");
-                } else { 
-                    HTMLReportGenerator.commentsDir = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-d")) {
-                if (options[i].length < 2) {
-                    err.msg("No directory specified after -d option.");
-                } else {
-                    HTMLReportGenerator.outputDir = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-javadocnew")) {
-                if (options[i].length < 2) {
-                    err.msg("No location specified after -javadocnew option.");
-                } else {
-                    HTMLReportGenerator.newDocPrefix = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-javadocold")) {
-                if (options[i].length < 2) {
-                    err.msg("No location specified after -javadocold option.");
-                } else {
-                    HTMLReportGenerator.oldDocPrefix = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-baseuri")) {
-                if (options[i].length < 2) {
-                    err.msg("No base location specified after -baseURI option.");
-                } else {
-                    RootDocToXML.baseURI = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-excludeclass")) {
-                if (options[i].length < 2) {
-                    err.msg("No level (public|protected|package|private) specified after -excludeclass option.");
-                } else { 
-                    String level = options[i][1];
-                    if (level.compareTo("public") != 0 &&
-                        level.compareTo("protected") != 0 &&
-                        level.compareTo("package") != 0 &&
-                        level.compareTo("private") != 0) {
-                        err.msg("Level specified after -excludeclass option must be one of (public|protected|package|private).");
-                    } else {
-                        RootDocToXML.classVisibilityLevel = level;
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-nosuggest");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Do not add suggested comments to all, or the removed, added or changed sections";
                     }
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-excludemember")) {
-                if (options[i].length < 2) {
-                    err.msg("No level (public|protected|package|private) specified after -excludemember option.");
-                } else { 
-                    String level = options[i][1];
-                    if (level.compareTo("public") != 0 &&
-                        level.compareTo("protected") != 0 &&
-                        level.compareTo("package") != 0 &&
-                        level.compareTo("private") != 0) {
-                        err.msg("Level specified after -excludemember option must be one of (public|protected|package|private).");
-                    } else {
-                        RootDocToXML.memberVisibilityLevel = level;
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() {
+                        return "[all|remove|add|change]";
                     }
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-firstsentence")) {
-                RootDocToXML.saveAllDocs = false;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-docchanges")) {
-                HTMLReportGenerator.reportDocChanges = true;
-                Diff.noDocDiffs = false;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-packagesonly")) {
-                RootDocToXML.packagesOnly = true;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-showallchanges")) {
-                Diff.showAllChanges = true;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-nosuggest")) {
-                if (options[i].length < 2) {
-                    err.msg("No level (all|remove|add|change) specified after -nosuggest option.");
-                } else { 
-                    String level = options[i][1];
-                    if (level.compareTo("all") != 0 &&
-                        level.compareTo("remove") != 0 &&
-                        level.compareTo("add") != 0 &&
-                        level.compareTo("change") != 0) {
-                        err.msg("Level specified after -nosuggest option must be one of (all|remove|add|change).");
-                    } else {
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        String level = arguments.get(0);
+                        if (level.compareTo("all") != 0 &&
+                            level.compareTo("remove") != 0 &&
+                            level.compareTo("add") != 0 &&
+                            level.compareTo("change") != 0) {
+                            JDiff.reporter.print(Diagnostic.Kind.ERROR,
+                                    "Level specified after -nosuggest option must be one of (all|remove|add|change).");
+                            return false;
+                        }
                         if (level.compareTo("removal") == 0)
                             HTMLReportGenerator.noCommentsOnRemovals = true;
                         else if (level.compareTo("add") == 0)
@@ -340,104 +571,123 @@
                             HTMLReportGenerator.noCommentsOnAdditions = true;
                             HTMLReportGenerator.noCommentsOnChanges = true;
                         }
+                        return true;
                     }
                 }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-checkcomments")) {
-                APIHandler.checkIsSentence = true;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-retainnonprinting")) {
-                RootDocToXML.stripNonPrintables = false;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-excludetag")) {
-                if (options[i].length < 2) {
-                    err.msg("No exclude tag specified after -excludetag option.");
-                } else { 
-                    RootDocToXML.excludeTag = options[i][1];
-                    RootDocToXML.excludeTag = RootDocToXML.excludeTag.trim();
-                    RootDocToXML.doExclude = true;
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-stats")) {
-                HTMLReportGenerator.doStats = true;
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-doctitle")) {
-                if (options[i].length < 2) {
-                    err.msg("No HTML text specified after -doctitle option.");
-                } else { 
-                    HTMLReportGenerator.docTitle = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-windowtitle")) {
-                if (options[i].length < 2) {
-                    err.msg("No text specified after -windowtitle option.");
-                } else { 
-                    HTMLReportGenerator.windowTitle = options[i][1];
-                }
-                continue;
-            }
-            if (options[i][0].toLowerCase().equals("-version")) {
-                System.out.println("JDiff version: " + JDiff.version);
-                System.exit(0);
-            }
-            if (options[i][0].toLowerCase().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()
+        );
 
-    /** Display the arguments for JDiff. */
-    public static void usage() {
-        System.err.println("JDiff version: " + JDiff.version);
-        System.err.println("");
-        System.err.println("Valid JDiff arguments:");
-        System.err.println("");
-        System.err.println("  -apiname <Name of a version>");
-        System.err.println("  -oldapi <Name of a version>");
-        System.err.println("  -newapi <Name of a version>");
-        
-        System.err.println("  Optional Arguments");
-        System.err.println();
-        System.err.println("  -d <directory> Destination directory for output HTML files");
-        System.err.println("  -oldapidir <directory> Location of the XML file for the old API");
-        System.err.println("  -newapidir <directory> Location of the XML file for the new API");
-        System.err.println("  -sourcepath <location of Java source files>");
-        System.err.println("  -javadocnew <location of existing Javadoc files for the new API>");
-        System.err.println("  -javadocold <location of existing Javadoc files for the old API>");
-        System.err.println("  -usercommentsdir <directory> Path to dir containing the user_comments* file(s)");
-        
-        System.err.println("  -baseURI <base> 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("  -nosuggest [all|remove|add|change] Do not add suggested comments to all, or the removed, added or chabged sections");
-        System.err.println("  -checkcomments Check that comments are sentences");
-        System.err.println("  -stripnonprinting Remove non-printable characters from comments.");
-        System.err.println("  -excludetag <tag> Define the Javadoc tag which implies exclusion");
-        System.err.println("  -stats Generate statistical output");
-        System.err.println("  -help       (generates this output)");
-        System.err.println("");
-        System.err.println("For more help, see jdiff.html");
+        // Option to enable checking that the comments end with a period.
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-checkcomments");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() {
+                        return "Check that comments are sentences";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        APIHandler.checkIsSentence = true;
+                        return true;
+                    }
+                }
+        );
+
+        // Option to retain non-printing characters in comments.
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-retainnonprinting");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() {
+                        return "Keep non-printable characters from comments.";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        RootDocToXML.stripNonPrintables = false;
+                        return true;
+                    }
+                }
+        );
+
+        // Option for the name of the exclude tag
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-excludetag");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() {
+                        return "Define the Javadoc tag which implies exclusion";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<tag>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        RootDocToXML.excludeTag = arguments.get(0);
+                        RootDocToXML.excludeTag = RootDocToXML.excludeTag.trim();
+                        RootDocToXML.doExclude = true;
+                        return true;
+                    }
+                }
+        );
+
+        // Generate statistical output
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-stats");
+                    @Override public int          getArgumentCount() { return 0; }
+                    @Override public String       getDescription() {
+                        return "Generate statistical output";
+                    }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return ""; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.doStats = true;
+                        return true;
+                    }
+                }
+        );
+
+        // Set the browser window title
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-windowtitle");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<title>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.windowTitle = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        // Set the report title
+
+        options.add(
+                new Option() {
+                    private final List<String> names = List.of("-doctitle");
+                    @Override public int          getArgumentCount() { return 1; }
+                    @Override public String       getDescription() { return ""; }
+                    @Override public Option.Kind  getKind() { return Option.Kind.STANDARD; }
+                    @Override public List<String> getNames() { return names; }
+                    @Override public String       getParameters() { return "<title>"; }
+                    @Override public boolean      process(String opt, List<String> arguments) {
+                        HTMLReportGenerator.docTitle = arguments.get(0);
+                        return true;
+                    }
+                }
+        );
+
+        return options;
     }
-    
-    /** 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;
 }
diff --git a/src/jdiff/RootDocToXML.java b/src/jdiff/RootDocToXML.java
index cb2f0e2..587d3ab 100755
--- a/src/jdiff/RootDocToXML.java
+++ b/src/jdiff/RootDocToXML.java
@@ -214,9 +214,10 @@
      * out as XML comments.
      */
     public void logOptions() {
-        outputFile.print("<!-- ");
-        outputFile.print(" Command line arguments = " + Options.cmdOptions);
-        outputFile.println(" -->");
+        // TODO: Get options from javadoc if possible.
+        // outputFile.print("<!-- ");
+        // outputFile.print(" Command line arguments = " + Options.cmdOptions);
+        // outputFile.println(" -->");
     }
 
     /**