package org.jgroups.blocks.executor;
import org.jgroups.util.Streamable;
import org.jgroups.util.Util;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.concurrent.Callable;
public class Executions {
/**
* This method should be used to convert a callable that would not normally
* be serializable, externalizable or streamable but has serializable,
* externalizable or streamable arguments to a constructor to construct it.
* <p>
* When the call method is called on the callable it will call the provided
* constructor passing in the given arguments. It will then invoke the call
* method on resulting callable that was created.
* <p>
* The amount of arguments cannot exceed {@link Byte#MAX_VALUE}. Also the
* constructor cannot exceed {@link Byte#MAX_VALUE} position in the
* constructor array returned from {@link Class#getConstructors()}
* <p>
* The amount of arguments must match the amount of arguments required
* by the constructor. Also the arguments must be compatibile with the
* types required of the constructor.
* <p>
* Unfortunately it isn't easy to pass a Constructor<? extends Callable<T>>
* so we can't pass back a callable that is properly typed. Also this
* forces the caller to cast their callable or returned value to the correct
* type manually.
*
* @param constructorToUse The constructor to use when creating the callable
* @param args The arguments to pass to the constructor
* @return The callable that will upon being called will instantiate the
* given callable using the constructor with the provided arguments
* and calls the call method
* @throws IllegalArgumentException This is thrown if the arguments are
* not serializable, externalizable or streamable. It can be thrown
* if the constructo is not accessible. It can also be thrown
* if too many arguments or the constructor is to high up in the
* constructo array returned by the class.
*/
public static Callable<?> serializableCallable(@SuppressWarnings("rawtypes")
Constructor<? extends Callable> constructorToUse, Object... args)
throws IllegalArgumentException {
if (args.length > (int)Byte.MAX_VALUE) {
throw new IllegalArgumentException(
"Max number of arguments exceeded: " + Byte.MAX_VALUE);
}
Class<?>[] params = constructorToUse.getParameterTypes();
if (params.length != args.length) {
throw new IllegalArgumentException("Number of arguments ["
+ args.length + "] doesn't match number of arguments for "
+ "constructor [" + params.length + "]");
}
for (int i = 0; i < args.length; ++i) {
Object arg = args[i];
if (arg instanceof Serializable ||
arg instanceof Streamable) {
Class<?> classArg = params[i];
if (!classArg.isInstance(arg)) {
throw new IllegalArgumentException("Argument [" + arg +
"] is not an instance of [" + classArg + "]");
}
}
else {
throw new IllegalArgumentException(
"Argument is not serializable, externalizable or streamable: " + arg);
}
}
@SuppressWarnings("unchecked")
Class<? extends Callable<?>> classToUse =
(Class<? extends Callable<?>>)constructorToUse.getDeclaringClass();
Constructor<?>[] constructors = classToUse.getConstructors();
byte constructorPosition = -1;
for (int i = 0; i < constructors.length; ++i) {
Constructor<?> constructor = constructors[i];
if (constructor.equals(constructorToUse)) {
if (i > (int)Byte.MAX_VALUE) {
throw new IllegalArgumentException(
"Constructor position in array cannot be higher than "
+ Byte.MAX_VALUE);
}
constructorPosition = (byte)i;
}
}
if (constructorPosition == -1) {
throw new IllegalArgumentException(
"Constructor was not found in public constructor array on class");
}
return new StreamableCallable(classToUse, constructorPosition, args);
}
protected static class StreamableCallable implements Callable<Object>, Streamable {
protected Class<? extends Callable<?>> _classCallable;
protected short _constructorNumber;
protected Object[] _args;
public StreamableCallable() {
}
public StreamableCallable(Class<? extends Callable<?>> classCallable,
byte constructorNumber, Object... args) {
_classCallable = classCallable;
_constructorNumber = constructorNumber;
_args = args;
}
@Override
public Object call() throws Exception {
@SuppressWarnings("unchecked")
// Unfortunately getConstructors doesn't return typed constructors
// correctly so we have to cast
Constructor<? extends Callable<?>> constructor =
(Constructor<? extends Callable<?>>) _classCallable
.getConstructors()[_constructorNumber];
Callable<?> callable = constructor.newInstance(_args);
return callable.call();
}
@Override
public void writeTo(DataOutput out) throws Exception {
Util.writeClass(_classCallable, out);
out.writeByte(_constructorNumber);
out.writeByte(_args.length);
for (Object arg : _args) {
try {
Util.writeObject(arg, out);
}
catch (Exception e) {
throw new IOException("failed to write arg " + arg);
}
}
}
@SuppressWarnings("unchecked")
@Override
public void readFrom(DataInput in) throws Exception {
try {
_classCallable = (Class<? extends Callable<?>>)Util.readClass(in);
}
catch (ClassNotFoundException e) {
throw new IOException("failed to read class from classname", e);
}
_constructorNumber = in.readByte();
short numberOfArgs = in.readByte();
_args = new Object[numberOfArgs];
for (int i = 0; i < numberOfArgs; ++i) {
try {
_args[i] = Util.readObject(in);
}
catch (Exception e) {
throw new IOException("failed to read arg", e);
}
}
}
// @see java.lang.Object#toString()
@Override
public String toString() {
return "StreamableCallable [class=" + _classCallable
+ ", constructor=" + _constructorNumber + ", arguments="
+ Arrays.toString(_args) + "]";
}
}
}