Skip to content

Instantly share code, notes, and snippets.

@sormuras
Last active November 6, 2016 16:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sormuras/3570453c39f787adb832 to your computer and use it in GitHub Desktop.
Save sormuras/3570453c39f787adb832 to your computer and use it in GitHub Desktop.
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.WeakHashMap;
import org.prevayler.Query;
import org.prevayler.SureTransactionWithQuery;
import org.prevayler.Transaction;
import org.prevayler.TransactionWithQuery;
/**
* Generic execution capsule implementation using method handles.
*
* @param <P>
* Prevalent system
* @author Christian Stein
*/
public class Command<P> implements Query<P, Object>, Transaction<P>, TransactionWithQuery<P, Object>, SureTransactionWithQuery<P, Object> {
/**
* Use this date instance for replace it with Prevaylers execution time on invocation.
*/
public static final Date EXECUTION_TIME = new Date(2306L);
/**
* Internal static hash map for method handles.
*/
private static final Map<Integer, MethodHandle> handles = Collections.synchronizedMap(new WeakHashMap<Integer, MethodHandle>());
private static final Lookup lookup = MethodHandles.publicLookup();
private static final long serialVersionUID = 0L;
protected final Object[] args;
protected final String name;
protected final MethodType type;
/**
* Construct a new Command.
*
* @param type
* method type, not nullable
* @param name
* method name, not nullable
* @param args
* method arguments, may be null, must not be longer than 255 large
*/
public Command(MethodType type, String name, Object... args) {
this.type = type;
this.name = name;
this.args = args;
}
/**
* @return a new method handle described by this Command fields
*/
protected MethodHandle createAndBindMethodHandle(P prevalentSystem) {
try {
return lookup.bind(prevalentSystem, name, type);
} catch (NoSuchMethodException | IllegalAccessException e) {
String details = String.format(" name '%s' and type '%s' for receiver '%s'", name, type, prevalentSystem);
throw new Error("Method handle creation failed for " + details, e);
}
}
/**
* @return a looked-up or newly created method handle describing this Command instance
*/
protected MethodHandle createOrGetHandle(P prevalentSystem) {
if (handles == null)
return createAndBindMethodHandle(prevalentSystem);
Integer key = new Integer(name.hashCode() + 37 * type.hashCode());
synchronized (handles) {
MethodHandle handle = handles.get(key);
if (handle == null) {
handle = createAndBindMethodHandle(prevalentSystem);
handles.put(key, handle);
}
return handle;
}
}
/**
* Executes this command on the prevalent system.
*
* @return the result of the query, may be null
*/
protected Object execute(P prevalentSystem, Date executionTime) {
MethodHandle handle = createOrGetHandle(prevalentSystem);
try {
Object[] arguments = args;
if (arguments != null && arguments.length > 0) {
// // expand varargs by unrolling the last parameter - which has to be an Object[]
if (handle.isVarargsCollector()) {
int n = arguments.length - 1;
Object[] vars = (Object[]) arguments[n];
arguments = new Object[n + vars.length];
System.arraycopy(args, 0, arguments, 0, n);
if (vars.length > 0) {
System.arraycopy(vars, 0, arguments, n, vars.length);
}
}
// replace template date instance in arguments
for (int i = 0; i < arguments.length; i++) {
Object argi = arguments[i];
if (argi == null)
continue;
if (argi.getClass() != Date.class)
continue;
if (((Date) argi).getTime() != EXECUTION_TIME.getTime())
continue;
arguments[i] = executionTime;
}
}
return handle.invokeWithArguments(arguments);
} catch (Throwable cause) {
throw new RuntimeException(name + "", cause);
}
}
@Override
public Object executeAndQuery(P prevalentSystem, Date executionTime) {
return execute(prevalentSystem, executionTime);
}
@Override
public void executeOn(P prevalentSystem, Date executionTime) {
execute(prevalentSystem, executionTime);
}
@Override
public Object query(P prevalentSystem, Date executionTime) throws Exception {
return execute(prevalentSystem, executionTime);
}
@Override
public String toString() {
return getClass().getSimpleName() + " [name=" + name + ", type=" + type + ", args=" + Arrays.toString(args) + "]";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment