You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ozone.apache.org by ad...@apache.org on 2026/04/07 10:33:05 UTC

(ozone) branch master updated: HDDS-14973. Update ScmInvokerCodeGenerator to generate getProxy() (#10049)

This is an automated email from the ASF dual-hosted git repository.

adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 9e775e6c8f6 HDDS-14973. Update ScmInvokerCodeGenerator to generate getProxy() (#10049)
9e775e6c8f6 is described below

commit 9e775e6c8f6217562dda239b1e38a25e44265808
Author: Tsz-Wo Nicholas Sze <sz...@apache.org>
AuthorDate: Tue Apr 7 03:32:59 2026 -0700

    HDDS-14973. Update ScmInvokerCodeGenerator to generate getProxy() (#10049)
---
 .../scm/block/DeletedBlockLogStateManager.java     |   4 +-
 .../hdds/scm/ha/ScmInvokerCodeGenerator.java       | 240 ----------
 .../DeletedBlockLogStateManagerInvoker.java        | 133 +++--
 .../hadoop/hdds/scm/ha/invoker/ScmInvoker.java     |  23 +-
 .../scm/ha/invoker/ScmInvokerCodeGenerator.java    | 533 +++++++++++++++++++++
 5 files changed, 606 insertions(+), 327 deletions(-)

diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java
index 6a8732cf8c8..41616527661 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/block/DeletedBlockLogStateManager.java
@@ -24,7 +24,7 @@
 import org.apache.hadoop.hdds.protocol.proto.SCMRatisProtocol.RequestType;
 import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
 import org.apache.hadoop.hdds.scm.ha.SCMHandler;
-import org.apache.hadoop.hdds.scm.ha.ScmInvokerCodeGenerator;
+import org.apache.hadoop.hdds.scm.ha.invoker.ScmInvokerCodeGenerator;
 import org.apache.hadoop.hdds.scm.metadata.Replicate;
 import org.apache.hadoop.hdds.utils.db.Table;
 
@@ -77,6 +77,6 @@ void reinitialize(Table<Long, DeletedBlocksTransaction> deletedBlocksTXTable,
       Table<String, ByteString> statefulConfigTable);
 
   static void main(String[] args) {
-    new ScmInvokerCodeGenerator(DeletedBlockLogStateManager.class, RequestType.BLOCK).generateClass();
+    ScmInvokerCodeGenerator.generate(DeletedBlockLogStateManager.class, true);
   }
 }
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/ScmInvokerCodeGenerator.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/ScmInvokerCodeGenerator.java
deleted file mode 100644
index cbe18118644..00000000000
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/ScmInvokerCodeGenerator.java
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.hadoop.hdds.scm.ha;
-
-import java.io.PrintStream;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Parameter;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Function;
-import org.apache.hadoop.hdds.protocol.proto.SCMRatisProtocol.RequestType;
-import org.apache.ratis.util.UncheckedAutoCloseable;
-
-/**
- * A tool to (manually) generate code for copy-paste.
- */
-public class ScmInvokerCodeGenerator {
-  static final DeclaredMethod INVOKE_LOCAL = new DeclaredMethod("invokeLocal",
-      new Class[]{String.class, Object[].class},
-      new String[]{"methodName", "params"},
-      Object.class,
-      new Class<?>[]{Exception.class});
-
-  private final Class<?> api;
-  private final String apiName;
-  private final String requestTypeName;
-  private final String invokerClassName;
-
-  private final PrintStream out = System.out;
-  private String indentation = "";
-
-  public ScmInvokerCodeGenerator(Class<?> api, RequestType type) {
-    this.api = api;
-    this.apiName = api.getSimpleName();
-    this.requestTypeName = type.name();
-    this.invokerClassName = apiName + "Invoker";
-  }
-
-  void printf(String format, Object... args) {
-    out.printf(indentation + format, args);
-  }
-
-  void println(String format, Object... args) {
-    printf(format, args);
-    out.println();
-  }
-
-  UncheckedAutoCloseable printScope() {
-    return printScope(true, true);
-  }
-
-  UncheckedAutoCloseable printScope(boolean codeBlock, boolean intend) {
-    out.println(codeBlock ? " {" : "");
-    if (intend) {
-      indentation += "  ";
-    }
-    return () -> {
-      if (intend) {
-        indentation = indentation.substring(2);
-      }
-      if (codeBlock) {
-        println("}");
-      }
-    };
-  }
-
-  static String classesToString(Class<?>[] classes) {
-    return arrayToString(classes, Class::getSimpleName);
-  }
-
-  static <T> String arrayToString(T[] objects, Function<T, String> toString) {
-    return Arrays.stream(objects)
-        .map(toString)
-        .reduce(null, (a, b) -> a == null ? b : a + ", " + b);
-  }
-
-  void printHeaderMethods() {
-    out.println();
-    printf("public %s(%s impl)", invokerClassName, apiName);
-    try (UncheckedAutoCloseable ignore = printScope()) {
-      println("this.impl = impl;");
-    }
-
-    out.println();
-    println("@Override");
-    printf("public RequestType getType()");
-    try (UncheckedAutoCloseable ignore = printScope()) {
-      println("return RequestType.%s;", requestTypeName);
-    }
-
-    out.println();
-    println("@Override");
-    printf("public Class<%s> getApi()", apiName);
-    try (UncheckedAutoCloseable ignore = printScope()) {
-      println("return %s.class;", apiName);
-    }
-
-    out.println();
-    println("@Override");
-    printf("public %s getImpl()", apiName);
-    try (UncheckedAutoCloseable ignore = printScope()) {
-      println("return impl;");
-    }
-  }
-
-  void printMethodSignature(DeclaredMethod method) {
-    printf("public %s %s(%s) throws %s",
-        method.getReturnType().getSimpleName(),
-        method.getName(),
-        method.getParameterString(),
-        classesToString(method.getExceptionTypes()));
-  }
-
-  void printCase(Method apiMethod, String actualParameter, AtomicInteger argCount) {
-    printf("case \"%s\":", apiMethod.getName());
-    try (UncheckedAutoCloseable ignore = printScope(false, true)) {
-      final Parameter[] apiParameters = apiMethod.getParameters();
-      final StringBuilder b = new StringBuilder();
-      for (int i = 0; i < apiParameters.length; i++) {
-        final Parameter p = apiParameters[i];
-        final String classname = p.getType().getSimpleName();
-        final String arg = "arg" + argCount.getAndIncrement();
-        b.append(arg).append(", ");
-        println("final %s %s = %s.length > %d ? (%s) %s[%d] : null;", classname, arg,
-            actualParameter, i, classname, actualParameter, i);
-      }
-      if (b.length() > 0) {
-        b.setLength(b.length() - 2);
-      }
-      if (apiMethod.getReturnType() == void.class) {
-        println("impl.%s(%s);", apiMethod.getName(), b);
-        println("return null;");
-      } else {
-        println("return impl.%s(%s);", apiMethod.getName(), b);
-      }
-    }
-    out.println();
-  }
-
-  void printSwitch(DeclaredMethod method) {
-    final String switchName = method.getParameterName(0);
-    printf("switch (%s)", switchName);
-    try (UncheckedAutoCloseable ignored = printScope(true, false)) {
-      final Method[] apiMethods = api.getMethods();
-      Arrays.sort(apiMethods, Comparator.comparing(Method::getName));
-      final AtomicInteger argCount = new AtomicInteger(0);
-      for (Method apiMethod : apiMethods) {
-        if (!apiMethod.isDefault() && !Modifier.isStatic(apiMethod.getModifiers())) {
-          printCase(apiMethod, method.getParameterName(1), argCount);
-        }
-      }
-
-      printf("default:");
-      try (UncheckedAutoCloseable ignore = printScope(false, true)) {
-        println("throw new IllegalArgumentException(\"Method not found: \" + %s + \" in %s\");",
-            switchName, apiName);
-      }
-    }
-  }
-
-  void printInvokeMethod(DeclaredMethod method) {
-    out.println();
-    println("@SuppressWarnings(\"unchecked\")");
-    println("@Override");
-    printMethodSignature(method);
-    try (UncheckedAutoCloseable ignored = printScope()) {
-      printSwitch(method);
-    }
-  }
-
-  public void generateClass() {
-    println("/** Code generated for %s.  Do not modify. */", apiName);
-    printf("public class %s implements ScmInvoker<%s>", invokerClassName, apiName);
-    try (UncheckedAutoCloseable ignored = printScope()) {
-      println("private final %s impl;", apiName);
-      printHeaderMethods();
-      printInvokeMethod(INVOKE_LOCAL);
-    }
-  }
-
-  static class DeclaredMethod {
-    private final String name;
-    private final Class<?>[] parameterTypes;
-    private final String[] parameterNames;
-    private final Class<?> returnType;
-    private final Class<?>[] exceptionTypes;
-
-    DeclaredMethod(String name, Class<?>[] parameterTypes, String[] parameterNames,
-        Class<?> returnType, Class<?>[] exceptionTypes) {
-      this.name = name;
-      this.parameterTypes = parameterTypes;
-      this.parameterNames = parameterNames;
-      this.returnType = returnType;
-      this.exceptionTypes = exceptionTypes;
-    }
-
-    String getName() {
-      return name;
-    }
-
-    String getParameterName(int i) {
-      return parameterNames[i];
-    }
-
-    String getParameterString() {
-      final StringBuilder b = new StringBuilder();
-      for (int i = 0; i < parameterNames.length; i++) {
-        b.append(parameterTypes[i].getSimpleName()).append(' ')
-            .append(parameterNames[i]).append(", ");
-      }
-      b.setLength(b.length() - 2);
-      return b.toString();
-    }
-
-    Class<?> getReturnType() {
-      return returnType;
-    }
-
-    Class<?>[] getExceptionTypes() {
-      return exceptionTypes;
-    }
-  }
-}
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/DeletedBlockLogStateManagerInvoker.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/DeletedBlockLogStateManagerInvoker.java
index 3a5c450640b..862481746d8 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/DeletedBlockLogStateManagerInvoker.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/DeletedBlockLogStateManagerInvoker.java
@@ -17,39 +17,32 @@
 
 package org.apache.hadoop.hdds.scm.ha.invoker;
 
-import com.google.protobuf.ByteString;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary;
 import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction;
 import org.apache.hadoop.hdds.scm.block.DeletedBlockLogStateManager;
 import org.apache.hadoop.hdds.scm.ha.SCMRatisServer;
 import org.apache.hadoop.hdds.utils.db.Table;
 
-/**
- * Invoker for DeletedBlockLogStateManager local (non-@Replicate) methods.
- */
+/** Code generated for {@link DeletedBlockLogStateManager}.  Do not modify. */
 public class DeletedBlockLogStateManagerInvoker extends ScmInvoker<DeletedBlockLogStateManager> {
-  private final DeletedBlockLogStateManager impl;
-
   enum ReplicateMethod implements NameAndParameterTypes {
-    addTransactionsToDB(new Class<?>[] {ArrayList.class, DeletedBlocksTransactionSummary.class}),
-    removeTransactionsFromDB(new Class<?>[] {ArrayList.class, DeletedBlocksTransactionSummary.class}),;
+    addTransactionsToDB(new Class<?>[][] {
+        null,
+        new Class<?>[] {ArrayList.class},
+        new Class<?>[] {ArrayList.class, DeletedBlocksTransactionSummary.class}
+    }),
+    removeTransactionsFromDB(new Class<?>[][] {
+        null,
+        new Class<?>[] {ArrayList.class},
+        new Class<?>[] {ArrayList.class, DeletedBlocksTransactionSummary.class}
+    });
 
     private final Class<?>[][] parameterTypes;
 
-    ReplicateMethod(Class<?>[] parameterTypes) {
-      final Class<?>[][] types = new Class<?>[parameterTypes.length + 1][];
-      for (int i = 0; i <= parameterTypes.length; ++i) {
-        types[i] = Arrays.copyOf(parameterTypes, i);
-      }
-      this.parameterTypes = types;
-    }
-
-    @Override
-    public String getName() {
-      return name();
+    ReplicateMethod(Class<?>[][] parameterTypes) {
+      this.parameterTypes = parameterTypes;
     }
 
     @Override
@@ -58,9 +51,8 @@ public Class<?>[] getParameterTypes(int numArgs) {
     }
   }
 
-  public DeletedBlockLogStateManagerInvoker(DeletedBlockLogStateManager impl, SCMRatisServer scmRatisServer) {
-    super(scmRatisServer);
-    this.impl = impl;
+  public DeletedBlockLogStateManagerInvoker(DeletedBlockLogStateManager impl, SCMRatisServer ratis) {
+    super(impl, DeletedBlockLogStateManagerInvoker::newProxy, ratis);
   }
 
   @Override
@@ -68,98 +60,81 @@ public Class<DeletedBlockLogStateManager> getApi() {
     return DeletedBlockLogStateManager.class;
   }
 
-  @Override
-  public DeletedBlockLogStateManager getImpl() {
-    return impl;
-  }
-
-  @Override
-  public DeletedBlockLogStateManager getProxy() {
+  static DeletedBlockLogStateManager newProxy(ScmInvoker<DeletedBlockLogStateManager> invoker) {
     return new DeletedBlockLogStateManager() {
+
       @Override
-      public void addTransactionsToDB(ArrayList<DeletedBlocksTransaction> txs)
-          throws IOException {
-        final Object[] args = {txs};
-        invokeRatisServer(ReplicateMethod.addTransactionsToDB, args);
+      public void addTransactionsToDB(ArrayList arg0) throws IOException {
+        final Object[] args = {arg0};
+        invoker.invokeReplicateDirect(ReplicateMethod.addTransactionsToDB, args);
       }
 
       @Override
-      public void addTransactionsToDB(ArrayList<DeletedBlocksTransaction> txs,
-          DeletedBlocksTransactionSummary summary) throws IOException {
-        final Object[] args = {txs, summary};
-        invokeRatisServer(ReplicateMethod.addTransactionsToDB, args);
+      public void addTransactionsToDB(ArrayList arg0, DeletedBlocksTransactionSummary arg1) throws IOException {
+        final Object[] args = {arg0, arg1};
+        invoker.invokeReplicateDirect(ReplicateMethod.addTransactionsToDB, args);
       }
 
       @Override
-      public void removeTransactionsFromDB(ArrayList<Long> txIDs)
-          throws IOException {
-        final Object[] args = {txIDs};
-        invokeRatisServer(ReplicateMethod.removeTransactionsFromDB, args);
+      public Table.KeyValueIterator<Long, DeletedBlocksTransaction> getReadOnlyIterator() throws IOException {
+        return invoker.getImpl().getReadOnlyIterator();
       }
 
       @Override
-      public void removeTransactionsFromDB(ArrayList<Long> txIDs,
-          DeletedBlocksTransactionSummary summary) throws IOException {
-        final Object[] args = {txIDs, summary};
-        invokeRatisServer(ReplicateMethod.removeTransactionsFromDB, args);
+      public void onFlush() {
+        invoker.getImpl().onFlush();
       }
 
       @Override
-      public Table.KeyValueIterator<Long, DeletedBlocksTransaction> getReadOnlyIterator() throws IOException {
-        return impl.getReadOnlyIterator();
+      public void reinitialize(Table arg0, Table arg1) {
+        invoker.getImpl().reinitialize(arg0, arg1);
       }
 
       @Override
-      public void onFlush() {
-        impl.onFlush();
+      public void removeTransactionsFromDB(ArrayList arg0) throws IOException {
+        final Object[] args = {arg0};
+        invoker.invokeReplicateDirect(ReplicateMethod.removeTransactionsFromDB, args);
       }
 
       @Override
-      public void reinitialize(
-          Table<Long, DeletedBlocksTransaction> deletedBlocksTXTable,
-          Table<String, ByteString> statefulConfigTable) {
-        impl.reinitialize(deletedBlocksTXTable, statefulConfigTable);
+      public void removeTransactionsFromDB(ArrayList arg0, DeletedBlocksTransactionSummary arg1) throws IOException {
+        final Object[] args = {arg0, arg1};
+        invoker.invokeReplicateDirect(ReplicateMethod.removeTransactionsFromDB, args);
       }
     };
   }
 
-  // Code generated for DeletedBlockLogStateManager.  Do not modify.
   @SuppressWarnings("unchecked")
   @Override
-  public Object invokeLocal(String methodName, Object[] params) throws Exception {
+  public Object invokeLocal(String methodName, Object[] p) throws Exception {
     switch (methodName) {
-    case "onFlush":
-      impl.onFlush();
-      return null;
-
     case "addTransactionsToDB":
-      final ArrayList arg0 =
-          params.length > 0 ? (ArrayList) params[0] : null;
-      final DeletedBlocksTransactionSummary arg1 =
-          params.length > 1 ? (DeletedBlocksTransactionSummary) params[1] : null;
-      impl.addTransactionsToDB(arg0, arg1);
-      return null;
-
-    case "removeTransactionsFromDB":
-      final ArrayList arg2 =
-          params.length > 0 ? (ArrayList) params[0] : null;
-      final DeletedBlocksTransactionSummary arg3 =
-          params.length > 1 ? (DeletedBlocksTransactionSummary) params[1] : null;
-      impl.removeTransactionsFromDB(arg2, arg3);
+      final ArrayList arg0 = p.length > 0 ? (ArrayList) p[0] : null;
+      final DeletedBlocksTransactionSummary arg1 = p.length > 1 ? (DeletedBlocksTransactionSummary) p[1] : null;
+      getImpl().addTransactionsToDB(arg0, arg1);
       return null;
 
     case "getReadOnlyIterator":
-      return impl.getReadOnlyIterator();
+      return getImpl().getReadOnlyIterator();
+
+    case "onFlush":
+      getImpl().onFlush();
+      return null;
 
     case "reinitialize":
-      final Table arg4 = params.length > 0 ? (Table) params[0] : null;
-      final Table arg5 = params.length > 1 ? (Table) params[1] : null;
-      impl.reinitialize(arg4, arg5);
+      final Table arg2 = p.length > 0 ? (Table) p[0] : null;
+      final Table arg3 = p.length > 1 ? (Table) p[1] : null;
+      getImpl().reinitialize(arg2, arg3);
+      return null;
+
+    case "removeTransactionsFromDB":
+      final ArrayList arg4 = p.length > 0 ? (ArrayList) p[0] : null;
+      final DeletedBlocksTransactionSummary arg5 = p.length > 1 ? (DeletedBlocksTransactionSummary) p[1] : null;
+      getImpl().removeTransactionsFromDB(arg4, arg5);
       return null;
 
     default:
-      throw new IllegalArgumentException("Method not found: " + methodName);
+      throw new IllegalArgumentException("Method not found: " + methodName + " in DeletedBlockLogStateManager");
     }
   }
-
 }
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvoker.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvoker.java
index 8aa7e71a496..6c8dad6d1d7 100644
--- a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvoker.java
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvoker.java
@@ -19,6 +19,7 @@
 
 import static org.apache.hadoop.hdds.scm.ha.SCMHAInvocationHandler.translateException;
 
+import java.util.function.Function;
 import org.apache.hadoop.hdds.protocol.proto.SCMRatisProtocol.RequestType;
 import org.apache.hadoop.hdds.scm.exceptions.SCMException;
 import org.apache.hadoop.hdds.scm.ha.SCMHandler;
@@ -30,9 +31,13 @@
  * Invokes methods without using reflection.
  */
 public abstract class ScmInvoker<T extends SCMHandler> {
+  private final T impl;
+  private final T proxy;
   private final SCMRatisServer ratisHandler;
 
-  ScmInvoker(SCMRatisServer ratisHandler) {
+  ScmInvoker(T impl, Function<ScmInvoker<T>, T> proxy, SCMRatisServer ratisHandler) {
+    this.impl = impl;
+    this.proxy = proxy.apply(this);
     this.ratisHandler = ratisHandler;
   }
 
@@ -42,16 +47,22 @@ public final RequestType getType() {
 
   public abstract Class<T> getApi();
 
-  public abstract T getImpl();
+  public final T getImpl() {
+    return impl;
+  }
 
-  public abstract T getProxy();
+  public final T getProxy() {
+    return proxy;
+  }
 
+  /** For non-@Replicate methods. */
   abstract Object invokeLocal(String methodName, Object[] args) throws Exception;
 
-  Object invokeRatisServer(NameAndParameterTypes method, Object[] args) throws SCMException {
+  /** For @Replicate DIRECT methods. */
+  final Object invokeReplicateDirect(NameAndParameterTypes method, Object[] args) throws SCMException {
     try {
       final SCMRatisRequest request = SCMRatisRequest.of(
-              getType(),  method.getName(), method.getParameterTypes(args.length), args);
+          getType(), method.name(), method.getParameterTypes(args.length), args);
       final SCMRatisResponse response = ratisHandler.submitRequest(request);
       if (response.isSuccess()) {
         return response.getResult();
@@ -63,7 +74,7 @@ Object invokeRatisServer(NameAndParameterTypes method, Object[] args) throws SCM
   }
 
   interface NameAndParameterTypes {
-    String getName();
+    String name();
     
     Class<?>[] getParameterTypes(int numArgs);
   }
diff --git a/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvokerCodeGenerator.java b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvokerCodeGenerator.java
new file mode 100644
index 00000000000..e9898b9532d
--- /dev/null
+++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvokerCodeGenerator.java
@@ -0,0 +1,533 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.hadoop.hdds.scm.ha.invoker;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.hadoop.hdds.scm.metadata.Replicate;
+import org.apache.ratis.util.Preconditions;
+import org.apache.ratis.util.UncheckedAutoCloseable;
+
+/**
+ * Generate code for {@link ScmInvoker} implementations.
+ * Step 1. Create the target java file in {@link #DIR}.  It will be used as an input for license header and imports.
+ * Step 2. Add main method to the API interface.
+ * Step 3. Manually fix imports.
+ * <p>
+ * Below is an example for generating the API interface FinalizationStateManager:
+ * Step 1. Copy FinalizationStateManager.java to DIR/FinalizationStateManagerInvoker.java
+ * Step 2. //FinalizationStateManager
+ *         static void main(String[] args) {
+ *           ScmInvokerCodeGenerator.generate(FinalizationStateManager.class, true);
+ *         }
+ * Step 3. Manually fix imports.
+ */
+public final class ScmInvokerCodeGenerator {
+  static final String DIR = "hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/";
+  static final int LINE_LENGTH = 120;
+
+  static final DeclaredMethod INVOKE_LOCAL = new DeclaredMethod("invokeLocal",
+      new Class[]{String.class, Object[].class},
+      new String[]{"methodName", "p"},
+      Object.class,
+      new Class<?>[]{Exception.class});
+
+  private final Class<?> api;
+  private final String apiName;
+  private final String invokerClassName;
+
+  private final StringWriter lineBuffer = new StringWriter();
+  private final StringWriter out = new StringWriter();
+  private String indentation = "";
+
+  private ScmInvokerCodeGenerator(Class<?> api) {
+    this.api = api;
+    this.apiName = api.getSimpleName();
+    this.invokerClassName = apiName + "Invoker";
+
+  }
+
+  void printf(String format, Object... args) {
+    printf(true, format, args);
+  }
+
+  void printf(boolean indent, String format, Object... args) {
+    if (indent) {
+      lineBuffer.append(indentation);
+    }
+    lineBuffer.append(String.format(format, args));
+  }
+
+  void println() {
+    println(false, "");
+  }
+
+  void println(String format, Object... args) {
+    println(true, format, args);
+  }
+
+  void println(boolean indent, String format, Object... args) {
+    printf(indent, format, args);
+
+    String s = lineBuffer.toString();
+    lineBuffer.getBuffer().setLength(0);
+
+    if (s.length() <= LINE_LENGTH) {
+      out.append(s).append(System.lineSeparator());
+      return;
+    }
+
+    // break long lines
+    int nonSpace = 0;
+    while (s.charAt(nonSpace) == ' ') {
+      nonSpace++;
+    }
+
+    final int i = s.lastIndexOf(' ', LINE_LENGTH);
+    out.append(s.substring(0, i))
+        .append(System.lineSeparator());
+    out.append(s.substring(0, nonSpace)) // original indentation
+        .append("    ")
+        .append(s.substring(i + 1))
+        .append(System.lineSeparator());
+  }
+
+  UncheckedAutoCloseable printScope() {
+    return printScope(true, 1);
+  }
+
+  UncheckedAutoCloseable printScope(boolean codeBlock, int intendLevel) {
+    println(false, codeBlock ? " {" : "");
+    for (int i = 0; i < intendLevel; i++) {
+      indentation += "  ";
+    }
+    return () -> {
+      if (intendLevel > 0) {
+        indentation = indentation.substring(2 * intendLevel);
+      }
+      if (codeBlock) {
+        println("}");
+      }
+    };
+  }
+
+  static String getClassname(Class<?> clazz) {
+    return getClassnameBuilder(clazz).toString();
+  }
+
+  static StringBuilder getClassnameBuilder(Class<?> clazz) {
+    final StringBuilder b = new StringBuilder(clazz.getSimpleName());
+    for (Class<?> c = clazz.getEnclosingClass(); c != null; c = c.getEnclosingClass()) {
+      final String name = c.getSimpleName();
+      if (name.endsWith("Protos")) {
+        break;
+      }
+      b.insert(0, name + ".");
+    }
+    return b;
+  }
+
+  static String getReturnTypeString(Method method) {
+    final StringBuilder b = getClassnameBuilder(method.getReturnType());
+    final String generic = method.getGenericReturnType().getTypeName();
+    int i = generic.indexOf('<');
+    if (i < 0) {
+      return b.toString();
+    }
+    b.append('<');
+    for (boolean more = true; more;) {
+      final int j = generic.indexOf(", ", i);
+      more = j >= 0;
+      try {
+        final Class<?> clazz = Class.forName(generic.substring(i + 1, more ? j : generic.length() - 1));
+        b.append(getClassname(clazz)).append(", ");
+      } catch (ClassNotFoundException e) {
+        throw new IllegalStateException("Failed to getReturnTypeString for  " + generic, e);
+      }
+      i = j + 1;
+    }
+    b.setLength(b.length() - 2);
+    return b.append('>').toString();
+  }
+
+  static String classesToString(Class<?>[] classes, String suffix) {
+    return arrayToString(classes, c -> getClassnameBuilder(c).append(suffix).toString());
+  }
+
+  static <T> String arrayToString(T[] objects, Function<T, String> toString) {
+    return Arrays.stream(objects)
+        .map(toString)
+        .reduce("", (a, b) -> a.isEmpty() ? b : a + ", " + b);
+  }
+
+  static String getThrowsString(Class<?>[] exceptions) {
+    if (exceptions.length == 0) {
+      return "";
+    }
+    return " throws " + classesToString(exceptions, "");
+  }
+
+  static String getParameterString(Class<?>[] types, String[] names) {
+    final StringBuilder b = new StringBuilder();
+    final int n = names != null ? names.length : types.length;
+    for (int i = 0; i < n; i++) {
+      b.append(getClassname(types[i])).append(' ')
+          .append(names != null ? names[i] : "arg" + i).append(", ");
+    }
+    if (b.length() > 0) {
+      b.setLength(b.length() - 2);
+    }
+    return b.toString();
+  }
+
+  static Predicate<Method> getFilter(Boolean isDefault, Boolean isDeprecated) {
+    return m -> {
+      if (Modifier.isStatic(m.getModifiers())) {
+        return false;
+      }
+      if (isDefault != null && isDefault != m.isDefault()) {
+        return false;
+      }
+      if (isDeprecated != null && isDeprecated == (m.getAnnotation(Deprecated.class) == null)) {
+        return false;
+      }
+      return true;
+    };
+  }
+
+  List<Method> getMethods(Boolean isDefault, Boolean isDeprecated) {
+    return getMethods(getFilter(isDefault, isDeprecated));
+  }
+
+  List<Method> getMethods(Predicate<Method> filter) {
+    return Arrays.stream(api.getMethods())
+        .filter(filter)
+        .sorted(Comparator.comparing(Method::getName).thenComparing(Method::getParameterCount))
+        .collect(Collectors.toList());
+  }
+
+  void printEnum() {
+    printf("enum ReplicateMethod implements NameAndParameterTypes");
+    try (UncheckedAutoCloseable ignore = printScope()) {
+      final List<Method> apiMethods = getMethods(null, null);
+      boolean first = true;
+      for (Method m : apiMethods) {
+        if (m.isDefault() || m.getAnnotation(Replicate.class) == null) {
+          continue;
+        }
+        if (first) {
+          first = false;
+        } else {
+          println(false, ",");
+        }
+
+        final List<Method> overrides = apiMethods.stream()
+            .filter(method -> method.getName().equals(m.getName()))
+            .sorted(Comparator.comparing(Method::getParameterCount))
+            .collect(Collectors.toList());
+        printEnumConstant(overrides);
+      }
+      println(false, ";");
+
+      printEnumBody();
+    }
+  }
+
+  private void printEnumBody() {
+    println();
+    println("private final Class<?>[][] parameterTypes;");
+
+    println();
+    printf("ReplicateMethod(Class<?>[][] parameterTypes)");
+    try (UncheckedAutoCloseable ignore = printScope()) {
+      println("this.parameterTypes = parameterTypes;");
+    }
+
+    println();
+    println("@Override");
+    printf("public Class<?>[] getParameterTypes(int numArgs)");
+    try (UncheckedAutoCloseable ignore = printScope()) {
+      println("return parameterTypes[numArgs];");
+    }
+  }
+
+  private void printEnumConstant(List<Method> overrides) {
+    final Class<?>[][] parameterTypes = getParameterTypes(overrides);
+    printf("%s(new Class<?>[][] {", overrides.get(0).getName());
+    try (UncheckedAutoCloseable ignore = printScope(false, 2)) {
+      for (int i = 0; i < parameterTypes.length; i++) {
+        if (i > 0) {
+          println(false, ",");
+        }
+
+        final Class<?>[] classes = parameterTypes[i];
+        if (classes == null) {
+          printf("null");
+        } else {
+          printf("new Class<?>[] {%s}", classesToString(classes, ".class"));
+        }
+      }
+    }
+    println();
+    printf("})");
+  }
+
+  static Class<?>[][] getParameterTypes(List<Method> overrides) {
+    final Method last = overrides.get(overrides.size() - 1);
+    final Class<?>[][] types = new Class<?>[last.getParameterCount() + 1][];
+    for (Method method : overrides) {
+      Preconditions.assertEquals(last.getName(), method.getName(), "methodName");
+      final int i = method.getParameterCount();
+      if (types[i] != null) {
+        throw new IllegalArgumentException("Duplicate method parameters found: " + overrides);
+      }
+      types[i] = method.getParameterTypes();
+    }
+    return types;
+  }
+
+  void printHeaderMethods() {
+    println();
+    printf("public %s(%s impl, SCMRatisServer ratis)", invokerClassName, apiName);
+    try (UncheckedAutoCloseable ignore = printScope()) {
+      println("super(impl, %s::newProxy, ratis);", invokerClassName);
+    }
+
+    println();
+    println("@Override");
+    printf("public Class<%s> getApi()", apiName);
+    try (UncheckedAutoCloseable ignore = printScope()) {
+      println("return %s.class;", apiName);
+    }
+  }
+
+  void printCase(Method apiMethod, String actualParameter, AtomicInteger argCount) {
+    printf("case \"%s\":", apiMethod.getName());
+    try (UncheckedAutoCloseable ignore = printScope(false, 1)) {
+      final Parameter[] apiParameters = apiMethod.getParameters();
+      final StringBuilder b = new StringBuilder();
+      for (int i = 0; i < apiParameters.length; i++) {
+        final Parameter p = apiParameters[i];
+        final String classname = getClassname(p.getType());
+        final String arg = "arg" + argCount.getAndIncrement();
+        b.append(arg).append(", ");
+        println("final %s %s = %s.length > %d ? (%s) %s[%d] : null;", classname, arg,
+            actualParameter, i, classname, actualParameter, i);
+      }
+      if (b.length() > 0) {
+        b.setLength(b.length() - 2);
+      }
+      if (apiMethod.getReturnType() == void.class) {
+        println("getImpl().%s(%s);", apiMethod.getName(), b);
+        println("return null;");
+      } else {
+        println("return getImpl().%s(%s);", apiMethod.getName(), b);
+      }
+    }
+    println();
+  }
+
+  void printSwitch(DeclaredMethod method) {
+    final String switchName = method.getParameterName(0);
+    printf("switch (%s)", switchName);
+    try (UncheckedAutoCloseable ignored = printScope(true, 0)) {
+      final AtomicInteger argCount = new AtomicInteger(0);
+      for (Method apiMethod : getMethods(false, null)) {
+        printCase(apiMethod, method.getParameterName(1), argCount);
+      }
+
+      printf("default:");
+      try (UncheckedAutoCloseable ignore = printScope(false, 1)) {
+        println("throw new IllegalArgumentException(\"Method not found: \" + %s + \" in %s\");",
+            switchName, apiName);
+      }
+    }
+  }
+
+  void printInvokeMethod(DeclaredMethod method) {
+    println();
+    println("@SuppressWarnings(\"unchecked\")");
+    println("@Override");
+    printf(method.getSignature());
+    try (UncheckedAutoCloseable ignored = printScope()) {
+      printSwitch(method);
+    }
+  }
+
+  void printProxyClassMethod(Method method) {
+    final Replicate r = method.getAnnotation(Replicate.class);
+    if (r == null && method.isDefault()) {
+      // Do not print non-Replicate default methods, just use default implementation.
+      return;
+    }
+
+    println();
+    println("@Override");
+    printf("public %s %s(%s)%s",
+        getReturnTypeString(method),
+        method.getName(),
+        getParameterString(method.getParameterTypes(), null),
+        getThrowsString(method.getExceptionTypes()));
+
+    try (UncheckedAutoCloseable ignored = printScope()) {
+      final String args = IntStream.range(0, method.getParameterCount())
+          .mapToObj(i -> "arg" + i)
+          .reduce("", (a, b) -> a.isEmpty() ? b : a + ", " + b);
+      final String returnString = method.getReturnType() == void.class ? "" : "return ";
+      if (r != null) {
+        final String type = r.invocationType() == Replicate.InvocationType.DIRECT ? "Direct" : "Client";
+        println("final Object[] args = {%s};", args);
+        println("%sinvoker.invokeReplicate%s(ReplicateMethod.%s, args);", returnString, type, method.getName());
+      } else {
+        println("%sinvoker.getImpl().%s(%s);", returnString, method.getName(), args);
+      }
+    }
+  }
+
+  void printProxyClass() {
+    printf("return new %s() {", apiName);
+    try (UncheckedAutoCloseable ignored = printScope(false, 1)) {
+      for (Method m : getMethods(null, false)) {
+        printProxyClassMethod(m);
+      }
+    }
+    println("};");
+  }
+
+  void printProxyMethod() {
+    println();
+    printf("static %s newProxy(ScmInvoker<%s> invoker)", apiName, apiName);
+    try (UncheckedAutoCloseable ignored = printScope()) {
+      printProxyClass();
+    }
+  }
+
+  public String generateClass() {
+    println("/** Code generated for {@link %s}.  Do not modify. */", apiName);
+    printf("public class %s extends ScmInvoker<%s>", invokerClassName, apiName);
+    try (UncheckedAutoCloseable ignored = printScope()) {
+      printEnum();
+      printHeaderMethods();
+      printProxyMethod();
+      printInvokeMethod(INVOKE_LOCAL);
+    }
+    return out.toString();
+  }
+
+  File updateFile(String classString) throws IOException {
+    final File java = new File(DIR, invokerClassName + ".java");
+    if (!java.isFile()) {
+      throw new FileNotFoundException("Not found: " + java.getAbsolutePath());
+    }
+    final File tmp = new File(DIR, invokerClassName + "_tmp.java");
+    if (tmp.exists()) {
+      throw new IOException("Already exist: " + java.getAbsolutePath());
+    }
+    try (InputStream inStream = Files.newInputStream(java.toPath());
+         BufferedReader in = new BufferedReader(new InputStreamReader(new BufferedInputStream(inStream), UTF_8));
+         OutputStream outStream = Files.newOutputStream(tmp.toPath(), StandardOpenOption.CREATE_NEW);
+         PrintWriter out = new PrintWriter(new OutputStreamWriter(outStream, UTF_8), true)) {
+      String line;
+      for (; (line = in.readLine()) != null;) {
+        out.println(line);
+        if (line.startsWith("import")) {
+          break;
+        }
+      }
+      for (; (line = in.readLine()) != null && line.startsWith("import");) {
+        out.println(line);
+      }
+      out.println();
+      out.print(classString);
+    }
+
+    Files.move(tmp.toPath(), java.toPath(), StandardCopyOption.REPLACE_EXISTING);
+    return java;
+  }
+
+  public static void generate(Class<?> api, boolean updateFile) {
+    final ScmInvokerCodeGenerator generator = new ScmInvokerCodeGenerator(api);
+    final String classString = generator.generateClass();
+    if (!updateFile) {
+      System.out.println(classString);
+      return;
+    }
+
+    final File file;
+    try {
+      file = generator.updateFile(classString);
+    } catch (IOException e) {
+      throw new IllegalStateException("Failed to updateFile", e);
+    }
+    System.out.printf("Successfully update file: %s%n", file);
+  }
+
+  static class DeclaredMethod {
+    private final String name;
+    private final Class<?>[] parameterTypes;
+    private final String[] parameterNames;
+    private final Class<?> returnType;
+    private final Class<?>[] exceptionTypes;
+
+    DeclaredMethod(String name, Class<?>[] parameterTypes, String[] parameterNames,
+        Class<?> returnType, Class<?>[] exceptionTypes) {
+      this.name = name;
+      this.parameterTypes = parameterTypes;
+      this.parameterNames = parameterNames;
+      this.returnType = returnType;
+      this.exceptionTypes = exceptionTypes;
+    }
+
+    String getParameterName(int i) {
+      return parameterNames[i];
+    }
+
+    String getSignature() {
+      return String.format("public %s %s(%s)%s",
+          getClassname(returnType),
+          name,
+          getParameterString(parameterTypes, parameterNames),
+          getThrowsString(exceptionTypes));
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@ozone.apache.org
For additional commands, e-mail: commits-help@ozone.apache.org