diff --git a/joylive-bootstrap/joylive-bootstrap-premain/pom.xml b/joylive-bootstrap/joylive-bootstrap-premain/pom.xml
index 81955ba51..ec0fff5d6 100644
--- a/joylive-bootstrap/joylive-bootstrap-premain/pom.xml
+++ b/joylive-bootstrap/joylive-bootstrap-premain/pom.xml
@@ -13,6 +13,7 @@
live
+ 1.82
@@ -20,6 +21,11 @@
com.jd.live
joylive-bootstrap-api
+
+ com.beust
+ jcommander
+ ${jcommander.version}
+
@@ -47,6 +53,40 @@
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+
+
+
+ com.beust:jcommander
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+ com.beust
+ com.jd.live.agent.shaded.com.beust
+
+
+
+
+
+
diff --git a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/AgentLoader.java b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/AgentLoader.java
index 4071c286b..711ee370f 100644
--- a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/AgentLoader.java
+++ b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/AgentLoader.java
@@ -1,66 +1,119 @@
package com.jd.live.agent.bootstrap;
-import com.sun.tools.attach.AgentInitializationException;
-import com.sun.tools.attach.AgentLoadException;
-import com.sun.tools.attach.AttachNotSupportedException;
-import com.sun.tools.attach.VirtualMachine;
-import com.sun.tools.attach.VirtualMachineDescriptor;
+import com.jd.live.agent.bootstrap.option.AgentOption;
+import com.jd.live.agent.bootstrap.option.OptionParser;
+import com.sun.tools.attach.*;
import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import static com.jd.live.agent.bootstrap.LivePath.LIVE_JAR;
+
+/**
+ * The {@code AgentLoader} class provides functionality to attach a Java agent to a running JVM.
+ * It parses command-line options, identifies the target JVM, and loads the agent into it.
+ */
public class AgentLoader {
+
private AgentLoader() {
}
- public static void main(String[] args)
- throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
- List vmDescriptors = VirtualMachine.list();
-
- if (vmDescriptors.isEmpty()) {
- System.out.println("No Java process found!");
- return;
+ /**
+ * The main method to start the agent loader. It parses the command-line arguments,
+ * finds the target JVM, and loads the specified agent into it.
+ *
+ * @param args the command-line arguments
+ * @throws IOException if an I/O error occurs
+ * @throws AttachNotSupportedException if the target JVM does not support attaching
+ * @throws AgentLoadException if the agent cannot be loaded
+ * @throws AgentInitializationException if the agent initialization fails
+ */
+ public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
+ AgentOption option = OptionParser.parse(args);
+ if (option != null) {
+ VirtualMachineDescriptor descriptor = getVmDescriptor(option);
+ if (descriptor != null) {
+ File path = getPath(option);
+ if (path != null) {
+ option.setAgentPath(path.getAbsolutePath());
+ VirtualMachine vm = VirtualMachine.attach(descriptor);
+ // Launch Agent
+ vm.loadAgent(new File(path, LIVE_JAR).getPath(), option.getAgentArgs());
+ vm.detach();
+ }
+ }
}
+ }
- System.out.println("Select the Java process that you want to use the agent.");
- for (int i = 0; i < vmDescriptors.size(); i++) {
- VirtualMachineDescriptor descriptor = vmDescriptors.get(i);
- System.out.println(i + ": " + descriptor.id() + " " + descriptor.displayName());
+ /**
+ * Retrieves the path to the agent directory. If the path is not provided or is invalid,
+ * it prompts the user to enter a valid path interactively.
+ *
+ * @param option the agent options
+ * @return the valid agent directory path
+ * @throws IOException if an I/O error occurs while reading input
+ */
+ private static File getPath(AgentOption option) throws IOException {
+ String path = option.getAgentPath();
+ File file = path == null || path.isEmpty() ? LivePath.getRootPath(System.getenv(), null) : new File(path);
+ long counter = 0;
+ while (file == null || !option.isValidPath(file)) {
+ counter++;
+ if (option.isInteractive()) {
+ System.out.print(counter == 1
+ ? "Enter agent directory (the live.jar in this directory is used as the entry by default):"
+ : "Enter agent directory:");
+ path = new BufferedReader(new InputStreamReader(System.in)).readLine();
+ file = path == null || path.isEmpty() ? null : new File(path.trim());
+ } else {
+ System.out.println("The agent directory is invalid. path=" + file);
+ return null;
+ }
}
+ return file;
+ }
- // Read the sequence number entered by the user
- BufferedReader userInputReader = new BufferedReader(new InputStreamReader(System.in));
- System.out.print("Please enter the Java program number to be used by the agent:");
- int selectedProcessIndex = Integer.parseInt(userInputReader.readLine());
-
- if (selectedProcessIndex < 0 || selectedProcessIndex >= vmDescriptors.size()) {
- System.out.println("Invalid program number!");
- return;
+ /**
+ * Retrieves the descriptor of the target JVM. If the process ID is not provided or is invalid,
+ * it prompts the user to select a valid JVM process interactively.
+ *
+ * @param option the agent options
+ * @return the descriptor of the target JVM
+ * @throws IOException if an I/O error occurs while reading input
+ */
+ private static VirtualMachineDescriptor getVmDescriptor(AgentOption option) throws IOException {
+ String pid = AgentOption.getPid();
+ Map descriptors = VirtualMachine.list().stream()
+ .filter(v -> !v.id().equals(pid))
+ .collect(Collectors.toMap(VirtualMachineDescriptor::id, v -> v));
+ String jvmId = option.getProcessId();
+ if (!descriptors.isEmpty() && option.isInteractive()) {
+ long counter = 0;
+ while (jvmId == null || jvmId.isEmpty() || !descriptors.containsKey(jvmId)) {
+ counter++;
+ if (counter == 1) {
+ System.out.println("Select the java process id to be attached.");
+ for (VirtualMachineDescriptor vm : descriptors.values()) {
+ System.out.println(vm.id() + " " + vm.displayName());
+ }
+ }
+ System.out.print("Please enter the pid:");
+ // Read the jvm id entered by the user
+ jvmId = new BufferedReader(new InputStreamReader(System.in)).readLine();
+ jvmId = jvmId == null || jvmId.isEmpty() ? null : jvmId.trim();
+ }
}
-
- // Connect to the selected virtual machine
- VirtualMachineDescriptor selectedDescriptor = vmDescriptors.get(selectedProcessIndex);
- System.out.println("The process ID you selected is:" + selectedDescriptor.id());
-
- VirtualMachine vm = VirtualMachine.attach(selectedDescriptor);
-
- // Obtain the agent directory
- System.out.print("Enter the directory where the agent is located (the live.jar in this directory is used as the entry by default):");
- String agentPath = userInputReader.readLine();
-
- // Obtain the parameters of the incoming agent
- System.out.print("Please enter the parameters passed to the agent (can be empty, the default parameter is agentPath):");
- String agentArgs = "agentPath=" + agentPath + "," + userInputReader.readLine();
- userInputReader.close();
-
- try {
- // Launch Agent
- vm.loadAgent(agentPath + "/live.jar", agentArgs);
- vm.detach();
- } catch (Exception e) {
- e.printStackTrace();
+ VirtualMachineDescriptor descriptor = jvmId == null || jvmId.isEmpty()
+ ? null : descriptors.get(jvmId);
+ if (descriptor == null) {
+ System.out.println("The java process is not found. pid=" + jvmId);
+ return null;
}
+
+ return descriptor;
}
}
\ No newline at end of file
diff --git a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LiveAgent.java b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LiveAgent.java
index 20442f025..e10085ace 100644
--- a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LiveAgent.java
+++ b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LiveAgent.java
@@ -29,8 +29,6 @@
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
-import java.security.CodeSource;
-import java.security.ProtectionDomain;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@@ -51,26 +49,6 @@
public class LiveAgent {
private static final Logger logger = Logger.getLogger(LiveAgent.class.getName());
- public static final String KEY_AGENT_PATH = "LIVE_AGENT_ROOT";
-
- public static final String ARG_AGENT_PATH = "agentPath";
-
- private static final String DIR_LIB = "lib";
-
- private static final String DIR_LIB_SYSTEM = "system";
-
- private static final String DIR_LIB_CORE = "core";
-
- private static final String DIR_CONFIG = "config";
-
- private static final String BOOTSTRAP_METHOD_INSTALL = "install";
-
- private static final String BOOTSTRAP_METHOD_EXECUTE = "execute";
-
- private static final String BOOTSTRAP_CLASS = "com.jd.live.agent.core.bootstrap.Bootstrap";
-
- private static final String ARG_COMMAND = "command";
-
private static final int STATUS_INITIAL = 0;
private static final int STATUS_SYSTEM_LIB = 1;
@@ -81,9 +59,13 @@ public class LiveAgent {
private static final int STATUS_INSTALL_FAILED = 4;
- private static final String JAR_FILE_PREFIX = "jar:file:";
+ private static final String BOOTSTRAP_CLASS = "com.jd.live.agent.core.bootstrap.Bootstrap";
+
+ private static final String BOOTSTRAP_METHOD_INSTALL = "install";
- private static final String LIVE_AGENT_PATH = "LiveAgent.path";
+ private static final String BOOTSTRAP_METHOD_EXECUTE = "execute";
+
+ private static final String ARG_COMMAND = "command";
private static final String BOOTSTRAP_PROPERTIES = "bootstrap.properties";
@@ -144,12 +126,16 @@ private static synchronized void launch(String arguments, Instrumentation instru
// Parse the arguments and environment to prepare for the agent setup.
Map args = createArgs(arguments);
Map env = createEnv();
- File root = getRootPath(env, args);
- File libDir = new File(root, DIR_LIB);
- File configDir = new File(root, DIR_CONFIG);
+ File root = LivePath.getRootPath(env, args);
+ if (root != null) {
+ // Update the environment with the determined agent path.
+ env.put(LivePath.KEY_AGENT_PATH, root.getPath());
+ }
+ File libDir = new File(root, LivePath.DIR_LIB);
+ File configDir = new File(root, LivePath.DIR_CONFIG);
Map bootstrapConfig = createBootstrapConfig(configDir);
- File[] systemLibs = getLibs(new File(libDir, DIR_LIB_SYSTEM));
- File[] coreLibs = getLibs(new File(libDir, DIR_LIB_CORE));
+ File[] systemLibs = getLibs(new File(libDir, LivePath.DIR_LIB_SYSTEM));
+ File[] coreLibs = getLibs(new File(libDir, LivePath.DIR_LIB_CORE));
URL[] coreLibUrls = getUrls(coreLibs);
String command = (String) args.get(ARG_COMMAND);
@@ -294,48 +280,6 @@ private static void addSystemPath(Instrumentation instrumentation, File[] files)
logger.addHandler(new LogHandler());
}
- /**
- * Determines the root path of the agent based on the provided arguments and environment.
- *
- * @param env A map containing the environment configuration.
- * @param args A map containing the agent's arguments.
- * @return A file representing the root path of the agent.
- */
- private static File getRootPath(Map env, Map args) {
- File result = null;
- String root = (String) args.get(ARG_AGENT_PATH);
- if (root == null || root.isEmpty()) {
- root = (String) env.get(KEY_AGENT_PATH);
- if (root == null || root.isEmpty()) {
- ProtectionDomain protectionDomain = LiveAgent.class.getProtectionDomain();
- CodeSource codeSource = protectionDomain == null ? null : protectionDomain.getCodeSource();
- if (codeSource != null) {
- String path = urlDecode(codeSource.getLocation().getPath());
- result = new File(path).getParentFile();
- } else {
- URL url = ClassLoader.getSystemClassLoader().getResource(LIVE_AGENT_PATH);
- if (url != null) {
- String path = url.toString();
- if (path.startsWith(JAR_FILE_PREFIX)) {
- int pos = path.lastIndexOf('/');
- int end = path.lastIndexOf('/', pos - 1);
- result = new File(urlDecode(path.substring(JAR_FILE_PREFIX.length(), end)));
- }
- }
- }
- } else {
- result = new File(root);
- }
- } else {
- result = new File(root);
- }
- if (result != null) {
- // Update the environment with the determined agent path.
- env.put(KEY_AGENT_PATH, result.getPath());
- }
- return result;
- }
-
/**
* Creates a class loader with specified URLs, configuration path, and a configuration function.
*
@@ -365,7 +309,7 @@ private static Map createArgs(String args) {
Map result = new HashMap<>();
if (args != null) {
// Split the input string into parts using the semicolon as a delimiter.
- String[] parts = args.trim().split(";");
+ String[] parts = args.trim().split("[;,]");
for (String arg : parts) {
// Find the index of the equal sign to separate key and value.
int index = arg.indexOf('=');
@@ -478,21 +422,6 @@ private static void close(URLClassLoader classLoader) {
}
}
- /**
- * Decodes a URL encoded string using UTF-8 encoding.
- *
- * @param value The string to be decoded.
- * @return The decoded string.
- */
- private static String urlDecode(String value) {
- try {
- return java.net.URLDecoder.decode(value, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- // Returns the original value if UTF-8 encoding is not supported.
- return value;
- }
- }
-
private static class LogHandler extends Handler {
final com.jd.live.agent.bootstrap.logger.Logger delegate = LoggerFactory.getLogger(LogHandler.class);
diff --git a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LivePath.java b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LivePath.java
new file mode 100644
index 000000000..7e01c4ca1
--- /dev/null
+++ b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/LivePath.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright © ${year} ${owner} (${email})
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jd.live.agent.bootstrap;
+
+import java.io.File;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.ProtectionDomain;
+import java.util.Map;
+
+public abstract class LivePath {
+
+ public static final String KEY_AGENT_PATH = "LIVE_AGENT_ROOT";
+
+ public static final String ARG_AGENT_PATH = "agentPath";
+
+ public static final String DIR_LIB = "lib";
+
+ public static final String DIR_LIB_SYSTEM = "system";
+
+ public static final String DIR_LIB_CORE = "core";
+
+ public static final String DIR_CONFIG = "config";
+
+ public static final String DIR_PLUGIN = "plugin";
+
+ public static final String JAR_FILE_PREFIX = "jar:file:";
+
+ public static final String LIVE_AGENT_PATH = "LiveAgent.path";
+
+ public static final String LIVE_JAR = "live.jar";
+
+ /**
+ * Determines the root path of the agent based on the provided arguments and environment.
+ *
+ * @param env A map containing the environment configuration.
+ * @param args A map containing the agent's arguments.
+ * @return A file representing the root path of the agent.
+ */
+ public static File getRootPath(Map env, Map args) {
+ File result = null;
+ String root = args == null ? null : (String) args.get(LivePath.ARG_AGENT_PATH);
+ if (root == null || root.isEmpty()) {
+ root = (String) env.get(LivePath.KEY_AGENT_PATH);
+ if (root == null || root.isEmpty()) {
+ ProtectionDomain protectionDomain = LiveAgent.class.getProtectionDomain();
+ CodeSource codeSource = protectionDomain == null ? null : protectionDomain.getCodeSource();
+ if (codeSource != null) {
+ String path = urlDecode(codeSource.getLocation().getPath());
+ result = new File(path).getParentFile();
+ } else {
+ URL url = ClassLoader.getSystemClassLoader().getResource(LivePath.LIVE_AGENT_PATH);
+ if (url != null) {
+ String path = url.toString();
+ if (path.startsWith(LivePath.JAR_FILE_PREFIX)) {
+ int pos = path.lastIndexOf('/');
+ int end = path.lastIndexOf('/', pos - 1);
+ result = new File(urlDecode(path.substring(LivePath.JAR_FILE_PREFIX.length(), end)));
+ }
+ }
+ }
+ } else {
+ result = new File(root);
+ }
+ } else {
+ result = new File(root);
+ }
+ return result;
+ }
+
+ /**
+ * Decodes a URL encoded string using UTF-8 encoding.
+ *
+ * @param value The string to be decoded.
+ * @return The decoded string.
+ */
+ private static String urlDecode(String value) {
+ try {
+ return java.net.URLDecoder.decode(value, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // Returns the original value if UTF-8 encoding is not supported.
+ return value;
+ }
+ }
+
+}
diff --git a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/option/AgentOption.java b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/option/AgentOption.java
new file mode 100644
index 000000000..a1357bd59
--- /dev/null
+++ b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/option/AgentOption.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright © ${year} ${owner} (${email})
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jd.live.agent.bootstrap.option;
+
+import com.beust.jcommander.DynamicParameter;
+import com.beust.jcommander.Parameter;
+import com.jd.live.agent.bootstrap.LivePath;
+
+import java.io.File;
+import java.lang.management.ManagementFactory;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The {@code AgentOption} class represents the command-line options for loading a Java agent.
+ * It includes options for specifying the target JVM process ID, the agent path, agent arguments, and other settings.
+ */
+public class AgentOption {
+
+ @Parameter(names = {"-p", "-pid"}, description = "The target JVM process ID")
+ private String processId;
+
+ @Parameter(names = {"-t", "-path"}, description = "The agent root path")
+ private String agentPath;
+
+ @DynamicParameter(names = {"-a", "-arg"}, description = "The agent argument")
+ private Map args;
+
+ @Parameter(names = {"-h", "-help"}, help = true, description = "The help information")
+ private boolean help;
+
+ @Parameter(names = {"-i", "-interactive"}, description = "The interactive mode")
+ private boolean interactive = true;
+
+ public String getProcessId() {
+ return processId;
+ }
+
+ public void setProcessId(String processId) {
+ this.processId = processId;
+ }
+
+ public String getAgentPath() {
+ return agentPath;
+ }
+
+ public void setAgentPath(String agentPath) {
+ this.agentPath = agentPath;
+ }
+
+ public Map getArgs() {
+ return args;
+ }
+
+ public void setArgs(Map args) {
+ this.args = args;
+ }
+
+ public void addArg(String key, String value) {
+ if (key != null && !key.isEmpty() && value != null && !value.isEmpty()) {
+ if (args == null) {
+ args = new HashMap<>();
+ }
+ args.put(key, value);
+ }
+ }
+
+ public boolean isHelp() {
+ return help;
+ }
+
+ public void setHelp(boolean help) {
+ this.help = help;
+ }
+
+ public boolean isInteractive() {
+ return interactive;
+ }
+
+ public void setInteractive(boolean interactive) {
+ this.interactive = interactive;
+ }
+
+ /**
+ * Constructs the agent arguments as a single string.
+ *
+ * @return the agent arguments string
+ */
+ public String getAgentArgs() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(LivePath.ARG_AGENT_PATH).append("=").append(agentPath);
+ if (args != null && !args.isEmpty()) {
+ args.forEach((k, v) -> sb.append(k).append("=").append(v).append(","));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Validates the agent path by checking if the necessary files and directories exist.
+ *
+ * @param root the root directory to validate
+ * @return {@code true} if the path is valid, {@code false} otherwise
+ */
+ public boolean isValidPath(File root) {
+ if (root == null || !root.exists() || !root.isDirectory()) {
+ return false;
+ }
+ File file = new File(root, LivePath.LIVE_JAR);
+ File libDir = new File(root, LivePath.DIR_LIB);
+ File configDir = new File(root, LivePath.DIR_CONFIG);
+ return file.exists() && libDir.exists() && configDir.exists() && libDir.isDirectory() && configDir.isDirectory();
+ }
+
+ /**
+ * Gets the current JVM process ID.
+ *
+ * @return the current process ID
+ */
+ public static String getPid() {
+ String name = ManagementFactory.getRuntimeMXBean().getName();
+ return name.split("@")[0];
+ }
+}
\ No newline at end of file
diff --git a/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/option/OptionParser.java b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/option/OptionParser.java
new file mode 100644
index 000000000..0bf429175
--- /dev/null
+++ b/joylive-bootstrap/joylive-bootstrap-premain/src/main/java/com/jd/live/agent/bootstrap/option/OptionParser.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright © ${year} ${owner} (${email})
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jd.live.agent.bootstrap.option;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.UnixStyleUsageFormatter;
+
+/**
+ * The {@code OptionParser} class provides a method to parse command-line arguments
+ * into an {@link AgentOption} object using the JCommander library.
+ */
+public class OptionParser {
+
+ /**
+ * Parses the given command-line arguments into an {@link AgentOption} object.
+ * If the help option is specified, it prints the usage information and returns {@code null}.
+ *
+ * @param args the command-line arguments to parse
+ * @return the parsed {@link AgentOption} object, or {@code null} if help is requested
+ */
+ public static AgentOption parse(String[] args) {
+ AgentOption option = new AgentOption();
+ JCommander commander = JCommander.newBuilder().addObject(option).build();
+ commander.setUsageFormatter(new UnixStyleUsageFormatter(commander));
+ commander.parse(args);
+ if (option.isHelp()) {
+ commander.usage();
+ return null;
+ }
+ return option;
+ }
+}
\ No newline at end of file