/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util;
import com.intellij.openapi.diagnostic.Logger;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ReflectionUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.util.ReflectionUtil");
private ReflectionUtil() {
}
@Nullable
public static Type resolveVariable(@NotNull TypeVariable variable, @NotNull Class classType) {
return resolveVariable(variable, classType, true);
}
@Nullable
public static Type resolveVariable(@NotNull TypeVariable variable, @NotNull Class classType, boolean resolveInInterfacesOnly) {
final Class aClass = getRawType(classType);
int index = ArrayUtilRt.find(ReflectionCache.getTypeParameters(aClass), variable);
if (index >= 0) {
return variable;
}
final Class[] classes = ReflectionCache.getInterfaces(aClass);
final Type[] genericInterfaces = ReflectionCache.getGenericInterfaces(aClass);
for (int i = 0; i <= classes.length; i++) {
Class anInterface;
if (i < classes.length) {
anInterface = classes[i];
}
else {
anInterface = ReflectionCache.getSuperClass(aClass);
if (resolveInInterfacesOnly || anInterface == null) {
continue;
}
}
final Type resolved = resolveVariable(variable, anInterface);
if (resolved instanceof Class || resolved instanceof ParameterizedType) {
return resolved;
}
if (resolved instanceof TypeVariable) {
final TypeVariable typeVariable = (TypeVariable)resolved;
index = ArrayUtilRt.find(ReflectionCache.getTypeParameters(anInterface), typeVariable);
if (index < 0) {
LOG.error("Cannot resolve type variable:\n" + "typeVariable = " + typeVariable + "\n" + "genericDeclaration = " +
declarationToString(typeVariable.getGenericDeclaration()) + "\n" + "searching in " + declarationToString(anInterface));
}
final Type type = i < genericInterfaces.length ? genericInterfaces[i] : aClass.getGenericSuperclass();
if (type instanceof Class) {
return Object.class;
}
if (type instanceof ParameterizedType) {
return getActualTypeArguments((ParameterizedType)type)[index];
}
throw new AssertionError("Invalid type: " + type);
}
}
return null;
}
@NotNull
public static String declarationToString(@NotNull GenericDeclaration anInterface) {
return anInterface.toString()
+ Arrays.asList(anInterface.getTypeParameters())
+ " loaded by " + ((Class)anInterface).getClassLoader();
}
public static Class<?> getRawType(@NotNull Type type) {
if (type instanceof Class) {
return (Class)type;
}
if (type instanceof ParameterizedType) {
return getRawType(((ParameterizedType)type).getRawType());
}
if (type instanceof GenericArrayType) {
//todo[peter] don't create new instance each time
return Array.newInstance(getRawType(((GenericArrayType)type).getGenericComponentType()), 0).getClass();
}
assert false : type;
return null;
}
@NotNull
public static Type[] getActualTypeArguments(@NotNull ParameterizedType parameterizedType) {
return ReflectionCache.getActualTypeArguments(parameterizedType);
}
@Nullable
public static Class<?> substituteGenericType(@NotNull Type genericType, @NotNull Type classType) {
if (genericType instanceof TypeVariable) {
final Class<?> aClass = getRawType(classType);
final Type type = resolveVariable((TypeVariable)genericType, aClass);
if (type instanceof Class) {
return (Class)type;
}
if (type instanceof ParameterizedType) {
return (Class<?>)((ParameterizedType)type).getRawType();
}
if (type instanceof TypeVariable && classType instanceof ParameterizedType) {
final int index = ArrayUtilRt.find(ReflectionCache.getTypeParameters(aClass), type);
if (index >= 0) {
return getRawType(getActualTypeArguments((ParameterizedType)classType)[index]);
}
}
} else {
return getRawType(genericType);
}
return null;
}
@NotNull
public static List<Field> collectFields(@NotNull Class clazz) {
List<Field> result = new ArrayList<Field>();
collectFields(clazz, result);
return result;
}
@NotNull
public static Field findField(@NotNull Class clazz, @Nullable Class type, @NotNull String name) throws NoSuchFieldException {
List<Field> fields = collectFields(clazz);
for (Field each : fields) {
if (name.equals(each.getName()) && (type == null || each.getType().equals(type))) return each;
}
throw new NoSuchFieldException("Class: " + clazz + " name: " + name + " type: " + type);
}
@NotNull
public static Field findAssignableField(@NotNull Class clazz, @NotNull Class type, @NotNull String name) throws NoSuchFieldException {
List<Field> fields = collectFields(clazz);
for (Field each : fields) {
if (name.equals(each.getName()) && type.isAssignableFrom(each.getType())) return each;
}
throw new NoSuchFieldException("Class: " + clazz + " name: " + name + " type: " + type);
}
private static void collectFields(@NotNull Class clazz, @NotNull List<Field> result) {
final Field[] fields = clazz.getDeclaredFields();
result.addAll(Arrays.asList(fields));
final Class superClass = clazz.getSuperclass();
if (superClass != null) {
collectFields(superClass, result);
}
final Class[] interfaces = clazz.getInterfaces();
for (Class each : interfaces) {
collectFields(each, result);
}
}
public static void resetField(@NotNull Class clazz, @NotNull Class type, @NotNull String name) {
try {
resetField(null, findField(clazz, type, name));
}
catch (NoSuchFieldException e) {
LOG.info(e);
}
}
public static void resetField(@NotNull Object object, @NotNull Class type, @NotNull String name) {
try {
resetField(object, findField(object.getClass(), type, name));
}
catch (NoSuchFieldException e) {
LOG.info(e);
}
}
public static void resetField(@NotNull Object object, @NotNull String name) {
try {
resetField(object, findField(object.getClass(), null, name));
}
catch (NoSuchFieldException e) {
LOG.info(e);
}
}
public static void resetField(@Nullable final Object object, @NotNull Field field) {
field.setAccessible(true);
Class<?> type = field.getType();
try {
if (type.isPrimitive()) {
if (boolean.class.equals(type)) {
field.set(object, Boolean.FALSE);
}
else if (int.class.equals(type)) {
field.set(object, new Integer(0));
}
else if (double.class.equals(type)) {
field.set(object, new Double(0));
}
else if (float.class.equals(type)) {
field.set(object, new Float(0));
}
}
else {
field.set(object, null);
}
}
catch (IllegalAccessException e) {
LOG.info(e);
}
}
@Nullable
public static Method findMethod(@NotNull Method[] methods, @NonNls @NotNull String name, @NotNull Class... parameters) {
for (final Method method : methods) {
if (name.equals(method.getName()) && Arrays.equals(parameters, method.getParameterTypes())) return method;
}
return null;
}
@Nullable
public static Method getMethod(@NotNull Class aClass, @NonNls @NotNull String name, @NotNull Class... parameters) {
return findMethod(ReflectionCache.getMethods(aClass), name, parameters);
}
@Nullable
public static Method getDeclaredMethod(@NotNull Class aClass, @NonNls @NotNull String name, @NotNull Class... parameters) {
return findMethod(aClass.getDeclaredMethods(), name, parameters);
}
public static <T> T getField(@NotNull Class objectClass, Object object, @NotNull Class<T> type, @NotNull @NonNls String name) {
try {
final Field field = findAssignableField(objectClass, type, name);
field.setAccessible(true);
return (T)field.get(object);
}
catch (NoSuchFieldException e) {
LOG.debug(e);
return null;
}
catch (IllegalAccessException e) {
LOG.debug(e);
return null;
}
}
public static Type resolveVariableInHierarchy(@NotNull TypeVariable variable, @NotNull Class aClass) {
Type type;
Class current = aClass;
while ((type = resolveVariable(variable, current, false)) == null) {
current = ReflectionCache.getSuperClass(current);
if (current == null) {
return null;
}
}
if (type instanceof TypeVariable) {
return resolveVariableInHierarchy((TypeVariable)type, aClass);
}
return type;
}
@NotNull
public static <T> Constructor<T> getDefaultConstructor(@NotNull Class<T> aClass) {
try {
final Constructor<T> constructor = aClass.getConstructor();
constructor.setAccessible(true);
return constructor;
}
catch (NoSuchMethodException e) {
throw new RuntimeException("No default constructor in " + aClass, e);
}
}
@NotNull
public static <T> T createInstance(@NotNull Constructor<T> constructor, @NotNull Object... args) {
try {
return constructor.newInstance(args);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void resetThreadLocals() {
try {
Field field = Thread.class.getDeclaredField("threadLocals");
field.setAccessible(true);
field.set(Thread.currentThread(), null);
}
catch (Throwable e) {
LOG.info(e);
}
}
private static class MySecurityManager extends SecurityManager {
private static final MySecurityManager INSTANCE = new MySecurityManager();
public Class[] getStack() {
return getClassContext();
}
}
/**
* Returns the class this method was called 'framesToSkip' frames up the caller hierarchy.
*
* NOTE:
* <b>Extremely expensive!
* Please consider not using it.
* These aren't the droids you're looking for!</b>
*/
@Nullable
public static Class findCallerClass(int framesToSkip) {
try {
Class[] stack = MySecurityManager.INSTANCE.getStack();
int indexFromTop = 1 + framesToSkip;
return stack.length > indexFromTop ? stack[indexFromTop] : null;
}
catch (Exception e) {
LOG.warn(e);
return null;
}
}
}