All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nohope.akka.invoke.MessageMethodInvoker Maven / Gradle / Ivy

There is a newer version: 0.2.0
Show newest version
package org.nohope.akka.invoke;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.ArrayUtils;
import org.nohope.reflection.IntrospectionUtils;

import javax.annotation.Nonnull;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static org.nohope.reflection.IntrospectionUtils.*;

/**
 * Date: 25.07.12
 * Time: 11:26
 */
public final class MessageMethodInvoker {
    /** Cache for @OnReceive messages. */
    static final Map CACHE = new ConcurrentHashMap<>();
    private static final Joiner JOINER = Joiner.on(", ").useForNull("null");


    private MessageMethodInvoker() {
    }

    /**
     * Invokes method annotated with {@link org.nohope.akka.OnReceive} and argument types
     * matching given message type.
     * 

* In case method with given signature was not found in given target class * then examines list of fallback objects for matching method. *

* It's possible to invoke multi-args methods passing {@code true} to * expandObjectArray argument. *

     *     class A {
     *         {@link org.nohope.akka.OnReceive @OnReceive}
     *         private void multiarg(final int param1, final double param2) {
     *         }
     *
     *         {@link org.nohope.akka.OnReceive @OnReceive}
     *         private void onearg(final Object[] param) {
     *         }
     *     }
     *
     *     // multi-arg method will be invoked
     *     invokeOnReceive(new A(), new Object[] {1, 2.3}, true);
     *
     *     // one-arg method will be invoked
     *     invokeOnReceive(new A(), new Object[] {1, 2.3}, false);
     * 
* * * @param target target object/class * @param message message to be passed to target object method * @param expandObjectArray {@code true} allows object array expanding * @param fallbackObjects fallback * @return method invocation result * @throws NoSuchMethodException if no or more than one * @OnReceive method found or no methods found in target object * and list of fallback objects */ public static Object invokeOnReceive(@Nonnull final Object target, @Nonnull final Object message, final boolean expandObjectArray, final ComparatorProvider provider, final Object... fallbackObjects) throws Exception { final boolean expandNeeded = expandObjectArray && message instanceof Object[]; final Class[] parameterTypes = expandNeeded ? getClasses((Object[]) message) : getClasses(message) ; final Object[] targetObjects = ArrayUtils.add(fallbackObjects, 0, target); final Class[] targetClasses = ArrayUtils.add(getClasses(fallbackObjects), 0, IntrospectionUtils.getClass(target)); final Method method = getOrCache(provider, parameterTypes, targetClasses); Object realTarget = null; for (int i = 0; i <= fallbackObjects.length; i++) { if (instanceOf(targetClasses[i], method.getDeclaringClass())) { realTarget = targetObjects[i]; } } if (realTarget == null) { throw new IllegalStateException("How did we even get here?"); } try { if (expandNeeded) { return invoke(method, realTarget, (Object[]) message); } else { return invoke(method, realTarget, message); } } catch (final InvocationTargetException e) { final Throwable targetException = e.getTargetException(); // trying to rethrow original exception if (targetException instanceof Exception) { throw (Exception) targetException; } else if (targetException instanceof Error) { throw (Error) targetException; } // unsupported exception - wrapping with runtime throw illegal(target, message, method, e); } catch (IllegalAccessException | NoSuchMethodException e) { // this exception shouldn't be processed by underlying client logic throw illegal(target, message, method, e); } } public static Object invokeOnReceive(final ComparatorProvider provider, final Object target, final Object message, final Object... handlers) throws Exception { return invokeOnReceive(target, message, false, provider, handlers); } public static Object invokeOnReceive(final Object target, final Object message, final Object... handlers) throws Exception { return invokeOnReceive(target, message, false, InvokeStrategy.CLOSEST_BY_PARAMETER, handlers); } private static Method searchMethod(final ComparatorProvider provider, final Class targetClass, final Class[] parameterTypes) throws NoSuchMethodException { final Set methods = searchMethods(targetClass, new SignatureMatcher(parameterTypes)); if (methods.isEmpty()) { return null; } if (methods.size() == 1) { return methods.iterator().next(); } final List allMethods = Lists.newArrayList(methods); final Comparator comparator = provider.getComparator(targetClass, parameterTypes[0]); Collections.sort(allMethods, comparator); final Method bestMatch = allMethods.get(0); final Method nextMatch = allMethods.get(1); if (comparator.compare(bestMatch, nextMatch) == 0) { throw new NoSuchMethodException( "Only one @OnReceive method expected to match (" + JOINER.join(getClassNames(parameterTypes)) + ") parameter types but found " + methods.size() + "; happened at instance of class " + targetClass.getCanonicalName() ); } return bestMatch; } private static Method getOrCache(final ComparatorProvider provider, final Class[] parameterTypes, final Class... fallbackClasses) throws NoSuchMethodException { final Signature pair = Signature.of(provider, parameterTypes, fallbackClasses); if (!CACHE.containsKey(pair)) { Method method = null; for (final Class clazz : fallbackClasses) { method = searchMethod(provider, clazz, parameterTypes); if (method != null) { break; } } if (method == null) { throw new NoSuchMethodException( "No @OnReceive methods found to match (" + JOINER.join(getClassNames(parameterTypes)) + ") parameter types for handlers [" + JOINER.join(getClassNames(fallbackClasses)) + ']' ); } CACHE.put(pair, method); return method; } return CACHE.get(pair); } private static IllegalArgumentException illegal(@Nonnull final Object target, @Nonnull final Object message, final Method method, final Throwable e) { return new IllegalArgumentException(MessageFormat.format( "Unable to invoke {0}.{1}({2})", getCanonicalClassName(target), method.getName(), getCanonicalClassName(message)), e); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy