diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java new file mode 100644 index 00000000000..fcce47feaf1 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/AdaptiveClassCodeGenerator.java @@ -0,0 +1,381 @@ +/* + * 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.dubbo.common.extension; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; + +/** + * Code generator for Adaptive class + */ +public class AdaptiveClassCodeGenerator { + + private static final Logger logger = LoggerFactory.getLogger(AdaptiveClassCodeGenerator.class); + + private static final String CLASSNAME_INVOCATION = "org.apache.dubbo.rpc.Invocation"; + + private static final String CODE_PACKAGE = "package %s;\n"; + + private static final String CODE_IMPORTS = "import %s;\n"; + + private static final String CODE_CLASS_DECLARATION = "public class %s$Adaptive implements %s {\n"; + + private static final String CODE_METHOD_DECLARATION = "public %s %s(%s) %s {\n%s}\n"; + + private static final String CODE_METHOD_ARGUMENT = "%s arg%d"; + + private static final String CODE_METHOD_THROWS = "throws %s"; + + private static final String CODE_UNSUPPORTED = "throw new UnsupportedOperationException(\"The method %s of interface %s is not adaptive method!\");\n"; + + private static final String CODE_URL_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n"; + + private static final String CODE_EXT_NAME_ASSIGNMENT = "String extName = %s;\n"; + + private static final String CODE_EXT_NAME_NULL_CHECK = "if(extName == null) " + + "throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n"; + + private static final String CODE_INVOCATION_ARGUMENT_NULL_CHECK = "if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\"); " + + "String methodName = arg%d.getMethodName();\n"; + + + private static final String CODE_EXTENSION_ASSIGNMENT = "%s extension = (% type; + + private String defaultExtName; + + public AdaptiveClassCodeGenerator(Class type, String defaultExtName) { + this.type = type; + this.defaultExtName = defaultExtName; + } + + /** + * test if given type has at least one method annotated with SPI + */ + private boolean hasAdaptiveMethod() { + return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class)); + } + + /** + * generate and return class code + */ + public String generate() { + // no need to generate adaptive class since there's no adaptive method found. + if (!hasAdaptiveMethod()) { + throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!"); + } + + StringBuilder code = new StringBuilder(); + code.append(generatePackageInfo()); + code.append(generateImports()); + code.append(generateClassDeclaration()); + + Method[] methods = type.getMethods(); + for (Method method : methods) { + code.append(generateMethod(method)); + } + code.append("}"); + + if (logger.isDebugEnabled()) { + logger.debug(code.toString()); + } + return code.toString(); + } + + /** + * generate package info + */ + private String generatePackageInfo() { + return String.format(CODE_PACKAGE, type.getPackage().getName()); + } + + /** + * generate imports + */ + private String generateImports() { + return String.format(CODE_IMPORTS, ExtensionLoader.class.getName()); + } + + /** + * generate class declaration + */ + private String generateClassDeclaration() { + return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName()); + } + + /** + * generate method not annotated with Adaptive with throwing unsupported exception + */ + private String generateUnsupported(Method method) { + return String.format(CODE_UNSUPPORTED, method, type.getName()); + } + + /** + * get index of parameter with type URL + */ + private int getUrlTypeIndex(Method method) { + int urlTypeIndex = -1; + Class[] pts = method.getParameterTypes(); + for (int i = 0; i < pts.length; ++i) { + if (pts[i].equals(URL.class)) { + urlTypeIndex = i; + break; + } + } + return urlTypeIndex; + } + + /** + * generate method declaration + */ + private String generateMethod(Method method) { + String methodReturnType = method.getReturnType().getCanonicalName(); + String methodName = method.getName(); + String methodContent = generateMethodContent(method); + String methodArgs = generateMethodArguments(method); + String methodThrows = generateMethodThrows(method); + return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent); + } + + /** + * generate method arguments + */ + private String generateMethodArguments(Method method) { + Class[] pts = method.getParameterTypes(); + return IntStream.range(0, pts.length) + .mapToObj(i -> String.format(CODE_METHOD_ARGUMENT, pts[i].getCanonicalName(), i)) + .collect(Collectors.joining(", ")); + } + + /** + * generate method throws + */ + private String generateMethodThrows(Method method) { + Class[] ets = method.getExceptionTypes(); + if (ets.length > 0) { + String list = Arrays.stream(ets).map(Class::getCanonicalName).collect(Collectors.joining(", ")); + return String.format(CODE_METHOD_THROWS, list); + } else { + return ""; + } + } + + /** + * generate method URL argument null check + */ + private String generateUrlNullCheck(int index) { + return String.format(CODE_URL_NULL_CHECK, index, URL.class.getName(), index); + } + + /** + * generate method content + */ + private String generateMethodContent(Method method) { + Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); + StringBuilder code = new StringBuilder(512); + if (adaptiveAnnotation == null) { + return generateUnsupported(method); + } else { + int urlTypeIndex = getUrlTypeIndex(method); + + // found parameter in URL type + if (urlTypeIndex != -1) { + // Null Point check + code.append(generateUrlNullCheck(urlTypeIndex)); + } else { + // did not find parameter in URL type + code.append(generateUrlAssignmentIndirectly(method)); + } + + String[] value = getMethodAdaptiveValue(adaptiveAnnotation); + + boolean hasInvocation = hasInvocationArgument(method); + + code.append(generateInvocationArgumentNullCheck(method)); + + code.append(generateExtNameAssignment(value, hasInvocation)); + // check extName == null? + code.append(generateExtNameNullCheck(value)); + + code.append(generateExtensionAssignment()); + + // return statement + code.append(generateReturnAndInovation(method)); + } + + return code.toString(); + } + + /** + * generate code for variable extName null check + */ + private String generateExtNameNullCheck(String[] value) { + return String.format(CODE_EXT_NAME_NULL_CHECK, type.getName(), Arrays.toString(value)); + } + + /** + * generate extName assigment code + */ + private String generateExtNameAssignment(String[] value, boolean hasInvocation) { + // TODO: refactor it + String getNameCode = null; + for (int i = value.length - 1; i >= 0; --i) { + if (i == value.length - 1) { + if (null != defaultExtName) { + if (!"protocol".equals(value[i])) { + if (hasInvocation) { + getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); + } else { + getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); + } + } else { + getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); + } + } else { + if (!"protocol".equals(value[i])) { + if (hasInvocation) { + getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); + } else { + getNameCode = String.format("url.getParameter(\"%s\")", value[i]); + } + } else { + getNameCode = "url.getProtocol()"; + } + } + } else { + if (!"protocol".equals(value[i])) { + if (hasInvocation) { + getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); + } else { + getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); + } + } else { + getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); + } + } + } + + return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode); + } + + /** + * @return + */ + private String generateExtensionAssignment() { + return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); + } + + /** + * generate method invocation statement and return it if necessary + */ + private String generateReturnAndInovation(Method method) { + String returnStatement = method.getReturnType().equals(void.class) ? "" : "return "; + + String args = Arrays.stream(method.getParameters()).map(Parameter::getName).collect(Collectors.joining(", ")); + + return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args); + } + + /** + * test if method has argument of type Invocation + */ + private boolean hasInvocationArgument(Method method) { + Class[] pts = method.getParameterTypes(); + return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName())); + } + + /** + * generate code to test argument of type Invocation is null + */ + private String generateInvocationArgumentNullCheck(Method method) { + Class[] pts = method.getParameterTypes(); + return IntStream.range(0, pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName())) + .mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i)) + .findFirst().orElse(""); + } + + /** + * get value of adaptive annotation or if empty return splitted simple name + */ + private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) { + String[] value = adaptiveAnnotation.value(); + // value is not set, use the value generated from class name as the key + if (value.length == 0) { + String splitName = StringUtils.camelToSplitName(type.getSimpleName(), "."); + value = new String[]{splitName}; + } + return value; + } + + /** + * get parameter with type URL from method parameter: + *

+ * test if parameter has method which returns type URL + *

+ * if not found, throws IllegalStateException + */ + private String generateUrlAssignmentIndirectly(Method method) { + Class[] pts = method.getParameterTypes(); + + // find URL getter method + for (int i = 0; i < pts.length; ++i) { + for (Method m : pts[i].getMethods()) { + String name = m.getName(); + if ((name.startsWith("get") || name.length() > 3) + && Modifier.isPublic(m.getModifiers()) + && !Modifier.isStatic(m.getModifiers()) + && m.getParameterTypes().length == 0 + && m.getReturnType() == URL.class) { + return generateGetUrlNullCheck(i, pts[i], name); + } + } + } + + // getter method not found, throw + throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName() + + ": not found url parameter or url attribute in parameters of method " + method.getName()); + + } + + /** + * 1, test if argi is null + * 2, test if argi.getXX() returns null + * 3, assign url with argi.getXX() + */ + private String generateGetUrlNullCheck(int index, Class type, String method) { + // Null point check + StringBuilder code = new StringBuilder(); + code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n", + index, type.getName())); + code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n", + index, method, type.getName(), method)); + + code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method)); + return code.toString(); + } + +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java index aa32b8f0fbb..d349ea06224 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java @@ -781,212 +781,12 @@ private Class getAdaptiveExtensionClass() { } private Class createAdaptiveExtensionClass() { - String code = createAdaptiveExtensionClassCode(); + String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); } - private String createAdaptiveExtensionClassCode() { - StringBuilder codeBuilder = new StringBuilder(); - Method[] methods = type.getMethods(); - boolean hasAdaptiveAnnotation = false; - for (Method m : methods) { - if (m.isAnnotationPresent(Adaptive.class)) { - hasAdaptiveAnnotation = true; - break; - } - } - // no need to generate adaptive class since there's no adaptive method found. - if (!hasAdaptiveAnnotation) { - throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!"); - } - - codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); - codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); - codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {"); - - for (Method method : methods) { - Class rt = method.getReturnType(); - Class[] pts = method.getParameterTypes(); - Class[] ets = method.getExceptionTypes(); - - Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); - StringBuilder code = new StringBuilder(512); - if (adaptiveAnnotation == null) { - code.append("throw new UnsupportedOperationException(\"The method ") - .append(method.toString()).append(" of interface ") - .append(type.getName()).append(" is not adaptive method!\");"); - } else { - int urlTypeIndex = -1; - for (int i = 0; i < pts.length; ++i) { - if (pts[i].equals(URL.class)) { - urlTypeIndex = i; - break; - } - } - // found parameter in URL type - if (urlTypeIndex != -1) { - // Null Point check - String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", - urlTypeIndex); - code.append(s); - - s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); - code.append(s); - } - // did not find parameter in URL type - else { - String attribMethod = null; - - // find URL getter method - LBL_PTS: - for (int i = 0; i < pts.length; ++i) { - Method[] ms = pts[i].getMethods(); - for (Method m : ms) { - String name = m.getName(); - if ((name.startsWith("get") || name.length() > 3) - && Modifier.isPublic(m.getModifiers()) - && !Modifier.isStatic(m.getModifiers()) - && m.getParameterTypes().length == 0 - && m.getReturnType() == URL.class) { - urlTypeIndex = i; - attribMethod = name; - break LBL_PTS; - } - } - } - if (attribMethod == null) { - throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName() - + ": not found url parameter or url attribute in parameters of method " + method.getName()); - } - - // Null point check - String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", - urlTypeIndex, pts[urlTypeIndex].getName()); - code.append(s); - s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", - urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); - code.append(s); - - s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod); - code.append(s); - } - - String[] value = adaptiveAnnotation.value(); - // value is not set, use the value generated from class name as the key - if (value.length == 0) { - String splitName = StringUtils.camelToSplitName(type.getSimpleName(), "."); - value = new String[]{splitName}; - } - - boolean hasInvocation = false; - for (int i = 0; i < pts.length; ++i) { - if (("org.apache.dubbo.rpc.Invocation").equals(pts[i].getName())) { - // Null Point check - String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i); - code.append(s); - s = String.format("\nString methodName = arg%d.getMethodName();", i); - code.append(s); - hasInvocation = true; - break; - } - } - - String defaultExtName = cachedDefaultName; - String getNameCode = null; - for (int i = value.length - 1; i >= 0; --i) { - if (i == value.length - 1) { - if (null != defaultExtName) { - if (!"protocol".equals(value[i])) { - if (hasInvocation) { - getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); - } else { - getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); - } - } else { - getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); - } - } else { - if (!"protocol".equals(value[i])) { - if (hasInvocation) { - getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); - } else { - getNameCode = String.format("url.getParameter(\"%s\")", value[i]); - } - } else { - getNameCode = "url.getProtocol()"; - } - } - } else { - if (!"protocol".equals(value[i])) { - if (hasInvocation) { - getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); - } else { - getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); - } - } else { - getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); - } - } - } - code.append("\nString extName = ").append(getNameCode).append(";"); - // check extName == null? - String s = String.format("\nif(extName == null) " + - "throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");", - type.getName(), Arrays.toString(value)); - code.append(s); - - s = String.format("\n%s extension = (% 0) { - codeBuilder.append(", "); - } - codeBuilder.append(pts[i].getCanonicalName()); - codeBuilder.append(" "); - codeBuilder.append("arg").append(i); - } - codeBuilder.append(")"); - if (ets.length > 0) { - codeBuilder.append(" throws "); - for (int i = 0; i < ets.length; i++) { - if (i > 0) { - codeBuilder.append(", "); - } - codeBuilder.append(ets[i].getCanonicalName()); - } - } - codeBuilder.append(" {"); - codeBuilder.append(code.toString()); - codeBuilder.append("\n}"); - } - codeBuilder.append("\n}"); - if (logger.isDebugEnabled()) { - logger.debug(codeBuilder.toString()); - } - return codeBuilder.toString(); - } - @Override public String toString() { return this.getClass().getName() + "[" + type.getName() + "]";