package com.googlecode.jsonrpc4j; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; /** * Utilities for reflection. */ @SuppressWarnings("unused") public abstract class ReflectionUtil { private static final Map<String, Set<Method>> methodCache = new ConcurrentHashMap<>(); private static final Map<Method, List<Class<?>>> parameterTypeCache = new ConcurrentHashMap<>(); private static final Map<Method, List<Annotation>> methodAnnotationCache = new ConcurrentHashMap<>(); private static final Map<Method, List<List<Annotation>>> methodParamAnnotationCache = new ConcurrentHashMap<>(); /** * Finds methods with the given name on the given class. * * @param classes the classes * @param name the method name * @return the methods */ static Set<Method> findCandidateMethods(Class<?>[] classes, String name) { StringBuilder sb = new StringBuilder(); for (Class<?> clazz : classes) { sb.append(clazz.getName()).append("::"); } String cacheKey = sb.append(name).toString(); if (methodCache.containsKey(cacheKey)) { return methodCache.get(cacheKey); } Set<Method> methods = new HashSet<>(); for (Class<?> clazz : classes) { for (Method method : clazz.getMethods()) { if (method.getName().equals(name) || annotationMatches(method, name)) { methods.add(method); } } } methods = Collections.unmodifiableSet(methods); methodCache.put(cacheKey, methods); return methods; } /** * Checks for the annotation {@link JsonRpcMethod} on {@code method} to see if its value matches {@code name} * * @param method the method to check * @param name the expected method name * @return true if {@code method} is named {@code name} */ private static boolean annotationMatches(Method method, String name) { if (method.isAnnotationPresent(JsonRpcMethod.class)) { JsonRpcMethod methodAnnotation = method.getAnnotation(JsonRpcMethod.class); if (methodAnnotation.value().equals(name)) { return true; } } return false; } /** * Returns the parameter types for the given {@link Method}. * * @param method the {@link Method} * @return the parameter types */ static List<Class<?>> getParameterTypes(Method method) { if (parameterTypeCache.containsKey(method)) { return parameterTypeCache.get(method); } List<Class<?>> types = new ArrayList<>(); Collections.addAll(types, method.getParameterTypes()); types = Collections.unmodifiableList(types); parameterTypeCache.put(method, types); return types; } /** * Returns {@link Annotation}s of the given type defined * on the given {@link Method}. * * @param <T> the {@link Annotation} type * @param method the {@link Method} * @param type the type * @return the {@link Annotation}s */ public static <T extends Annotation> List<T> getAnnotations(Method method, Class<T> type) { return filterAnnotations(getAnnotations(method), type); } private static <T extends Annotation> List<T> filterAnnotations(Collection<Annotation> annotations, Class<T> type) { List<T> result = new ArrayList<>(); for (Annotation annotation : annotations) { if (type.isInstance(annotation)) { result.add(type.cast(annotation)); } } return result; } /** * Returns all of the {@link Annotation}s defined on * the given {@link Method}. * * @param method the {@link Method} * @return the {@link Annotation}s */ private static List<Annotation> getAnnotations(Method method) { if (methodAnnotationCache.containsKey(method)) { return methodAnnotationCache.get(method); } List<Annotation> annotations = new ArrayList<>(); Collections.addAll(annotations, method.getAnnotations()); annotations = Collections.unmodifiableList(annotations); methodAnnotationCache.put(method, annotations); return annotations; } /** * Returns the first {@link Annotation} of the given type * defined on the given {@link Method}. * * @param <T> the type * @param method the method * @param type the type of annotation * @return the annotation or null */ public static <T extends Annotation> T getAnnotation(Method method, Class<T> type) { for (Annotation a : getAnnotations(method)) { if (type.isInstance(a)) { return type.cast(a); } } return null; } /** * Returns the parameter {@link Annotation}s of the * given type for the given {@link Method}. * * @param <T> the {@link Annotation} type * @param type the type * @param method the {@link Method} * @return the {@link Annotation}s */ static <T extends Annotation> List<List<T>> getParameterAnnotations(Method method, Class<T> type) { List<List<T>> annotations = new ArrayList<>(); for (List<Annotation> paramAnnotations : getParameterAnnotations(method)) { annotations.add(filterAnnotations(paramAnnotations, type)); } return annotations; } /** * Returns the parameter {@link Annotation}s for the * given {@link Method}. * * @param method the {@link Method} * @return the {@link Annotation}s */ private static List<List<Annotation>> getParameterAnnotations(Method method) { if (methodParamAnnotationCache.containsKey(method)) { return methodParamAnnotationCache.get(method); } List<List<Annotation>> annotations = new ArrayList<>(); for (Annotation[] paramAnnotations : method.getParameterAnnotations()) { List<Annotation> listAnnotations = new ArrayList<>(); Collections.addAll(listAnnotations, paramAnnotations); annotations.add(listAnnotations); } annotations = Collections.unmodifiableList(annotations); methodParamAnnotationCache.put(method, annotations); return annotations; } /** * Parses the given arguments for the given method optionally * turning them into named parameters. * * @param method the method * @param arguments the arguments * @return the parsed arguments */ public static Object parseArguments(Method method, Object[] arguments) { JsonRpcParamsPassMode paramsPassMode = JsonRpcParamsPassMode.AUTO; JsonRpcMethod jsonRpcMethod = getAnnotation(method, JsonRpcMethod.class); if (jsonRpcMethod != null) paramsPassMode = jsonRpcMethod.paramsPassMode(); Map<String, Object> namedParams = getNamedParameters(method, arguments); switch (paramsPassMode) { case ARRAY: if (namedParams.size() > 0) { Object[] parsed = new Object[namedParams.size()]; int i = 0; for (Object value : namedParams.values()) { parsed[i++] = value; } return parsed; } else { return arguments != null ? arguments : new Object[] {}; } case OBJECT: if (namedParams.size() > 0) { return namedParams; } else { if (arguments == null) return new Object[] {}; throw new RuntimeException( "OBJECT parameters pass mode is impossible without declaring JsonRpcParam annotations for all parameters on method " + method.getName()); } case AUTO: default: if (namedParams.size() > 0) { return namedParams; } else { return arguments != null ? arguments : new Object[] {}; } } } /** * Checks method for @JsonRpcParam annotations and returns named parameters. * * @param method the method * @param arguments the arguments * @return named parameters or empty if no annotations found * @throws RuntimeException if some parameters are annotated and others not */ private static Map<String, Object> getNamedParameters(Method method, Object[] arguments) { Map<String, Object> namedParams = new LinkedHashMap<>(); Annotation[][] paramAnnotations = method.getParameterAnnotations(); for (int i = 0; i < paramAnnotations.length; i++) { Annotation[] ann = paramAnnotations[i]; for (Annotation an : ann) { if (JsonRpcParam.class.isInstance(an)) { JsonRpcParam jAnn = (JsonRpcParam) an; namedParams.put(jAnn.value(), arguments[i]); break; } } } if (arguments != null && arguments.length > 0 && namedParams.size() > 0 && namedParams.size() != arguments.length) { throw new RuntimeException("JsonRpcParam annotations were not found for all parameters on method " + method.getName()); } return namedParams; } }