typeArgMapper) {
+ if (!hasSignature(method)) {
+ return null;
+ }
+
+ return getSignature(method, typeArgMapper);
+ }
+
+ private static boolean hasSignature(MethodInfo method) {
+ // JVMS 16, chapter 4.7.9.1. Signatures:
+ //
+ // Java compiler must emit ...
+ //
+ // A method signature for any method or constructor declaration which is either generic,
+ // or has a type variable or parameterized type as the return type or a formal parameter type,
+ // or has a type variable in a throws clause, or any combination thereof.
+
+ if (!method.typeParameters().isEmpty()) {
+ return true;
+ }
+
+ {
+ Type type = method.returnType();
+ if (type.kind() == Kind.TYPE_VARIABLE
+ || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE
+ || type.kind() == Kind.PARAMETERIZED_TYPE) {
+ return true;
+ }
+ }
+
+ for (Type type : method.parameters()) {
+ if (type.kind() == Kind.TYPE_VARIABLE
+ || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE
+ || type.kind() == Kind.PARAMETERIZED_TYPE) {
+ return true;
+ }
+ }
+
+ if (hasThrowsSignature(method)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean hasThrowsSignature(MethodInfo method) {
+ // JVMS 16, chapter 4.7.9.1. Signatures:
+ //
+ // If the throws clause of a method or constructor declaration does not involve type variables,
+ // then a compiler may treat the declaration as having no throws clause for the purpose of
+ // emitting a method signature.
+
+ // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because
+ //
+ // JLS 16, chapter 8.1.2. Generic Classes and Type Parameters:
+ //
+ // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable.
+
+ for (Type type : method.exceptions()) {
+ if (type.kind() == Kind.TYPE_VARIABLE
+ || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings.
* For example, given this method:
- *
+ *
*
* {@code
* public class Foo {
- * public List method(int a, T t){...}
+ * public List method(int a, T t){...}
* }
* }
*
- *
+ *
* This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if
* your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}.
- *
+ *
* @param method the method you want the signature for.
* @param typeArgMapper a mapping between type argument names and their bytecode signature.
* @return a bytecode signature for that method.
*/
public static String getSignature(MethodInfo method, Function typeArgMapper) {
- List parameters = method.parameters();
+ // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures
- StringBuilder signature = new StringBuilder("");
- for (TypeVariable typeVariable : method.typeParameters()) {
- if (signature.length() == 0)
- signature.append("<");
- else
- signature.append(",");
- signature.append(typeVariable.identifier()).append(":");
- // FIXME: only use the first bound
- toSignature(signature, typeVariable.bounds().get(0), typeArgMapper, false);
+ StringBuilder signature = new StringBuilder();
+
+ if (!method.typeParameters().isEmpty()) {
+ signature.append('<');
+ for (TypeVariable typeParameter : method.typeParameters()) {
+ typeParameter(typeParameter, signature, typeArgMapper);
+ }
+ signature.append('>');
}
- if (signature.length() > 0)
- signature.append(">");
- signature.append("(");
- for (Type type : parameters) {
+
+ signature.append('(');
+ for (Type type : method.parameters()) {
toSignature(signature, type, typeArgMapper, false);
}
- signature.append(")");
+ signature.append(')');
+
toSignature(signature, method.returnType(), typeArgMapper, false);
+
+ if (hasThrowsSignature(method)) {
+ for (Type exception : method.exceptions()) {
+ signature.append('^');
+ toSignature(signature, exception, typeArgMapper, false);
+ }
+ }
+
return signature.toString();
}
+ private static void typeParameter(TypeVariable typeParameter, StringBuilder result,
+ Function typeArgMapper) {
+ result.append(typeParameter.identifier());
+
+ if (hasImplicitObjectBound(typeParameter)) {
+ result.append(':');
+ }
+ for (Type bound : typeParameter.bounds()) {
+ result.append(':');
+ toSignature(result, bound, typeArgMapper, false);
+ }
+ }
+
+ private static boolean hasImplicitObjectBound(TypeVariable typeParameter) {
+ // TODO is there a better way? :-/
+ boolean result = false;
+ try {
+ Method method = TypeVariable.class.getDeclaredMethod("hasImplicitObjectBound");
+ method.setAccessible(true);
+ result = (Boolean) method.invoke(typeParameter);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ return result;
+ }
+
/**
* Returns the Java bytecode descriptor of a given Jandex MethodInfo using the given type argument mappings.
* For example, given this method:
@@ -176,30 +299,35 @@ private static void toSignature(StringBuilder sb, Type type, Function arguments = parameterizedType.arguments();
- for (int i = 0; i < arguments.size(); i++) {
- Type argType = arguments.get(i);
- toSignature(sb, argType, typeArgMapper, erased);
+ sb.append('<');
+ for (Type argument : parameterizedType.arguments()) {
+ toSignature(sb, argument, typeArgMapper, erased);
}
- sb.append(">");
+ sb.append('>');
}
- sb.append(";");
+ sb.append(';');
break;
case PRIMITIVE:
Primitive primitive = type.asPrimitiveType().primitive();
@@ -233,22 +361,40 @@ private static void toSignature(StringBuilder sb, Type type, Function;U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;Lio/quarkus/deployment/util/OuterRaw;)TT;");
+ assertSignature(OuterRaw.NestedRaw.class, "ccc",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterRaw$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;");
+ assertSignature(OuterRaw.NestedParam.class, "ddd",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterRaw$NestedParam;)TT;");
+ assertSignature(OuterRaw.NestedParamBound.class, "eee",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterRaw$NestedParamBound;)TT;^TV;");
+ assertSignature(OuterRaw.InnerRaw.class, "fff",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterRaw$InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;");
+ assertSignature(OuterRaw.InnerParam.class, "ggg",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParam;)TT;");
+ assertSignature(OuterRaw.InnerParamBound.class, "hhh",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParamBound;)TT;^TV;");
+ assertSignature(OuterRaw.InnerParamBound.DoubleInner.class, "iii",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;Lio/quarkus/deployment/util/OuterRaw$InnerParamBound.DoubleInner;)TT;^TV;");
+
+ assertSignature(OuterParam.class, "aaa",
+ "(Ljava/lang/String;TW;Lio/quarkus/deployment/util/OuterParam;)Ljava/lang/String;");
+ assertSignature(OuterParam.class, "bbb",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lio/quarkus/deployment/util/OuterParam;)TT;");
+ assertSignature(OuterParam.NestedRaw.class, "ccc",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterParam$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;");
+ assertSignature(OuterParam.NestedParam.class, "ddd",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterParam$NestedParam;)TT;");
+ assertSignature(OuterParam.NestedParamBound.class, "eee",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterParam$NestedParamBound;)TT;^TV;");
+ assertSignature(OuterParam.InnerRaw.class, "fff",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/deployment/util/OuterParam.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;");
+ assertSignature(OuterParam.InnerParam.class, "ggg",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParam;)TT;");
+ assertSignature(OuterParam.InnerParamBound.class, "hhh",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParamBound;)TT;^TV;");
+ assertSignature(OuterParam.InnerParamBound.DoubleInner.class, "iii",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;TW;Lio/quarkus/deployment/util/OuterParam.InnerParamBound.DoubleInner;)TT;^TV;");
+
+ assertSignature(OuterParamBound.class, "aaa",
+ "(Ljava/lang/String;TW;Lio/quarkus/deployment/util/OuterParamBound;)Ljava/lang/String;");
+ assertSignature(OuterParamBound.class, "bbb",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Lio/quarkus/deployment/util/OuterParamBound;)TT;");
+ assertSignature(OuterParamBound.NestedRaw.class, "ccc",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;Lio/quarkus/deployment/util/OuterParamBound$NestedRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;");
+ assertSignature(OuterParamBound.NestedParam.class, "ddd",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;Lio/quarkus/deployment/util/OuterParamBound$NestedParam;)TT;");
+ assertSignature(OuterParamBound.NestedParamBound.class, "eee",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;Lio/quarkus/deployment/util/OuterParamBound$NestedParamBound;)TT;^TV;");
+ assertSignature(OuterParamBound.InnerRaw.class, "fff",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerRaw;)TT;^Ljava/lang/IllegalArgumentException;^TV;");
+ assertSignature(OuterParamBound.InnerParam.class, "ggg",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<-TU;>;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParam;)TT;");
+ assertSignature(OuterParamBound.InnerParamBound.class, "hhh",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParamBound;)TT;^TV;");
+ assertSignature(OuterParamBound.InnerParamBound.DoubleInner.class, "iii",
+ ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<*>;TY;TX;TW;Lio/quarkus/deployment/util/OuterParamBound.InnerParamBound.DoubleInner;)TT;^TV;");
+ }
+
+ private static void assertSignature(Class> clazz, String method, String expectedSignature) {
+ DotName name = DotName.createSimple(clazz.getName());
+ for (MethodInfo methodInfo : index.getClassByName(name).methods()) {
+ if (method.equals(methodInfo.name())) {
+ String actualSignature = AsmUtil.getSignatureIfRequired(methodInfo);
+ assertEquals(expectedSignature, actualSignature);
+ return;
+ }
+ }
+
+ fail("Couldn't find method " + clazz.getName() + "#" + method + " in test index");
+ }
+
+ private static Index index(Class>... classes) {
+ Indexer indexer = new Indexer();
+ for (Class> clazz : classes) {
+ try {
+ try (InputStream stream = MethodGenericSignatureReconstructionTest.class.getClassLoader()
+ .getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
+ indexer.index(stream);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return indexer.complete();
+ }
+}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java
new file mode 100644
index 0000000000000..2a849c8e8120d
--- /dev/null
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParam.java
@@ -0,0 +1,63 @@
+package io.quarkus.deployment.util;
+
+import java.util.List;
+
+public class OuterParam {
+ public String aaa(String arg, W arg2, OuterParam self) throws IllegalArgumentException {
+ return null;
+ }
+
+ public , U extends Comparable, V extends Exception> T bbb(
+ U arg, W arg2, OuterParam self) {
+ return null;
+ }
+
+ public static class NestedRaw {
+ public , U extends Comparable, V extends Exception> T ccc(
+ List extends U> arg, NestedRaw self) throws IllegalArgumentException, V {
+ return null;
+ }
+ }
+
+ public static class NestedParam {
+ public , U extends Comparable, V extends Exception> T ddd(
+ List super U> arg, X arg2, NestedParam self) throws IllegalArgumentException {
+ return null;
+ }
+ }
+
+ public static class NestedParamBound> {
+ public , U extends Comparable, V extends Exception> T eee(
+ List> arg, X arg2, NestedParamBound self) throws V {
+ return null;
+ }
+ }
+
+ public class InnerRaw {
+ public , U extends Comparable, V extends Exception> T fff(
+ List extends U> arg, W arg2, InnerRaw self) throws IllegalArgumentException, V {
+ return null;
+ }
+ }
+
+ public class InnerParam {
+ public , U extends Comparable, V extends Exception> T ggg(
+ List super U> arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException {
+ return null;
+ }
+ }
+
+ public class InnerParamBound> {
+ public , U extends Comparable, V extends Exception> T hhh(
+ List> arg, X arg2, W arg3, InnerParamBound self) throws V {
+ return null;
+ }
+
+ public class DoubleInner {
+ public , U extends Comparable, V extends Exception> T iii(
+ List> arg, Y arg2, X arg3, W arg4, DoubleInner self) throws V {
+ return null;
+ }
+ }
+ }
+}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java
new file mode 100644
index 0000000000000..64097bc389245
--- /dev/null
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterParamBound.java
@@ -0,0 +1,63 @@
+package io.quarkus.deployment.util;
+
+import java.util.List;
+
+public class OuterParamBound> {
+ public String aaa(String arg, W arg2, OuterParamBound self) throws IllegalArgumentException {
+ return null;
+ }
+
+ public , U extends Comparable, V extends Exception> T bbb(
+ U arg, W arg2, OuterParamBound self) {
+ return null;
+ }
+
+ public static class NestedRaw {
+ public , U extends Comparable, V extends Exception> T ccc(
+ List extends U> arg, NestedRaw self) throws IllegalArgumentException, V {
+ return null;
+ }
+ }
+
+ public static class NestedParam {
+ public , U extends Comparable, V extends Exception> T ddd(
+ List super U> arg, X arg2, NestedParam self) throws IllegalArgumentException {
+ return null;
+ }
+ }
+
+ public static class NestedParamBound> {
+ public , U extends Comparable, V extends Exception> T eee(
+ List> arg, X arg2, NestedParamBound self) throws V {
+ return null;
+ }
+ }
+
+ public class InnerRaw {
+ public , U extends Comparable, V extends Exception> T fff(
+ List extends U> arg, W arg2, InnerRaw self) throws IllegalArgumentException, V {
+ return null;
+ }
+ }
+
+ public class InnerParam {
+ public , U extends Comparable, V extends Exception> T ggg(
+ List super U> arg, X arg2, W arg3, InnerParam self) throws IllegalArgumentException {
+ return null;
+ }
+ }
+
+ public class InnerParamBound> {
+ public , U extends Comparable, V extends Exception> T hhh(
+ List> arg, X arg2, W arg3, InnerParamBound self) throws V {
+ return null;
+ }
+
+ public class DoubleInner {
+ public , U extends Comparable, V extends Exception> T iii(
+ List> arg, Y arg2, X arg3, W arg4, DoubleInner self) throws V {
+ return null;
+ }
+ }
+ }
+}
diff --git a/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java
new file mode 100644
index 0000000000000..e78160420b0ca
--- /dev/null
+++ b/core/deployment/src/test/java/io/quarkus/deployment/util/OuterRaw.java
@@ -0,0 +1,63 @@
+package io.quarkus.deployment.util;
+
+import java.util.List;
+
+public class OuterRaw {
+ public String aaa(String arg, OuterRaw self) throws IllegalArgumentException {
+ return null;
+ }
+
+ public , U extends Comparable, V extends Exception> T bbb(
+ U arg, OuterRaw self) {
+ return null;
+ }
+
+ public static class NestedRaw {
+ public , U extends Comparable, V extends Exception> T ccc(
+ List extends U> arg, NestedRaw self) throws IllegalArgumentException, V {
+ return null;
+ }
+ }
+
+ public static class NestedParam {
+ public , U extends Comparable, V extends Exception> T ddd(
+ List super U> arg, X arg2, NestedParam self) throws IllegalArgumentException {
+ return null;
+ }
+ }
+
+ public static class NestedParamBound> {
+ public , U extends Comparable, V extends Exception> T eee(
+ List> arg, X arg2, NestedParamBound self) throws V {
+ return null;
+ }
+ }
+
+ public class InnerRaw {
+ public , U extends Comparable, V extends Exception> T fff(
+ List extends U> arg, InnerRaw self) throws IllegalArgumentException, V {
+ return null;
+ }
+ }
+
+ public class InnerParam {
+ public , U extends Comparable, V extends Exception> T ggg(
+ List super U> arg, X arg2, InnerParam self) throws IllegalArgumentException {
+ return null;
+ }
+ }
+
+ public class InnerParamBound> {
+ public , U extends Comparable, V extends Exception> T hhh(
+ List> arg, X arg2, InnerParamBound self) throws V {
+ return null;
+ }
+
+ public class DoubleInner {
+ public , U extends Comparable, V extends Exception> T iii(
+ List> arg, Y arg2, X arg3, DoubleInner self) throws V {
+ return null;
+ }
+ }
+ }
+}
diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java
index 8894a15c756b1..626a5cc8a028f 100644
--- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java
+++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/RestClientReactiveProcessor.java
@@ -58,6 +58,7 @@
import io.quarkus.deployment.builditem.ConfigurationTypeBuildItem;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.deployment.util.AsmUtil;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
@@ -307,21 +308,14 @@ void addRestClientBeans(Capabilities capabilities,
// return ((InterfaceClass)this.getDelegate()).get();
// }
MethodCreator methodCreator = classCreator.getMethodCreator(MethodDescriptor.of(method));
+ methodCreator.setSignature(AsmUtil.getSignatureIfRequired(method));
// copy method annotations, there can be interceptors bound to them:
for (AnnotationInstance annotation : method.annotations()) {
if (annotation.target().kind() == AnnotationTarget.Kind.METHOD
&& !BUILTIN_HTTP_ANNOTATIONS_TO_METHOD.containsKey(annotation.name())
&& !ResteasyReactiveDotNames.PATH.equals(annotation.name())) {
- AnnotationValue value = annotation.value();
- if (value != null && value.kind() == AnnotationValue.Kind.ARRAY
- && value.componentKind() == AnnotationValue.Kind.NESTED) {
- for (AnnotationInstance annotationInstance : value.asNestedArray()) {
- methodCreator.addAnnotation(annotationInstance);
- }
- } else {
- methodCreator.addAnnotation(annotation);
- }
+ methodCreator.addAnnotation(annotation);
}
}
diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java
index dc66f3b6bb255..750b68ef5e3e6 100644
--- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java
@@ -4,6 +4,7 @@
import static org.objectweb.asm.Type.BOOLEAN_TYPE;
import static org.objectweb.asm.Type.BYTE_TYPE;
import static org.objectweb.asm.Type.CHAR_TYPE;
+import static org.objectweb.asm.Type.DOUBLE_TYPE;
import static org.objectweb.asm.Type.FLOAT_TYPE;
import static org.objectweb.asm.Type.INT_TYPE;
import static org.objectweb.asm.Type.LONG_TYPE;
@@ -11,12 +12,14 @@
import static org.objectweb.asm.Type.VOID_TYPE;
import static org.objectweb.asm.Type.getType;
+import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.jboss.jandex.ArrayType;
+import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.ParameterizedType;
@@ -24,6 +27,8 @@
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;
import org.jboss.jandex.TypeVariable;
+import org.jboss.jandex.UnresolvedTypeVariable;
+import org.jboss.jandex.WildcardType;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -42,7 +47,8 @@ public class AsmUtil {
SHORT_TYPE,
INT_TYPE,
FLOAT_TYPE,
- LONG_TYPE);
+ LONG_TYPE,
+ DOUBLE_TYPE);
public static final List WRAPPERS = asList(
getType(Void.class),
getType(Boolean.class),
@@ -51,7 +57,8 @@ public class AsmUtil {
getType(Short.class),
getType(Integer.class),
getType(Float.class),
- getType(Long.class));
+ getType(Long.class),
+ getType(Double.class));
public static final Map WRAPPER_TO_PRIMITIVE = new HashMap<>();
static {
@@ -64,49 +71,168 @@ public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) {
return WRAPPERS.get(primitive.getSort());
}
+ /**
+ * Returns the Java bytecode signature of a given Jandex MethodInfo.
+ * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead.
+ *
+ * @param method the method you want the signature for
+ * @return a bytecode signature for that method, or {@code null} if signature is not required
+ */
+ public static String getSignatureIfRequired(MethodInfo method) {
+ return getSignatureIfRequired(method, ignored -> null);
+ }
+
+ /**
+ * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings.
+ * If the Java compiler doesn't have to emit a signature for the method, {@code null} is returned instead.
+ *
+ * @param method the method you want the signature for
+ * @param typeArgMapper a mapping between type argument names and their bytecode signatures
+ * @return a bytecode signature for that method, or {@code null} if signature is not required
+ */
+ public static String getSignatureIfRequired(MethodInfo method, Function typeArgMapper) {
+ if (!hasSignature(method)) {
+ return null;
+ }
+
+ return getSignature(method, typeArgMapper);
+ }
+
+ private static boolean hasSignature(MethodInfo method) {
+ // JVMS 16, chapter 4.7.9.1. Signatures:
+ //
+ // Java compiler must emit ...
+ //
+ // A method signature for any method or constructor declaration which is either generic,
+ // or has a type variable or parameterized type as the return type or a formal parameter type,
+ // or has a type variable in a throws clause, or any combination thereof.
+
+ if (!method.typeParameters().isEmpty()) {
+ return true;
+ }
+
+ {
+ Type type = method.returnType();
+ if (type.kind() == Kind.TYPE_VARIABLE
+ || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE
+ || type.kind() == Kind.PARAMETERIZED_TYPE) {
+ return true;
+ }
+ }
+
+ for (Type type : method.parameters()) {
+ if (type.kind() == Kind.TYPE_VARIABLE
+ || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE
+ || type.kind() == Kind.PARAMETERIZED_TYPE) {
+ return true;
+ }
+ }
+
+ if (hasThrowsSignature(method)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean hasThrowsSignature(MethodInfo method) {
+ // JVMS 16, chapter 4.7.9.1. Signatures:
+ //
+ // If the throws clause of a method or constructor declaration does not involve type variables,
+ // then a compiler may treat the declaration as having no throws clause for the purpose of
+ // emitting a method signature.
+
+ // also, no need to check if an exception type is of kind PARAMETERIZED_TYPE, because
+ //
+ // JLS 16, chapter 8.1.2. Generic Classes and Type Parameters:
+ //
+ // It is a compile-time error if a generic class is a direct or indirect subclass of Throwable.
+
+ for (Type type : method.exceptions()) {
+ if (type.kind() == Kind.TYPE_VARIABLE
+ || type.kind() == Kind.UNRESOLVED_TYPE_VARIABLE) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings.
* For example, given this method:
- *
+ *
*
* {@code
* public class Foo {
- * public List method(int a, T t){...}
+ * public List method(int a, T t){...}
* }
* }
*
- *
+ *
* This will return <R:Ljava/lang/Object;>(ILjava/lang/Integer;)Ljava/util/List<TR;>; if
* your {@code typeArgMapper} contains {@code T=Ljava/lang/Integer;}.
- *
+ *
* @param method the method you want the signature for.
* @param typeArgMapper a mapping between type argument names and their bytecode signature.
* @return a bytecode signature for that method.
*/
public static String getSignature(MethodInfo method, Function typeArgMapper) {
- List parameters = method.parameters();
+ // for grammar, see JVMS 16, chapter 4.7.9.1. Signatures
- StringBuilder signature = new StringBuilder("");
- for (TypeVariable typeVariable : method.typeParameters()) {
- if (signature.length() == 0)
- signature.append("<");
- else
- signature.append(",");
- signature.append(typeVariable.identifier()).append(":");
- // FIXME: only use the first bound
- toSignature(signature, typeVariable.bounds().get(0), typeArgMapper, false);
+ StringBuilder signature = new StringBuilder();
+
+ if (!method.typeParameters().isEmpty()) {
+ signature.append('<');
+ for (TypeVariable typeParameter : method.typeParameters()) {
+ typeParameter(typeParameter, signature, typeArgMapper);
+ }
+ signature.append('>');
}
- if (signature.length() > 0)
- signature.append(">");
- signature.append("(");
- for (Type type : parameters) {
+
+ signature.append('(');
+ for (Type type : method.parameters()) {
toSignature(signature, type, typeArgMapper, false);
}
- signature.append(")");
+ signature.append(')');
+
toSignature(signature, method.returnType(), typeArgMapper, false);
+
+ if (hasThrowsSignature(method)) {
+ for (Type exception : method.exceptions()) {
+ signature.append('^');
+ toSignature(signature, exception, typeArgMapper, false);
+ }
+ }
+
return signature.toString();
}
+ private static void typeParameter(TypeVariable typeParameter, StringBuilder result,
+ Function typeArgMapper) {
+ result.append(typeParameter.identifier());
+
+ if (hasImplicitObjectBound(typeParameter)) {
+ result.append(':');
+ }
+ for (Type bound : typeParameter.bounds()) {
+ result.append(':');
+ toSignature(result, bound, typeArgMapper, false);
+ }
+ }
+
+ private static boolean hasImplicitObjectBound(TypeVariable typeParameter) {
+ // TODO is there a better way? :-/
+ boolean result = false;
+ try {
+ Method method = TypeVariable.class.getDeclaredMethod("hasImplicitObjectBound");
+ method.setAccessible(true);
+ result = (Boolean) method.invoke(typeParameter);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ return result;
+ }
+
/**
* Returns the Java bytecode descriptor of a given Jandex MethodInfo using the given type argument mappings.
* For example, given this method:
@@ -172,30 +298,35 @@ private static void toSignature(StringBuilder sb, Type type, Function arguments = parameterizedType.arguments();
- for (int i = 0; i < arguments.size(); i++) {
- Type argType = arguments.get(i);
- toSignature(sb, argType, typeArgMapper, erased);
+ sb.append('<');
+ for (Type argument : parameterizedType.arguments()) {
+ toSignature(sb, argument, typeArgMapper, erased);
}
- sb.append(">");
+ sb.append('>');
}
- sb.append(";");
+ sb.append(';');
break;
case PRIMITIVE:
Primitive primitive = type.asPrimitiveType().primitive();
@@ -229,22 +360,40 @@ private static void toSignature(StringBuilder sb, Type type, FunctionIRETURN, LRETURN, FRETURN, DRETURN, RETURN for primitives/void,
* and ARETURN otherwise;
*
- * @param typeDescriptor the return Jandex Type.
+ * @param jandexType the return Jandex Type.
* @return the correct bytecode return instruction for that return type descriptor.
*/
public static int getReturnInstruction(Type jandexType) {