Skip to content

Commit 6dd249a

Browse files
committed
ClassUtils: add methods for precise field typing
These methods exploit generics to deduce more details of the type of the field in question, with respect to a particular (usually sub-)class. See the method javadoc for further details. This introduces an (encapsulated) dependency on the excellent gentyref library, which does the bulk of the work. Many thanks to Wouter Coekaerts for creating gentyref, and for his patience in explaining some of the finer points of Java generics: coekie/gentyref#2
1 parent 9701387 commit 6dd249a

File tree

2 files changed

+99
-0
lines changed

2 files changed

+99
-0
lines changed

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@
2424
<version>1.4</version>
2525
</dependency>
2626

27+
<dependency>
28+
<groupId>com.googlecode.gentyref</groupId>
29+
<artifactId>gentyref</artifactId>
30+
<version>1.1.0</version>
31+
</dependency>
32+
2733
<dependency>
2834
<groupId>junit</groupId>
2935
<artifactId>junit</artifactId>

src/main/java/org/scijava/util/ClassUtils.java

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,13 @@
3131

3232
package org.scijava.util;
3333

34+
import com.googlecode.gentyref.GenericTypeReflector;
35+
3436
import java.io.File;
3537
import java.lang.annotation.Annotation;
3638
import java.lang.reflect.Array;
3739
import java.lang.reflect.Field;
40+
import java.lang.reflect.Type;
3841
import java.net.MalformedURLException;
3942
import java.net.URL;
4043
import java.util.ArrayList;
@@ -404,6 +407,96 @@ public static Field getField(final Class<?> c, final String fieldName) {
404407
}
405408
}
406409

410+
/**
411+
* Returns the "safe" type(s) of the given field, as viewed from the specified
412+
* type. This may be narrower than what {@link Field#getType()} returns, if
413+
* the field is declared in a superclass, or {@code type} has a type parameter
414+
* that is used in the type of the field.
415+
* <p>
416+
* For example, suppose we have the following three classes:
417+
* </p>
418+
*
419+
* <pre>
420+
* public class Thing&lt;T&gt; {
421+
* public T thing;
422+
* }
423+
*
424+
* public class NumberThing&lt;N extends Number&gt; extends Thing&lt;N&gt; { }
425+
*
426+
* public class IntegerThing extends NumberThing&lt;Integer&gt; { }
427+
* </pre>
428+
*
429+
* Then this method operates as follows:
430+
*
431+
* <pre>
432+
* field = ClassUtils.getField(Thing.class, "thing");
433+
*
434+
* field.getType(); // Object
435+
*
436+
* ClassUtils.getTypes(field, Thing.class).get(0); // Object
437+
* ClassUtils.getTypes(field, NumberThing.class).get(0); // Number
438+
* ClassUtils.getTypes(field, IntegerThing.class).get(0); // Integer
439+
* </pre>
440+
*
441+
* <p>
442+
* In cases of complex generics which take the intersection of
443+
* multiple types using the {@code &} operator, there may be multiple types
444+
* returned by this method. For example:
445+
* </p>
446+
*
447+
* <pre>
448+
* public class ComplexThing&lt;T extends Serializable &amp; Cloneable&gt; extends Thing&lt;T&gt; { }
449+
*
450+
* ClassUtils.getTypes(field, ComplexThing.class); // Serializable, Cloneable
451+
* </pre>
452+
*
453+
* @see #getGenericType(Field, Class)
454+
*/
455+
public static List<Class<?>> getTypes(final Field field, final Class<?> type)
456+
{
457+
final Type genericType = getGenericType(field, type);
458+
return GenericTypeReflector.getUpperBoundClassAndInterfaces(genericType);
459+
}
460+
461+
/**
462+
* Returns the "safe" generic type of the given field, as viewed from the
463+
* given type. This may be narrower than what {@link Field#getGenericType()}
464+
* returns, if the field is declared in a superclass, or {@code type} has a
465+
* type parameter that is used in the type of the field.
466+
* <p>
467+
* For example, suppose we have the following three classes:
468+
* </p>
469+
*
470+
* <pre>
471+
* public class Thing&lt;T&gt; {
472+
* public T thing;
473+
* }
474+
*
475+
* public class NumberThing&lt;N extends Number&gt; extends Thing&lt;N&gt; { }
476+
*
477+
* public class IntegerThing extends NumberThing&lt;Integer&gt; { }
478+
* </pre>
479+
*
480+
* Then this method operates as follows:
481+
*
482+
* <pre>
483+
* field = ClassUtils.getField(Thing.class, "thing");
484+
*
485+
* field.getType(); // Object
486+
* field.getGenericType(); // T
487+
*
488+
* ClassUtils.getGenericType(field, Thing.class); // T
489+
* ClassUtils.getGenericType(field, NumberThing.class); // N extends Number
490+
* ClassUtils.getGenericType(field, IntegerThing.class); // Integer
491+
* </pre>
492+
*
493+
* @see #getTypes(Field, Class)
494+
*/
495+
public static Type getGenericType(final Field field, final Class<?> type) {
496+
final Type wildType = GenericTypeReflector.addWildcardParameters(type);
497+
return GenericTypeReflector.getExactFieldType(field, wildType);
498+
}
499+
407500
/**
408501
* Gets the given field's value of the specified object instance, or null if
409502
* the value cannot be obtained.

0 commit comments

Comments
 (0)