Daniel Sperry
Daniel Sperry

Reputation: 4491

How to convert method.getGenericReturnType() to a jvm type signature

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

Answers (2)

Daniel Sperry
Daniel Sperry

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

SubOptimal
SubOptimal

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

Related Questions