blob: 56d9f790f77c98536eb262d25317706557724253 [file] [log] [blame]
/*
* Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.jvnet.tiger_types;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
/**
* Type arithmetic functions.
*
* @author Kohsuke Kawaguchi
*/
public class Types {
private static final TypeVisitor<Type,Class> baseClassFinder = new TypeVisitor<Type,Class>() {
public Type onClass(Class c, Class sup) {
// t is a raw type
if(sup==c)
return sup;
Type r;
Type sc = c.getGenericSuperclass();
if(sc!=null) {
r = visit(sc,sup);
if(r!=null) return r;
}
for( Type i : c.getGenericInterfaces() ) {
r = visit(i,sup);
if(r!=null) return r;
}
return null;
}
public Type onParameterizdType(ParameterizedType p, Class sup) {
Class raw = (Class) p.getRawType();
if(raw==sup) {
// p is of the form sup<...>
return p;
} else {
// recursively visit super class/interfaces
Type r = raw.getGenericSuperclass();
if(r!=null)
r = visit(bind(r,raw,p),sup);
if(r!=null)
return r;
for( Type i : raw.getGenericInterfaces() ) {
r = visit(bind(i,raw,p),sup);
if(r!=null) return r;
}
return null;
}
}
public Type onGenericArray(GenericArrayType g, Class sup) {
// not clear what I should do here
return null;
}
public Type onVariable(TypeVariable v, Class sup) {
return visit(v.getBounds()[0],sup);
}
public Type onWildcard(WildcardType w, Class sup) {
// not clear what I should do here
return null;
}
/**
* Replaces the type variables in {@code t} by its actual arguments.
*
* @param decl
* provides a list of type variables. See {@link GenericDeclaration#getTypeParameters()}
* @param args
* actual arguments. See {@link ParameterizedType#getActualTypeArguments()}
*/
private Type bind( Type t, GenericDeclaration decl, ParameterizedType args ) {
return binder.visit(t,new BinderArg(decl,args.getActualTypeArguments()));
}
};
private static class BinderArg {
final TypeVariable[] params;
final Type[] args;
BinderArg(TypeVariable[] params, Type[] args) {
this.params = params;
this.args = args;
assert params.length==args.length;
}
public BinderArg( GenericDeclaration decl, Type[] args ) {
this(decl.getTypeParameters(),args);
}
Type replace( TypeVariable v ) {
for(int i=0; i<params.length; i++)
if(params[i].equals(v))
return args[i];
return v; // this is a free variable
}
}
private static final TypeVisitor<Type,BinderArg> binder = new TypeVisitor<Type,BinderArg>() {
public Type onClass(Class c, BinderArg args) {
return c;
}
public Type onParameterizdType(ParameterizedType p, BinderArg args) {
Type[] params = p.getActualTypeArguments();
boolean different = false;
for( int i=0; i<params.length; i++ ) {
Type t = params[i];
params[i] = visit(t,args);
different |= t!=params[i];
}
Type newOwner = p.getOwnerType();
if(newOwner!=null)
newOwner = visit(newOwner,args);
different |= p.getOwnerType()!=newOwner;
if(!different) return p;
return new ParameterizedTypeImpl( (Class<?>)p.getRawType(), params, newOwner );
}
public Type onGenericArray(GenericArrayType g, BinderArg types) {
Type c = visit(g.getGenericComponentType(),types);
if(c==g.getGenericComponentType()) return g;
return new GenericArrayTypeImpl(c);
}
public Type onVariable(TypeVariable v, BinderArg types) {
return types.replace(v);
}
public Type onWildcard(WildcardType w, BinderArg types) {
// TODO: this is probably still incorrect
// bind( "? extends T" ) with T= "? extends Foo" should be "? extends Foo",
// not "? extends (? extends Foo)"
Type[] lb = w.getLowerBounds();
Type[] ub = w.getUpperBounds();
boolean diff = false;
for( int i=0; i<lb.length; i++ ) {
Type t = lb[i];
lb[i] = visit(t,types);
diff |= (t!=lb[i]);
}
for( int i=0; i<ub.length; i++ ) {
Type t = ub[i];
ub[i] = visit(t,types);
diff |= (t!=ub[i]);
}
if(!diff) return w;
return new WildcardTypeImpl(lb,ub);
}
};
/**
* Gets the parameterization of the given base type.
*
* <p>
* For example, given the following
* <pre><xmp>
* interface Foo<T> extends List<List<T>> {}
* interface Bar extends Foo<String> {}
* </xmp></pre>
* This method works like this:
* <pre><xmp>
* getBaseClass( Bar, List ) = List<List<String>
* getBaseClass( Bar, Foo ) = Foo<String>
* getBaseClass( Foo<? extends Number>, Collection ) = Collection<List<? extends Number>>
* getBaseClass( ArrayList<? extends BigInteger>, List ) = List<? extends BigInteger>
* </xmp></pre>
*
* @param type
* The type that derives from {@code baseType}
* @param baseType
* The class whose parameterization we are interested in.
* @return
* The use of {@code baseType} in {@code type}.
* or null if the type is not assignable to the base type.
*/
public static Type getBaseClass(Type type, Class baseType) {
return baseClassFinder.visit(type,baseType);
}
/**
* Gets the display name of the type object
*
* @return
* a human-readable name that the type represents.
*/
public static String getTypeName(Type type) {
if (type instanceof Class) {
Class c = (Class) type;
if(c.isArray())
return getTypeName(c.getComponentType())+"[]";
return c.getName();
}
return type.toString();
}
/**
* Checks if {@code sub} is a sub-type of {@code sup}.
*/
public static boolean isSubClassOf(Type sub, Type sup) {
return erasure(sup).isAssignableFrom(erasure(sub));
}
/**
* Implements the logic for {@link #erasure(Type)}.
*/
private static final TypeVisitor<Class,Void> eraser = new TypeVisitor<Class,Void>() {
public Class onClass(Class c,Void v) {
return c;
}
public Class onParameterizdType(ParameterizedType p,Void v) {
// TODO: why getRawType returns Type? not Class?
return visit(p.getRawType(),null);
}
public Class onGenericArray(GenericArrayType g,Void v) {
return Array.newInstance(
visit(g.getGenericComponentType(),null),
0 ).getClass();
}
public Class onVariable(TypeVariable t,Void v) {
return visit(t.getBounds()[0],null);
}
public Class onWildcard(WildcardType w,Void v) {
return visit(w.getUpperBounds()[0],null);
}
};
/**
* Returns the {@link Class} representation of the given type.
*
* This corresponds to the notion of the erasure in JSR-14.
*
* <p>
* It made me realize how difficult it is to define the common navigation
* layer for two different underlying reflection library. The other way
* is to throw away the entire parameterization and go to the wrapper approach.
*/
public static <T> Class<T> erasure(Type t) {
return eraser.visit(t,null);
}
/**
* Returns the {@link Type} object that represents {@code clazz&lt;T1,T2,T3>}.
*/
public static ParameterizedType createParameterizedType( Class rawType, Type... arguments ) {
return new ParameterizedTypeImpl(rawType,arguments,null);
}
/**
* Checks if the type is an array type.
*/
public static boolean isArray(Type t) {
if (t instanceof Class) {
Class c = (Class) t;
return c.isArray();
}
if(t instanceof GenericArrayType)
return true;
return false;
}
/**
* Checks if the type is an array type but not byte[].
*/
public static boolean isArrayButNotByteArray(Type t) {
if (t instanceof Class) {
Class c = (Class) t;
return c.isArray() && c!=byte[].class;
}
if(t instanceof GenericArrayType) {
t = ((GenericArrayType)t).getGenericComponentType();
return t!=Byte.TYPE;
}
return false;
}
/**
* Gets the component type of the array.
*
* @param t
* must be an array.
*/
public static Type getComponentType(Type t) {
if (t instanceof Class) {
Class c = (Class) t;
return c.getComponentType();
}
if(t instanceof GenericArrayType)
return ((GenericArrayType)t).getGenericComponentType();
throw new IllegalArgumentException();
}
/**
* Gets the i-th type argument from a parameterized type.
*
* <p>
* Unlike {@link #getTypeArgument(Type, int, Type)}, this method
* throws {@link IllegalArgumentException} if the given type is
* not parameterized.
*/
public static Type getTypeArgument(Type type, int i) {
Type r = getTypeArgument(type, i, null);
if(r==null)
throw new IllegalArgumentException();
return r;
}
/**
* Gets the i-th type argument from a parameterized type.
*
* <p>
* For example, {@code getTypeArgument([Map<Integer,String>],0)=Integer}
* If the given type is not a parameterized type, returns the specified
* default value.
*
* <p>
* This is convenient for handling raw types and parameterized types uniformly.
*
* @throws IndexOutOfBoundsException
* If i is out of range.
*/
public static Type getTypeArgument(Type type, int i, Type defaultValue) {
if (type instanceof ParameterizedType) {
ParameterizedType p = (ParameterizedType) type;
return fix(p.getActualTypeArguments()[i]);
} else
return defaultValue;
}
/**
* Checks if the given type is a primitive type.
*/
public static boolean isPrimitive(Type type) {
if (type instanceof Class) {
Class c = (Class) type;
return c.isPrimitive();
}
return false;
}
public static boolean isOverriding(Method method, Class base) {
// this isn't actually correct,
// as the JLS considers
// class Derived extends Base<Integer> {
// Integer getX() { ... }
// }
// class Base<T> {
// T getX() { ... }
// }
// to be overrided. Handling this correctly needs a careful implementation
String name = method.getName();
Class[] params = method.getParameterTypes();
while(base!=null) {
try {
if(null != base.getDeclaredMethod(name,params))
return true;
} catch (NoSuchMethodException e) {
// recursively go into the base class
}
base = base.getSuperclass();
}
return false;
}
/**
* JDK 5.0 has a bug of createing {@link GenericArrayType} where it shouldn't.
* fix that manually to work around the problem.
*
* See bug 6202725.
*/
private static Type fix(Type t) {
if(!(t instanceof GenericArrayType))
return t;
GenericArrayType gat = (GenericArrayType) t;
if(gat.getGenericComponentType() instanceof Class) {
Class c = (Class) gat.getGenericComponentType();
return Array.newInstance(c,0).getClass();
}
return t;
}
}