com.gh.bmd.jrt.android.v4.core.DefaultObjectContextRoutineBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.gh.bmd.jrt.android.v4.core.DefaultObjectContextRoutineBuilder.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.gh.bmd.jrt.android.v4.core;

import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;

import com.gh.bmd.jrt.android.annotation.CacheStrategy;
import com.gh.bmd.jrt.android.annotation.ClashResolution;
import com.gh.bmd.jrt.android.annotation.Id;
import com.gh.bmd.jrt.android.builder.ContextRoutineBuilder;
import com.gh.bmd.jrt.android.builder.FactoryContext;
import com.gh.bmd.jrt.android.builder.InvocationContextRoutineBuilder;
import com.gh.bmd.jrt.android.builder.ObjectContextRoutineBuilder;
import com.gh.bmd.jrt.android.invocation.ContextInvocation;
import com.gh.bmd.jrt.android.invocation.ContextSingleCallInvocation;
import com.gh.bmd.jrt.annotation.Bind;
import com.gh.bmd.jrt.annotation.Pass;
import com.gh.bmd.jrt.annotation.Pass.PassMode;
import com.gh.bmd.jrt.annotation.ShareGroup;
import com.gh.bmd.jrt.annotation.Timeout;
import com.gh.bmd.jrt.annotation.TimeoutAction;
import com.gh.bmd.jrt.builder.RoutineConfiguration;
import com.gh.bmd.jrt.builder.RoutineConfiguration.Builder;
import com.gh.bmd.jrt.builder.RoutineConfiguration.OrderType;
import com.gh.bmd.jrt.channel.OutputChannel;
import com.gh.bmd.jrt.channel.ParameterChannel;
import com.gh.bmd.jrt.channel.ResultChannel;
import com.gh.bmd.jrt.common.ClassToken;
import com.gh.bmd.jrt.common.InvocationException;
import com.gh.bmd.jrt.common.Reflection;
import com.gh.bmd.jrt.common.RoutineException;
import com.gh.bmd.jrt.log.Logger;
import com.gh.bmd.jrt.routine.Routine;

import java.lang.annotation.Annotation;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import static com.gh.bmd.jrt.builder.RoutineBuilders.getParamMode;
import static com.gh.bmd.jrt.builder.RoutineBuilders.getReturnMode;
import static com.gh.bmd.jrt.builder.RoutineBuilders.getSharedMutex;
import static com.gh.bmd.jrt.common.Reflection.boxingClass;
import static com.gh.bmd.jrt.common.Reflection.findConstructor;

/**
 * Class implementing a builder of routine objects based on methods of a concrete object instance.
 * <p/>
 * Created by Davide on 4/6/2015.
 */
class DefaultObjectContextRoutineBuilder implements ObjectContextRoutineBuilder {

    private static final HashMap<String, Class<?>> sPrimitiveClassMap = new HashMap<String, Class<?>>();

    private final WeakReference<Object> mContext;

    private final Class<?> mTargetClass;

    private Object[] mArgs = Reflection.NO_ARGS;

    private CacheStrategyType mCacheStrategyType;

    private ClashResolutionType mClashResolutionType;

    private RoutineConfiguration mConfiguration;

    private int mInvocationId = ContextRoutineBuilder.AUTO;

    private String mShareGroup;

    /**
     * Constructor.
     *
     * @param activity    the context activity.
     * @param targetClass the invocation class token.
     * @throws java.lang.NullPointerException if the activity or class token are null.
     */
    DefaultObjectContextRoutineBuilder(@Nonnull final FragmentActivity activity,
            @Nonnull final Class<?> targetClass) {

        this((Object) activity, targetClass);
    }

    /**
     * Constructor.
     *
     * @param fragment    the context fragment.
     * @param targetClass the invocation class token.
     * @throws java.lang.NullPointerException if the fragment or class token are null.
     */
    DefaultObjectContextRoutineBuilder(@Nonnull final Fragment fragment, @Nonnull final Class<?> targetClass) {

        this((Object) fragment, targetClass);
    }

    /**
     * Constructor.
     *
     * @param context     the routine context.
     * @param targetClass the target object class.
     * @throws java.lang.NullPointerException if any of the parameter is null.
     */
    @SuppressWarnings("ConstantConditions")
    private DefaultObjectContextRoutineBuilder(@Nonnull final Object context, @Nonnull final Class<?> targetClass) {

        if (context == null) {

            throw new NullPointerException("the routine context must not be null");
        }

        mContext = new WeakReference<Object>(context);
        mTargetClass = targetClass;
        final HashSet<String> bindingSet = new HashSet<String>();

        for (final Method method : targetClass.getMethods()) {

            final Bind annotation = method.getAnnotation(Bind.class);

            if (annotation != null) {

                final String name = annotation.value();

                if (bindingSet.contains(name)) {

                    throw new IllegalArgumentException(
                            "the name '" + name + "' has already been used to identify a different" + " method");
                }

                bindingSet.add(name);
            }
        }
    }

    @Nonnull
    private static Class<?>[] forNames(@Nonnull final String[] names) throws ClassNotFoundException {

        final int length = names.length;
        final Class<?>[] classes = new Class[length];
        final HashMap<String, Class<?>> classMap = sPrimitiveClassMap;

        for (int i = 0; i < length; i++) {

            final String name = names[i];
            final Class<?> primitiveClass = classMap.get(name);

            if (primitiveClass != null) {

                classes[i] = primitiveClass;

            } else {

                classes[i] = Class.forName(name);
            }
        }

        return classes;
    }

    @Nullable
    private static Method getAnnotatedMethod(@Nonnull final Class<?> targetClass, @Nonnull final String name) {

        Method targetMethod = null;

        for (final Method method : targetClass.getMethods()) {

            final Bind annotation = method.getAnnotation(Bind.class);

            if ((annotation != null) && name.equals(annotation.value())) {

                targetMethod = method;
                break;
            }
        }

        if (targetMethod == null) {

            for (final Method method : targetClass.getDeclaredMethods()) {

                final Bind annotation = method.getAnnotation(Bind.class);

                if ((annotation != null) && name.equals(annotation.value())) {

                    targetMethod = method;
                    break;
                }
            }
        }

        return targetMethod;
    }

    @Nonnull
    private static <INPUT, OUTPUT> InvocationContextRoutineBuilder<INPUT, OUTPUT> getBuilder(
            @Nonnull WeakReference<Object> contextReference,
            @Nonnull final ClassToken<? extends ContextInvocation<INPUT, OUTPUT>> classToken) {

        final Object context = contextReference.get();

        if (context == null) {

            throw new IllegalStateException("the routine context has been destroyed");
        }

        if (context instanceof FragmentActivity) {

            return JRoutine.onActivity((FragmentActivity) context, classToken);

        } else if (context instanceof Fragment) {

            return JRoutine.onFragment((Fragment) context, classToken);
        }

        throw new IllegalArgumentException("invalid context type: " + context.getClass().getCanonicalName());
    }

    @Nonnull
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    private static Object getInstance(@Nonnull final Context context, @Nonnull final Class<?> targetClass,
            @Nonnull final Object[] args)
            throws IllegalAccessException, InvocationTargetException, InstantiationException {

        Object target = null;

        if (context instanceof FactoryContext) {

            // the context here is always the application
            synchronized (context) {

                target = ((FactoryContext) context).geInstance(targetClass, args);
            }
        }

        if (target == null) {

            target = findConstructor(targetClass, args).newInstance(args);

        } else if (!targetClass.isInstance(target)) {

            throw new InstantiationException();
        }

        return target;
    }

    @Nonnull
    private static String[] toNames(@Nonnull final Class<?>[] classes) {

        final int length = classes.length;
        final String[] names = new String[length];

        for (int i = 0; i < length; i++) {

            names[i] = classes[i].getName();
        }

        return names;
    }

    @Nullable
    private static CacheStrategyType withCacheAnnotation(@Nullable final CacheStrategyType strategyType,
            @Nonnull final Method method) {

        final CacheStrategy cacheAnnotation = method.getAnnotation(CacheStrategy.class);

        if (cacheAnnotation != null) {

            return cacheAnnotation.value();
        }

        return strategyType;
    }

    @Nullable
    private static ClashResolutionType withClashAnnotation(@Nullable final ClashResolutionType resolutionType,
            @Nonnull final Method method) {

        final ClashResolution clashAnnotation = method.getAnnotation(ClashResolution.class);

        if (clashAnnotation != null) {

            return clashAnnotation.value();
        }

        return resolutionType;
    }

    private static int withIdAnnotation(final int invocationId, @Nonnull final Method method) {

        final Id idAnnotation = method.getAnnotation(Id.class);

        if (idAnnotation != null) {

            return idAnnotation.value();
        }

        return invocationId;
    }

    @Nullable
    private static String withShareAnnotation(@Nullable final String shareGroup, @Nonnull final Method method) {

        final ShareGroup shareGroupAnnotation = method.getAnnotation(ShareGroup.class);

        if (shareGroupAnnotation != null) {

            return shareGroupAnnotation.value();
        }

        return shareGroup;
    }

    @Nonnull
    private static Builder withTimeoutAnnotation(@Nullable final RoutineConfiguration configuration,
            @Nonnull final Method method) {

        final Builder builder = RoutineConfiguration.notNull(configuration).builderFrom();
        final Timeout timeoutAnnotation = method.getAnnotation(Timeout.class);

        if (timeoutAnnotation != null) {

            builder.withReadTimeout(timeoutAnnotation.value(), timeoutAnnotation.unit());
        }

        final TimeoutAction actionAnnotation = method.getAnnotation(TimeoutAction.class);

        if (actionAnnotation != null) {

            builder.onReadTimeout(actionAnnotation.value());
        }

        return builder;
    }

    @Nonnull
    public <INPUT, OUTPUT> Routine<INPUT, OUTPUT> boundMethod(@Nonnull final String name) {

        final Class<?> targetClass = mTargetClass;
        final Method targetMethod = getAnnotatedMethod(targetClass, name);

        if (targetMethod == null) {

            throw new IllegalArgumentException("no annotated method with name '" + name + "' has been found");
        }

        final RoutineConfiguration configuration = mConfiguration;

        if (configuration != null) {

            warn(configuration);
        }

        final BoundMethodToken<INPUT, OUTPUT> classToken = new BoundMethodToken<INPUT, OUTPUT>();
        final Object[] args = mArgs;
        final String shareGroup = withShareAnnotation(mShareGroup, targetMethod);
        final RoutineConfiguration routineConfiguration = withTimeoutAnnotation(configuration, targetMethod)
                .withInputOrder(OrderType.PASSING_ORDER).buildConfiguration();
        final int invocationId = withIdAnnotation(mInvocationId, targetMethod);
        final ClashResolutionType resolutionType = withClashAnnotation(mClashResolutionType, targetMethod);
        final CacheStrategyType strategyType = withCacheAnnotation(mCacheStrategyType, targetMethod);
        return getBuilder(mContext, classToken).withArgs(targetClass.getName(), args, shareGroup, name)
                .withConfiguration(routineConfiguration).withId(invocationId).onClash(resolutionType)
                .onComplete(strategyType).buildRoutine();
    }

    @Nonnull
    public <INPUT, OUTPUT> Routine<INPUT, OUTPUT> method(@Nonnull final String name,
            @Nonnull final Class<?>... parameterTypes) {

        Method targetMethod;
        final Class<?> targetClass = mTargetClass;

        try {

            targetMethod = targetClass.getMethod(name, parameterTypes);

        } catch (final NoSuchMethodException ignored) {

            try {

                targetMethod = targetClass.getDeclaredMethod(name, parameterTypes);

            } catch (final NoSuchMethodException e) {

                throw new IllegalArgumentException(e);
            }
        }

        final RoutineConfiguration configuration = mConfiguration;

        if (configuration != null) {

            warn(configuration);
        }

        final MethodSignatureToken<INPUT, OUTPUT> classToken = new MethodSignatureToken<INPUT, OUTPUT>();
        final Object[] args = mArgs;
        final String shareGroup = withShareAnnotation(mShareGroup, targetMethod);
        final RoutineConfiguration routineConfiguration = withTimeoutAnnotation(configuration, targetMethod)
                .withInputOrder(OrderType.PASSING_ORDER).buildConfiguration();
        final int invocationId = withIdAnnotation(mInvocationId, targetMethod);
        final ClashResolutionType resolutionType = withClashAnnotation(mClashResolutionType, targetMethod);
        final CacheStrategyType strategyType = withCacheAnnotation(mCacheStrategyType, targetMethod);
        return getBuilder(mContext, classToken)
                .withArgs(targetClass.getName(), args, shareGroup, name, toNames(parameterTypes))
                .withConfiguration(routineConfiguration).withId(invocationId).onClash(resolutionType)
                .onComplete(strategyType).buildRoutine();
    }

    @Nonnull
    public <INPUT, OUTPUT> Routine<INPUT, OUTPUT> method(@Nonnull final Method method) {

        return method(method.getName(), method.getParameterTypes());
    }

    @Nonnull
    public <TYPE> TYPE buildProxy(@Nonnull final Class<TYPE> itf) {

        if (!itf.isInterface()) {

            throw new IllegalArgumentException(
                    "the specified class is not an interface: " + itf.getCanonicalName());
        }

        final RoutineConfiguration configuration = mConfiguration;

        if (configuration != null) {

            warn(configuration);
        }

        final Object proxy = Proxy.newProxyInstance(itf.getClassLoader(), new Class[] { itf },
                new ProxyInvocationHandler(this, itf));
        return itf.cast(proxy);
    }

    @Nonnull
    public <TYPE> TYPE buildProxy(@Nonnull final ClassToken<TYPE> itf) {

        return buildProxy(itf.getRawClass());
    }

    @Nonnull
    public ObjectContextRoutineBuilder withConfiguration(@Nullable final RoutineConfiguration configuration) {

        mConfiguration = configuration;
        return this;
    }

    @Nonnull
    public ObjectContextRoutineBuilder withShareGroup(@Nullable final String group) {

        mShareGroup = group;
        return this;
    }

    @Nonnull
    public ObjectContextRoutineBuilder onClash(@Nullable final ClashResolutionType resolutionType) {

        mClashResolutionType = resolutionType;
        return this;
    }

    @Nonnull
    public ObjectContextRoutineBuilder onComplete(@Nullable final CacheStrategyType strategyType) {

        mCacheStrategyType = strategyType;
        return this;
    }

    @Nonnull
    public ObjectContextRoutineBuilder withArgs(@Nullable final Object... args) {

        mArgs = (args == null) ? Reflection.NO_ARGS : args.clone();
        return this;
    }

    @Nonnull
    public ObjectContextRoutineBuilder withId(final int invocationId) {

        mInvocationId = invocationId;
        return this;
    }

    /**
     * Logs any warning related to ignored options in the specified configuration.
     *
     * @param configuration the routine configuration.
     */
    private void warn(@Nonnull final RoutineConfiguration configuration) {

        Logger logger = null;
        final OrderType inputOrder = configuration.getInputOrderOr(null);

        if (inputOrder != null) {

            logger = Logger.newLogger(configuration, this);
            logger.wrn("the specified input order will be ignored: %s", inputOrder);
        }

        final OrderType outputOrder = configuration.getOutputOrderOr(null);

        if (outputOrder != null) {

            if (logger == null) {

                logger = Logger.newLogger(configuration, this);
            }

            logger.wrn("the specified output order will be ignored: %s", outputOrder);
        }
    }

    /**
     * Bound method invocation.
     *
     * @param <INPUT>  the input data type.
     * @param <OUTPUT> the output data type.
     */
    private static class BoundMethodInvocation<INPUT, OUTPUT> extends ContextSingleCallInvocation<INPUT, OUTPUT> {

        private final Object[] mArgs;

        private final String mBindingName;

        private final String mShareGroup;

        private final Class<?> mTargetClass;

        private Routine<INPUT, OUTPUT> mRoutine;

        /**
         * Constructor.
         *
         * @param targetClassName the target object class name.
         * @param args            the factory constructor arguments.
         * @param shareGroup      the share group name.
         * @param name            the binding name.
         */
        @SuppressWarnings("unchecked")
        public BoundMethodInvocation(@Nonnull final String targetClassName, @Nonnull final Object[] args,
                @Nullable final String shareGroup, @Nonnull final String name) throws ClassNotFoundException {

            mTargetClass = Class.forName(targetClassName);
            mArgs = args;
            mShareGroup = shareGroup;
            mBindingName = name;
        }

        @Override
        public void onCall(@Nonnull final List<? extends INPUT> inputs,
                @Nonnull final ResultChannel<OUTPUT> result) {

            result.pass(mRoutine.callSync(inputs));
        }

        @Override
        public void onContext(@Nonnull final Context context) {

            super.onContext(context);

            try {

                mRoutine = JRoutine.on(getInstance(context, mTargetClass, mArgs)).withShareGroup(mShareGroup)
                        .boundMethod(mBindingName);

            } catch (final RoutineException e) {

                throw e;

            } catch (final Throwable t) {

                throw new InvocationException(t);
            }
        }
    }

    /**
     * Class token of a {@link BoundMethodInvocation}.
     *
     * @param <INPUT>  the input data type.
     * @param <OUTPUT> the output data type.
     */
    private static class BoundMethodToken<INPUT, OUTPUT> extends ClassToken<BoundMethodInvocation<INPUT, OUTPUT>> {

    }

    /**
     * Generic method invocation.
     *
     * @param <INPUT>  the input data type.
     * @param <OUTPUT> the output data type.
     */
    private static class MethodSignatureInvocation<INPUT, OUTPUT>
            extends ContextSingleCallInvocation<INPUT, OUTPUT> {

        private final Object[] mArgs;

        private final String mMethodName;

        private final Class<?>[] mParameterTypes;

        private final String mShareGroup;

        private final Class<?> mTargetClass;

        private Routine<INPUT, OUTPUT> mRoutine;

        /**
         * Constructor.
         *
         * @param targetClassName the target object class name.
         * @param args            the factory constructor arguments.
         * @param shareGroup      the share group name.
         * @param name            the method name.
         * @param parameterTypes  the method parameter type names.
         * @throws java.lang.ClassNotFoundException if one of the specified classes is not found.
         */
        @SuppressWarnings("unchecked")
        public MethodSignatureInvocation(@Nonnull final String targetClassName, @Nonnull final Object[] args,
                @Nullable final String shareGroup, @Nonnull final String name,
                @Nonnull final String[] parameterTypes) throws ClassNotFoundException {

            mTargetClass = Class.forName(targetClassName);
            mArgs = args;
            mShareGroup = shareGroup;
            mMethodName = name;
            mParameterTypes = forNames(parameterTypes);
        }

        @Override
        public void onCall(@Nonnull final List<? extends INPUT> inputs,
                @Nonnull final ResultChannel<OUTPUT> result) {

            result.pass(mRoutine.callSync(inputs));
        }

        @Override
        public void onContext(@Nonnull final Context context) {

            super.onContext(context);

            try {

                mRoutine = JRoutine.on(getInstance(context, mTargetClass, mArgs)).withShareGroup(mShareGroup)
                        .method(mMethodName, mParameterTypes);

            } catch (final RoutineException e) {

                throw e;

            } catch (final Throwable t) {

                throw new InvocationException(t);
            }
        }
    }

    /**
     * Class token of a {@link MethodSignatureInvocation}.
     *
     * @param <INPUT>  the input data type.
     * @param <OUTPUT> the output data type.
     */
    private static class MethodSignatureToken<INPUT, OUTPUT>
            extends ClassToken<MethodSignatureInvocation<INPUT, OUTPUT>> {

    }

    /**
     * Proxy method invocation.
     */
    private static class ProxyInvocation extends ContextSingleCallInvocation<Object, Object> {

        private final Object[] mArgs;

        private final boolean mIsInputCollection;

        private final boolean mIsOutputCollection;

        private final String mMethodName;

        private final Class<?>[] mParameterTypes;

        private final Class<?> mProxyClass;

        private final String mShareGroup;

        private final Class<?> mTargetClass;

        private final Class<?>[] mTargetParameterTypes;

        private Object mMutex;

        private Object mTarget;

        /**
         * Constructor.
         *
         * @param proxyClassName       the proxy class name.
         * @param targetClassName      the target object class name.
         * @param args                 the factory constructor arguments.
         * @param shareGroup           the share group name.
         * @param name                 the method name.
         * @param parameterTypes       the method parameter type names.
         * @param targetParameterTypes the target method parameter type names.
         * @param isInputCollection    whether the input is a collection.
         * @param isOutputCollection   whether the output is a collection.
         * @throws java.lang.ClassNotFoundException if one of the specified classes is not found.
         */
        @SuppressWarnings("unchecked")
        public ProxyInvocation(@Nonnull final String proxyClassName, @Nonnull final String targetClassName,
                @Nonnull final Object[] args, @Nullable final String shareGroup, @Nonnull final String name,
                @Nonnull final String[] parameterTypes, @Nonnull final String[] targetParameterTypes,
                final boolean isInputCollection, final boolean isOutputCollection) throws ClassNotFoundException {

            mProxyClass = Class.forName(proxyClassName);
            mTargetClass = Class.forName(targetClassName);
            mArgs = args;
            mShareGroup = shareGroup;
            mMethodName = name;
            mParameterTypes = forNames(parameterTypes);
            mTargetParameterTypes = forNames(targetParameterTypes);
            mIsInputCollection = isInputCollection;
            mIsOutputCollection = isOutputCollection;
            mMutex = this;
        }

        @Nonnull
        private Method getTargetMethod(@Nonnull final Method method, @Nonnull final Class<?>[] targetParameterTypes)
                throws NoSuchMethodException {

            String name = null;
            Method targetMethod = null;
            final Class<?> targetClass = mTarget.getClass();
            final Bind annotation = method.getAnnotation(Bind.class);

            if (annotation != null) {

                name = annotation.value();
                targetMethod = getAnnotatedMethod(targetClass, name);
            }

            if (targetMethod == null) {

                if (name == null) {

                    name = method.getName();
                }

                try {

                    targetMethod = targetClass.getMethod(name, targetParameterTypes);

                } catch (final NoSuchMethodException ignored) {

                }

                if (targetMethod == null) {

                    targetMethod = targetClass.getDeclaredMethod(name, targetParameterTypes);
                }
            }

            return targetMethod;
        }

        @Override
        @SuppressWarnings("SynchronizeOnNonFinalField")
        public void onCall(@Nonnull final List<?> objects, @Nonnull final ResultChannel<Object> result) {

            try {

                final Class<?>[] parameterTypes = mParameterTypes;
                final Class<?>[] targetParameterTypes = mTargetParameterTypes;
                final Method method = mProxyClass.getMethod(mMethodName, parameterTypes);
                final Method targetMethod = getTargetMethod(method, targetParameterTypes);
                final Class<?> returnType = targetMethod.getReturnType();
                final Pass annotation = method.getAnnotation(Pass.class);
                final Class<?> expectedType;

                if (annotation != null) {

                    expectedType = annotation.value();

                } else {

                    expectedType = method.getReturnType();
                }

                if (!returnType.isAssignableFrom(expectedType)) {

                    throw new IllegalArgumentException("the proxy method has incompatible return type: " + method);
                }

                final Object methodResult;

                synchronized (mMutex) {

                    if (mIsInputCollection) {

                        final Class<?> paramType = targetParameterTypes[0];

                        if (paramType.isArray()) {

                            final int size = objects.size();
                            final Object array = Array.newInstance(paramType.getComponentType(), size);

                            for (int i = 0; i < size; i++) {

                                Array.set(array, i, objects.get(i));
                            }

                            methodResult = targetMethod.invoke(mTarget, array);

                        } else {

                            methodResult = targetMethod.invoke(mTarget, objects);
                        }

                    } else {

                        methodResult = targetMethod.invoke(mTarget, objects.toArray());
                    }
                }

                if (!Void.class.equals(boxingClass(returnType))) {

                    if (mIsOutputCollection) {

                        if (returnType.isArray()) {

                            if (methodResult != null) {

                                final int l = Array.getLength(methodResult);

                                for (int i = 0; i < l; ++i) {

                                    result.pass(Array.get(methodResult, i));
                                }
                            }

                        } else {

                            result.pass((Iterable<?>) methodResult);
                        }

                    } else {

                        result.pass(methodResult);
                    }
                }

            } catch (final RoutineException e) {

                throw e;

            } catch (final InvocationTargetException e) {

                throw new InvocationException(e.getCause());

            } catch (final Throwable t) {

                throw new InvocationException(t);
            }
        }

        @Override
        public void onContext(@Nonnull final Context context) {

            super.onContext(context);

            try {

                mTarget = getInstance(context, mTargetClass, mArgs);
                final String shareGroup = mShareGroup;

                if (!ShareGroup.NONE.equals(shareGroup)) {

                    mMutex = getSharedMutex(mTarget, shareGroup);
                }

            } catch (final RoutineException e) {

                throw e;

            } catch (final Throwable t) {

                throw new InvocationException(t);
            }
        }
    }

    /**
     * Invocation handler adapting a different interface to the target object instance.
     */
    private static class ProxyInvocationHandler implements InvocationHandler {

        private final Object[] mArgs;

        private final CacheStrategyType mCacheStrategyType;

        private final ClashResolutionType mClashResolutionType;

        private final RoutineConfiguration mConfiguration;

        private final WeakReference<Object> mContext;

        private final int mInvocationId;

        private final Class<?> mProxyClass;

        private final String mShareGroup;

        private final Class<?> mTargetClass;

        /**
         * Constructor.
         *
         * @param builder    the builder instance.
         * @param proxyClass the proxy class.
         */
        private ProxyInvocationHandler(@Nonnull final DefaultObjectContextRoutineBuilder builder,
                @Nonnull final Class<?> proxyClass) {

            mContext = builder.mContext;
            mTargetClass = builder.mTargetClass;
            mArgs = builder.mArgs;
            mConfiguration = RoutineConfiguration.notNull(builder.mConfiguration).builderFrom()
                    .buildConfiguration();
            mInvocationId = builder.mInvocationId;
            mClashResolutionType = builder.mClashResolutionType;
            mCacheStrategyType = builder.mCacheStrategyType;
            mShareGroup = builder.mShareGroup;
            mProxyClass = proxyClass;
        }

        public Object invoke(final Object proxy, @Nonnull final Method method, final Object[] args)
                throws Throwable {

            boolean isParallel = false;
            final Class<?>[] parameterTypes = method.getParameterTypes();
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            final int length = args.length;
            final boolean[] isAsync = new boolean[length];
            final Class<?>[] targetParameterTypes = new Class<?>[length];
            boolean isInputCollection = false;

            for (int i = 0; i < length; i++) {

                final PassMode paramMode = getParamMode(method, i);

                if (paramMode != null) {

                    isAsync[i] = true;
                    isParallel = (paramMode == PassMode.PARALLEL);
                    isInputCollection = (paramMode == PassMode.COLLECTION);

                    for (final Annotation annotation : parameterAnnotations[i]) {

                        if (annotation.annotationType() == Pass.class) {

                            targetParameterTypes[i] = ((Pass) annotation).value();
                            break;
                        }
                    }

                } else {

                    targetParameterTypes[i] = parameterTypes[i];
                }
            }

            PassMode returnMode = null;
            final Class<?> returnType = method.getReturnType();
            final Pass methodAnnotation = method.getAnnotation(Pass.class);

            if (methodAnnotation != null) {

                returnMode = getReturnMode(method);
            }

            final String shareGroup = withShareAnnotation(mShareGroup, method);
            final boolean isOutputCollection = (returnMode == PassMode.COLLECTION);
            final Builder builder = withTimeoutAnnotation(mConfiguration, method);
            final RoutineConfiguration configuration = builder
                    .withInputOrder((isParallel) ? OrderType.NONE : OrderType.PASSING_ORDER)
                    .withOutputOrder((returnMode == PassMode.COLLECTION) ? OrderType.PASSING_ORDER : OrderType.NONE)
                    .buildConfiguration();
            final int invocationId = withIdAnnotation(mInvocationId, method);
            final ClashResolutionType resolutionType = withClashAnnotation(mClashResolutionType, method);
            final CacheStrategyType strategyType = withCacheAnnotation(mCacheStrategyType, method);
            final InvocationContextRoutineBuilder<Object, Object> routineBuilder = getBuilder(mContext,
                    ClassToken.tokenOf(ProxyInvocation.class));
            final Routine<Object, Object> routine = routineBuilder
                    .withArgs(mProxyClass.getName(), mTargetClass.getName(), mArgs, shareGroup, method.getName(),
                            toNames(parameterTypes), toNames(targetParameterTypes), isInputCollection,
                            isOutputCollection)
                    .withConfiguration(configuration).withId(invocationId).onClash(resolutionType)
                    .onComplete(strategyType).buildRoutine();
            final ParameterChannel<Object, Object> parameterChannel = (isParallel) ? routine.invokeParallel()
                    : routine.invokeAsync();

            for (int i = 0; i < length; i++) {

                if (isAsync[i]) {

                    final Class<?> parameterType = parameterTypes[i];

                    if (OutputChannel.class.isAssignableFrom(parameterType)) {

                        parameterChannel.pass((OutputChannel<?>) args[i]);

                    } else if (args[i] == null) {

                        parameterChannel.pass((Object[]) null);

                    } else if (parameterType.isArray()) {

                        final Object array = args[i];
                        final int size = Array.getLength(array);

                        for (int j = 0; j < size; j++) {

                            parameterChannel.pass(Array.get(array, j));
                        }

                    } else if (Iterable.class.isAssignableFrom(parameterType)) {

                        for (final Object object : ((Iterable<?>) args[i])) {

                            parameterChannel.pass(object);
                        }
                    }

                } else {

                    parameterChannel.pass(args[i]);
                }
            }

            final OutputChannel<Object> outputChannel = parameterChannel.result();

            if (!Void.class.equals(boxingClass(returnType))) {

                if (methodAnnotation != null) {

                    if (OutputChannel.class.isAssignableFrom(returnType)) {

                        return outputChannel;
                    }

                    if (returnType.isAssignableFrom(List.class)) {

                        return outputChannel.readAll();
                    }

                    if (returnType.isArray()) {

                        final List<Object> results = outputChannel.readAll();
                        final int size = results.size();
                        final Object array = Array.newInstance(returnType.getComponentType(), size);

                        for (int i = 0; i < size; ++i) {

                            Array.set(array, i, results.get(i));
                        }

                        return array;
                    }
                }

                return outputChannel.readNext();
            }

            return null;
        }
    }

    static {

        final HashMap<String, Class<?>> classMap = sPrimitiveClassMap;
        classMap.put(byte.class.getName(), byte.class);
        classMap.put(char.class.getName(), char.class);
        classMap.put(int.class.getName(), int.class);
        classMap.put(long.class.getName(), long.class);
        classMap.put(float.class.getName(), float.class);
        classMap.put(double.class.getName(), double.class);
        classMap.put(short.class.getName(), short.class);
        classMap.put(void.class.getName(), void.class);
    }
}