Reputation: 4491
How to convert an instance of java.lang.reflect.Type
into a jvm generic type signature?
Type type = method.getGenericReturnType();
String signature = toTypeSig(type);
For instance this Type:
Map<String, Map<?, ? super Integer>>
Must become this String:
"Ljava/util/Map<Ljava/lang/String;Ljava/util/Map<*-Ljava/lang/Integer;>;>;"
How do people using orb.objectweb.asm.ClassWriter
solve this?
I've done it iterating over the Type information. But I'd prefer something more robust. (I've posted my own solution as an answer because it's a bit long)
Upvotes: 0
Views: 1164
Reputation: 4491
My final solution. I'm posting for reference. Since it might be useful when method.setAccessible(true)
is not available.
This is more or less what I ended up using. Because I needed to transform the generic return type into a class signature. And I had no use for the type variables.
It works well for methods without type parameters. For methods with generic type parameters it tries to replace the parameters.
Processing the generic return type of this method: <P> List<P> someMethod()
results in "Ljava/util/List<Ljava/lang/Object;>;"
static String toGenericSignature(final Type type)
{
StringBuilder sb = new StringBuilder();
toGenericSignature(sb, type);
return sb.toString();
}
static void toGenericSignature(StringBuilder sb, final Type type)
{
if (type instanceof GenericArrayType)
{
sb.append("[");
toGenericSignature(sb, ((GenericArrayType) type).getGenericComponentType());
}
else if (type instanceof ParameterizedType)
{
ParameterizedType pt = (ParameterizedType) type;
sb.append('L');
sb.append(((Class) pt.getRawType()).getName().replace('.', '/'));
sb.append('<');
for (Type p : pt.getActualTypeArguments())
{
toGenericSignature(sb, p);
}
sb.append(">;");
}
else if (type instanceof Class)
{
Class clazz = (Class) type;
if (!clazz.isPrimitive() && !clazz.isArray())
{
sb.append('L');
sb.append(clazz.getName().replace('.', '/'));
sb.append(';');
}
else
{
sb.append(clazz.getName().replace('.', '/'));
}
}
else if (type instanceof WildcardType)
{
WildcardType wc = (WildcardType) type;
Type[] lowerBounds = wc.getLowerBounds();
Type[] upperBounds = wc.getUpperBounds();
boolean hasLower = lowerBounds != null && lowerBounds.length > 0;
boolean hasUpper = upperBounds != null && upperBounds.length > 0;
if (hasUpper && hasLower && Object.class.equals(lowerBounds[0]) && Object.class.equals(upperBounds[0]))
{
sb.append('*');
}
else if (hasLower)
{
sb.append("-");
for (Type b : lowerBounds)
{
toGenericSignature(sb, b);
}
}
else if (hasUpper)
{
if (upperBounds.length == 1 && Object.class.equals(upperBounds[0]))
{
sb.append("*");
}
else
{
sb.append("+");
for (Type b : upperBounds)
{
toGenericSignature(sb, b);
}
}
}
else
{
sb.append('*');
}
}
else if (type instanceof TypeVariable)
{
//sb.append("T");
//sb.append(((TypeVariable) type).getName());
//sb.append(";");
// work around: replaces the type variable with it's first bound.
toGenericSignature(sb, ((TypeVariable) type).getBounds()[0]);
}
else
{
throw new IllegalArgumentException("Invalid type: " + type);
}
}
Upvotes: 1
Reputation: 22963
I guess you want to get the jvm signature from a class file (or did you mean from a source file?)
From a class following could be a solution
public class GetSignature {
// the method for which you want to retrieve the signature
Map<String, Map<?, ? super Integer>> someMethod() {
return null;
}
public static void main(String[] args) throws Exception {
// make the private method in class Method accessible
Method methodGenericSignature = Method.class.getDeclaredMethod(
"getGenericSignature",
(Class<?>[]) null
);
methodGenericSignature.setAccessible(true);
// get the signature from the method
Method method = GetSignature.class.getDeclaredMethod("someMethod",
(Class<?>[]) null
);
String returnValue = (String) methodGenericSignature.invoke(method,
(Object[]) null
);
System.out.println("signature: " + returnValue);
}
}
output
signature: ()Ljava/util/Map<Ljava/lang/String;Ljava/util/Map<*-Ljava/lang/Integer;>;>;
edit A small snipped to demonstrate how to get the signature using asm.
public class GetSignatureDemo {
public static void main(String[] args) throws IOException {
InputStream is = new FileInputStream("/tmp/GetSignature.class");
ClassReader classReader = new ClassReader(is);
classReader.accept(getClassVisitor(), 0);
}
private static ClassVisitor getClassVisitor() {
return new ClassVisitor(Opcodes.ASM4) {
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor, String signature, String[] exceptions) {
System.out.printf(
"method: %s descriptor: %s signature: %s%n",
name,
descriptor,
signature
);
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
}
}
sample output
method: someMethod descriptor: ()Ljava/util/Map; signature: ()Ljava/util/Map<Ljava/lang/String;Ljava/util/Map<*-Ljava/lang/Integer;>;>;
Upvotes: 2