aQute.lib.aspects.Aspects Maven / Gradle / Ivy
The newest version!
package aQute.lib.aspects;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import aQute.bnd.exceptions.BiFunctionWithException;
import aQute.bnd.exceptions.ConsumerWithException;
import aQute.bnd.exceptions.Exceptions;
import aQute.bnd.exceptions.FunctionWithException;
import aQute.bnd.exceptions.RunnableWithException;
import aQute.bnd.exceptions.SupplierWithException;
import aQute.lib.converter.Converter;
/**
* Minute library to do some aspect oriented programming without dragging in the
* world. Should not be used for high performance things.
*/
public class Aspects {
static MethodHandles.Lookup publicLookup = MethodHandles.publicLookup();
static final Object[] EMPTY = new Object[0];
public static final Object NORETURN = new Object();
public static final Object DEFAULT = new Object();
public static class Invocation {
final Object proxy;
final Method method;
final Object[] args;
Invocation(Object proxy, Method method, Object[] args) {
this.proxy = proxy;
this.method = method;
this.args = args == null ? EMPTY : args;
}
@Override
public String toString() {
return "Invocation [" + (method != null ? "method=" + method.getName() + ", " : "")
+ (args != null ? "args=" + Arrays.toString(args) : "") + "]";
}
}
/**
* A builder to create a proxy that delegates to another object but can
* intercept calls, put something before, after and around calls.
*
* @param the delegate type
*/
public interface InterceptBuilder {
/**
* Intercept a method call with a lambda. This is the generic form. For
* 0, 1, and 2 argument forms there are more convenient shortcuts. The
* number of types and the given arguments to the lambda must match
*
* @param intercept the lambda to intercept
* @param name the method name
* @param types the types
*/
InterceptBuilder intercept(FunctionWithException intercept, String name,
Class>... types);
/**
* Intercept a no method call
*
* @param intercept the no method lambda
* @param name the name of the method without arguments
*/
InterceptBuilder intercept(SupplierWithException intercept, String name);
/**
* Intercept a no method call
*
* @param intercept the no method lambda
* @param name the name of the method without arguments
*/
InterceptBuilder intercept(RunnableWithException intercept, String name);
/**
* Intercept a one argument method call
*
* @param intercept the one argument method lambda
* @param name the name of the method with one argument
*/
InterceptBuilder intercept(FunctionWithException intercept, String name, Class type);
/**
* Intercept a two argument method call
*
* @param intercept the two argument method lambda
* @param name the name of the method with two arguments
*/
InterceptBuilder intercept(BiFunctionWithException intercept, String name, Class aType,
Class bType);
/**
* Provide a function that is called with the method calling function.
* The around can setup some function around the call, which is passed
* as a callable. The around should setup whatever it wants to setup,
* call the callable, tear down what was setup and then return the
* result of the callable. Exceptions can be passed upwards.
*
* @param around the around advice (the argument is a callable that
* should be calle
*/
InterceptBuilder around(BiFunctionWithException, Object> around);
/**
* Provide a function that is called before the method is called. The
* argument to the Consumer is the array of arguments (which is never
* null). The consumer may change these arguments but should of course
* be extremely careful to not change the types in a way that would make
* the method call impossible.
*
* @param before advice
*/
InterceptBuilder before(ConsumerWithException before);
/**
* Provide a function that is called after the method is called. The
* given parameter to the function are the arguments and the result of
* the previous methods. The function should return the result, modified
* if so needed.
*
* @param after advice
*/
InterceptBuilder after(BiFunctionWithException after);
/**
* Called when an exception occurs
*
* @param exception the throw exception
*/
InterceptBuilder onException(BiFunctionWithException exception);
/**
* Build the proxy
*/
T build();
}
/**
* Create an intercepting proxy using a builder
*
* @param type the type of the proxy
* @param delegate the delegate to delegate to
* @return a builder
*/
@SuppressWarnings("unchecked")
public static InterceptBuilder intercept(Class type, T delegate) {
assert Objects.nonNull(type);
assert Objects.nonNull(delegate);
return new InterceptBuilder() {
final Map> methods = new HashMap<>();
ConsumerWithException before = null;
BiFunctionWithException, Object> around = (x, c) -> c.call();
BiFunctionWithException after = null;
BiFunctionWithException exceptions = null;
{
try {
for (Method m : type.getMethods()) {
methods.putIfAbsent(m, inv -> m.invoke(delegate, inv.args));
}
} catch (Exception e) {
throw Exceptions.duck(e);
}
}
@Override
public InterceptBuilder intercept(FunctionWithException intercept, String name,
Class>... types) {
try {
Method method = type.getMethod(name, types);
methods.put(method, intercept);
return this;
} catch (NoSuchMethodException nsme) {
try {
Method method = Object.class.getMethod(name, types);
methods.put(method, intercept);
} catch (NoSuchMethodException e) {
throw Exceptions.duck(e);
}
return this;
} catch (Throwable e) {
throw Exceptions.duck(e);
}
}
@Override
public InterceptBuilder intercept(RunnableWithException intercept, String name) {
return intercept(inv -> {
intercept.run();
return null;
}, name);
}
@Override
public InterceptBuilder intercept(SupplierWithException intercept, String name) {
return intercept((Invocation inv) -> intercept.get(), name);
}
@Override
public InterceptBuilder intercept(FunctionWithException intercept, String name,
Class aType) {
return intercept((Invocation inv) -> intercept.apply((A) inv.args[0]), name, aType);
}
@Override
public InterceptBuilder intercept(BiFunctionWithException intercept, String name,
Class aType, Class bType) {
return intercept((Invocation inv) -> intercept.apply((A) inv.args[0], (B) inv.args[1]), name, aType,
bType);
}
@Override
public InterceptBuilder around(BiFunctionWithException, Object> around) {
if (this.around == null) {
this.around = around;
} else {
BiFunctionWithException, Object> previous = this.around;
this.around = (inv, callable) -> around.apply(inv, () -> previous.apply(inv, callable));
}
return this;
}
@Override
public InterceptBuilder before(ConsumerWithException before) {
if (this.before == null)
this.before = before;
else {
ConsumerWithException previous = this.before;
this.before = args -> {
previous.accept(args);
before.accept(args);
};
}
return this;
}
@Override
public InterceptBuilder after(BiFunctionWithException after) {
if (this.after == null)
this.after = after;
else {
BiFunctionWithException previous = this.after;
this.after = (args, result) -> {
result = previous.apply(args, result);
return after.apply(args, result);
};
}
return this;
}
@Override
public InterceptBuilder onException(BiFunctionWithException exceptions) {
if (this.exceptions == null)
this.exceptions = exceptions;
else {
BiFunctionWithException previous = this.exceptions;
this.exceptions = (invocation, except) -> {
Object result = previous.apply(invocation, except);
if (result != NORETURN)
return result;
return exceptions.apply(invocation, except);
};
}
return this;
}
@Override
public T build() {
try {
if (before == null)
this.before = inv -> {};
if (after == null)
this.after = (inv, x) -> x;
if (exceptions == null)
this.exceptions = (inv, exc) -> {
throw Exceptions.duck(Exceptions.unrollCause(exc, InvocationTargetException.class));
};
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {
type
}, this::invoke);
} catch (Exception e) {
throw Exceptions.duck(e);
}
}
Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Invocation inv = new Invocation(proxy, method, args);
try {
before.accept(inv);
FunctionWithException target = methods.get(method);
Object result = around.apply(inv, () -> target.apply(inv));
return after.apply(inv, result);
} catch (Throwable t) {
Object result = exceptions.apply(inv, t);
if (result == DEFAULT) {
return Converter.cnv(method.getGenericReturnType(), null);
}
if (result != NORETURN)
return result;
throw t;
}
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy