AroundInvocationHandler.java
/*-
* #%L
* io.earcam.instrumental.proxy
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.instrumental.proxy.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import io.earcam.unexceptional.Exceptional;
/**
* <p>
* AroundInvocationHandler class.
* </p>
*
*/
public class AroundInvocationHandler<X, T> implements InvocationHandler {
/**
* Can change method and/or args or throw, returns a context object
*
* @param <C> arbitrary context object
*/
public static interface PreInvoke<C> {
/**
* @param invocation the pending invocation
* @return context the context object
*/
C preInvoke(Invocation invocation);
}
/**
* Can change return or throw, and consumes context object
*
* @param <C> arbitrary context object
*/
public static interface PostInvoke<C> {
/**
* @param invocation the invocation and result execution
* @param context the context object
*/
void postInvoke(Invocation invocation, C context);
}
private T proxied;
PreInvoke<X> pre;
PostInvoke<X> post;
static class InvocationResult {
Object value;
boolean wasThrown;
public void setReturn(Object result)
{
this.value = result;
wasThrown = false;
}
public void setThrown(Throwable thrown)
{
value = thrown;
wasThrown = true;
}
}
public static class Invocation {
Object proxy;
Method method;
Object[] args;
InvocationResult result = new InvocationResult();
public Invocation(Object proxy, Method method, Object... args)
{
this.proxy = proxy;
this.method = method;
this.args = args;
}
}
/**
* <p>
* Constructor for AroundInvocationHandler.
* </p>
*
* @param pre the pre-invocation handler, see
* {@link io.earcam.instrumental.proxy.handler.AroundInvocationHandler.PreInvoke}
* @param proxied the underlying object to wrap around
* @param post the post-invocation handler, see
* {@link io.earcam.instrumental.proxy.handler.AroundInvocationHandler.PostInvoke}
*/
public AroundInvocationHandler(PreInvoke<X> pre, T proxied, PostInvoke<X> post)
{
this.pre = pre;
this.post = post;
this.proxied = proxied;
}
AroundInvocationHandler(T proxied)
{
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Invocation invocation = new Invocation(proxy, method, args);
X context = null;
try {
context = pre.preInvoke(invocation);
invocation.result.setReturn(invocation.method.invoke(proxied, invocation.args));
} catch(Throwable thrown) { // NOSONAR
invocation.result.setThrown(thrown);
}
post.postInvoke(invocation, context);
return handleResult(invocation);
}
private Object handleResult(Invocation invocation)
{
if(invocation.result.wasThrown) {
throw Exceptional.throwAsUnchecked((Throwable) invocation.result.value);
}
return invocation.result.value;
}
}