Export to GitHub

gentyref - issue #16

As-seen-from


Posted on Jul 21, 2011 by Happy Rhino

Consider the following test:

===

package com.googlecode.gentyref;

import junit.framework.TestCase;

import java.lang.reflect.Method; import java.lang.reflect.Type;

public class AsSeenFromTest extends TestCase { public class GenericOuter<T> { public class Inner { public T get() { return null; } }

public Type getExactReturnType() throws NoSuchMethodException {
  final Class&lt;?&gt; inner = Inner.class;
  final Method get = inner.getMethod(&quot;get&quot;);
  return GenericTypeReflector.getExactReturnType(get, inner);
}

}

public class StringOuter extends GenericOuter<String> { }

public void test() throws NoSuchMethodException { assertEquals(String.class, new StringOuter().getExactReturnType()); } }

===

Currently it fails because Inner.get(), when looked at from GenericOuter<T>, returns T, which is not bound and hence gets erased.

However, if we look at Inner.get() from StringOuter, T would be bound to String. Such "change of perspective" is not currently implemented.

I was wondering how hard it is to create enhanced versions of GenericTypeReflector.getExactXXX() methods that take an extra parameter: asSeenFrom. This parameter would denote a sub-type of one of declaring type's owner types for purposes of type variable resolution. Then I would be able to write:

  return GenericTypeReflector.getExactReturnType(get, inner, getClass());

which will look at Inner.get() from perspective of current class, in my case StringOuter, and return String.

Comment #1

Posted on Jul 21, 2011 by Quick Cat

The getExactReturnType method itself can handle it fine, the problem is you need a way to give it the Inner type with the right outer type. One way to do this is by constructing it using a TypeToken: Type inner = new TypeToken(){}.getType(); final Method get = GenericOuter.Inner.class.getMethod("get"); assertEquals(String.class, GenericTypeReflector.getExactReturnType(get, inner));

But TypeTokens can only be used for type constants, so you can't use that in your GenericOuter.getExactReturnType. There should be some way in gentyref to programatically create an inner type given the outer one. A naive way to do that would be just by making ParameterizedTypeImpl public; but there are just too many ways to misuse that. A first attempt to use ParameterizedTypeImpl directly to implement this could be like this:

public Type getExactReturnType() throws NoSuchMethodException { Type inner = new ParameterizedTypeImpl(Inner.class, new Type[]{}, getClass()); Method get = Inner.class.getMethod("get"); return GenericTypeReflector.getExactReturnType(get, inner); }

But that's wrong, and it'll give a UnresolvedTypeVariableException. The catch is that, in the original TypeToken version, there's more going on that you don't see: "inner" there is actually the ParameterizedType "GenericOuter.Inner" because the type "StringOuter.Inner" doesn't really exist. The Java syntax allows you to write it, but it doesn't exist in the type system; it get immediately converted into "GenericOuter.Inner". The ParameterizedTypeImpl class doesn't prevent you from creating nonexistent types., it assumes the one using it knows what he's doing. A naive way to "use it right" is to just to change the getClass() into the exact "GenericOuter" type of getClass():

Type exactOuter = GenericTypeReflector.getExactSuperType(this.getClass(), GenericOuter.class);
final Type inner = new ParameterizedTypeImpl(Inner.class, new Type[]{}, exactOuter);

That works for your example, but it is still wrong: it doesn't handle raw types properly. Doing "new GenericOuter().getExactReturnType()" would create a ParameterizedType "GenericOuter.Inner", but that's not a real ParameterizedType because it's missing a type parameter for GenericOuter. So it's again a type that's not supposed to exist.

I guess I'd add a method like this in gentyref: public static Type createClassType(Class clazz, Type[] arguments, Type outer); And it seems best to make it lenient: * If outer is not of the right class (e.g. StringOuter instead of GenericOuter) it should just fix that * If outer is raw, it should just return the raw type

But it should also do some checking, and throw an exception if it's really being misused: * outer must be specified if and only if clazz is an inner class * outer must be appropriate for the given class * The number of type arguments must be correct. Maybe it should accept "null" there and go on if there are 0 arguments expected, and make it a raw type otherwise. * The arguments should satisfy the bounds of the type variables. But that might be a lot more work to implement (with bounds recursively referencing themselves and all...). Maybe I should leave that out for now...

Comment #2

Posted on Aug 7, 2011 by Quick Cat

This is implemented now. See http://code.google.com/p/gentyref/source/browse/src/test/java/com/googlecode/gentyref/factory/Issue16Test.java

I'm planning to expand TypeFactory to also create other types, and make sure it properly deals with wildcards, and then a release of 1.2.0 should follow.

Status: Fixed

Labels:
Type-Enhancement Priority-Medium FixVersion-1.2.0