This is the third part of the Playing With args4j series. For your convenience you can find other parts in the table of contents in Part 1 – Mixins
Let’s see how we can implement the setters. Idea is as following: we will pass a lambda indicating the getter of the parameter. Next, we will create a dynamic proxy using cglib, call the getter (which should have no side effects) and then we will intercept the actual call to extract the getter method. Next, we will parse the annotation and call the setter.
First, we add the magical setter:
1 2 3 4 |
default < T, U> void set(Function<T, U> getter, U value, Class<T> klass) { Method method = MethodReferenceUtils.getReferencedMethod(getter, klass); set(method.getAnnotation(Option.class).name(), value); } |
We accept three parameters: lambda representing the getter, value to set, and a class representing the object. We extract the getter method, get its annotation and execute the normal setter.
Next, we implement the reflection magic:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; public class MethodReferenceUtils { @SuppressWarnings("unchecked") public static <T, U> Method getReferencedMethod(Function<T, U> getter, Class<T> klass) { AtomicReference<Method> ref = new AtomicReference<>(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(klass); enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> { ref.set(method); return null; }); try { getter.apply((T) enhancer.create()); } catch (ClassCastException e) { throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", klass)); } Method method = ref.get(); if (method == null) { throw new IllegalArgumentException(String.format("Invalid method reference on class [%s]", klass)); } return method; } } |
We create an enhancer to capture the call. Next, we execute the lambda (which is a getter), capture it and store the reference. Finally, we return the method.
Now we can call it like this:
1 |
parameters.set(IJobParameters::parameter, "Value", JobParameters.class); |
And this works like a charm.