Last active
November 6, 2016 16:16
-
-
Save sormuras/3570453c39f787adb832 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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