Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make method TypeToken.toGenericType() public #1645

Open
gissuebot opened this issue Oct 31, 2014 · 26 comments
Open

Make method TypeToken.toGenericType() public #1645

gissuebot opened this issue Oct 31, 2014 · 26 comments

Comments

@gissuebot
Copy link

Original issue created by archie.cobbs on 2014-01-24 at 09:56 PM


I don't know if this is even possible, but it seems like it should be...

I'd like the ability to take the output of TypeToken.toString() and parse it back into the equivalent TypeToken.

For example, one should be able to say:

   foo = TypeToken.parse("java.util.List<java.lang.String>");

and have foo equal to new TypeToken<List<String>>() {}.

NOTE 1: it seems reasonable to have the restriction that no type variables are allowed (would it even make sense to allow them? If you did you'd have to have some way of deciding whether "T" was a type variable or a class name).

NOTE 2: a ClassLoader should probably be a parameter to the parse method

I tried to implement this myself but it seems impossible without using package/private methods or generating bytecode (if it's possible to implement this idea using the standard public TypeToken API, please advise on how it would be done).

For example, this doesn't work:

TypeVariable tv = List.class.getTypeParameters()[0];
System.out.println(new TypeResolver().where(
    tv, String.class).resolveType(List.class));

prints:

java.util.List

instead of:

java.util.List<String>

Also can't use TypeToken.of(List.class).getTypes(), which returns:

java.util.List, java.util.Collection<E>, java.lang.Iterable<E>

instead of:

java.util.List<E>, java.util.Collection<E>, java.lang.Iterable<E>

The special case for the first item in the list seems odd (is the raw "List" type really a sub-type of Iterable<E>?)

What seems to be stopping this is the ability to convert a raw class into its parameterized Type or TypeToken. E.g., wish there was a method like this:

public Type Class.getGenericType();

That is, this feature request could be answered by adding a new method that does the equivalent, e.g.:

public TypeToken<? extends T>  TypeToken.getGenericType();

So that e.g. TypeToken.of(List.class).getGenericType() -> List<E>

I'm guessing all of this is impossible for some reason I don't understand or it would already be there....

Thanks.

@gissuebot
Copy link
Author

Original comment posted by lowasser@google.com on 2014-01-24 at 10:09 PM


(No comment entered for this change.)


Labels: Package-Reflect, Type-Addition

@gissuebot
Copy link
Author

Original comment posted by archie.cobbs on 2014-01-24 at 10:15 PM


Snooping through the code, it looks like such a method as suggested at the end of comment #1 already exists:

static <T> TypeToken<? extends T> TypeToken.toGenericType()

Any reason this method can't be made public?

@gissuebot
Copy link
Author

Original comment posted by archie.cobbs on 2014-01-27 at 04:46 PM


Just to clarify things a bit here, as this issue has evolved and the issue title is no longer accurate.

The original request for a String parser is no longer needed... I can do that part myself, but need TypeToken.toGenericType() to made public in order for it to be feasible.

So instead what is being requested here is simply that TypeToken.toGenericType() be made public.

This method provides useful functionality that is not available anywhere else; and you can consider the original string parsing question as a motivating example (i.e., how could you implement string parsing without it?).

@gissuebot
Copy link
Author

Original comment posted by archie.cobbs on 2014-02-22 at 09:29 PM


Please change the title of this feature request to:

  Make method TypeToken.toGenericType() public

Thanks.

@gissuebot
Copy link
Author

Original comment posted by cpovirk@google.com on 2014-02-25 at 04:54 PM


(No comment entered for this change.)

@gissuebot
Copy link
Author

Original comment posted by benyu@google.com on 2014-06-25 at 08:57 PM


Exposing toGenericType() sounds reasonable.

Although can you show us how it's going to be used in this use case? I wonder if it can be done with existing utilities such as where(), getSupertype(), getSubtype() etc.

@gissuebot
Copy link
Author

Original comment posted by archie.cobbs on 2014-06-26 at 12:11 AM


As mentioned above, the original motivating example is a TypeToken parser (converts String -> TypeToken).

I tried to implement one using all available public API's but it seemed impossible.

The root problem is that e.g. TypeToken.of(List.class) returns "java.util.List", not "java.util.List<E>". Because of this, there's no way to take an arbitrary Class object representing a parameterized type, create a corresponding TypeToken, and then set the type parameters for the TypeToken.

However, if TypeToken.toGenericType() is available then that makes it easy.

You can see the current implementation of the parser here:

  https://code.google.com/p/jsimpledb/source/browse/trunk/src/java/org/jsimpledb/util/TypeTokenParser.java

Currently it uses reflection to access Types.newParameterizedType() directly (note, it invokes Types.newParameterizedType() instead of TypeToken.toGenericType() because the parser doesn't handle arrays).

I could be completely missing something... if so, I'd be interested to see how you'd rewrite TypeTokenParser.java using only existing public API's.

Thanks.

@gissuebot
Copy link
Author

Original comment posted by benyu@google.com on 2014-06-27 at 01:26 AM


I know the dependency is kinda weird. But ignore that for a moment, if you had Guice's Types.newParamterizedType(), would you still have needed toGenericType()?

Were you trying to use toGenericType() to implement newParameterizedType()?

Although it doesn't seem easy to do though? For example, when parsing "List<String>", after calling List.class.toGenericType(), you have TypeToken<List<E>>, but there isn't any easy way to turn that into List<String> unless you use reflection to get the type variable <E> and do substitution using TypeResolver.

@gissuebot
Copy link
Author

Original comment posted by benyu@google.com on 2014-06-27 at 01:48 AM


Oh. I guess you sort of implied it's what you are going to do: use clazz.getTypeParameters() to get the type vars and use TypeResolver to substitute. So never mind my previous question then.

It still seems like newParameterizedType() is what's really needed.

It was asked before that newParameterizedType() be exposed. That Guice already has it was one of the reasons we haven't done that, yet.

@gissuebot
Copy link
Author

Original comment posted by archie.cobbs on 2014-06-27 at 02:21 AM


Exposing newParameterizedType() would work. I don't currently use Guice, so that option would be inconvenient. Seems like guava should have it's own version for completeness' sake, though that's a matter of opinion I suppose.

Thanks.

@gissuebot
Copy link
Author

Original comment posted by benyu@google.com on 2014-06-27 at 02:40 AM


Okay. Whether it's possible to use Guice was what I was curious to know.

I think you have a perfectly reasonable use case here.

I'm torn between the two options:

  1. Just expose newParameterizedType(). But then we overlap with Guice. And it'd be weird not to also open up newArrayType(), subtypeOf(), supertypeOf() etc.
  2. Expose toGenericTye(). It doesn't immediately solve the issue, but it makes it easier to do because you can then use TypeResolver to implement newParameterizedType(). And who knows, there may be other use cases of it.

Or, maybe even more directly:

  1. Just offer a TypeToken parser.

Hmm...


Status: Research

@archiecobbs
Copy link

Regarding "Just offer a TypeToken parser", that's too limited a solution. The functionality provided by Types.newParameterizedType() is generally useful.

For example, I came across another use case whereby I needed a method that would "wildcardify" any raw type. For example, for some class Foo with 3 generic type parameters, this would return TypeToken<Foo<?, ?, ?>>. Of course in general it would work with any Class<?> parameter.

The easiest (only?) way to do this was to call Types.newParameterizedType(rawtype, array) where array is an array of unbounded ? WildcardType objects constructed manually.

@fluentfuture
Copy link

Technically, a type token parser can still work, right?

for (Class<?> raw : array) {
  TypeToken<?> typeToken = TypeToken.parse(raw.getName() + "<?, ?, ?>");
}

@archiecobbs
Copy link

Yep, that's right... didn't think of that.. though that's a little messier, especially when the number of generic type parameters can vary.

FWIW here's what I'm currently doing...

    private static final WildcardType QUESTION_MARK = new WildcardType() {

        @Override
        public Type[] getUpperBounds() {
            return new Type[] { Object.class };
        }

        @Override
        public Type[] getLowerBounds() {
            return new Type[0];
        }

        @Override
        public String toString() {
            return "?";
        }
    };

    ...

    /**
     * Parameterize the raw type with wildcards.
     */
    public static <T> TypeToken<? extends T> getWildcardedType(Class<T> type) {
        if (type == null)
            throw new IllegalArgumentException("null type");
        final TypeVariable<Class<T>>[] typeVariables = type.getTypeParameters();
        if (typeVariables.length == 0)
            return TypeToken.of(type);
        final WildcardType[] questionMarks = new WildcardType[typeVariables.length];
        Arrays.fill(questionMarks, QUESTION_MARK);
        return Util.newParameterizedType(type, questionMarks);
    }

However I still would rather see the more general functionality exposed. There are sure to be other use cases that could benefit besides the ones I've come across.

@fluentfuture
Copy link

It's better if you don't have to manually implement WildcardType or any of these Type impls.

Your WildcardType impl wouldn't be equal to the JDK built-in wildcards when you get them through reflection like method.getGenericReturnType().

And then, getUpperBounds() isn't necessarily Object.class. Even if it's "?" literally. If type is Enum.class, the upper bound is actually Enum<?>.

The use case you mentioned above seems to call for a different utility: toWildcardType().

Of course, if there is TypeToken.parse(), you can perhaps get it with:

TypeToken.parse(type.getName() + "<" + Joiner.on(",").join(
    nCopies(type.getTypeParameters().length, "?") + ">");

If we offer TypeToken.parse(), we should make sure we get the upper/lower bounds right.

@archiecobbs
Copy link

Yes I agree my current solution is a hack... right now there's no other (easy) way to do it. This is of course more evidence that there are functionality holes remaining to be filled. Thanks.

@fluentfuture
Copy link

Agreed.

Based on earlier use cases in this thread and the one you just brought up, I'm more convinced that a parse() utility would be the omnipotent solution to most of these problems.

In contrast, either toGenericType() or toWildcardType() would only solve a small subset of the problems. For newParameterizedType() to solve your use case, we'd still need newWildcardType() and even then, user code is left prone to getting the type bounds wrong:

newParameterizedType(Enum.class, newWildcardType()); // Oops, wrong!

Compared to providing 5 extra API methods (newParameterizedType, newWildcardType, supertypeOf, subtypeOf, newTypeVariable) that are somewhat hard to use, a simple parse() method is also simpler from API perspective.

I'll run this idea internally by others and see if I can convince enough people. If anything, I suspect a question could come up as "what is the user really trying to do through creating the wildcard type?". If you could provide some more information behind this use case, that might help.

Thanks!

@archiecobbs
Copy link

My current use case requiring WildcardType is that I'm trying to build a list of all supertypes of a type.

As described above, for reasons I don't fully understand TypeToken.getTypes() doesn't include the "wildcarded" type (e.g., TypeToken.of(List.class) does not include List<?>).

So as a workaround, I'm adding the "wildcarded" type to the list manually.

In any case, the whole wildcard thing is a digression. The larger issues is that it seems like there should be a programmatic way to take a raw Class object and build an instance of the corresponding generic type with arbitrary type parameters. The fact that there's no way to do this seems like a weird hole in the functionality. The two options discussed, (a) using Guice, or (b) building and then parsing a String that would use some new parsing method are acceptable I guess, but it seems like it would be a whole lot simpler to just make public a method that is already written and sitting there :)

As a side note, a parsing method would be nice to have for it's own reasons. E.g. it would make it easy to serialize a TypeToken in an XML document.

@fluentfuture
Copy link

By getTypes() not including wildcard types, do you mean this?

List<String>.getTypes() =>
[Collection<String>, Iterable<String>, Object]
    + [List<?>, List<? extends Comparable>, List<? extends CharSequence>, Collection<?>, ..., Iterable<?>, ...]

?

I believe List<?>.getTypes() should return Iterable<?> etc.

About exposing the public methods that already sit there, the concerns have been:

  1. It's not a single method to make public. It's a pack of them: newParamterizedType() newParamterizedTypeWithOwner(), newArrayType(), supertypeOf(), subtypeOf().
  2. These methods as they are today (internal utilities only), don't do type checking. So if you call newParameterizedType(String.class, String.class), it will let you. But for a public utility method, that seems a bit too loose.
  3. Even if we expose all of them, we still don't have a good enough solution to your "wildcardify" use case. As mentioned earlier, newParameterizedType(cls, subtypeOf(Object.class)) can easily get the bounds wrong. In contrast, the parse() utility gets the job done: parse(cls.getName() + "<?>");
  4. Yeah. Guice already has it. So we aren't adding significant values by just duplicating the API here.

Good point about the serialization/deserialization of TypeToken. Thanks!

@archiecobbs
Copy link

By getTypes() not including wildcard types, do you mean this?

I mean the output of:

import com.google.common.reflect.TypeToken;
public class xx {
    public static void main(String[] args) throws Exception {
        System.out.println(TypeToken.of(java.util.List.class).getTypes());
    }
}

is

[java.util.List, java.util.Collection<E>, java.lang.Iterable<E>]

which does not include java.util.List<?>.

Thinking about this more I realize now that java.util.List a not sub-type of java.util.List<?> - it's the other way around. So that behavior is correct. Earlier I thought getTypes() might be an easy way to "wildcard" a type (e.g., generate TypeToken<List<?>> from List.class) but that is not the case.

Here's a little more background: the code I'm working with is attempting to track types using TypeToken, which works great. In order to maintain type safety, it is also trying to follow a "no raw types" rule that states there should never be any raw types - all tracked types are genericized. But another requirement is that it allows a user to specify a type at runtime by providing a Class object. When that occurs, in order to follow the rule, that type needs to be converted into its genericized equivalent in a type-safe manner. This is when the "wildcarding" operation is needed.

@fluentfuture
Copy link

Here's a tiny TypeParser, and Types

It lacks type bounds checking, but delegates to Guava for the Type implementations.

@jodastephen
Copy link
Contributor

Just wanted to add that a TypeToken parse(String) method would be useful to me. Joda-Convert provides a standard way to convert an object to and from a string (for JSON/XML etc). Being able to do this with TypeToken is desirable (as it can already handle Class).

I also happen to think that the API should also provide a public newParamterizedType() is some form. For example, I'd like to be able to store in a database the TypeToken Foo<Bar>, but using two columns, one for Foo.class and one for Bar.class. While putting it together again using a parse() method would work, a newParamterizedType() would be more obvious. And yes, I understand the risks of creating stupid types.

@findepi
Copy link
Contributor

findepi commented Oct 15, 2015

Just wanted to add that public newParamterizedType() is what I need too. It seems all reasons where already given and the proposed parse method sounds just cool. However, manually constructing "List" + "<" + "String" + ">" just to get it parsed sounds awkward.

If the Types.newParamterizedType() in Guava is not as public-ready as the one already public in Guice, why don't you copy/move that one into Guava? It's seems that the Guava is THE library for all Java shortcomings, not Guice.

Alternatives to throwing in Guice into the projects are using Apache TypeUtils or Spring 4 ResolvableType. Either way, it just doesn't seem right when using TypeToken API to have to go to another library (Guive, Commons Lang or Spring) just to construct equivalent of TypeToken<List<String>> where "List" and "String" are given at run-time.

@archiecobbs
Copy link

PING.

2.5+ years and still waiting for Types.newParamterizedType() to be made public... can we get some resolution to this?

To summarize: there are legitimate situations where we need to convert a raw class (represented by a Class object) back into its generic type (represented by a TypeToken object) using caller-supplied type parameters (represented by Type objects).

This is exactly what Types.newParamterizedType() does.

It's there. It's useful. Let's do this!!

@archiecobbs
Copy link

Wow, this bug is so old I almost forgot about it... coming up on 8 years.

They grow up so fast...

Would be funny if it weren't so sad...

@archiecobbs
Copy link

Oops, I missed this bug's tenth birthday back in January.

HAPPY TENTH BIRTHDAY!!

So close, and yet so far...

diff --git a/guava/src/com/google/common/reflect/Types.java b/guava/src/com/google/common/reflect/Types.java
index 0b54e6a..3c06d13 100644
--- a/guava/src/com/google/common/reflect/Types.java
+++ b/guava/src/com/google/common/reflect/Types.java
@@ -91,7 +91,7 @@ final class Types {
   }
 
   /** Returns a type where {@code rawType} is parameterized by {@code arguments}. */
-  static ParameterizedType newParameterizedType(Class<?> rawType, Type... arguments) {
+  public static ParameterizedType newParameterizedType(Class<?> rawType, Type... arguments) {
     return new ParameterizedTypeImpl(
         ClassOwnership.JVM_BEHAVIOR.getOwnerType(rawType), rawType, arguments);
   }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants