package tc.oc.commons.core.inspect; import java.lang.invoke.MethodHandle; import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.cache.LoadingCache; import com.google.common.reflect.TypeToken; import tc.oc.commons.core.reflect.Fields; import tc.oc.commons.core.reflect.Members; import tc.oc.commons.core.reflect.MethodHandleUtils; import tc.oc.commons.core.reflect.Methods; import tc.oc.commons.core.util.CacheUtils; public class ReflectiveProperty implements InspectableProperty { private static final LoadingCache<Class<? extends Inspectable>, List<ReflectiveProperty>> CACHE = CacheUtils.newCache( inspectable -> Stream.concat(Members.annotations(Inspectable.Inspect.class, Fields.declaredInAncestors(inspectable)) .merge(ReflectiveProperty::of), Members.annotations(Inspectable.Inspect.class, Methods.declaredMethodsInAncestors(inspectable)) .merge(ReflectiveProperty::of)) .collect(Collectors.toList()) ); public static <I extends Inspectable> List<ReflectiveProperty> all(Class<I> type) { return CACHE.getUnchecked(type); } public static ReflectiveProperty of(Member member, Inspectable.Inspect annotation) { if(member instanceof Field) { return of((Field) member, annotation); } else if(member instanceof Method) { return of((Method) member, annotation); } else { throw new IllegalArgumentException("Don't know how to inspect a " + member.getClass().getName()); } } public static ReflectiveProperty of(Field field, Inspectable.Inspect annotation) { field.setAccessible(true); return new ReflectiveProperty( annotation.name().length() > 0 ? annotation.name() : field.getName(), TypeToken.of(field.getGenericType()), new Inspection(annotation), MethodHandleUtils.privateUnreflectGetter(field) ); } public static ReflectiveProperty of(Method method, Inspectable.Inspect annotation) { if(method.getParameterTypes().length > 0) { throw new IllegalArgumentException("Can't inspect a method with parameters"); } method.setAccessible(true); return new ReflectiveProperty( annotation.name().length() > 0 ? annotation.name() : Methods.removeBeanPrefix(method.getName()), TypeToken.of(method.getGenericReturnType()), new Inspection(annotation), MethodHandleUtils.privateUnreflect(method) ); } private final String name; private final TypeToken<?> type; private final Inspection options; private final MethodHandle handle; ReflectiveProperty(String name, TypeToken<?> type, Inspection options, MethodHandle handle) { this.name = name; this.type = type; this.options = options; this.handle = handle; } @Override public String name() { return name; } @Override public Inspection options() { return options; } @Override public Object value(Inspectable inspectable) throws Throwable { return handle.invoke(inspectable); } }