package org.javers.common.reflection;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import org.javers.common.collections.Lists;
import java.util.*;
import org.javers.common.collections.Primitives;
import org.javers.common.collections.WellKnownValueTypes;
import org.javers.common.exception.JaversException;
import org.javers.common.exception.JaversExceptionCode;
import org.javers.common.validation.Validate;
import org.javers.core.Javers;
import org.slf4j.Logger;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import static org.slf4j.LoggerFactory.getLogger;
/**
* @author bartosz walacik
*/
public class ReflectionUtil {
private static final Logger logger = getLogger(ReflectionUtil.class);
public static boolean isClassPresent(String className) {
try {
Class.forName(className, false, Javers.class.getClassLoader());
return true;
}
catch (Throwable ex) {
// Class or one of its dependencies is not present...
return false;
}
}
/**
* throws RuntimeException if class is not found
*/
public static Class classForName(String className) {
try {
return Class.forName(className, false, Javers.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new JaversException(ex);
}
}
public static Object invokeGetter(Object target, String getterName) {
Validate.argumentsAreNotNull(target, getterName);
try {
Method m = target.getClass().getMethod(getterName);
return m.invoke(target);
}catch (Exception e ) {
throw new JaversException(e);
}
}
/**
* Creates new instance of public or package-private class.
* Calls first, not-private constructor
*/
public static Object newInstance(Class clazz, ArgumentResolver resolver){
Validate.argumentIsNotNull(clazz);
for (Constructor constructor : clazz.getDeclaredConstructors()) {
if (isPrivate(constructor) || isProtected(constructor)) {
continue;
}
Class [] types = constructor.getParameterTypes();
Object[] params = new Object[types.length];
for (int i=0; i<types.length; i++){
try {
params[i] = resolver.resolve(types[i]);
} catch (JaversException e){
logger.error("failed to create new instance of "+clazz.getName()+", argument resolver for arg["+i+"] " +
types[i].getName() + " thrown exception: "+e.getMessage());
throw e;
}
}
try {
constructor.setAccessible(true);
return constructor.newInstance(params);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
throw new JaversException(JaversExceptionCode.NO_PUBLIC_CONSTRUCTOR,clazz.getName());
}
public static List<JaversField> getAllPersistentFields(Class methodSource) {
List<JaversField> result = new ArrayList<>();
for(JaversField field : getAllFields(methodSource)) {
if (isPersistentField(field.getRawMember())) {
result.add(field);
}
}
return result;
}
public static List<JaversGetter> getAllGetters(Class methodSource) {
JaversGetterFactory getterFactory = new JaversGetterFactory(methodSource);
return getterFactory.getAllGetters();
}
public static List<JaversField> getAllFields(Class<?> methodSource) {
JaversFieldFactory fieldFactory = new JaversFieldFactory(methodSource);
return fieldFactory.getAllFields();
}
private static boolean isPersistentField(Field field) {
return !Modifier.isTransient(field.getModifiers()) &&
!Modifier.isStatic(field.getModifiers()) &&
!field.getName().equals("this$0"); //owner of inner class
}
private static boolean isPrivate(Member member){
return Modifier.isPrivate(member.getModifiers());
}
private static boolean isProtected(Member member){
return Modifier.isProtected(member.getModifiers());
}
/**
* Makes sense for {@link ParameterizedType}
*/
public static List<Type> getAllTypeArguments(Type javaType) {
if (!(javaType instanceof ParameterizedType)) {
return Collections.emptyList();
}
return Lists.immutableListOf(((ParameterizedType) javaType).getActualTypeArguments());
}
public static List<Class<?>> findClasses(Class<? extends Annotation> annotation, String... packages) {
Validate.argumentsAreNotNull(annotation, packages);
List<String> names = new FastClasspathScanner(packages).scan().getNamesOfClassesWithAnnotation(annotation);
List<Class<?>> classes = new ArrayList<>();
for (String className : names) {
classes.add(classForName(className));
}
return classes;
}
public static Optional<Type> isConcreteType(Type javaType){
if (javaType instanceof Class || javaType instanceof ParameterizedType) {
return Optional.of(javaType);
} else if (javaType instanceof WildcardType) {
// If the wildcard type has an explicit upper bound (i.e. not Object), we use that
WildcardType wildcardType = (WildcardType) javaType;
if (wildcardType.getLowerBounds().length == 0) {
for (Type type : wildcardType.getUpperBounds()) {
if (type instanceof Class && ((Class<?>) type).equals(Object.class)) {
continue;
}
return Optional.of(type);
}
}
}
return Optional.empty();
}
/**
* for example: Map<String, String> -> Map
*/
public static Class extractClass(Type javaType) {
if (javaType instanceof ParameterizedType
&& ((ParameterizedType)javaType).getRawType() instanceof Class){
return (Class)((ParameterizedType)javaType).getRawType();
} else if (javaType instanceof GenericArrayType) {
return Object[].class;
} else if (javaType instanceof Class) {
return (Class)javaType;
}
throw new JaversException(JaversExceptionCode.CLASS_EXTRACTION_ERROR, javaType);
}
public static boolean isAnnotationPresentInHierarchy(Class<?> clazz, Class<? extends Annotation> ann){
Class<?> current = clazz;
while (current != null && current != Object.class){
if (current.isAnnotationPresent(ann)){
return true;
}
current = current.getSuperclass();
}
return false;
}
public static int calculateHierarchyDistance(Class<?> clazz, Class<?> parent) {
Class<?> current = clazz;
int distance = 0;
//search in class hierarchy
while (current != null) {
//try class
if (parent == current) {
return distance;
}
//try interfaces
for (Class<?> interf : current.getInterfaces()) {
if (parent == interf) {
return distance + 1;
}
}
//step up in class hierarchy
current = current.getSuperclass();
distance++;
}
return Integer.MAX_VALUE;
}
public static String reflectiveToString(Object cdoId) {
if (cdoId == null){
return "";
}
if (cdoId instanceof String) {
return (String) cdoId;
}
if (WellKnownValueTypes.isValueType(cdoId) || Primitives.isPrimitiveOrBox(cdoId)){
return cdoId.toString();
}
StringBuilder ret = new StringBuilder();
for (JaversField f : getAllPersistentFields(cdoId.getClass()) ){
Object val = f.getEvenIfPrivate(cdoId);
if (val != null) {
ret.append(val.toString());
}
ret.append(",");
}
if (ret.length() == 0) {
return cdoId.toString();
}
else{
ret.delete(ret.length()-1, ret.length());
return ret.toString();
}
}
public static boolean isAssignableFromAny(Class clazz, Class<?>[] assignableFrom) {
for (Class<?> standardPrimitive : assignableFrom) {
if (standardPrimitive.isAssignableFrom(clazz)) {
return true;
}
}
return false;
}
public static <T> T getAnnotationValue(Annotation ann, String propertyName) {
return (T) ReflectionUtil.invokeGetter(ann, propertyName);
}
}