Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class.forName equivalent for creating ParameterizedType's from String

Tags:

Calling java.lang.reflect.Type.toString() provides very nice representation of generic types:

@Test
public void typeToStringWithTypeToken() {
    assertEquals("java.util.List<java.lang.String>", new TypeToken<List<String>>() {}.getType().toString());
}

What I need is the reverse of Type.toString() method, i.e. a method that can create Types from given string representation:

public static Type parseTypeString(String typeString) throws ClassNotFoundException {
    if (typeString.indexOf('<') == -1) {
        return Class.forName(typeString);
    }
    // ??? how to implement rest
    throw new IllegalStateException("not implemented");
}

Which can pass following tests with specialized types:

@Test
public void justSimpleClassName() throws Exception {
    assertEquals(Integer.class, parseTypeString("java.lang.Integer"));
}

@Test
public void listOfInteger() throws Exception {
    assertEquals(new TypeToken<List<Integer>>() {}.getType(), parseTypeString("java.util.List<java.lang.Integer>"));
}

@Test
public void complexType() throws Exception {
    assertEquals(new TypeToken<Map<List<Integer>, Set<String>>>() {}.getType(), parseTypeString("java.util.Map<java.util.List<java.lang.Integer>, java.util.Set<java.lang.String>>"));
}

I couldn't find a library or a SO question that addresses this problem.

like image 295
b10y Avatar asked Sep 08 '16 22:09

b10y


2 Answers

The output of java.lang.reflect.Type.toString() is implementation specific.
The behaviour you are seeing is part of Gson's ParameterizedTypeImpl
Due to type erasure, classes in Java don’t have type parameters at runtime.
The Type from gson doesn’t represent an actual loaded Class.

See Get Type of ParameterizedType from generic for related information.

like image 43
Magnus Avatar answered Sep 25 '22 16:09

Magnus


Well... You can do it by yourself using for example antlr4 using following grammar

type returns[ClassBuilder value]
    : cls=CLASS          { $value = ClassBuilder.parse($cls.text); }
    | cls=CLASS          { $value = ClassBuilder.parse($cls.text); }
      LT head=type       { $value.add($head.value); }
        (COMMA tail=type { $value.add($tail.value); })* GT
    ;

GT  : '>'
    ;

LT  : '<'
    ;

COMMA
    : ','
    ;

CLASS
    : ('a'..'z'|'A'..'Z') ('a'..'z'|'A'..'Z'|'0'..'9'|'$'|'.'|'_')*
    ;

Where ClassBuilder looks like

public class ClassBuilder {
    private final Class<?> clazz;
    private final List<ClassBuilder> parameters = new ArrayList<>();

    public ClassBuilder(final Class<?> clazz) {
        this.clazz = clazz;
    }

    public static ClassBuilder parse(String clazz) {
        try {
            return new ClassBuilder(Class.forName(clazz));
        } catch (ClassNotFoundException e) {
            // do better handling here
            throw new IllegalStateException(e);
        }
    }

    public void add(ClassBuilder builder) {
        parameters.add(builder);
    }

    public Type build() {
        // class is not parametrized
        if (parameters.isEmpty()) {
            return clazz;
        }

        return ParameterizedTypeImpl.make(
                clazz,
                parameters.stream()
                          .map(ClassBuilder::build)
                          .toArray(Type[]::new),
                null);
    }
}

And then finally parse string

CharStream stream = 
    new ANTLRInputStream(
        "java.util.Map<java.util.List<java.lang.Integer>, java.util.Set<java.lang.String>>"
    );

TokenStream tokenStream = 
    new CommonTokenStream(new ParametrizedTypeLexer(stream));
ParametrizedTypeParser parser = 
    new ParametrizedTypeParser(tokenStream);

assertEquals(
        new TypeToken<Map<List<Integer>, Set<String>>>() {}.getType(),
        parser.type().value.build()
);

You can see working example here.

NOTE

CLASS lexer rule may be a bit inaccurate. Also this parser only applies to non-primitive classes and parametrized types, but you of course extend it to support wildcard types as well.

like image 155
vsminkov Avatar answered Sep 24 '22 16:09

vsminkov