package com.redhat.ceylon.model.loader.impl.reflect.mirror;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.redhat.ceylon.model.loader.AbstractModelLoader;
import com.redhat.ceylon.model.loader.mirror.AnnotationMirror;
import com.redhat.ceylon.model.loader.mirror.TypeParameterMirror;
public class ReflectionUtils {
static final Class<? extends Annotation> IGNORE_ANNOTATION = ReflectionUtils.getClass(AbstractModelLoader.CEYLON_IGNORE_ANNOTATION);
public static Map<String, AnnotationMirror> getAnnotations(AnnotatedElement annotated) {
Annotation[] annotations = annotated.getDeclaredAnnotations();
return getAnnotations(annotations);
}
public static Map<String, AnnotationMirror> getAnnotations(Annotation[] annotations) {
if(annotations.length == 0)
return Collections.<String,AnnotationMirror>emptyMap();
Map<String, AnnotationMirror> map = new HashMap<String,AnnotationMirror>();
for(int i=annotations.length-1;i>=0;i--){
Annotation annotation = annotations[i];
map.put(annotation.annotationType().getName(), new ReflectionAnnotation(annotation));
}
return map;
}
public static List<TypeParameterMirror> getTypeParameters(GenericDeclaration decl) {
TypeVariable<?>[] javaTypeParameters = decl.getTypeParameters();
List<TypeParameterMirror> typeParameters = new ArrayList<TypeParameterMirror>(javaTypeParameters.length);
for(Type javaTypeParameter : javaTypeParameters)
typeParameters.add(new ReflectionTypeParameter(javaTypeParameter));
return typeParameters;
}
/* Required to fix the distribution build, so that we can build the compiler before building the language module */
@SuppressWarnings("unchecked")
private static Class<? extends Annotation> getClass(String string) {
try {
return (Class<? extends Annotation>) Class.forName(string);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static String getPackageName(Class<?> klass) {
// Package pkg;
if(klass.isPrimitive() || klass.isArray()){
// primitives and arrays don't have a package, so we pretend they come from java.lang
return "java.lang";
/*
* We used to have the following code, but getPackage() is stateless and does a bunch of expensive things
* every time we invoke it, and even invoking it once, it allocates more crap than the long road below,
* as proved by profiling, so we use the long road which is cheaper.
}else if((pkg = klass.getPackage()) != null){
// short road
return pkg.getName();
*/
}else{
// long road
while(klass.getEnclosingClass() != null){
klass = klass.getEnclosingClass();
}
String name = klass.getName();
int lastDot = name.lastIndexOf('.');
if(lastDot == -1)
return "";//empty package
else
return name.substring(0, lastDot);
}
}
private enum OverXing {
Overloading, Overriding
}
public static boolean isOverridingMethod(Method method) {
return isOverXingMethod(OverXing.Overriding, method);
}
public static boolean isOverloadingMethod(Method method) {
return isOverXingMethod(OverXing.Overloading, method);
}
private static boolean isOverXingMethod(OverXing searchType, Method method) {
// fast exit
if(Modifier.isPrivate(method.getModifiers()))
return false;
String name = method.getName();
Class<?>[] parameterTypes;
parameterTypes = ((Method)method).getParameterTypes();
Class<?> declaringClass = method.getDeclaringClass();
// make sure we don't visit interfaces more than once
Set<Class<?>> visited = new HashSet<Class<?>>();
// try the superclass first
Class<?> superclass = declaringClass.getSuperclass();
if(superclass != null){
if(isOverXingMethodInClassRecursive(searchType, method, name, parameterTypes, declaringClass, superclass, visited))
return true;
}
// now try the interfaces
for(Class<?> interfce : declaringClass.getInterfaces()){
if(isOverXingMethodInClassRecursive(searchType, method, name, parameterTypes, declaringClass, interfce, visited))
return true;
}
// not overriding anything
return false;
}
private static boolean isOverXingMethodInClassRecursive(OverXing searchType, Method method, String name, Class<?>[] parameterTypes, Class<?> declaringClass, Class<?> klass,
Set<Class<?>> visited) {
if(!visited.add(klass))
return false;
if(isOverXingMethodInClass(searchType, method, name, parameterTypes, declaringClass, klass))
return true;
// try the superclass first
Class<?> superclass = klass.getSuperclass();
if(superclass != null){
if(isOverXingMethodInClassRecursive(searchType, method, name, parameterTypes, declaringClass, superclass, visited))
return true;
}
// now try the interfaces
for(Class<?> interfce : klass.getInterfaces()){
if(isOverXingMethodInClassRecursive(searchType, method, name, parameterTypes, declaringClass, interfce, visited))
return true;
}
// not overriding anything here
return false;
}
private static boolean isOverXingMethodInClass(OverXing searchType, Method method, String name, Class<?>[] parameterTypes, Class<?> declaringClass, Class<?> klass) {
switch(searchType){
case Overloading:
return isOverloadingMethodInClass(method, name, parameterTypes, declaringClass, klass);
case Overriding:
return isOverridingMethodInClass(method, name, parameterTypes, declaringClass, klass);
default:
throw new RuntimeException("Non-exhaustive switch");
}
}
private static boolean isOverridingMethodInClass(Method method, String name, Class<?>[] parameterTypes, Class<?> declaringClass, Class<?> lookupClass) {
try {
Method m = lookupClass.getDeclaredMethod(name, parameterTypes);
// present
return !m.isBridge() && !m.isSynthetic() && !Modifier.isPrivate(m.getModifiers())
&& !m.isAnnotationPresent(IGNORE_ANNOTATION)
&& !isHiddenMethod(m);
} catch (Exception e) {
// not present
}
NEXT_METHOD:
for(Method m : lookupClass.getDeclaredMethods()){
if(!m.getName().equals(name)
|| m.isBridge()
|| m.isSynthetic()
|| m.isAnnotationPresent(IGNORE_ANNOTATION)
|| Modifier.isFinal(m.getModifiers())
|| Modifier.isPrivate(m.getModifiers())
|| isHiddenMethod(m))
continue;
if(m.getParameterTypes().length != parameterTypes.length)
continue;
int i=0;
try {
// get the type argument mappings for that method's container
// FIXME: do we need to cache this?
Map<TypeVariable<?>, Class<?>> typeArguments = getTypeArguments(declaringClass, m.getDeclaringClass(), Collections.<TypeVariable<?>, Class<?>>emptyMap());
for(Type t : m.getGenericParameterTypes()){
try{
Class<?> parameterErasure = getParameterErasure(typeArguments, t);
if(parameterErasure != parameterTypes[i++])
continue NEXT_METHOD;
}catch(ClassNotFoundException x){
// if we can't get the param types, we're not overloading/overriding it anyways
continue NEXT_METHOD;
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException("Can't find type arguments for "+declaringClass, e);
}
// must be the same?
return true;
}
return false;
}
private static boolean isOverloadingMethodInClass(Method method, String name, Class<?>[] parameterTypes, Class<?> declaringClass, Class<?> lookupClass) {
for(Method m : lookupClass.getDeclaredMethods()){
if(!m.getName().equals(name)
|| m.isBridge()
|| m.isSynthetic()
|| m.isAnnotationPresent(IGNORE_ANNOTATION)
|| Modifier.isPrivate(m.getModifiers())
|| isHiddenMethod(m))
continue;
if(m.getParameterTypes().length != parameterTypes.length)
return true;
int i=0;
try{
// get the type argument mappings for that method's container
// FIXME: do we need to cache this?
Map<TypeVariable<?>, Class<?>> typeArguments = getTypeArguments(declaringClass, m.getDeclaringClass(), Collections.<TypeVariable<?>, Class<?>>emptyMap());
for(Type t : m.getGenericParameterTypes()){
try{
Class<?> parameterErasure = getParameterErasure(typeArguments, t);
if(parameterErasure != parameterTypes[i++])
return true;
}catch(ClassNotFoundException x){
// if we can't get the param types, we're overloading it anyways
return true;
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException("Can't find type arguments for "+declaringClass, e);
}
// must be the overriding, check the next one
}
// no overload here
return false;
}
private static boolean isHiddenMethod(Method m) {
return m.getDeclaringClass() == Object.class
&& (m.getName().equals("finalize")
|| m.getName().equals("clone"));
}
private static Class<?> getParameterErasure(Map<TypeVariable<?>, Class<?>> typeArguments, Type t) throws ClassNotFoundException {
if(t instanceof Class){
// non-parameterised, must be the same as the one we're looking up
return (Class<?>) t;
}else if(t instanceof TypeVariable){
Class<?> parameterErasure = typeArguments.get(t);
/*
* Erasure bounds Note:
* In practice it looks like we never need erasure bounds, because even if we implement a raw interface,
* the overriding method will have its parameter types erased to the proper classes (apparently it can't
* substitute new type variables for the parameters, if it implements a raw interface and overrides a
* method whose arguments are generics, so it must be classes) and when we look up the method with the
* parameter types we will find them directly with this method's parameter types in the direct lookup above.
*/
if(parameterErasure == null)
parameterErasure = Object.class;
return parameterErasure;
}else if(t instanceof ParameterizedType){
return (Class<?>) ((ParameterizedType) t).getRawType();
}else if(t instanceof GenericArrayType){
GenericArrayType gt = (GenericArrayType) t;
Class<?> componentType = getParameterErasure(typeArguments, gt.getGenericComponentType());
ClassLoader classLoader = componentType.getClassLoader();
String name;
if(componentType.isArray()){
// just add a leading "["
name = "["+componentType.getName();
}else if(componentType == boolean.class){
name = "[Z";
}else if(componentType == byte.class){
name = "[B";
}else if(componentType == char.class){
name = "[C";
}else if(componentType == double.class){
name = "[D";
}else if(componentType == float.class){
name = "[F";
}else if(componentType == int.class){
name = "[I";
}else if(componentType == long.class){
name = "[J";
}else if(componentType == short.class){
name = "[S";
}else{
// must be an object non-array class
name = "[L"+componentType.getName()+";";
}
return classLoader != null ? classLoader.loadClass(name) : Class.forName(name);
}else
throw new RuntimeException("Unknown parameter type: "+t);
}
private static Map<TypeVariable<?>, Class<?>> getTypeArguments(Class<?> base, Class<?> searchedSuperType, Map<TypeVariable<?>, Class<?>> baseTypeArguments) throws ClassNotFoundException{
// fast exit for non-generics
if(searchedSuperType.getTypeParameters().length == 0)
return Collections.<TypeVariable<?>, Class<?>>emptyMap();
if(base.equals(searchedSuperType)){
return baseTypeArguments;
}
Map<TypeVariable<?>, Class<?>> ret = null;
// look for it in our super class
if(base.getSuperclass() != null){
Type superclass = base.getGenericSuperclass();
ret = getTypeArgumentsForSuperType(superclass, searchedSuperType, baseTypeArguments);
}
if(ret != null)
return ret;
// if not, look for interfaces, but only if the super type in question is an interface, otherwise there's no point
if(searchedSuperType.isInterface()){
for(Type superinterface : base.getGenericInterfaces()){
ret = getTypeArgumentsForSuperType(superinterface, searchedSuperType, baseTypeArguments);
if(ret != null)
return ret;
}
}
// no match
return null;
}
private static Map<TypeVariable<?>, Class<?>> getTypeArgumentsForSuperType(Type superclass, Class<?> searchedSuperType, Map<TypeVariable<?>, Class<?>> baseTypeArguments) throws ClassNotFoundException {
if(superclass instanceof Class){
// not generic, or raw
Class<?> sc = (Class<?>) superclass;
return getTypeArguments(sc, searchedSuperType, getTypeArguments(sc, Collections.<TypeVariable<?>, Class<?>>emptyMap()));
}else if(superclass instanceof ParameterizedType){
Map<TypeVariable<?>, Class<?>> newTypeArgs = getTypeArgumentsMap((ParameterizedType) superclass, baseTypeArguments);
Class<?> sc = (Class<?>) ((ParameterizedType) superclass).getRawType();
return getTypeArguments(sc, searchedSuperType, newTypeArgs);
}else{
throw new RuntimeException("Unknown superclass type: "+superclass);
}
}
private static Map<TypeVariable<?>, Class<?>> getTypeArgumentsMap(ParameterizedType pt, Map<TypeVariable<?>, Class<?>> baseTypeArguments) throws ClassNotFoundException {
Map<TypeVariable<?>, Class<?>> typeArgsMap = new HashMap<TypeVariable<?>, Class<?>>();
addTypeArguments(pt, typeArgsMap, baseTypeArguments);
while(pt.getOwnerType() instanceof ParameterizedType){
pt = (ParameterizedType) pt.getOwnerType();
addTypeArguments(pt, typeArgsMap, baseTypeArguments);
}
return typeArgsMap;
}
private static void addTypeArguments(ParameterizedType pt, Map<TypeVariable<?>, Class<?>> typeArgsMap, Map<TypeVariable<?>, Class<?>> baseTypeArguments) throws ClassNotFoundException {
Class<?> sc = (Class<?>) pt.getRawType();
Type[] typeArguments = pt.getActualTypeArguments();
int i=0;
for(TypeVariable<?> tv : sc.getTypeParameters()){
Type ta = typeArguments[i++];
Class<?> erasedTa = getParameterErasure(baseTypeArguments, ta);
typeArgsMap.put(tv, erasedTa);
}
}
private static Map<TypeVariable<?>, Class<?>> getTypeArguments(Class<?> base, Map<TypeVariable<?>, Class<?>> baseTypeArguments) {
Map<TypeVariable<?>, Class<?>> ret = new HashMap<TypeVariable<?>, Class<?>>();
for(TypeVariable<?> tv : base.getTypeParameters()){
Class<?> typeArg = baseTypeArguments.get(tv);
/*
* See Erasure bounds Note above
*/
if(typeArg == null)
typeArg = Object.class;
ret.put(tv, typeArg);
}
return ret;
}
}