hudi-agent commented on code in PR #19970:
URL: https://github.com/apache/hudi/pull/19970#discussion_r4140103681


##########
hudi-cli/src/main/java/org/apache/hudi/cli/commands/SparkMain.java:
##########
@@ -139,8 +173,128 @@ public static void addAppArgs(SparkLauncher 
sparkLauncher, SparkMain.SparkComman
     sparkLauncher.addAppArgs(args);
   }
 
-  public static void main(String[] args) {
-    ValidationUtils.checkArgument(args.length >= 4);
+  /**
+   * Adds the command invocation as fully named options: {@code --command X 
--master Y --memory Z}
+   * plus {@code -Dkey=value} pairs for every command argument. This makes the 
launcher command
+   * self-describing and removes the risk of mis-aligned positional arguments 
at call sites.
+   *
+   * @param namedArgs flat name/value pairs for the command's declared 
arguments. Every name
+   *                  except propsFilePath must be supplied; explicit null 
values become empty
+   *                  strings, preserving the positional slots for optional 
values.
+   */
+  public static void addNamedAppArgs(SparkLauncher sparkLauncher, 
SparkMain.SparkCommand cmd, String master, String memory, String... namedArgs) {
+    ValidationUtils.checkArgument(namedArgs.length % 2 == 0, "namedArgs must 
be flat name/value pairs");
+    Map<String, String> params = new LinkedHashMap<>();
+    for (int i = 0; i < namedArgs.length; i += 2) {
+      ValidationUtils.checkArgument(!params.containsKey(namedArgs[i]), 
"Duplicate command argument: " + namedArgs[i]);
+      params.put(namedArgs[i], namedArgs[i + 1] == null ? "" : namedArgs[i + 
1]);
+    }
+    validateNamedParams(cmd, params);
+    sparkLauncher.addAppArgs("--command", cmd.toString());
+    sparkLauncher.addAppArgs("--master", master);
+    sparkLauncher.addAppArgs("--memory", memory);
+    params.forEach((name, value) -> sparkLauncher.addAppArgs("-D" + name + "=" 
+ value));
+  }
+
+  private static void validateNamedParams(SparkCommand command, Map<String, 
String> params) {
+    ValidationUtils.checkArgument(command.paramNames.length > 0, "Named 
arguments are not supported for " + command);
+    List<String> paramNames = Arrays.asList(command.paramNames);
+    for (String name : params.keySet()) {
+      ValidationUtils.checkArgument(paramNames.contains(name), "Unknown 
argument for " + command + ": " + name
+          + ". Pass Spark/Hoodie configs as trailing key=value arguments 
without -D.");
+    }
+    for (String name : paramNames) {
+      ValidationUtils.checkArgument(params.containsKey(name) || 
"propsFilePath".equals(name),
+          "Missing argument for " + command + ": " + name);
+    }
+  }
+
+  private static boolean isNamedInvocation(String[] argv) {
+    return argv.length > 0 && argv[0].startsWith("-");
+  }
+
+  /**
+   * Translates {@code --command X --master Y --memory Z -Dkey=value...} into 
the positional
+   * form {@code [X, Y, Z, arg1, arg2, ...]} expected by the legacy invocation 
path. The named
+   * -D arguments must match the command's positional argument names. 
Spark/Hoodie configs
+   * are supplied as trailing {@code key=value} arguments.
+   */
+  @VisibleForTesting
+  static String[] namedArgsToPositional(String[] argv) {
+    NamedArgs namedArgs = parseNamedArgs(argv);
+    SparkCommand command = SparkCommand.valueOf(namedArgs.command);
+    validateNamedParams(command, namedArgs.params);
+    List<String> positional = new ArrayList<>();
+    positional.add(namedArgs.command);
+    positional.add(namedArgs.master);
+    positional.add(namedArgs.memory);
+    for (String name : command.paramNames) {
+      positional.add(namedArgs.params.getOrDefault(name, ""));
+    }
+    positional.addAll(namedArgs.trailingConfigs);
+    return positional.toArray(new String[0]);
+  }
+
+  private static NamedArgs parseNamedArgs(String[] argv) {
+    NamedArgs namedArgs = new NamedArgs();
+    List<String> options = new ArrayList<>();
+    for (int i = 0; i < argv.length; i++) {
+      String arg = argv[i];
+      if ("--".equals(arg)) {
+        namedArgs.trailingConfigs.addAll(Arrays.asList(argv).subList(i + 1, 
argv.length));
+        break;
+      } else if (arg.startsWith("-")) {
+        options.add(arg);
+        if (Arrays.asList("--command", "-command", "--master", "-master", 
"--memory", "-memory", "-D").contains(arg)) {

Review Comment:
   🤖 nit: this inline list repeats the option names from the `@Parameter` 
annotations on `NamedArgs`. Could you move it into a `private static final 
Set<String>` constant that sits next to `NamedArgs`, so a new or renamed option 
only has to be updated in one place?
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-cli/src/main/java/org/apache/hudi/cli/commands/SparkMain.java:
##########
@@ -139,8 +173,128 @@ public static void addAppArgs(SparkLauncher 
sparkLauncher, SparkMain.SparkComman
     sparkLauncher.addAppArgs(args);
   }
 
-  public static void main(String[] args) {
-    ValidationUtils.checkArgument(args.length >= 4);
+  /**
+   * Adds the command invocation as fully named options: {@code --command X 
--master Y --memory Z}
+   * plus {@code -Dkey=value} pairs for every command argument. This makes the 
launcher command
+   * self-describing and removes the risk of mis-aligned positional arguments 
at call sites.
+   *
+   * @param namedArgs flat name/value pairs for the command's declared 
arguments. Every name
+   *                  except propsFilePath must be supplied; explicit null 
values become empty
+   *                  strings, preserving the positional slots for optional 
values.
+   */
+  public static void addNamedAppArgs(SparkLauncher sparkLauncher, 
SparkMain.SparkCommand cmd, String master, String memory, String... namedArgs) {
+    ValidationUtils.checkArgument(namedArgs.length % 2 == 0, "namedArgs must 
be flat name/value pairs");
+    Map<String, String> params = new LinkedHashMap<>();
+    for (int i = 0; i < namedArgs.length; i += 2) {
+      ValidationUtils.checkArgument(!params.containsKey(namedArgs[i]), 
"Duplicate command argument: " + namedArgs[i]);
+      params.put(namedArgs[i], namedArgs[i + 1] == null ? "" : namedArgs[i + 
1]);
+    }
+    validateNamedParams(cmd, params);
+    sparkLauncher.addAppArgs("--command", cmd.toString());
+    sparkLauncher.addAppArgs("--master", master);
+    sparkLauncher.addAppArgs("--memory", memory);
+    params.forEach((name, value) -> sparkLauncher.addAppArgs("-D" + name + "=" 
+ value));
+  }
+
+  private static void validateNamedParams(SparkCommand command, Map<String, 
String> params) {
+    ValidationUtils.checkArgument(command.paramNames.length > 0, "Named 
arguments are not supported for " + command);
+    List<String> paramNames = Arrays.asList(command.paramNames);
+    for (String name : params.keySet()) {
+      ValidationUtils.checkArgument(paramNames.contains(name), "Unknown 
argument for " + command + ": " + name
+          + ". Pass Spark/Hoodie configs as trailing key=value arguments 
without -D.");
+    }
+    for (String name : paramNames) {
+      ValidationUtils.checkArgument(params.containsKey(name) || 
"propsFilePath".equals(name),

Review Comment:
   🤖 nit: `"propsFilePath"` is a magic string that quietly makes one argument 
optional. Could you pull it into a named constant like 
`OPTIONAL_PROPS_FILE_PATH_ARG`, or add a short comment explaining why it's the 
only optional argument?
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to