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 adaa88e864f7..86e3bbf5012b 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 @@ -23,7 +23,7 @@ import org.apache.hadoop.hdds.protocol.proto.HddsProtos.DeletedBlocksTransactionSummary; 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.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; @@ -70,6 +70,6 @@ void reinitialize(Table deletedBlocksTXTable, Table statefulConfigTable); static void main(String[] args) { - new ScmInvokerCodeGenerator(DeletedBlockLogStateManager.class, RequestType.BLOCK).generateClass(); + ScmInvokerCodeGenerator.generate(DeletedBlockLogStateManager.class, RequestType.BLOCK, 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 cbe181186447..000000000000 --- 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 String arrayToString(T[] objects, Function 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 ba1317b079b8..47ed5865ff5f 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,10 +17,8 @@ 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.SCMRatisProtocol.RequestType; import org.apache.hadoop.hdds.protocol.proto.StorageContainerDatanodeProtocolProtos.DeletedBlocksTransaction; @@ -28,29 +26,24 @@ 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 DeletedBlockLogStateManager. Do not modify. */ public class DeletedBlockLogStateManagerInvoker extends ScmInvoker { - 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 @@ -59,9 +52,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 @@ -74,98 +66,81 @@ public Class getApi() { return DeletedBlockLogStateManager.class; } - @Override - public DeletedBlockLogStateManager getImpl() { - return impl; - } - - @Override - public DeletedBlockLogStateManager getProxy() { + static DeletedBlockLogStateManager newProxy(ScmInvoker invoker) { return new DeletedBlockLogStateManager() { + @Override - public void addTransactionsToDB(ArrayList txs) - throws IOException { - final Object[] args = {txs}; - 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 addTransactionsToDB(ArrayList txs, - DeletedBlocksTransactionSummary summary) throws IOException { - final Object[] args = {txs, summary}; - invokeRatisServer(ReplicateMethod.addTransactionsToDB, args); + public void addTransactionsToDB(ArrayList arg0) throws IOException { + final Object[] args = {arg0}; + invoker.invokeReplicateDirect(ReplicateMethod.addTransactionsToDB, args); } @Override - public void removeTransactionsFromDB(ArrayList txIDs) - throws IOException { - final Object[] args = {txIDs}; - invokeRatisServer(ReplicateMethod.removeTransactionsFromDB, args); + public Table.KeyValueIterator getReadOnlyIterator() throws IOException { + return invoker.getImpl().getReadOnlyIterator(); } @Override - public void removeTransactionsFromDB(ArrayList txIDs, - DeletedBlocksTransactionSummary summary) throws IOException { - final Object[] args = {txIDs, summary}; - invokeRatisServer(ReplicateMethod.removeTransactionsFromDB, args); + public void onFlush() { + invoker.getImpl().onFlush(); } @Override - public Table.KeyValueIterator 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 deletedBlocksTXTable, - Table 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 322cf177ad61..82e3a5183f83 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.SCMRatisRequest; @@ -29,9 +30,13 @@ * Invokes methods without using reflection. */ public abstract class ScmInvoker { + private final T impl; + private final T proxy; private final SCMRatisServer ratisHandler; - ScmInvoker(SCMRatisServer ratisHandler) { + ScmInvoker(T impl, Function, T> proxy, SCMRatisServer ratisHandler) { + this.impl = impl; + this.proxy = proxy.apply(this); this.ratisHandler = ratisHandler; } @@ -39,16 +44,22 @@ public abstract class ScmInvoker { public abstract Class 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(); @@ -60,7 +71,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 000000000000..e73b47d9829d --- /dev/null +++ b/hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/ScmInvokerCodeGenerator.java @@ -0,0 +1,502 @@ +/* + * 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.protocol.proto.SCMRatisProtocol.RequestType; +import org.apache.hadoop.hdds.scm.metadata.Replicate; +import org.apache.ratis.util.Preconditions; +import org.apache.ratis.util.UncheckedAutoCloseable; + +/** + * A tool to (manually) generate code for copy-paste. + */ +public final class ScmInvokerCodeGenerator { + 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 requestTypeName; + private final String invokerClassName; + + private final StringWriter out = new StringWriter(); + private String indentation = ""; + + private 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) { + printf(true, format, args); + } + + void printf(boolean indent, String format, Object... args) { + if (indent) { + out.append(indentation); + } + out.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); + out.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 String arrayToString(T[] objects, Function 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 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 getMethods(Boolean isDefault, Boolean isDeprecated) { + return getMethods(getFilter(isDefault, isDeprecated)); + } + + List getMethods(Predicate filter) { + return Arrays.stream(api.getMethods()) + .filter(filter) + .sorted(Comparator.comparing(Method::getName)) + .collect(Collectors.toList()); + } + + void printEnum() { + printf("enum ReplicateMethod implements NameAndParameterTypes"); + try (UncheckedAutoCloseable ignore = printScope()) { + final List apiMethods = getMethods(null, null); + for (int i = 0; i < apiMethods.size(); i++) { + final Method m = apiMethods.get(i); + if (m.isDefault() || m.getAnnotation(Replicate.class) == null) { + continue; + } + if (i > 0) { + println(false, ","); + } + + final List 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 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 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 RequestType getType()"); + try (UncheckedAutoCloseable ignore = printScope()) { + println("return RequestType.%s;", requestTypeName); + } + + 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) { + 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 Replicate r = method.getAnnotation(Replicate.class); + 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() { + out.getBuffer().setLength(0); + println("/** Code generated for %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 String dir = "hadoop-hdds/server-scm/src/main/java/org/apache/hadoop/hdds/scm/ha/invoker/"; + 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, RequestType type, boolean updateFile) { + final ScmInvokerCodeGenerator generator = new ScmInvokerCodeGenerator(api, type); + 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)); + } + } +}