blob: 2194ec516c466abeb26048f6f3987127e5106599 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.websocket.common.util;
import java.lang.reflect.Constructor;
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;
public class ReflectUtils
{
private static class GenericRef
{
// The base class reference lookup started from
private final Class<?> baseClass;
// The interface that we are interested in
private final Class<?> ifaceClass;
// The actual class generic interface was found on
Class<?> genericClass;
// The found genericType
public Type genericType;
private int genericIndex;
public GenericRef(final Class<?> baseClass, final Class<?> ifaceClass)
{
this.baseClass = baseClass;
this.ifaceClass = ifaceClass;
}
public boolean needsUnwrap()
{
return (genericClass == null) && (genericType != null) && (genericType instanceof TypeVariable<?>);
}
public void setGenericFromType(Type type, int index)
{
// debug("setGenericFromType(%s,%d)",toShortName(type),index);
this.genericType = type;
this.genericIndex = index;
if (type instanceof Class)
{
this.genericClass = (Class<?>)type;
}
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("GenericRef [baseClass=");
builder.append(baseClass);
builder.append(", ifaceClass=");
builder.append(ifaceClass);
builder.append(", genericType=");
builder.append(genericType);
builder.append(", genericClass=");
builder.append(genericClass);
builder.append("]");
return builder.toString();
}
}
private static StringBuilder appendTypeName(StringBuilder sb, Type type, boolean ellipses)
{
if (type instanceof Class<?>)
{
Class<?> ctype = (Class<?>)type;
if (ctype.isArray())
{
try
{
int dimensions = 0;
while (ctype.isArray())
{
dimensions++;
ctype = ctype.getComponentType();
}
sb.append(ctype.getName());
for (int i = 0; i < dimensions; i++)
{
if (ellipses)
{
sb.append("...");
}
else
{
sb.append("[]");
}
}
return sb;
}
catch (Throwable ignore)
{
// ignore
}
}
sb.append(ctype.getName());
}
else
{
sb.append(type.toString());
}
return sb;
}
/**
* Given a Base (concrete) Class, find the interface specified, and return its concrete Generic class declaration.
*
* @param baseClass
* the base (concrete) class to look in
* @param ifaceClass
* the interface of interest
* @return the (concrete) generic class that the interface exposes
*/
public static Class<?> findGenericClassFor(Class<?> baseClass, Class<?> ifaceClass)
{
GenericRef ref = new GenericRef(baseClass,ifaceClass);
if (resolveGenericRef(ref,baseClass))
{
// debug("Generic Found: %s",ref.genericClass);
return ref.genericClass;
}
// debug("Generic not found: %s",ref);
return null;
}
private static int findTypeParameterIndex(Class<?> clazz, TypeVariable<?> needVar)
{
// debug("findTypeParameterIndex(%s, [%s])",toShortName(clazz),toShortName(needVar));
TypeVariable<?> params[] = clazz.getTypeParameters();
for (int i = 0; i < params.length; i++)
{
if (params[i].getName().equals(needVar.getName()))
{
// debug("Type Parameter found at index: [%d]",i);
return i;
}
}
// debug("Type Parameter NOT found");
return -1;
}
public static boolean isDefaultConstructable(Class<?> clazz)
{
int mods = clazz.getModifiers();
if (Modifier.isAbstract(mods) || !Modifier.isPublic(mods))
{
// Needs to be public, non-abstract
return false;
}
Class<?>[] noargs = new Class<?>[0];
try
{
// Needs to have a no-args constructor
Constructor<?> constructor = clazz.getConstructor(noargs);
// Constructor needs to be public
return Modifier.isPublic(constructor.getModifiers());
}
catch (NoSuchMethodException | SecurityException e)
{
return false;
}
}
private static boolean resolveGenericRef(GenericRef ref, Class<?> clazz, Type type)
{
if (type instanceof Class)
{
if (type == ref.ifaceClass)
{
// is this a straight ref or a TypeVariable?
// debug("Found ref (as class): %s",toShortName(type));
ref.setGenericFromType(type,0);
return true;
}
else
{
// Keep digging
return resolveGenericRef(ref,type);
}
}
if (type instanceof ParameterizedType)
{
ParameterizedType ptype = (ParameterizedType)type;
Type rawType = ptype.getRawType();
if (rawType == ref.ifaceClass)
{
// debug("Found ref on [%s] as ParameterizedType [%s]",toShortName(clazz),toShortName(ptype));
// Always get the raw type parameter, let unwrap() solve for what it is
ref.setGenericFromType(ptype.getActualTypeArguments()[0],0);
return true;
}
else
{
// Keep digging
return resolveGenericRef(ref,rawType);
}
}
return false;
}
private static boolean resolveGenericRef(GenericRef ref, Type type)
{
if ((type == null) || (type == Object.class))
{
return false;
}
if (type instanceof Class)
{
Class<?> clazz = (Class<?>)type;
// prevent spinning off into Serialization and other parts of the
// standard tree that we could care less about
if (clazz.getName().matches("^javax*\\..*"))
{
return false;
}
Type ifaces[] = clazz.getGenericInterfaces();
for (Type iface : ifaces)
{
// debug("resolve %s interface[]: %s",toShortName(clazz),toShortName(iface));
if (resolveGenericRef(ref,clazz,iface))
{
if (ref.needsUnwrap())
{
// debug("## Unwrap class %s::%s",toShortName(clazz),toShortName(iface));
TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
// debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
// attempt to find typeParameter on class itself
int typeParamIdx = findTypeParameterIndex(clazz,needVar);
// debug("type param index for %s[%s] is [%d]",toShortName(clazz),toShortName(needVar),typeParamIdx);
if (typeParamIdx >= 0)
{
// found a type parameter, use it
// debug("unwrap from class [%s] - typeParameters[%d]",toShortName(clazz),typeParamIdx);
TypeVariable<?> params[] = clazz.getTypeParameters();
if (params.length >= typeParamIdx)
{
ref.setGenericFromType(params[typeParamIdx],typeParamIdx);
}
}
else if (iface instanceof ParameterizedType)
{
// use actual args on interface
Type arg = ((ParameterizedType)iface).getActualTypeArguments()[ref.genericIndex];
ref.setGenericFromType(arg,ref.genericIndex);
}
}
return true;
}
}
type = clazz.getGenericSuperclass();
return resolveGenericRef(ref,type);
}
if (type instanceof ParameterizedType)
{
ParameterizedType ptype = (ParameterizedType)type;
Class<?> rawClass = (Class<?>)ptype.getRawType();
if (resolveGenericRef(ref,rawClass))
{
if (ref.needsUnwrap())
{
// debug("## Unwrap ParameterizedType %s::%s",toShortName(type),toShortName(rawClass));
TypeVariable<?> needVar = (TypeVariable<?>)ref.genericType;
// debug("needs unwrap of type var [%s] - index [%d]",toShortName(needVar),ref.genericIndex);
int typeParamIdx = findTypeParameterIndex(rawClass,needVar);
// debug("type paramIdx of %s::%s is index [%d]",toShortName(rawClass),toShortName(needVar),typeParamIdx);
Type arg = ptype.getActualTypeArguments()[typeParamIdx];
ref.setGenericFromType(arg,typeParamIdx);
return true;
}
}
}
return false;
}
public static String toShortName(Type type)
{
if (type == null)
{
return "<null>";
}
if (type instanceof Class)
{
String name = ((Class<?>)type).getName();
return trimClassName(name);
}
if (type instanceof ParameterizedType)
{
ParameterizedType ptype = (ParameterizedType)type;
StringBuilder str = new StringBuilder();
str.append(trimClassName(((Class<?>)ptype.getRawType()).getName()));
str.append("<");
Type args[] = ptype.getActualTypeArguments();
for (int i = 0; i < args.length; i++)
{
if (i > 0)
{
str.append(",");
}
str.append(args[i]);
}
str.append(">");
return str.toString();
}
return type.toString();
}
public static String toString(Class<?> pojo, Method method)
{
StringBuilder str = new StringBuilder();
// method modifiers
int mod = method.getModifiers() & Modifier.methodModifiers();
if (mod != 0)
{
str.append(Modifier.toString(mod)).append(' ');
}
// return type
Type retType = method.getGenericReturnType();
appendTypeName(str,retType,false).append(' ');
// class name
str.append(pojo.getName());
str.append("#");
// method name
str.append(method.getName());
// method parameters
str.append('(');
Type[] params = method.getGenericParameterTypes();
for (int j = 0; j < params.length; j++)
{
boolean ellipses = method.isVarArgs() && (j == (params.length - 1));
appendTypeName(str,params[j],ellipses);
if (j < (params.length - 1))
{
str.append(", ");
}
}
str.append(')');
// TODO: show exceptions?
return str.toString();
}
public static String trimClassName(String name)
{
int idx = name.lastIndexOf('.');
name = name.substring(idx + 1);
idx = name.lastIndexOf('$');
if (idx >= 0)
{
name = name.substring(idx + 1);
}
return name;
}
}