package org.testory.common;
import static java.util.Arrays.asList;
import static org.testory.common.Checks.checkArgument;
import static org.testory.common.Checks.checkNotNull;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Classes {
public static void setAccessible(final AccessibleObject accessible) {
checkNotNull(accessible);
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
accessible.setAccessible(true);
return null;
}
});
}
public static boolean hasMethod(String name, Class<?>[] parameters, Class<?> type) {
checkNotNull(name);
checkNotNull(parameters);
checkArgument(!asList(parameters).contains(null));
checkNotNull(type);
try {
type.getMethod(name, parameters);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
public static boolean canAssign(@Nullable Object instance, Class<?> type) {
checkNotNull(type);
return type.isPrimitive()
? canConvert(instance, type)
: instance == null || type.isAssignableFrom(instance.getClass());
}
private static boolean canConvert(Object instance, Class<?> type) {
checkArgument(type.isPrimitive());
if (type == void.class) {
return false;
}
try {
@SuppressWarnings("unused")
class Methods {
// @formatter:off
void method(boolean a) {}
void method(char a) {}
void method(byte a) {}
void method(short a) {}
void method(int a) {}
void method(long a) {}
void method(float a) {}
void method(double a) {}
// @formatter:on
}
Methods.class.getDeclaredMethod("method", type).invoke(new Methods(), instance);
return true;
} catch (IllegalArgumentException e) {
return false;
} catch (ReflectiveOperationException e) {
throw new LinkageError(null, e);
}
}
public static boolean canReturn(@Nullable Object object, Method method) {
checkNotNull(method);
return canAssign(object, method.getReturnType());
}
public static boolean canThrow(Throwable throwable, Method method) {
checkNotNull(throwable);
checkNotNull(method);
for (Class<?> exceptionType : method.getExceptionTypes()) {
if (exceptionType.isInstance(throwable)) {
return true;
}
}
return throwable instanceof RuntimeException || throwable instanceof Error;
}
public static boolean canInvoke(Method method, @Nullable Object instance, Object... arguments) {
checkNotNull(method);
checkNotNull(arguments);
return correctInstance(instance, method) && correctArguments(arguments, method);
}
private static boolean correctInstance(@Nullable Object instance, Method method) {
return Modifier.isStatic(method.getModifiers())
|| method.getDeclaringClass().isInstance(instance);
}
private static boolean correctArguments(Object[] arguments, Method method) {
Class<?>[] parameters = method.getParameterTypes();
if (parameters.length != arguments.length) {
return false;
}
for (int i = 0; i < parameters.length; i++) {
if (!canAssign(arguments[i], parameters[i])) {
return false;
}
}
return true;
}
public static <T> T defaultValue(Class<T> type) {
checkNotNull(type);
return (T) defaultValues.get(type);
}
private static final Map<Class<?>, Object> defaultValues = defaultValues();
private static Map<Class<?>, Object> defaultValues() {
Map<Class<?>, Object> map = new HashMap<>();
map.put(boolean.class, false);
map.put(char.class, '\0');
map.put(byte.class, (byte) 0);
map.put(short.class, (short) 0);
map.put(int.class, 0);
map.put(long.class, 0L);
map.put(float.class, 0f);
map.put(double.class, 0.0);
return Collections.unmodifiableMap(map);
}
public static Class<?> tryWrap(Class<?> type) {
checkNotNull(type);
return wrapping.containsKey(type)
? wrapping.get(type)
: type;
}
private static final Map<Class<?>, Class<?>> wrapping = wrapping();
private static Map<Class<?>, Class<?>> wrapping() {
Map<Class<?>, Class<?>> map = new HashMap<>();
map.put(void.class, Void.class);
map.put(boolean.class, Boolean.class);
map.put(char.class, Character.class);
map.put(byte.class, Byte.class);
map.put(short.class, Short.class);
map.put(int.class, Integer.class);
map.put(long.class, Long.class);
map.put(float.class, Float.class);
map.put(double.class, Double.class);
return Collections.unmodifiableMap(map);
}
}