/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.sesame.config; import java.lang.annotation.Annotation; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.Type; import java.lang.reflect.UndeclaredThrowableException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; import javax.inject.Inject; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.opengamma.core.config.Config; import com.opengamma.sesame.function.FunctionMetadata; import com.opengamma.sesame.function.Parameter; import com.opengamma.sesame.proxy.ProxyInvocationHandler; import com.opengamma.util.ArgumentChecker; import com.thoughtworks.paranamer.AdaptiveParanamer; import com.thoughtworks.paranamer.AnnotationParanamer; import com.thoughtworks.paranamer.BytecodeReadingParanamer; import com.thoughtworks.paranamer.CachingParanamer; import com.thoughtworks.paranamer.Paranamer; import com.thoughtworks.paranamer.PositionalParanamer; /** * Utilities to assist working with functions in the calculation engine. */ public final class EngineUtils { /** * Cache of the results of {@link #getSupertypes}. */ private static ConcurrentMap<Class<?>, Set<Class<?>>> s_supertypes = Maps.newConcurrentMap(); /** * Cache of the results of {@link #getInterfaces}. */ private static ConcurrentMap<Class<?>, Set<Class<?>>> s_interfaces = Maps.newConcurrentMap(); /** * Paranamer instance. */ private static Paranamer s_paranamer = new CachingParanamer( new AdaptiveParanamer( new BytecodeReadingParanamer(), new AnnotationParanamer(), new PositionalParanamer())); /** * Restricted constructor. */ private EngineUtils() { } //------------------------------------------------------------------------- /** * Returns a constructor for the engine to build instances of type. * <p> * Only public constructors are considered. * If there is only one constructor it is used. If there are multiple constructors * then one, and only one, must be annotated with {@link Inject}. * * @param <T> the type * @param type the type to find the constructor for, not null * @return the constructor the engine should use for building instances, not null * @throws IllegalArgumentException if there isn't a valid constructor */ public static <T> Constructor<T> getConstructor(Class<T> type) { @SuppressWarnings("unchecked") Constructor<T>[] constructors = (Constructor<T>[]) type.getConstructors(); if (constructors.length == 0) { throw new IllegalArgumentException("No public constructor found: " + type.getName()); } // one constructor if (constructors.length == 1) { return constructors[0]; } // many constructors List<Constructor<T>> injectableConstructors = new ArrayList<>(); for (Constructor<T> constructor : constructors) { Inject annotation = constructor.getAnnotation(Inject.class); if (annotation != null) { injectableConstructors.add(constructor); } } if (injectableConstructors.size() > 1) { throw new IllegalArgumentException("Multiple public constructors annotated with @Inject, but only one is allowed: " + type.getName()); } if (injectableConstructors.isEmpty()) { throw new IllegalArgumentException("Multiple public constructors found but none annotated with @Inject: " + type.getName()); } return injectableConstructors.get(0); } //------------------------------------------------------------------------- /** * Gets the parameters of the method. * * @param method the method to examine, not null * @return the list of parameters, not null */ public static List<Parameter> getParameters(Method method) { return getParameters(method, method.getDeclaringClass(), method.getGenericParameterTypes(), method.getParameterAnnotations()); } /** * Gets the parameters of the constructor. * * @param constructor the constructor to examine, not null * @return the list of parameters, not null */ public static List<Parameter> getParameters(Constructor<?> constructor) { return getParameters(constructor, constructor.getDeclaringClass(), constructor.getGenericParameterTypes(), constructor.getParameterAnnotations()); } private static List<Parameter> getParameters(AccessibleObject ctorOrMethod, Class<?> declaringClass, Type[] genericTypes, Annotation[][] allAnnotations) { String[] paramNames = s_paranamer.lookupParameterNames(ctorOrMethod); List<Parameter> parameters = Lists.newArrayList(); for (int i = 0; i < genericTypes.length; i++) { Map<Class<?>, Annotation> annotationMap = Maps.newHashMap(); Type genericType = genericTypes[i]; Annotation[] annotations = allAnnotations[i]; for (Annotation annotation : annotations) { annotationMap.put(annotation.annotationType(), annotation); } parameters.add(new Parameter(declaringClass, paramNames[i], genericType, i, annotationMap)); } return parameters; } //------------------------------------------------------------------------- /** * Creates function metadata for a named method on a class. * <p> * This is for testing and isn't intended to be robust. * * @param functionType the interface declaring the function method, not null * @param methodName the name of the method, not null * @return the meta-data for the function, not null */ public static FunctionMetadata createMetadata(Class<?> functionType, String methodName) { return new FunctionMetadata(getMethod(functionType, methodName)); } //------------------------------------------------------------------------- /** * Returns a named method on a type. * <p> * Only public methods are considered. * This only works if there is exactly one method with a matching name. * If there are zero or multiple methods with a matching name an exception is thrown. * * @param type the type declaring the method, not null * @param methodName the name of the method, not null * @return the method, not null * @throws IllegalArgumentException if the method is not found, * or there is more than one method in the class with a matching name */ public static Method getMethod(Class<?> type, String methodName) { Method[] methods = type.getMethods(); List<Method> foundMethods = new ArrayList<>(); for (Method method : methods) { if (methodName.equals(method.getName())) { foundMethods.add(method); } } if (foundMethods.size() > 1) { throw new IllegalArgumentException("Multiple methods found matching name: " + methodName); } if (foundMethods.isEmpty()) { throw new IllegalArgumentException("No method found: " + methodName); } return foundMethods.get(0); } //------------------------------------------------------------------------- /** * Gets the complete set of supertypes of a class. * <p> * This includes superclasses and all interfaces. * * @param type the type to examine, not null * @return the set of supertypes, not null */ public static Set<Class<?>> getSupertypes(Class<?> type) { Set<Class<?>> existingSupertypes = s_supertypes.get(type); if (existingSupertypes != null) { return existingSupertypes; } Set<Class<?>> supertypes = Sets.newLinkedHashSet(); Set<Class<?>> interfaces = Sets.newLinkedHashSet(); getSupertypes(type, supertypes, interfaces); supertypes.addAll(interfaces); Set<Class<?>> cachedSupertypes = Collections.unmodifiableSet(supertypes); s_supertypes.put(type, cachedSupertypes); return cachedSupertypes; } /** * Gets the complete set of interfaces of a type. * * @param type the type to examine, not null * @return the set of interfaces, not null */ public static Set<Class<?>> getInterfaces(Class<?> type) { Set<Class<?>> existingInterfaces = s_interfaces.get(type); if (existingInterfaces != null) { return existingInterfaces; } Set<Class<?>> interfaces = Sets.newLinkedHashSet(); getSupertypes(type, Sets.<Class<?>>newLinkedHashSet(), interfaces); Set<Class<?>> cachedInterfaces = Collections.unmodifiableSet(interfaces); s_interfaces.put(type, cachedInterfaces); return cachedInterfaces; } private static void getSupertypes(Class<?> type, Set<Class<?>> supertypeAccumulator, Set<Class<?>> interfaceAccumulator) { supertypeAccumulator.add(type); getInterfaces(type.getInterfaces(), interfaceAccumulator); Class<?> superclass = type.getSuperclass(); if (superclass != null) { getSupertypes(superclass, supertypeAccumulator, interfaceAccumulator); } } private static void getInterfaces(Class<?>[] interfaces, Set<Class<?>> accumulator) { accumulator.addAll(Arrays.asList(interfaces)); for (Class<?> iFace : interfaces) { getInterfaces(iFace.getInterfaces(), accumulator); } } //------------------------------------------------------------------------- /** * Checks if at least one method has the specified annotation. * * @param type the type to examine, not null * @param annotation the annotation to find, not null * @return true if at least one method has the annotation */ public static boolean hasMethodAnnotation(Class<?> type, Class<? extends Annotation> annotation) { for (Method method : type.getMethods()) { if (method.getAnnotation(annotation) != null) { return true; } } return false; } //------------------------------------------------------------------------- /** * Returns the real object behind a proxy. * <p> * If object isn't a proxy it is returned. If it's a proxy the underlying object is returned. If there are multiple * proxies this method recurses until it finds the real object. * All proxies must have an invocation handler of type {@link ProxyInvocationHandler}. * * @param object an object, possibly a proxy, not null * @return the real object behind the proxy, not null */ public static Object getProxiedObject(Object object) { // if object isn't a proxy then we've reached the end of the chain of proxies if (!Proxy.isProxyClass(object.getClass())) { return object; } ProxyInvocationHandler invocationHandler = (ProxyInvocationHandler) Proxy.getInvocationHandler(object); return invocationHandler.getProxiedObject(); } /** * Converts a set of inputs to security types. * * @param inputs the collection of input securities/trades * @return a set of the types of the securities */ public static Set<Class<?>> getInputTypes(List<?> inputs) { Set<Class<?>> inputTypes = new HashSet<>(); for (Object input : ArgumentChecker.notNull(inputs, "inputs")) { inputTypes.add(input.getClass()); } return inputTypes; } /** * Returns the cause of an exception if it's more meaningful than * the exception itself. {@link InvocationTargetException} and * {@link UndeclaredThrowableException} are exceptions which are * thrown by the proxies in the engine when the underlying function * being proxied throws an exception. Generally these wrap the * underlying exceptions so they don't add anything except noise to * the stack traces. Unwrapping the underlying exceptions makes it * much easier to see what actually went wrong. * * @param ex an exception * @return the underlying cause of the exception */ public static Exception getCause(Exception ex) { return exceptionHasMoreMeaningfulCause(ex) ? getCause((Exception) ex.getCause()) : ex; } private static boolean exceptionHasMoreMeaningfulCause(Exception ex) { return (ex instanceof InvocationTargetException || ex instanceof UndeclaredThrowableException) && ex.getCause() != null && ex.getCause() instanceof Exception; } /** * @param type a type * @return true if the type is annotated with {@link Config} */ public static boolean isConfig(Class<?> type) { return type.getAnnotation(Config.class) != null; } }