Repository: skiptests/astraea
Pull request #231 (2022-02-14 02:51:57)

Move all converter/validator to individual java file
避免我們養出一個肥大的utils


Patch:
diff --git a/app/src/main/java/org/astraea/argument/ArgumentUtil.java b/app/src/main/java/org/astraea/argument/ArgumentUtil.java
index ed162774..70a0a300 100644
--- a/app/src/main/java/org/astraea/argument/ArgumentUtil.java
+++ b/app/src/main/java/org/astraea/argument/ArgumentUtil.java
@@ -1,17 +1,8 @@
 package org.astraea.argument;
 
-import com.beust.jcommander.IParameterValidator;
-import com.beust.jcommander.IStringConverter;
 import com.beust.jcommander.JCommander;
 import com.beust.jcommander.ParameterException;
 import com.beust.jcommander.UnixStyleUsageFormatter;
-import java.time.Duration;
-import java.util.Collection;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /*
  * A tool used to parse command line arguments.
@@ -43,141 +34,4 @@ public class ArgumentUtil {
     }
     return toolArgument;
   }
-
-  /* Validate Classes */
-  public static class NotEmptyString implements IParameterValidator {
-    @Override
-    public void validate(String name, String value) throws ParameterException {
-      if (value == null || value.equals(""))
-        throw new ParameterException(name + " should not be empty.");
-    }
-  }
-
-  public static class PositiveLong implements IParameterValidator {
-    @Override
-    public void validate(String name, String value) throws ParameterException {
-      if (Long.parseLong(value) <= 0) throw new ParameterException(name + " should be positive.");
-    }
-  }
-
-  public static class NonNegativeLong implements IParameterValidator {
-    @Override
-    public void validate(String name, String value) throws ParameterException {
-      if (Long.parseLong(value) < 0)
-        throw new ParameterException(name + " should not be negative.");
-    }
-  }
-
-  /* Converter classes */
-  public static class StringSetConverter implements IStringConverter<Set<String>> {
-    @Override
-    public Set<String> convert(String value) {
-      return requireNonEmpty(Set.of(value.split(",")));
-    }
-  }
-
-  public static class IntegerSetConverter implements IStringConverter<Set<Integer>> {
-    @Override
-    public Set<Integer> convert(String value) {
-      return requireNonEmpty(
-          Stream.of(value.split(",")).map(Integer::valueOf).collect(Collectors.toSet()));
-    }
-  }
-
-  public static class BooleanConverter implements IStringConverter<Boolean> {
-    @Override
-    public Boolean convert(String value) {
-      return Boolean.valueOf(value);
-    }
-  }
-
-  public static class ShortConverter implements IStringConverter<Short> {
-    @Override
-    public Short convert(String value) {
-      return Short.valueOf(value);
-    }
-  }
-
-  /**
-   * A converter for time unit.
-   *
-   * <p>This converter is able to transform following time string into corresponding {@link
-   * Duration}.
-   *
-   * <ul>
-   *   <li>{@code "30s"} to {@code Duration.ofSeconds(30)}
-   *   <li>{@code "1m"} to {@code Duration.ofMinutes(1)}
-   *   <li>{@code "24h"} to {@code Duration.ofHours(24)}
-   *   <li>{@code "7day"} to {@code Duration.ofDays(7)}
-   *   <li>{@code "7days"} to {@code Duration.ofDays(7)}
-   *   <li>{@code "350ms"} to {@code Duration.ofMillis(350)}
-   *   <li>{@code "123us"} to {@code Duration.ofNanos(123 * 1000)}
-   *   <li>{@code "100ns"} to {@code Duration.ofNanos(100)}
-   * </ul>
-   *
-   * If no unit specified, second unit will be used:
-   *
-   * <ul>
-   *   <li>{@code "1"} to {@code Duration.ofSeconds(1)}
-   *   <li>{@code "0"} to {@code Duration.ofSeconds(0)}
-   * </ul>
-   *
-   * Currently, negative time is not supported. So the following example doesn't work.
-   *
-   * <ul>
-   *   <li><b>(doesn't work)</b> {@code "-1" to {@code Duration.ofSeconds(-1)}}
-   * </ul>
-   *
-   * Currently, floating value time is not supported. So the following example doesn't work.
-   *
-   * <ul>
-   *   <li><b>(doesn't work)</b> {@code "0.5" to {@code Duration.ofMillis(500)}}
-   * </ul>
-   */
-  public static class DurationConverter implements IStringConverter<Duration>, IParameterValidator {
-
-    static final Pattern TIME_PATTERN =
-        Pattern.compile("^(?<value>[0-9]+)(?<unit>days|day|h|m|s|ms|us|ns|)$");
-
-    @Override
-    public Duration convert(String input) {
-      Matcher matcher = TIME_PATTERN.matcher(input);
-      if (matcher.find()) {
-        long value = Long.parseLong(matcher.group("value"));
-        String unit = matcher.group("unit");
-        switch (unit) {
-          case "days":
-          case "day":
-            return Duration.ofDays(value);
-          case "h":
-            return Duration.ofHours(value);
-          case "m":
-            return Duration.ofMinutes(value);
-          case "ms":
-            return Duration.ofMillis(value);
-          case "us":
-            return Duration.ofNanos(value * 1000);
-          case "ns":
-            return Duration.ofNanos(value);
-          case "s":
-          default:
-            return Duration.ofSeconds(value);
-        }
-      } else {
-        throw new IllegalArgumentException("value \"" + input + "\" doesn't match any time format");
-      }
-    }
-
-    @Override
-    public void validate(String name, String value) throws ParameterException {
-      if (!TIME_PATTERN.matcher(value).find())
-        throw new ParameterException(
-            "field \"" + name + "\"'s value \"" + value + "\" doesn't match time format");
-    }
-  }
-
-  private static <C extends Collection<T>, T> C requireNonEmpty(C collection) {
-    if (collection.isEmpty()) throw new ParameterException("array type can't be empty");
-    return collection;
-  }
 }
diff --git a/app/src/main/java/org/astraea/argument/BasicArgument.java b/app/src/main/java/org/astraea/argument/BasicArgument.java
index b16ad2d0..5d1c9c8d 100644
--- a/app/src/main/java/org/astraea/argument/BasicArgument.java
+++ b/app/src/main/java/org/astraea/argument/BasicArgument.java
@@ -6,13 +6,14 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.stream.Collectors;
 import org.apache.kafka.clients.CommonClientConfigs;
+import org.astraea.argument.validator.NotEmptyString;
 
 /** This basic argument defines the common property used by all kafka clients. */
 public abstract class BasicArgument {
   @Parameter(
       names = {"--bootstrap.servers"},
       description = "String: server to connect to",
-      validateWith = ArgumentUtil.NotEmptyString.class,
+      validateWith = NotEmptyString.class,
       required = true)
   public String brokers;
 
diff --git a/app/src/main/java/org/astraea/argument/BasicArgumentWithPropFile.java b/app/src/main/java/org/astraea/argument/BasicArgumentWithPropFile.java
index d7f7ae9d..4414fb48 100644
--- a/app/src/main/java/org/astraea/argument/BasicArgumentWithPropFile.java
+++ b/app/src/main/java/org/astraea/argument/BasicArgumentWithPropFile.java
@@ -2,12 +2,13 @@ package org.astraea.argument;
 
 import com.beust.jcommander.Parameter;
 import java.util.Map;
+import org.astraea.argument.validator.NotEmptyString;
 
 public abstract class BasicArgumentWithPropFile extends BasicArgument {
   @Parameter(
       names = {"--prop.file"},
       description = "the file path containing the properties to be passed to kafka admin",
-      validateWith = ArgumentUtil.NotEmptyString.class)
+      validateWith = NotEmptyString.class)
   public String propFile;
 
   /**
diff --git a/app/src/main/java/org/astraea/argument/converter/DurationConverter.java b/app/src/main/java/org/astraea/argument/converter/DurationConverter.java
new file mode 100644
index 00000000..3afdbd40
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/converter/DurationConverter.java
@@ -0,0 +1,85 @@
+package org.astraea.argument.converter;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.ParameterException;
+import java.time.Duration;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A converter for time unit.
+ *
+ * <p>This converter is able to transform following time string into corresponding {@link Duration}.
+ *
+ * <ul>
+ *   <li>{@code "30s"} to {@code Duration.ofSeconds(30)}
+ *   <li>{@code "1m"} to {@code Duration.ofMinutes(1)}
+ *   <li>{@code "24h"} to {@code Duration.ofHours(24)}
+ *   <li>{@code "7day"} to {@code Duration.ofDays(7)}
+ *   <li>{@code "7days"} to {@code Duration.ofDays(7)}
+ *   <li>{@code "350ms"} to {@code Duration.ofMillis(350)}
+ *   <li>{@code "123us"} to {@code Duration.ofNanos(123 * 1000)}
+ *   <li>{@code "100ns"} to {@code Duration.ofNanos(100)}
+ * </ul>
+ *
+ * If no unit specified, second unit will be used:
+ *
+ * <ul>
+ *   <li>{@code "1"} to {@code Duration.ofSeconds(1)}
+ *   <li>{@code "0"} to {@code Duration.ofSeconds(0)}
+ * </ul>
+ *
+ * Currently, negative time is not supported. So the following example doesn't work.
+ *
+ * <ul>
+ *   <li><b>(doesn't work)</b> {@code "-1" to {@code Duration.ofSeconds(-1)}}
+ * </ul>
+ *
+ * Currently, floating value time is not supported. So the following example doesn't work.
+ *
+ * <ul>
+ *   <li><b>(doesn't work)</b> {@code "0.5" to {@code Duration.ofMillis(500)}}
+ * </ul>
+ */
+public class DurationConverter implements IStringConverter<Duration>, IParameterValidator {
+
+  static final Pattern TIME_PATTERN =
+      Pattern.compile("^(?<value>[0-9]+)(?<unit>days|day|h|m|s|ms|us|ns|)$");
+
+  @Override
+  public Duration convert(String input) {
+    Matcher matcher = TIME_PATTERN.matcher(input);
+    if (matcher.find()) {
+      long value = Long.parseLong(matcher.group("value"));
+      String unit = matcher.group("unit");
+      switch (unit) {
+        case "days":
+        case "day":
+          return Duration.ofDays(value);
+        case "h":
+          return Duration.ofHours(value);
+        case "m":
+          return Duration.ofMinutes(value);
+        case "ms":
+          return Duration.ofMillis(value);
+        case "us":
+          return Duration.ofNanos(value * 1000);
+        case "ns":
+          return Duration.ofNanos(value);
+        case "s":
+        default:
+          return Duration.ofSeconds(value);
+      }
+    } else {
+      throw new IllegalArgumentException("value \"" + input + "\" doesn't match any time format");
+    }
+  }
+
+  @Override
+  public void validate(String name, String value) throws ParameterException {
+    if (!TIME_PATTERN.matcher(value).find())
+      throw new ParameterException(
+          "field \"" + name + "\"'s value \"" + value + "\" doesn't match time format");
+  }
+}
diff --git a/app/src/main/java/org/astraea/argument/converter/IntegerSetConverter.java b/app/src/main/java/org/astraea/argument/converter/IntegerSetConverter.java
new file mode 100644
index 00000000..e87c21b2
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/converter/IntegerSetConverter.java
@@ -0,0 +1,15 @@
+package org.astraea.argument.converter;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.ParameterException;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class IntegerSetConverter implements IStringConverter<Set<Integer>> {
+  @Override
+  public Set<Integer> convert(String value) {
+    if (value.isEmpty()) throw new ParameterException("array type can't be empty");
+    return Stream.of(value.split(",")).map(Integer::valueOf).collect(Collectors.toSet());
+  }
+}
diff --git a/app/src/main/java/org/astraea/argument/converter/ShortConverter.java b/app/src/main/java/org/astraea/argument/converter/ShortConverter.java
new file mode 100644
index 00000000..b8390649
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/converter/ShortConverter.java
@@ -0,0 +1,10 @@
+package org.astraea.argument.converter;
+
+import com.beust.jcommander.IStringConverter;
+
+public class ShortConverter implements IStringConverter<Short> {
+  @Override
+  public Short convert(String value) {
+    return Short.valueOf(value);
+  }
+}
diff --git a/app/src/main/java/org/astraea/argument/converter/StringSetConverter.java b/app/src/main/java/org/astraea/argument/converter/StringSetConverter.java
new file mode 100644
index 00000000..8acf4979
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/converter/StringSetConverter.java
@@ -0,0 +1,13 @@
+package org.astraea.argument.converter;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.ParameterException;
+import java.util.Set;
+
+public class StringSetConverter implements IStringConverter<Set<String>> {
+  @Override
+  public Set<String> convert(String value) {
+    if (value.isEmpty()) throw new ParameterException("array type can't be empty");
+    return Set.of(value.split(","));
+  }
+}
diff --git a/app/src/main/java/org/astraea/argument/validator/NonNegativeLong.java b/app/src/main/java/org/astraea/argument/validator/NonNegativeLong.java
new file mode 100644
index 00000000..5a01a432
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/validator/NonNegativeLong.java
@@ -0,0 +1,11 @@
+package org.astraea.argument.validator;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.ParameterException;
+
+public class NonNegativeLong implements IParameterValidator {
+  @Override
+  public void validate(String name, String value) throws ParameterException {
+    if (Long.parseLong(value) < 0) throw new ParameterException(name + " should not be negative.");
+  }
+}
diff --git a/app/src/main/java/org/astraea/argument/validator/NotEmptyString.java b/app/src/main/java/org/astraea/argument/validator/NotEmptyString.java
new file mode 100644
index 00000000..078169a3
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/validator/NotEmptyString.java
@@ -0,0 +1,12 @@
+package org.astraea.argument.validator;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.ParameterException;
+
+public class NotEmptyString implements IParameterValidator {
+  @Override
+  public void validate(String name, String value) throws ParameterException {
+    if (value == null || value.equals(""))
+      throw new ParameterException(name + " should not be empty.");
+  }
+}
diff --git a/app/src/main/java/org/astraea/argument/validator/PositiveLong.java b/app/src/main/java/org/astraea/argument/validator/PositiveLong.java
new file mode 100644
index 00000000..c41fc60d
--- /dev/null
+++ b/app/src/main/java/org/astraea/argument/validator/PositiveLong.java
@@ -0,0 +1,11 @@
+package org.astraea.argument.validator;
+
+import com.beust.jcommander.IParameterValidator;
+import com.beust.jcommander.ParameterException;
+
+public class PositiveLong implements IParameterValidator {
+  @Override
+  public void validate(String name, String value) throws ParameterException {
+    if (Long.parseLong(value) <= 0) throw new ParameterException(name + " should be positive.");
+  }
+}
diff --git a/app/src/main/java/org/astraea/automation/Automation.java b/app/src/main/java/org/astraea/automation/Automation.java
index 50fcd198..d89ee564 100644
--- a/app/src/main/java/org/astraea/automation/Automation.java
+++ b/app/src/main/java/org/astraea/automation/Automation.java
@@ -11,6 +11,7 @@ import org.apache.kafka.clients.admin.AdminClient;
 import org.apache.kafka.clients.admin.AdminClientConfig;
 import org.apache.kafka.clients.admin.KafkaAdminClient;
 import org.astraea.argument.ArgumentUtil;
+import org.astraea.argument.validator.NotEmptyString;
 import org.astraea.performance.Performance;
 
 /**
@@ -91,7 +92,7 @@ public class Automation {
     @Parameter(
         names = {"--file"},
         description = "String: automation.properties address",
-        validateWith = ArgumentUtil.NotEmptyString.class)
+        validateWith = NotEmptyString.class)
     String address = "";
   }
 }
diff --git a/app/src/main/java/org/astraea/metrics/MetricExplorer.java b/app/src/main/java/org/astraea/metrics/MetricExplorer.java
index 3e96fcaf..0459e306 100644
--- a/app/src/main/java/org/astraea/metrics/MetricExplorer.java
+++ b/app/src/main/java/org/astraea/metrics/MetricExplorer.java
@@ -14,6 +14,7 @@ import java.util.stream.Stream;
 import javax.management.openmbean.CompositeDataSupport;
 import javax.management.remote.JMXServiceURL;
 import org.astraea.argument.ArgumentUtil;
+import org.astraea.argument.validator.NotEmptyString;
 import org.astraea.metrics.jmx.BeanObject;
 import org.astraea.metrics.jmx.BeanQuery;
 import org.astraea.metrics.jmx.MBeanClient;
@@ -175,7 +176,7 @@ public class MetricExplorer {
         names = {"--jmx.server"},
         description =
             "The JMX server address to connect to, support [hostname:port] style or JMX URI format",
-        validateWith = ArgumentUtil.NotEmptyString.class,
+        validateWith = NotEmptyString.class,
         converter = Argument.JmxServerUrlConverter.class,
         required = true)
     JMXServiceURL jmxServer;
diff --git a/app/src/main/java/org/astraea/performance/ExeTime.java b/app/src/main/java/org/astraea/performance/ExeTime.java
index 6ffbd8fc..45062436 100644
--- a/app/src/main/java/org/astraea/performance/ExeTime.java
+++ b/app/src/main/java/org/astraea/performance/ExeTime.java
@@ -6,7 +6,7 @@ import com.beust.jcommander.ParameterException;
 import java.time.Duration;
 import java.util.function.BiFunction;
 import java.util.regex.Pattern;
-import org.astraea.argument.ArgumentUtil;
+import org.astraea.argument.converter.DurationConverter;
 
 /**
  * Two kind of running modes. One runs for a duration of time. The other runs for a number of
@@ -21,7 +21,7 @@ interface ExeTime {
       final long records = Long.parseLong(exeTime.replace("records", ""));
       return ExeTime.of((completeRecords, ignore) -> 100D * completeRecords / records, exeTime);
     }
-    final Duration duration = new ArgumentUtil.DurationConverter().convert(exeTime);
+    final Duration duration = new DurationConverter().convert(exeTime);
     return ExeTime.of((ignore, elapsedTime) -> 100D * elapsedTime / duration.toMillis(), exeTime);
   }
 
diff --git a/app/src/main/java/org/astraea/performance/Performance.java b/app/src/main/java/org/astraea/performance/Performance.java
index cca22e18..96a5978c 100644
--- a/app/src/main/java/org/astraea/performance/Performance.java
+++ b/app/src/main/java/org/astraea/performance/Performance.java
@@ -26,6 +26,10 @@ import org.apache.kafka.common.record.CompressionType;
 import org.astraea.Utils;
 import org.astraea.argument.ArgumentUtil;
 import org.astraea.argument.BasicArgumentWithPropFile;
+import org.astraea.argument.converter.ShortConverter;
+import org.astraea.argument.validator.NonNegativeLong;
+import org.astraea.argument.validator.NotEmptyString;
+import org.astraea.argument.validator.PositiveLong;
 import org.astraea.concurrent.ThreadPool;
 import org.astraea.consumer.Consumer;
 import org.astraea.producer.Producer;
@@ -234,32 +238,32 @@ public class Performance {
     @Parameter(
         names = {"--topic"},
         description = "String: topic name",
-        validateWith = ArgumentUtil.NotEmptyString.class)
+        validateWith = NotEmptyString.class)
     String topic = "testPerformance-" + System.currentTimeMillis();
 
     @Parameter(
         names = {"--partitions"},
         description = "Integer: number of partitions to create the topic",
-        validateWith = ArgumentUtil.PositiveLong.class)
+        validateWith = PositiveLong.class)
     int partitions = 1;
 
     @Parameter(
         names = {"--replicas"},
         description = "Integer: number of replica to create the topic",
-        validateWith = ArgumentUtil.PositiveLong.class,
-        converter = ArgumentUtil.ShortConverter.class)
+        validateWith = PositiveLong.class,
+        converter = ShortConverter.class)
     short replicas = 1;
 
     @Parameter(
         names = {"--producers"},
         description = "Integer: number of producers to produce records",
-        validateWith = ArgumentUtil.PositiveLong.class)
+        validateWith = PositiveLong.class)
     int producers = 1;
 
     @Parameter(
         names = {"--consumers"},
         description = "Integer: number of consumers to consume records",
-        validateWith = ArgumentUtil.NonNegativeLong.class)
+        validateWith = NonNegativeLong.class)
     int consumers = 1;
 
     @Parameter(
@@ -288,13 +292,13 @@ public class Performance {
         names = {"--jmx.servers"},
         description =
             "String: server to get jmx metrics <jmx_server>@<broker_id>[,<jmx_server>@<broker_id>]*",
-        validateWith = ArgumentUtil.NotEmptyString.class)
+        validateWith = NotEmptyString.class)
     String jmxServers = "";
 
     @Parameter(
         names = {"--partitioner"},
         description = "String: the full class name of the desired partitioner",
-        validateWith = ArgumentUtil.NotEmptyString.class)
+        validateWith = NotEmptyString.class)
     String partitioner = DefaultPartitioner.class.getName();
 
     public Map<String, Object> producerProps() {
@@ -327,7 +331,7 @@ public class Performance {
         names = {"--specify.broker"},
         description =
             "String: Used with SpecifyBrokerPartitioner to specify the brokers that partitioner can send.",
-        validateWith = ArgumentUtil.NotEmptyString.class)
+        validateWith = NotEmptyString.class)
     List<Integer> specifyBroker = List.of(-1);
   }
 
diff --git a/app/src/main/java/org/astraea/topic/ReplicaCollie.java b/app/src/main/java/org/astraea/topic/ReplicaCollie.java
index dcc21f45..6ebb658e 100644
--- a/app/src/main/java/org/astraea/topic/ReplicaCollie.java
+++ b/app/src/main/java/org/astraea/topic/ReplicaCollie.java
@@ -1,12 +1,16 @@
 package org.astraea.topic;
 
 import com.beust.jcommander.Parameter;
+import com.beust.jcommander.converters.BooleanConverter;
 import java.io.IOException;
 import java.util.*;
 import java.util.stream.Collectors;
 import org.apache.kafka.common.TopicPartition;
 import org.astraea.argument.ArgumentUtil;
 import org.astraea.argument.BasicArgumentWithPropFile;
+import org.astraea.argument.converter.IntegerSetConverter;
+import org.astraea.argument.converter.StringSetConverter;
+import org.astraea.argument.validator.NotEmptyString;
 
 public class ReplicaCollie {
   static final String UNKNOWN = "unknown";
@@ -297,45 +301,45 @@ public class ReplicaCollie {
     @Parameter(
         names = {"--topics"},
         description = "Those topics' partitions will get reassigned. Empty means all topics",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.StringSetConverter.class)
+        validateWith = NotEmptyString.class,
+        converter = StringSetConverter.class)
     public Set<String> topics = Collections.emptySet();
 
     @Parameter(
         names = {"--from"},
         description = "Those brokers won't hold any replicas of topics (defined by --topics)",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.IntegerSetConverter.class,
+        validateWith = NotEmptyString.class,
+        converter = IntegerSetConverter.class,
         required = true)
     Set<Integer> fromBrokers;
 
     @Parameter(
         names = {"--to"},
         description = "The replicas of topics (defined by --topic) will be moved to those brokers",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.IntegerSetConverter.class)
+        validateWith = NotEmptyString.class,
+        converter = IntegerSetConverter.class)
     Set<Integer> toBrokers = Collections.emptySet();
 
     @Parameter(
         names = {"--partitions"},
         description = "all partitions that will be moved",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.IntegerSetConverter.class)
+        validateWith = NotEmptyString.class,
+        converter = IntegerSetConverter.class)
     Set<Integer> partitions = Collections.emptySet();
 
     @Parameter(
         names = {"--path"},
         description = "The partition that will be moved to",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.StringSetConverter.class)
+        validateWith = NotEmptyString.class,
+        converter = StringSetConverter.class)
     Set<String> path = Collections.emptySet();
 
     @Parameter(
         names = {"--verify"},
         description =
             "True if you just want to see the new assignment instead of executing the plan",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.BooleanConverter.class)
+        validateWith = NotEmptyString.class,
+        converter = BooleanConverter.class)
     boolean verify = false;
   }
 }
diff --git a/app/src/main/java/org/astraea/topic/ReplicaSyncingMonitor.java b/app/src/main/java/org/astraea/topic/ReplicaSyncingMonitor.java
index f753479b..de62b154 100644
--- a/app/src/main/java/org/astraea/topic/ReplicaSyncingMonitor.java
+++ b/app/src/main/java/org/astraea/topic/ReplicaSyncingMonitor.java
@@ -15,6 +15,8 @@ import org.apache.kafka.common.TopicPartitionReplica;
 import org.astraea.Utils;
 import org.astraea.argument.ArgumentUtil;
 import org.astraea.argument.BasicArgumentWithPropFile;
+import org.astraea.argument.converter.DurationConverter;
+import org.astraea.argument.validator.NotEmptyString;
 import org.astraea.utils.DataRate;
 import org.astraea.utils.DataSize;
 import org.astraea.utils.DataUnit;
@@ -298,7 +300,7 @@ public class ReplicaSyncingMonitor {
     @Parameter(
         names = {"--topics"},
         description = "String: topics to track, use all non-synced topics by default",
-        validateWith = ArgumentUtil.NotEmptyString.class)
+        validateWith = NotEmptyString.class)
     public Set<String> topics = Set.of();
 
     @Parameter(
@@ -312,8 +314,8 @@ public class ReplicaSyncingMonitor {
         description =
             "Time: the time interval between replica state check, support multiple time unit like 10s, 500ms and 100us. "
                 + "If no time unit specified, second unit will be used.",
-        validateWith = ArgumentUtil.DurationConverter.class,
-        converter = ArgumentUtil.DurationConverter.class)
+        validateWith = DurationConverter.class,
+        converter = DurationConverter.class)
     public Duration interval = Duration.ofSeconds(1);
   }
 }
diff --git a/app/src/main/java/org/astraea/topic/TopicExplorer.java b/app/src/main/java/org/astraea/topic/TopicExplorer.java
index 5ad05426..acb801cb 100644
--- a/app/src/main/java/org/astraea/topic/TopicExplorer.java
+++ b/app/src/main/java/org/astraea/topic/TopicExplorer.java
@@ -13,6 +13,8 @@ import java.util.stream.Stream;
 import org.apache.kafka.common.TopicPartition;
 import org.astraea.argument.ArgumentUtil;
 import org.astraea.argument.BasicArgumentWithPropFile;
+import org.astraea.argument.converter.StringSetConverter;
+import org.astraea.argument.validator.NotEmptyString;
 import org.astraea.utils.DataSize;
 import org.astraea.utils.DataUnit;
 
@@ -424,8 +426,8 @@ public class TopicExplorer {
     @Parameter(
         names = {"--topics"},
         description = "the topics to show all offset-related information. Empty means all topics",
-        validateWith = ArgumentUtil.NotEmptyString.class,
-        converter = ArgumentUtil.StringSetConverter.class)
+        validateWith = NotEmptyString.class,
+        converter = StringSetConverter.class)
     public Set<String> topics = Collections.emptySet();
   }
 }


Test patch:
diff --git a/app/src/test/java/org/astraea/argument/ArgumentUtilTest.java b/app/src/test/java/org/astraea/argument/ArgumentUtilTest.java
index fb506568..2a3ed8d0 100644
--- a/app/src/test/java/org/astraea/argument/ArgumentUtilTest.java
+++ b/app/src/test/java/org/astraea/argument/ArgumentUtilTest.java
@@ -6,6 +6,11 @@ import java.time.Duration;
 import java.util.Set;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
+import org.astraea.argument.converter.DurationConverter;
+import org.astraea.argument.converter.StringSetConverter;
+import org.astraea.argument.validator.NonNegativeLong;
+import org.astraea.argument.validator.NotEmptyString;
+import org.astraea.argument.validator.PositiveLong;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -17,28 +22,28 @@ public class ArgumentUtilTest {
   public static class FakeParameter {
     @Parameter(
         names = {"--require"},
-        validateWith = ArgumentUtil.NotEmptyString.class,
+        validateWith = NotEmptyString.class,
         required = true)
     public String require;
 
     @Parameter(
         names = {"--longPositive"},
-        validateWith = ArgumentUtil.PositiveLong.class)
+        validateWith = PositiveLong.class)
     public long longPositive;
 
     @Parameter(
         names = {"--longNotNegative"},
-        validateWith = ArgumentUtil.NonNegativeLong.class)
+        validateWith = NonNegativeLong.class)
     public int longNotNegative = 1;
 
     @Parameter(
         names = {"--durationConvert"},
-        converter = ArgumentUtil.DurationConverter.class)
+        converter = DurationConverter.class)
     public Duration durationConvert;
 
     @Parameter(
         names = {"--setConverter"},
-        converter = ArgumentUtil.StringSetConverter.class,
+        converter = StringSetConverter.class,
         variableArity = true)
     public Set<String> setConverter;
   }
@@ -124,7 +129,7 @@ public class ArgumentUtilTest {
   @ParameterizedTest(name = "[{index}] time string \"{0}\" will match duration \"{1}\"")
   @MethodSource("testDurationConvertorTestcases")
   public void testDurationConvertorConvert(String timeString, Duration expectedDuration) {
-    ArgumentUtil.DurationConverter durationConverter = new ArgumentUtil.DurationConverter();
+    var durationConverter = new DurationConverter();
 
     Assertions.assertEquals(expectedDuration, durationConverter.convert(timeString));
   }
@@ -160,8 +165,7 @@ public class ArgumentUtilTest {
         (Supplier<Boolean>)
             () -> {
               try {
-                ArgumentUtil.DurationConverter durationConverter =
-                    new ArgumentUtil.DurationConverter();
+                var durationConverter = new DurationConverter();
                 durationConverter.validate("key", timeString);
                 return true;
               } catch (ParameterException ignored) {