package com.github.rschmitt.dynamicobject.internal; import com.github.rschmitt.dynamicobject.Cached; import com.github.rschmitt.dynamicobject.DynamicObject; import com.github.rschmitt.dynamicobject.Key; import com.github.rschmitt.dynamicobject.Meta; import com.github.rschmitt.dynamicobject.Required; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Stream; import static com.github.rschmitt.dynamicobject.internal.ClojureStuff.cachedRead; import static java.util.stream.Collectors.toSet; class Reflection { static <D extends DynamicObject<D>> Collection<Method> requiredFields(Class<D> type) { Collection<Method> fields = fieldGetters(type); return fields.stream().filter(Reflection::isRequired).collect(toSet()); } static <D extends DynamicObject<D>> Set<Object> cachedKeys(Class<D> type) { return Arrays.stream(type.getMethods()) .flatMap(Reflection::getCachedKeysForMethod) .collect(toSet()); } private static Stream<Object> getCachedKeysForMethod(Method method) { if (isGetter(method)) { if (method.getAnnotation(Cached.class) != null) { return Stream.of(getKeyForGetter(method)); } else { return Stream.empty(); } } else if (isBuilder(method)) { if (method.getAnnotation(Cached.class) != null) { return Stream.of(getKeyForBuilder(method)); } else { // If the getter has an annotation it'll contribute it directly return Stream.empty(); } } else { return Stream.empty(); } } static <D extends DynamicObject<D>> Collection<Method> fieldGetters(Class<D> type) { Collection<Method> ret = new LinkedHashSet<>(); for (Method method : type.getDeclaredMethods()) if (isGetter(method)) ret.add(method); return ret; } private static boolean isBuilder(Method method) { return method.getParameterCount() == 1 && method.getDeclaringClass().isAssignableFrom(method.getReturnType()); } private static boolean isAnyGetter(Method method) { return method.getParameterCount() == 0 && !method.isDefault() && !method.isSynthetic() && (method.getModifiers() & Modifier.STATIC) == 0 && method.getReturnType() != Void.TYPE; } private static boolean isGetter(Method method) { return isAnyGetter(method) && !isMetadataGetter(method); } static boolean isMetadataGetter(Method method) { return isAnyGetter(method) && hasAnnotation(method, Meta.class); } static boolean isRequired(Method getter) { return hasAnnotation(getter, Required.class); } private static boolean hasAnnotation(Method method, Class<? extends Annotation> ann) { List<Annotation> annotations = Arrays.asList(method.getAnnotations()); for (Annotation annotation : annotations) if (annotation.annotationType().equals(ann)) return true; return false; } static boolean isMetadataBuilder(Method method) { if (method.getParameterCount() != 1) return false; if (hasAnnotation(method, Meta.class)) return true; if (hasAnnotation(method, Key.class)) return false; Method correspondingGetter = getCorrespondingGetter(method); return hasAnnotation(correspondingGetter, Meta.class); } static Object getKeyForGetter(Method method) { Key annotation = getMethodAnnotation(method, Key.class); if (annotation == null) return stringToKey(":" + method.getName()); else return stringToKey(annotation.value()); } private static Object stringToKey(String keyName) { if (keyName.charAt(0) == ':') return cachedRead(keyName); else return keyName; } @SuppressWarnings("unchecked") private static <T> T getMethodAnnotation(Method method, Class<T> annotationType) { for (Annotation annotation : method.getAnnotations()) if (annotation.annotationType().equals(annotationType)) return (T) annotation; return null; } static Object getKeyForBuilder(Method method) { Key annotation = getMethodAnnotation(method, Key.class); if (annotation == null) return getKeyForGetter(getCorrespondingGetter(method)); else return stringToKey(annotation.value()); } private static Method getCorrespondingGetter(Method builderMethod) { try { Class<?> type = builderMethod.getDeclaringClass(); Method correspondingGetter = type.getMethod(builderMethod.getName()); return correspondingGetter; } catch (NoSuchMethodException ex) { throw new IllegalStateException("Builder method " + builderMethod + " must have a corresponding getter method or a @Key annotation.", ex); } } static Class<?> getRawType(Type type) { if (type instanceof Class) return (Class<?>) type; else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; return (Class<?>) parameterizedType.getRawType(); } else throw new UnsupportedOperationException(); } static Type getTypeArgument(Type type, int idx) { if (type instanceof Class) return Object.class; else if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; return parameterizedType.getActualTypeArguments()[idx]; } else throw new UnsupportedOperationException(); } }