Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.dm.jrt.core.RoutineBuilders Maven / Gradle / Ivy
/*
* 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.github.dm.jrt.core;
import com.github.dm.jrt.annotation.Alias;
import com.github.dm.jrt.annotation.CoreInstances;
import com.github.dm.jrt.annotation.Input;
import com.github.dm.jrt.annotation.Input.InputMode;
import com.github.dm.jrt.annotation.InputMaxSize;
import com.github.dm.jrt.annotation.InputOrder;
import com.github.dm.jrt.annotation.InputTimeout;
import com.github.dm.jrt.annotation.Inputs;
import com.github.dm.jrt.annotation.Invoke;
import com.github.dm.jrt.annotation.Invoke.InvocationMode;
import com.github.dm.jrt.annotation.MaxInstances;
import com.github.dm.jrt.annotation.Output;
import com.github.dm.jrt.annotation.Output.OutputMode;
import com.github.dm.jrt.annotation.OutputMaxSize;
import com.github.dm.jrt.annotation.OutputOrder;
import com.github.dm.jrt.annotation.OutputTimeout;
import com.github.dm.jrt.annotation.Priority;
import com.github.dm.jrt.annotation.ReadTimeout;
import com.github.dm.jrt.annotation.ReadTimeoutAction;
import com.github.dm.jrt.annotation.SharedFields;
import com.github.dm.jrt.builder.InvocationConfiguration;
import com.github.dm.jrt.builder.ProxyConfiguration;
import com.github.dm.jrt.channel.InvocationChannel;
import com.github.dm.jrt.channel.OutputChannel;
import com.github.dm.jrt.channel.ResultChannel;
import com.github.dm.jrt.channel.RoutineException;
import com.github.dm.jrt.invocation.InvocationException;
import com.github.dm.jrt.invocation.InvocationInterruptedException;
import com.github.dm.jrt.routine.Routine;
import com.github.dm.jrt.util.Mutex;
import com.github.dm.jrt.util.Reflection;
import com.github.dm.jrt.util.WeakIdentityHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import static com.github.dm.jrt.util.Reflection.asArgs;
/**
* Utility class used to manage cached objects shared by routine builders.
*
* Created by davide-maestroni on 03/23/2015.
*/
public class RoutineBuilders {
private static final WeakIdentityHashMap, Map> sAliasMethods =
new WeakIdentityHashMap, Map>();
private static final WeakIdentityHashMap> sLocks =
new WeakIdentityHashMap>();
private static final WeakIdentityHashMap, Map> sMethods =
new WeakIdentityHashMap, Map>();
private static final WeakIdentityHashMap sMutexes =
new WeakIdentityHashMap();
/**
* Avoid direct instantiation.
*/
protected RoutineBuilders() {
}
/**
* Calls the specified target method from inside a routine invocation.
*
* @param targetMethod the target method.
* @param mutex the method mutex.
* @param target the target instance.
* @param objects the input objects.
* @param result the invocation result channel.
* @param inputMode the input transfer mode.
* @param outputMode the output transfer mode.
* @throws com.github.dm.jrt.channel.RoutineException in case of errors.
*/
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
public static void callFromInvocation(@NotNull final Method targetMethod,
@NotNull final Mutex mutex, @NotNull final Object target,
@NotNull final List> objects, @NotNull final ResultChannel result,
@Nullable final InputMode inputMode, @Nullable final OutputMode outputMode) {
Reflection.makeAccessible(targetMethod);
try {
final Object methodResult;
mutex.acquire();
try {
final Object[] args;
if (inputMode == InputMode.COLLECTION) {
final Class> paramClass = targetMethod.getParameterTypes()[0];
if (paramClass.isArray()) {
final int size = objects.size();
final Object array = Array.newInstance(paramClass.getComponentType(), size);
for (int i = 0; i < size; ++i) {
Array.set(array, i, objects.get(i));
}
args = asArgs(array);
} else {
args = asArgs(objects);
}
} else {
args = objects.toArray(new Object[objects.size()]);
}
methodResult = targetMethod.invoke(target, args);
} finally {
mutex.release();
}
final Class> returnType = targetMethod.getReturnType();
if (!Void.class.equals(Reflection.boxingClass(returnType))) {
if (outputMode == OutputMode.ELEMENT) {
if (returnType.isArray()) {
if (methodResult != null) {
result.orderByCall();
final int length = Array.getLength(methodResult);
for (int i = 0; i < length; ++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);
}
}
/**
* Returns a configuration properly modified by taking into account the annotations added to the
* specified method.
*
* @param configuration the initial configuration.
* @param method the target method.
* @return the modified configuration.
* @see com.github.dm.jrt.annotation.CoreInstances CoreInstances
* @see com.github.dm.jrt.annotation.InputMaxSize InputMaxSize
* @see com.github.dm.jrt.annotation.InputOrder InputOrder
* @see com.github.dm.jrt.annotation.InputTimeout InputTimeout
* @see com.github.dm.jrt.annotation.MaxInstances MaxInstances
* @see com.github.dm.jrt.annotation.OutputMaxSize OutputMaxSize
* @see com.github.dm.jrt.annotation.OutputOrder OutputOrder
* @see com.github.dm.jrt.annotation.OutputTimeout OutputTimeout
* @see com.github.dm.jrt.annotation.Priority Priority
* @see com.github.dm.jrt.annotation.ReadTimeout ReadTimeout
* @see com.github.dm.jrt.annotation.ReadTimeoutAction ReadTimeoutAction
*/
@NotNull
public static InvocationConfiguration configurationWithAnnotations(
@Nullable final InvocationConfiguration configuration, @NotNull final Method method) {
final InvocationConfiguration.Builder builder =
InvocationConfiguration.builderFrom(configuration);
final CoreInstances coreInstancesAnnotation = method.getAnnotation(CoreInstances.class);
if (coreInstancesAnnotation != null) {
builder.withCoreInstances(coreInstancesAnnotation.value());
}
final InputMaxSize inputSizeAnnotation = method.getAnnotation(InputMaxSize.class);
if (inputSizeAnnotation != null) {
builder.withInputMaxSize(inputSizeAnnotation.value());
}
final InputOrder inputOrderAnnotation = method.getAnnotation(InputOrder.class);
if (inputOrderAnnotation != null) {
builder.withInputOrder(inputOrderAnnotation.value());
}
final InputTimeout inputTimeoutAnnotation = method.getAnnotation(InputTimeout.class);
if (inputTimeoutAnnotation != null) {
builder.withInputTimeout(inputTimeoutAnnotation.value(), inputTimeoutAnnotation.unit());
}
final MaxInstances maxInstancesAnnotation = method.getAnnotation(MaxInstances.class);
if (maxInstancesAnnotation != null) {
builder.withMaxInstances(maxInstancesAnnotation.value());
}
final OutputMaxSize outputSizeAnnotation = method.getAnnotation(OutputMaxSize.class);
if (outputSizeAnnotation != null) {
builder.withOutputMaxSize(outputSizeAnnotation.value());
}
final OutputOrder outputOrderAnnotation = method.getAnnotation(OutputOrder.class);
if (outputOrderAnnotation != null) {
builder.withOutputOrder(outputOrderAnnotation.value());
}
final OutputTimeout outputTimeoutAnnotation = method.getAnnotation(OutputTimeout.class);
if (outputTimeoutAnnotation != null) {
builder.withOutputTimeout(outputTimeoutAnnotation.value(),
outputTimeoutAnnotation.unit());
}
final Priority priorityAnnotation = method.getAnnotation(Priority.class);
if (priorityAnnotation != null) {
builder.withPriority(priorityAnnotation.value());
}
final ReadTimeout readTimeoutAnnotation = method.getAnnotation(ReadTimeout.class);
if (readTimeoutAnnotation != null) {
builder.withReadTimeout(readTimeoutAnnotation.value(), readTimeoutAnnotation.unit());
}
final ReadTimeoutAction actionAnnotation = method.getAnnotation(ReadTimeoutAction.class);
if (actionAnnotation != null) {
builder.withReadTimeoutAction(actionAnnotation.value());
}
return builder.set();
}
/**
* Returns a configuration properly modified by taking into account the annotations added to the
* specified method.
*
* @param configuration the initial configuration.
* @param method the target method.
* @return the modified configuration.
* @see com.github.dm.jrt.annotation.SharedFields SharedFields
*/
@NotNull
public static ProxyConfiguration configurationWithAnnotations(
@Nullable final ProxyConfiguration configuration, @NotNull final Method method) {
final ProxyConfiguration.Builder builder =
ProxyConfiguration.builderFrom(configuration);
final SharedFields sharedFieldsAnnotation = method.getAnnotation(SharedFields.class);
if (sharedFieldsAnnotation != null) {
builder.withSharedFields(sharedFieldsAnnotation.value());
}
return builder.set();
}
/**
* Gets the method annotated with the specified alias name.
*
* @param name the alias name.
* @param targetClass the target class.
* @return the method.
* @throws java.lang.IllegalArgumentException if no method with the specified alias name was
* found.
*/
@Nullable
public static Method getAnnotatedMethod(@NotNull final String name,
@NotNull final Class> targetClass) {
synchronized (sAliasMethods) {
final WeakIdentityHashMap, Map> aliasMethods = sAliasMethods;
Map methodMap = aliasMethods.get(targetClass);
if (methodMap == null) {
methodMap = new HashMap();
fillMap(methodMap, targetClass.getMethods());
final HashMap declaredMethodMap = new HashMap();
fillMap(declaredMethodMap, targetClass.getDeclaredMethods());
for (final Entry methodEntry : declaredMethodMap.entrySet()) {
final String methodName = methodEntry.getKey();
if (!methodMap.containsKey(methodName)) {
methodMap.put(methodName, methodEntry.getValue());
}
}
aliasMethods.put(targetClass, methodMap);
}
return methodMap.get(name);
}
}
/**
* Gets the input transfer mode associated to the specified method parameter, while also
* validating the use of the {@link com.github.dm.jrt.annotation.Input Input} annotation.
* In case no annotation is present, the function will return with null.
*
* @param method the proxy method.
* @param index the index of the parameter.
* @return the input mode.
* @throws java.lang.IllegalArgumentException if the method has been incorrectly annotated.
* @see com.github.dm.jrt.annotation.Input Input
*/
@Nullable
public static InputMode getInputMode(@NotNull final Method method, final int index) {
Input inputAnnotation = null;
final Annotation[][] annotations = method.getParameterAnnotations();
for (final Annotation annotation : annotations[index]) {
if (annotation.annotationType() == Input.class) {
inputAnnotation = (Input) annotation;
break;
}
}
if (inputAnnotation == null) {
return null;
}
InputMode inputMode = inputAnnotation.mode();
final Class>[] parameterTypes = method.getParameterTypes();
final Class> parameterType = parameterTypes[index];
if (inputMode == InputMode.CHANNEL) {
if (!OutputChannel.class.isAssignableFrom(parameterType)) {
throw new IllegalArgumentException(
"[" + method + "] an async input with mode " + InputMode.CHANNEL
+ " must extends an " + OutputChannel.class.getCanonicalName());
}
} else if (inputMode == InputMode.COLLECTION) {
if (!OutputChannel.class.isAssignableFrom(parameterType)) {
throw new IllegalArgumentException(
"[" + method + "] an async input with mode " + InputMode.COLLECTION
+ " must extends an " + OutputChannel.class.getCanonicalName());
}
final Class> paramClass = inputAnnotation.value();
if (!paramClass.isArray() && !paramClass.isAssignableFrom(List.class)) {
throw new IllegalArgumentException(
"[" + method + "] an async input with mode " + InputMode.COLLECTION
+ " must be bound to an array or a superclass of "
+ List.class.getCanonicalName());
}
final int length = parameterTypes.length;
if (length > 1) {
throw new IllegalArgumentException(
"[" + method + "] an async input with mode " + InputMode.COLLECTION +
" cannot be applied to a method taking " + length
+ " input parameters");
}
} else { // InputMode.ELEMENT
final boolean isArray = parameterType.isArray();
if (!isArray && !Iterable.class.isAssignableFrom(parameterType)) {
throw new IllegalArgumentException(
"[" + method + "] an async input with mode " + InputMode.ELEMENT
+ " must be an array or implement an "
+ Iterable.class.getCanonicalName());
}
final Class> paramClass = inputAnnotation.value();
if (isArray && !Reflection.boxingClass(paramClass)
.isAssignableFrom(Reflection.boxingClass(
parameterType.getComponentType()))) {
throw new IllegalArgumentException(
"[" + method + "] the async input array with mode " + InputMode.ELEMENT
+ " does not match the bound type: "
+ paramClass.getCanonicalName());
}
final int length = parameterTypes.length;
if (length > 1) {
throw new IllegalArgumentException(
"[" + method + "] an async input with mode " + InputMode.ELEMENT
+ " cannot be applied to a method taking " + length
+ " input parameters");
}
}
return inputMode;
}
/**
* Gets the routine invocation mode associated to the specified method, while also validating
* the use of the {@link com.github.dm.jrt.annotation.Invoke Invoke} annotation.
* In case no annotation is present, the function will return with null.
*
* @param method the proxy method.
* @return the input mode.
* @throws java.lang.IllegalArgumentException if the method has been incorrectly annotated.
* @see com.github.dm.jrt.annotation.Invoke Invoke
*/
@Nullable
public static InvocationMode getInvocationMode(@NotNull final Method method) {
final Invoke methodAnnotation = method.getAnnotation(Invoke.class);
if (methodAnnotation == null) {
return null;
}
final InvocationMode invocationMode = methodAnnotation.value();
if ((invocationMode == InvocationMode.PARALLEL) && (method.getParameterTypes().length
> 1)) {
throw new IllegalArgumentException(
"methods annotated with invocation mode " + InvocationMode.PARALLEL
+ " must have at maximum one input parameter: " + method);
}
return invocationMode;
}
/**
* Gets the output transfer mode of the return type of the specified method, while also
* validating the use of the {@link com.github.dm.jrt.annotation.Output Output} annotation.
* In case no annotation is present, the function will return with null.
*
* @param method the proxy method.
* @param targetReturnType the target return type.
* @return the output mode.
* @throws java.lang.IllegalArgumentException if the method has been incorrectly annotated.
* @see com.github.dm.jrt.annotation.Output Output
*/
@Nullable
public static OutputMode getOutputMode(@NotNull final Method method,
@NotNull final Class> targetReturnType) {
final Output outputAnnotation = method.getAnnotation(Output.class);
if (outputAnnotation == null) {
return null;
}
final Class> returnType = method.getReturnType();
OutputMode outputMode = outputAnnotation.value();
if (outputMode == OutputMode.CHANNEL) {
if (!returnType.isAssignableFrom(OutputChannel.class)) {
final String channelClassName = OutputChannel.class.getCanonicalName();
throw new IllegalArgumentException(
"[" + method + "] an async output with mode " + OutputMode.CHANNEL
+ " must be a superclass of " + channelClassName);
}
} else if (outputMode == OutputMode.ELEMENT) {
if (!returnType.isAssignableFrom(OutputChannel.class)) {
final String channelClassName = OutputChannel.class.getCanonicalName();
throw new IllegalArgumentException(
"[" + method + "] an async output with mode " + OutputMode.CHANNEL
+ " must be a superclass of " + channelClassName);
}
if (!targetReturnType.isArray() && !Iterable.class.isAssignableFrom(targetReturnType)) {
throw new IllegalArgumentException(
"[" + method + "] an async output with mode " + OutputMode.ELEMENT
+ " must be bound to an array or a type implementing an "
+ Iterable.class.getCanonicalName());
}
} else { // OutputMode.COLLECTION
if (!returnType.isArray() && !returnType.isAssignableFrom(List.class)) {
throw new IllegalArgumentException(
"[" + method + "] an async output with mode " + OutputMode.COLLECTION
+ " must be an array or a superclass of "
+ List.class.getCanonicalName());
}
if (returnType.isArray() && !Reflection.boxingClass(returnType.getComponentType())
.isAssignableFrom(Reflection.boxingClass(
targetReturnType))) {
throw new IllegalArgumentException(
"[" + method + "] the async output array with mode " + OutputMode.COLLECTION
+ " does not match the bound type: "
+ targetReturnType.getCanonicalName());
}
}
return outputMode;
}
/**
* Returns the cached mutex associated with the specified target and shared fields.
* If the cache was empty, it is filled with a new object automatically created.
* If the target is null {@link Mutex#NO_MUTEX} will be returned.
*
* @param target the target object instance.
* @param sharedFields the shared field names.
* @return the cached mutex.
*/
@NotNull
public static Mutex getSharedMutex(@Nullable final Object target,
@Nullable final List sharedFields) {
if ((target == null) || ((sharedFields != null) && sharedFields.isEmpty())) {
return Mutex.NO_MUTEX;
}
ExchangeMutex exchangeMutex;
synchronized (sMutexes) {
final WeakIdentityHashMap mutexes = sMutexes;
exchangeMutex = mutexes.get(target);
if (exchangeMutex == null) {
exchangeMutex = new ExchangeMutex();
mutexes.put(target, exchangeMutex);
}
}
if (sharedFields == null) {
return exchangeMutex;
}
synchronized (sLocks) {
final WeakIdentityHashMap> locksCache = sLocks;
Map lockMap = locksCache.get(target);
if (lockMap == null) {
lockMap = new HashMap();
locksCache.put(target, lockMap);
}
final TreeSet nameSet = new TreeSet(sharedFields);
final int size = nameSet.size();
final ReentrantLock[] locks = new ReentrantLock[size];
int i = 0;
for (final String name : nameSet) {
ReentrantLock lock = lockMap.get(name);
if (lock == null) {
lock = new ReentrantLock();
lockMap.put(name, lock);
}
locks[i++] = lock;
}
return new BuilderMutex(exchangeMutex, locks);
}
}
/**
* Gets info about the method targeted by the specified proxy one.
*
* @param proxyMethod the proxy method.
* @param targetClass the target class.
* @return the method info.
* @throws java.lang.IllegalArgumentException if no target method was found.
*/
@NotNull
public static MethodInfo getTargetMethodInfo(@NotNull final Method proxyMethod,
@NotNull final Class> targetClass) {
MethodInfo methodInfo;
synchronized (sMethods) {
final WeakIdentityHashMap, Map> methodCache = sMethods;
Map methodMap = methodCache.get(targetClass);
if (methodMap == null) {
methodMap = new HashMap();
methodCache.put(targetClass, methodMap);
}
methodInfo = methodMap.get(proxyMethod);
if (methodInfo == null) {
final InvocationMode invocationMode = getInvocationMode(proxyMethod);
InputMode inputMode = null;
OutputMode outputMode = null;
final Class>[] targetParameterTypes;
final Inputs inputsAnnotation = proxyMethod.getAnnotation(Inputs.class);
if (inputsAnnotation != null) {
if (proxyMethod.getParameterTypes().length > 0) {
throw new IllegalArgumentException(
"methods annotated with " + Inputs.class.getSimpleName()
+ " must have no input parameters: " + proxyMethod);
}
final Class> returnType = proxyMethod.getReturnType();
if (!returnType.isAssignableFrom(InvocationChannel.class)
&& !returnType.isAssignableFrom(Routine.class)) {
throw new IllegalArgumentException(
"the proxy method has incompatible return type: " + proxyMethod);
}
targetParameterTypes = inputsAnnotation.value();
inputMode = InputMode.CHANNEL;
outputMode = OutputMode.CHANNEL;
} else {
targetParameterTypes = proxyMethod.getParameterTypes();
final Annotation[][] annotations = proxyMethod.getParameterAnnotations();
final int length = annotations.length;
for (int i = 0; i < length; ++i) {
final InputMode paramMode = getInputMode(proxyMethod, i);
if (paramMode != null) {
inputMode = paramMode;
for (final Annotation paramAnnotation : annotations[i]) {
if (paramAnnotation.annotationType() == Input.class) {
targetParameterTypes[i] = ((Input) paramAnnotation).value();
break;
}
}
}
}
}
if ((invocationMode == InvocationMode.PARALLEL) && (targetParameterTypes.length
> 1)) {
throw new IllegalArgumentException(
"methods annotated with invocation mode " + InvocationMode.PARALLEL
+ " must have no input parameters: " + proxyMethod);
}
final Method targetMethod =
getTargetMethod(proxyMethod, targetClass, targetParameterTypes);
final Class> returnType = proxyMethod.getReturnType();
final Class> targetReturnType = targetMethod.getReturnType();
final Output outputAnnotation = proxyMethod.getAnnotation(Output.class);
boolean isError = false;
if (outputAnnotation != null) {
outputMode = getOutputMode(proxyMethod, targetReturnType);
if ((outputMode == OutputMode.COLLECTION) && returnType.isArray()) {
isError = !Reflection.boxingClass(returnType.getComponentType())
.isAssignableFrom(
Reflection.boxingClass(targetReturnType));
}
} else if (inputsAnnotation == null) {
isError = !returnType.isAssignableFrom(targetReturnType);
}
if (isError) {
throw new IllegalArgumentException(
"the proxy method has incompatible return type: " + proxyMethod);
}
methodInfo = new MethodInfo(targetMethod, invocationMode, inputMode, outputMode);
methodMap.put(proxyMethod, methodInfo);
}
}
return methodInfo;
}
/**
* Invokes the routine wrapping the specified method.
*
* @param routine the routine to be called.
* @param method the target method.
* @param args the method arguments.
* @param invocationMode the routine invocation mode.
* @param inputMode the input transfer mode.
* @param outputMode the output transfer mode.
* @return the invocation output.
* @throws com.github.dm.jrt.channel.RoutineException in case of errors.
*/
@Nullable
@SuppressWarnings("unchecked")
public static Object invokeRoutine(@NotNull final Routine routine,
@NotNull final Method method, @NotNull final Object[] args,
@Nullable final InvocationMode invocationMode, @Nullable final InputMode inputMode,
@Nullable final OutputMode outputMode) {
final Class> returnType = method.getReturnType();
if (method.isAnnotationPresent(Inputs.class)) {
if (returnType.isAssignableFrom(InvocationChannel.class)) {
return (invocationMode == InvocationMode.SYNC) ? routine.syncInvoke()
: (invocationMode == InvocationMode.PARALLEL) ? routine.parallelInvoke()
: routine.asyncInvoke();
}
return routine;
}
final OutputChannel outputChannel;
final InvocationChannel invocationChannel =
(invocationMode == InvocationMode.SYNC) ? routine.syncInvoke()
: (invocationMode == InvocationMode.PARALLEL) ? routine.parallelInvoke()
: routine.asyncInvoke();
if (inputMode == InputMode.ELEMENT) {
final Class> parameterType = method.getParameterTypes()[0];
final Object arg = args[0];
if (arg == null) {
invocationChannel.pass((Iterable) null);
} else if (OutputChannel.class.isAssignableFrom(parameterType)) {
invocationChannel.pass((OutputChannel) arg);
} else if (parameterType.isArray()) {
final int length = Array.getLength(arg);
for (int i = 0; i < length; ++i) {
invocationChannel.pass(Array.get(arg, i));
}
} else {
final Iterable> iterable = (Iterable>) arg;
for (final Object input : iterable) {
invocationChannel.pass(input);
}
}
outputChannel = invocationChannel.result();
} else if (inputMode == InputMode.CHANNEL) {
invocationChannel.orderByCall();
final Class>[] parameterTypes = method.getParameterTypes();
final int length = args.length;
for (int i = 0; i < length; ++i) {
final Object arg = args[i];
if (OutputChannel.class.isAssignableFrom(parameterTypes[i])) {
invocationChannel.pass((OutputChannel) arg);
} else {
invocationChannel.pass(arg);
}
}
outputChannel = invocationChannel.result();
} else if (inputMode == InputMode.COLLECTION) {
outputChannel = routine.asyncInvoke()
.orderByCall()
.pass((OutputChannel) args[0])
.result();
} else {
outputChannel = routine.asyncCall(args);
}
if (!Void.class.equals(Reflection.boxingClass(returnType))) {
if (outputMode != null) {
if (OutputChannel.class.isAssignableFrom(returnType)) {
return outputChannel;
}
if (returnType.isAssignableFrom(List.class)) {
return outputChannel.all();
}
if (returnType.isArray()) {
final List results = outputChannel.all();
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.all().iterator().next();
}
outputChannel.checkComplete();
return null;
}
private static void fillMap(@NotNull final Map map,
@NotNull final Method[] methods) {
for (final Method method : methods) {
final Alias annotation = method.getAnnotation(Alias.class);
if (annotation != null) {
final String name = annotation.value();
if (map.containsKey(name)) {
throw new IllegalArgumentException(
"the name '" + name + "' has already been used to identify a different"
+ " method");
}
map.put(name, method);
}
}
}
@NotNull
private static Method getTargetMethod(@NotNull final Method method,
@NotNull final Class> targetClass, @NotNull final Class>[] targetParameterTypes) {
String name = null;
Method targetMethod = null;
final Alias annotation = method.getAnnotation(Alias.class);
if (annotation != null) {
name = annotation.value();
targetMethod = getAnnotatedMethod(name, targetClass);
}
if (targetMethod == null) {
if (name == null) {
name = method.getName();
}
targetMethod = Reflection.findMethod(targetClass, name, targetParameterTypes);
} else {
// Validate method parameters
Reflection.findMethod(targetClass, targetMethod.getName(), targetParameterTypes);
}
return targetMethod;
}
/**
* Data class storing information about the target method.
*/
@SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD",
justification = "this is an immutable data class")
public static class MethodInfo {
/**
* The input transfer mode.
*/
public final InputMode inputMode;
/**
* The routine invocation mode.
*/
public final InvocationMode invocationMode;
/**
* The target method.
*/
public final Method method;
/**
* The output transfer mode.
*/
public final OutputMode outputMode;
/**
* Constructor.
*
* @param method the target method.
* @param invocationMode the invocation mode.
* @param inputMode the input mode.
* @param outputMode the output mode.
*/
private MethodInfo(@NotNull final Method method,
@Nullable final InvocationMode invocationMode, @Nullable final InputMode inputMode,
@Nullable final OutputMode outputMode) {
this.method = method;
this.invocationMode = invocationMode;
this.inputMode = inputMode;
this.outputMode = outputMode;
}
}
/**
* Mutex implementation.
*/
private static class BuilderMutex implements Mutex {
private final ReentrantLock[] mLocks;
private final ExchangeMutex mMutex;
/**
* Constructor.
*
* @param mutex the exchange mutex.
* @param locks the locks.
*/
private BuilderMutex(@NotNull final ExchangeMutex mutex,
@NotNull final ReentrantLock[] locks) {
mMutex = mutex;
mLocks = locks;
}
public void acquire() {
mMutex.acquirePartialMutex();
for (final ReentrantLock lock : mLocks) {
lock.lock();
}
}
public void release() {
final ReentrantLock[] locks = mLocks;
final int length = locks.length;
for (int i = length - 1; i >= 0; --i) {
locks[i].unlock();
}
mMutex.releasePartialMutex();
}
}
/**
* Class used to synchronize between partial and full mutexes.
*/
private static class ExchangeMutex implements Mutex {
private final ReentrantLock mLock = new ReentrantLock();
private final Object mMutex = new Object();
private int mFullMutexCount;
private int mPartialMutexCount;
/**
* Acquires a partial mutex making sure that no full one is already taken.
*/
public void acquirePartialMutex() {
try {
synchronized (mMutex) {
while (mFullMutexCount > 0) {
mMutex.wait();
}
++mPartialMutexCount;
}
} catch (final InterruptedException e) {
InvocationInterruptedException.ignoreIfPossible(e);
}
}
/**
* Releases a partial mutex.
*/
public void releasePartialMutex() {
synchronized (mMutex) {
--mPartialMutexCount;
mMutex.notifyAll();
}
}
public void acquire() {
try {
synchronized (mMutex) {
while (mPartialMutexCount > 0) {
mMutex.wait();
}
++mFullMutexCount;
}
mLock.lock();
} catch (final InterruptedException e) {
InvocationInterruptedException.ignoreIfPossible(e);
}
}
public void release() {
mLock.unlock();
synchronized (mMutex) {
--mFullMutexCount;
mMutex.notifyAll();
}
}
}
}