8226204: SA: Refactoring for option processing in SALauncher
authorysuenaga
Mon, 19 Aug 2019 19:43:28 +0900
changeset 57794 ffdb18fb88b9
parent 57793 d372747e8f08
child 57795 2e58f5d927a6
8226204: SA: Refactoring for option processing in SALauncher Reviewed-by: cjplummer, sspitsyn
src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java
--- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java	Mon Aug 19 11:14:50 2019 +0100
+++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/SALauncher.java	Mon Aug 19 19:43:28 2019 +0900
@@ -26,6 +26,11 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
 
 import sun.jvm.hotspot.tools.JStack;
 import sun.jvm.hotspot.tools.JMap;
@@ -132,44 +137,51 @@
     }
 
     private static boolean toolHelp(String toolName) {
-        if (toolName.equals("jstack")) {
-            return jstackHelp();
-        }
-        if (toolName.equals("jinfo")) {
-            return jinfoHelp();
-        }
-        if (toolName.equals("jmap")) {
-            return jmapHelp();
+        switch (toolName) {
+            case "jstack":
+                return jstackHelp();
+            case "jinfo":
+                return jinfoHelp();
+            case "jmap":
+                return jmapHelp();
+            case "jsnap":
+                return jsnapHelp();
+            case "debugd":
+                return debugdHelp();
+            case "hsdb":
+            case "clhsdb":
+                return commonHelp(toolName);
+            default:
+                return launcherHelp();
         }
-        if (toolName.equals("jsnap")) {
-            return jsnapHelp();
-        }
-        if (toolName.equals("debugd")) {
-            return debugdHelp();
-        }
-        if (toolName.equals("hsdb")) {
-            return commonHelp("hsdb");
-        }
-        if (toolName.equals("clhsdb")) {
-            return commonHelp("clhsdb");
-        }
-        return launcherHelp();
     }
 
     private static final String NO_REMOTE = null;
 
-    private static void buildAttachArgs(ArrayList<String> newArgs, String pid,
-                                  String exe, String core, String remote, boolean allowEmpty) {
-        if (!allowEmpty && (pid == null) && (exe == null) && (remote == NO_REMOTE)) {
+    private static String[] buildAttachArgs(Map<String, String> newArgMap,
+                                            boolean allowEmpty) {
+        String pid = newArgMap.remove("pid");
+        String exe = newArgMap.remove("exe");
+        String core = newArgMap.remove("core");
+        String connect = newArgMap.remove("connect");
+        if (!allowEmpty && (pid == null) && (exe == null) && (connect == NO_REMOTE)) {
             throw new SAGetoptException("You have to set --pid or --exe or --connect.");
         }
 
+        List<String> newArgs = new ArrayList<>();
+        for (var entry : newArgMap.entrySet()) {
+            newArgs.add(entry.getKey());
+            if (entry.getValue() != null) {
+                newArgs.add(entry.getValue());
+            }
+        }
+
         if (pid != null) { // Attach to live process
             if (exe != null) {
                 throw new SAGetoptException("Unnecessary argument: --exe");
             } else if (core != null) {
                 throw new SAGetoptException("Unnecessary argument: --core");
-            } else if (remote != NO_REMOTE) {
+            } else if (connect != NO_REMOTE) {
                 throw new SAGetoptException("Unnecessary argument: --connect");
             } else if (!pid.matches("^\\d+$")) {
                 throw new SAGetoptException("Invalid pid: " + pid);
@@ -177,7 +189,7 @@
 
             newArgs.add(pid);
         } else if (exe != null) {
-            if (remote != NO_REMOTE) {
+            if (connect != NO_REMOTE) {
                 throw new SAGetoptException("Unnecessary argument: --connect");
             } else if (exe.length() == 0) {
                 throw new SAGetoptException("You have to set --exe.");
@@ -190,264 +202,144 @@
             }
 
             newArgs.add(core);
-        } else if (remote != NO_REMOTE) {
-            newArgs.add(remote);
+        } else if (connect != NO_REMOTE) {
+            newArgs.add(connect);
         }
+
+        return newArgs.toArray(new String[0]);
+    }
+
+    /**
+     * This method converts jhsdb-style options (oldArgs) to old fashioned
+     * style. SALauncher delegates the work to the entry point of each tool.
+     * Thus we need to convert the arguments.
+     * For example, `jhsdb jstack --mixed` needs to be converted to `jstack -m`.
+     *
+     * longOptsMap holds the rule how this method should convert the args.
+     * The key is the name of jhsdb style, the value is the name of
+     * old fashioned style. If you want to convert mixed option in jstack,
+     * you need to set "mixed" to the key, and to set "-m" to the value
+     * in longOptsMap. If the option have the value, you need to add "=" to
+     * the key like "exe=".
+     *
+     * You also can set the options which cannot be mapped to old fashioned
+     * arguments. For example, `jhsdb jmap --binaryheap` cannot be mapped to
+     * `jmap` option directly. But you set it to longOptsMap, then you can know
+     * the user sets "binaryheap" option, and SALauncher should set
+     * "-heap:format:b" to jmap option.
+     *
+     * This method returns the map of the old fashioned key/val pairs.
+     * It can be used to build args in string array at buildAttachArgs().
+     */
+    private static Map<String, String> parseOptions(String[] oldArgs,
+                                                    Map<String, String> longOptsMap) {
+        SAGetopt sg = new SAGetopt(oldArgs);
+        String[] longOpts = longOptsMap.keySet().toArray(new String[0]);
+        Map<String, String> newArgMap = new HashMap<>();
+
+        /*
+         * Parse each jhsdb-style option via SAGetopt.
+         * SAGetopt parses and validates the argument. If the user passes invalid
+         * option, SAGetoptException will be occurred at SAGetopt::next.
+         * Thus there is no need to validate it here.
+         *
+         * We can get option value via SAGetopt::get. If jhsdb-style option has
+         * '=' at the tail, we put old fashioned option with it to newArgMap.
+         */
+        String s;
+        while ((s = sg.next(null, longOpts)) != null) {
+            var val = longOptsMap.get(s);
+            if (val != null) {
+                newArgMap.put(val, null);
+            } else {
+                val = longOptsMap.get(s + "=");
+                if (val != null) {
+                    newArgMap.put(val, sg.getOptarg());
+                }
+            }
+        }
+
+        return newArgMap;
     }
 
     private static void runCLHSDB(String[] oldArgs) {
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid="};
-
-        ArrayList<String> newArgs = new ArrayList();
-        String pid = null;
-        String exe = null;
-        String core = null;
-        String s = null;
-
-        while((s = sg.next(null, longOpts)) != null) {
-            if (s.equals("exe")) {
-                exe = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("core")) {
-                core = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("pid")) {
-                pid = sg.getOptarg();
-                continue;
-            }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, true);
-        CLHSDB.main(newArgs.toArray(new String[newArgs.size()]));
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
+        CLHSDB.main(buildAttachArgs(newArgMap, true));
     }
 
     private static void runHSDB(String[] oldArgs) {
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid="};
-
-        ArrayList<String> newArgs = new ArrayList();
-        String pid = null;
-        String exe = null;
-        String core = null;
-        String s = null;
-
-        while((s = sg.next(null, longOpts)) != null) {
-            if (s.equals("exe")) {
-                exe = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("core")) {
-                core = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("pid")) {
-                pid = sg.getOptarg();
-                continue;
-            }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, true);
-        HSDB.main(newArgs.toArray(new String[newArgs.size()]));
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
+        HSDB.main(buildAttachArgs(newArgMap, true));
     }
 
     private static void runJSTACK(String[] oldArgs) {
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid=", "connect=",
-                                 "mixed", "locks"};
-
-        ArrayList<String> newArgs = new ArrayList();
-        String pid = null;
-        String exe = null;
-        String core = null;
-        String remote = NO_REMOTE;
-        String s = null;
-
-        while((s = sg.next(null, longOpts)) != null) {
-            if (s.equals("exe")) {
-                exe = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("core")) {
-                core = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("pid")) {
-                pid = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("connect")) {
-                remote = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("mixed")) {
-                newArgs.add("-m");
-                continue;
-            }
-            if (s.equals("locks")) {
-                newArgs.add("-l");
-                continue;
-            }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, remote, false);
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid",
+                                                 "connect=", "connect",
+                                                 "mixed", "-m",
+                                                 "locks", "-l");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
         JStack jstack = new JStack(false, false);
-        jstack.runWithArgs(newArgs.toArray(new String[newArgs.size()]));
+        jstack.runWithArgs(buildAttachArgs(newArgMap, false));
     }
 
     private static void runJMAP(String[] oldArgs) {
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid=", "connect=",
-              "heap", "binaryheap", "dumpfile=", "histo", "clstats", "finalizerinfo"};
-
-        ArrayList<String> newArgs = new ArrayList();
-        String pid = null;
-        String exe = null;
-        String core = null;
-        String remote = NO_REMOTE;
-        String s = null;
-        String dumpfile = null;
-        boolean requestHeapdump = false;
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid",
+                                                 "connect=", "connect",
+                                                 "heap", "-heap",
+                                                 "binaryheap", "binaryheap",
+                                                 "dumpfile=", "dumpfile",
+                                                 "histo", "-histo",
+                                                 "clstats", "-clstats",
+                                                 "finalizerinfo", "-finalizerinfo");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
 
-        while((s = sg.next(null, longOpts)) != null) {
-            if (s.equals("exe")) {
-                exe = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("core")) {
-                core = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("pid")) {
-                pid = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("connect")) {
-                remote = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("heap")) {
-                newArgs.add("-heap");
-                continue;
-            }
-            if (s.equals("binaryheap")) {
-                requestHeapdump = true;
-                continue;
-            }
-            if (s.equals("dumpfile")) {
-                dumpfile = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("histo")) {
-                newArgs.add("-histo");
-                continue;
-            }
-            if (s.equals("clstats")) {
-                newArgs.add("-clstats");
-                continue;
-            }
-            if (s.equals("finalizerinfo")) {
-                newArgs.add("-finalizerinfo");
-                continue;
+        boolean requestHeapdump = newArgMap.containsKey("binaryheap");
+        String dumpfile = newArgMap.get("dumpfile");
+        if (!requestHeapdump && (dumpfile != null)) {
+            throw new IllegalArgumentException("Unexpected argument: dumpfile");
+        }
+        if (requestHeapdump) {
+            if (dumpfile == null) {
+                newArgMap.put("-heap:format=b", null);
+            } else {
+                newArgMap.put("-heap:format=b,file=" + dumpfile, null);
             }
         }
 
-        if (!requestHeapdump && (dumpfile != null)) {
-            throw new IllegalArgumentException("Unexpected argument dumpfile");
-        }
-        if (requestHeapdump) {
-            if (dumpfile == null) {
-                newArgs.add("-heap:format=b");
-            } else {
-                newArgs.add("-heap:format=b,file=" + dumpfile);
-            }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, remote, false);
-        JMap.main(newArgs.toArray(new String[newArgs.size()]));
+        newArgMap.remove("binaryheap");
+        newArgMap.remove("dumpfile");
+        JMap.main(buildAttachArgs(newArgMap, false));
     }
 
     private static void runJINFO(String[] oldArgs) {
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid=", "connect=",
-                                     "flags", "sysprops"};
-
-        ArrayList<String> newArgs = new ArrayList();
-        String exe = null;
-        String pid = null;
-        String core = null;
-        String remote = NO_REMOTE;
-        String s = null;
-
-        while((s = sg.next(null, longOpts)) != null) {
-            if (s.equals("exe")) {
-                exe = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("core")) {
-                core = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("pid")) {
-                pid = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("connect")) {
-                remote = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("flags")) {
-                newArgs.add("-flags");
-                continue;
-            }
-            if (s.equals("sysprops")) {
-                newArgs.add("-sysprops");
-                continue;
-            }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, remote, false);
-        JInfo.main(newArgs.toArray(new String[newArgs.size()]));
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid",
+                                                 "connect=", "connect",
+                                                 "flags", "-flags",
+                                                 "sysprops", "-sysprops");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
+        JInfo.main(buildAttachArgs(newArgMap, false));
     }
 
     private static void runJSNAP(String[] oldArgs) {
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid=", "connect=", "all"};
-
-        ArrayList<String> newArgs = new ArrayList();
-        String exe = null;
-        String pid = null;
-        String core = null;
-        String remote = NO_REMOTE;
-        String s = null;
-
-        while((s = sg.next(null, longOpts)) != null) {
-            if (s.equals("exe")) {
-                exe = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("core")) {
-                core = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("pid")) {
-                pid = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("connect")) {
-                remote = sg.getOptarg();
-                continue;
-            }
-            if (s.equals("all")) {
-                newArgs.add("-a");
-                continue;
-            }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, remote, false);
-        JSnap.main(newArgs.toArray(new String[newArgs.size()]));
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid",
+                                                 "connect=", "connect",
+                                                 "all", "-a");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
+        JSnap.main(buildAttachArgs(newArgMap, false));
     }
 
     private static void runDEBUGD(String[] oldArgs) {
@@ -457,44 +349,34 @@
         // attaching to SA agent.
         System.setProperty("sun.jvm.hotspot.debugger.useWindbgDebugger", "true");
 
-        SAGetopt sg = new SAGetopt(oldArgs);
-        String[] longOpts = {"exe=", "core=", "pid=", "serverid="};
-
-        ArrayList<String> newArgs = new ArrayList<>();
-        String exe = null;
-        String pid = null;
-        String core = null;
-        String s = null;
-        String serverid = null;
+        Map<String, String> longOptsMap = Map.of("exe=", "exe",
+                                                 "core=", "core",
+                                                 "pid=", "pid",
+                                                 "serverid=", "serverid");
+        Map<String, String> newArgMap = parseOptions(oldArgs, longOptsMap);
+        var serverid = newArgMap.remove("serverid");
+        List<String> newArgArray = new ArrayList<>();
+        newArgArray.addAll(Arrays.asList(buildAttachArgs(newArgMap, false)));
 
-        while((s = sg.next(null, longOpts)) != null) {
-          if (s.equals("exe")) {
-              exe = sg.getOptarg();
-              continue;
-          }
-          if (s.equals("core")) {
-              core = sg.getOptarg();
-              continue;
-          }
-          if (s.equals("pid")) {
-              pid = sg.getOptarg();
-              continue;
-          }
-          if (s.equals("serverid")) {
-              serverid = sg.getOptarg();
-              continue;
-          }
-        }
-
-        buildAttachArgs(newArgs, pid, exe, core, NO_REMOTE, false);
+        // `serverid` must be located at the tail.
         if (serverid != null) {
-            newArgs.add(serverid);
+            newArgArray.add(serverid);
         }
 
         // delegate to the actual SA debug server.
-        sun.jvm.hotspot.DebugServer.main(newArgs.toArray(new String[newArgs.size()]));
+        DebugServer.main(newArgArray.toArray(new String[0]));
     }
 
+    // Key: tool name, Value: launcher method
+    private static Map<String, Consumer<String[]>> toolMap =
+        Map.of("clhsdb", SALauncher::runCLHSDB,
+               "hsdb", SALauncher::runHSDB,
+               "jstack", SALauncher::runJSTACK,
+               "jmap", SALauncher::runJMAP,
+               "jinfo", SALauncher::runJINFO,
+               "jsnap", SALauncher::runJSNAP,
+               "debugd", SALauncher::runDEBUGD);
+
     public static void main(String[] args) {
         // Provide a help
         if (args.length == 0) {
@@ -517,44 +399,12 @@
         String[] oldArgs = Arrays.copyOfRange(args, 1, args.length);
 
         try {
-            // Run SA interactive mode
-            if (args[0].equals("clhsdb")) {
-                runCLHSDB(oldArgs);
-                return;
-            }
-
-            if (args[0].equals("hsdb")) {
-                runHSDB(oldArgs);
-                return;
-            }
-
-            // Run SA tmtools mode
-            if (args[0].equals("jstack")) {
-                runJSTACK(oldArgs);
-                return;
+            var func = toolMap.get(args[0]);
+            if (func == null) {
+                throw new SAGetoptException("Unknown tool: " + args[0]);
+            } else {
+                func.accept(oldArgs);
             }
-
-            if (args[0].equals("jmap")) {
-                runJMAP(oldArgs);
-                return;
-            }
-
-            if (args[0].equals("jinfo")) {
-                runJINFO(oldArgs);
-                return;
-            }
-
-            if (args[0].equals("jsnap")) {
-                runJSNAP(oldArgs);
-                return;
-            }
-
-            if (args[0].equals("debugd")) {
-                runDEBUGD(oldArgs);
-                return;
-            }
-
-            throw new SAGetoptException("Unknown tool: " + args[0]);
         } catch (SAGetoptException e) {
             System.err.println(e.getMessage());
             toolHelp(args[0]);