I am trying to pass two generics with known types (accumulationFunction, resultBindings) into the reflective invoke routine below, but I'm having trouble. Can someone help me understand how this can be achieved?
package com.amir.method.compiler;
//hidden imports
import java.util.Set;
public class CompiledReferencedAttributeMethod implements CompiledMethod {
final Class<?> generatedClazz;
//how do i pass the arguments below into the invoke routine??
final KnownWorkData<AccumulationFunction> accumulationFunction;
final KnownWorkData<Set<Executable<InstanceSetValue>>> resultBindings;
public CompiledReferencedAttributeMethod(final int hash,
final KnownWorkData<AccumulationFunction> accumulationFunction,
final KnownWorkData<Set<Executable<InstanceSetValue>>> resultBindings) {
this.generatedClazz = ReferencedAttributeMethodGenerator.get().compileMethod(
"com.amir.hotspot.GeneratedGRAMethod" +hash, "dynamicGra", accumulationFunction, resultBindings);
this.accumulationFunction = accumulationFunction;
this.resultBindings = resultBindings;
}
#Override
public WorkDataValue invokeCompiled(final Instance onInst,
final ExecutionParms parm,
final WorkDataDefinition returntype,
final TaskContext context) {
try {
return (WorkDataValue) this.generatedClazz
.getMethod("dynamicGra",
Instance.class,
ExecutionParms.class,
WorkDataDefinition.class,
TaskContext.class)
.invoke(null, onInst, parm, returntype, this.accumulationFunction, this.resultBindings, context);
} catch(Exception e) {
throw new ExecuteCompiledMethodException(this.toString(), e);
}
}
}
As you cannot overload method with generics and different parameters (see: Oracle’s tutorial “Restrictions on Generics”) you don't have to bother about generics with the reflection API.
There is no ambiguity, you can simply get your method like that:
Method m = this.generatedClazz.getMethod("dynamicGra",
Instance.class,
ExecutionParms.class,
WorkDataDefinition.class,
KnownWorkData.class,
KnownWorkData.class,
TaskContext.class);
Then you invoked with null so it means that dynamicGra must be static, if it's not the case you must pass the instance of generatedClazz on which you want to call the method :
Object instance = this.generatedClazz.newInstance(); // A new one or any other reference of generatedClazz
m.invoke(instance, onInst, parm, returntype, this.accumulationFunction, this.resultBindings, context);
Related
We are working with mvc design pattern, where all the data is stored under map.
I want to iterate over all the classes in the system and for each to check what the method is putting on the map and what does the method get from the map.
For example for the next code:
private void myFunc()
{
Object obj = model.get("mykey");
Object obj2 = model.get("mykey2");
.....
model.put("mykey3", "aaa");
}
I want to know that in this function we have 2 gets: mykey and mykey2 and 1 put: mykey3
How can I do it with the code.
Thanks.
You tagged this with "reflection", but that will not work. Reflection only allows you to inspect "signatures". You can use it to identify the methods of a class, and the arguments of the methods.
It absolutely doesn't help you to identify what each method is doing.
In order to find out about that, you would need to either parse the java source code side, or byte code classes. As in: write code that reads that content, and understands "enough" of it to find such places. Which is a very challenging effort. And of course: it is very easy to bypass all such "scanner" code, by doing things such as:
List<String> keysToUpdate = Arrays.asList("key1", "key2");
for (String key : keysToUpdate) {
... does something about each key
Bang. How would you ever write code that reliable finds the keys for that?
When you found that code, now imagine that the list isn't instantiated there, but far away, and past as argument? When you figured how to solve that, now consider code that uses reflection to acquire the model object, and calls method on that. See? For any "scanner" that you write down, there will be ways to make that fail.
Thus the real answer is that you are already going down the wrong rabbit hole:
You should never have written:
Object obj = model.get("mykey");
but something like
Object obj = model.get(SOME_CONSTANT_FOR_KEY_X);
Meaning: there is no good way to control such stuff. The best you can do is to make sure that all keys are constants, coming from a central place. Because then you can at least go in, and for each key in that list of constants, you can have your IDE tell you about their usage.
NOTES
I assumed that your situation is complicated enough that simple or advanced text search in codebase doesn't help you.
This is a hack not a generic solution, designed only for testing and diagnosis purposes.
To use this hack, you must be able to change your code and replace the actual model with the proxy instance while you're testing/diagnosing. If you can't do this, then you have to use an even more advanced hack, i.e. byte-code engineering with BCEL, ASM, etc.
Dynamic proxies have drawbacks on code performance, therefore not an ideal choice for production mode.
Using map for storing model is not a good idea. Instead a well-defined type system, i.e. Java classes, should be used.
A general design pattern for a problem like this is proxy. An intermediate object between your actual model and the caller that can intercept the calls, collect statistics, or even interfere with the original call. The proxied model ultimately sends everything to the actual model.
An obvious proxy is to simply wrap the actual model into another map, e.g.
public class MapProxy<K, V> implements Map<K, V> {
public MapProxy(final Map<K, V> actual) {
}
// implement ALL methods and redirect them to the actual model
}
Now, reflection doesn't help you with this directly, but can help with implementing a proxy faster using dynamic proxies (Dynamic Proxy Classes), e.g.
#SuppressWarnings("unchecked")
private Map<String, Object> proxy(final Map<String, Object> model) {
final InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Collect usage stats or intervene
return method.invoke(model, args);
}
};
return (Map<String, Object>) Proxy.newProxyInstance(Map.class.getClassLoader(),
new Class<?>[] { Map.class }, handler);
}
NOTE: Either case you need to be able to replace the actual model with the proxied model at least for the duration of your test.
With another trick, you can find out who called which method of your model. Simply by accessing Thread.currentThread().getStackTrace() and retrieving the appropriate element.
Now puting all the pieces together:
InvocationLog.java
public final class InvocationLog {
private Method method;
private Object[] arguments;
private StackTraceElement caller;
public InvocationLog(Method method, Object[] arguments, StackTraceElement caller) {
this.method = method;
this.arguments = arguments;
this.caller = caller;
}
public Method getMethod() { return this.method; }
public Object[] getArguments() { return this.arguments; }
public StackTraceElement getCaller() { return this.caller; }
#Override
public String toString() {
return String.format("%s (%s): %s",
method == null ? "<init>" : method.getName(),
arguments == null ? "" : Arrays.toString(arguments),
caller == null ? "" : caller.toString());
}
}
ModelWatch.java
public final class ModelWatch {
private final Map<String, Object> modelProxy;
private final List<InvocationLog> logs = new ArrayList<>();
public ModelWatch(final Map<String, Object> model) {
modelProxy = proxy(model);
}
#SuppressWarnings("unchecked")
private Map<String, Object> proxy(final Map<String, Object> model) {
final InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method, args, Thread.currentThread().getStackTrace());
return method.invoke(model, args);
}
};
return (Map<String, Object>) Proxy.newProxyInstance(Map.class.getClassLoader(),
new Class<?>[] { Map.class }, handler);
}
private void log(Method method, Object[] arguments, StackTraceElement[] stack) {
logs.add(new InvocationLog(method, arguments, stack[3]));
// 0: Thread.getStackTrace
// 1: InvocationHandler.invoke
// 2: <Proxy>
// 3: <Caller>
}
public Map<String, Object> getModelProxy() { return modelProxy; }
public List<InvocationLog> getLogs() { return logs; }
}
To put it in use:
private Map<String, Object> actualModel = new HashMap<String, Object>();
private ModelWatch modelWatch = new ModelWatch(model);
private Map<String, Object> model = modelWatch.getModelProxy();
// Calls to model ...
modelWatch.getLogs() // Retrieve model activity
I'm trying to remove the method body of test() in the following program so that nothing is printed to the Console. I'm using using ASM 5.2 but everything I've tried doesn't seem to have any effect.
Can someone explain what I'm doing wrong and also point me to some up-to-date tutorials or documentation on ASM? Almost everything Iv'e found on Stackoverflow and the ASM website seems outdated and/or unhelpful.
public class BytecodeMods {
public static void main(String[] args) throws Exception {
disableMethod(BytecodeMods.class.getMethod("test"));
test();
}
public static void test() {
System.out.println("This is a test");
}
private static void disableMethod(Method method) {
new MethodReplacer()
.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, method.getName(), Type.getMethodDescriptor(method), null, null);
}
public static class MethodReplacer extends ClassVisitor {
public MethodReplacer() {
super(Opcodes.ASM5);
}
#Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return null;
}
}
}
You are not supposed to invoke the methods of a visitor directly.
The correct way to use a ClassVisitor, is to create a ClassReader with the class file bytes of the class you’re interested in and pass the class visitor to the its accept method. Then, all the visit methods will be called by the class reader according to the artifacts found in the class file.
In this regard, you should not consider the documentation outdated, just because it refers to an older version number. E.g. this document describes that process correctly and it speaks for the library that no fundamental change was necessary between the versions 2 and 5.
Still, visiting a class does not change it. It helps analyzing it and perform actions when encountering a certain artifact. Note that returning null is not an actual action.
If you want to create a modified class, you need a ClassWriter to produce the class. A ClassWriter implements ClassVisitor, also class visitors can be chained, so you can easily create a custom visitor delegating to a writer, that will produce a class file identical to the original one, unless you override a method to intercept the recreation of a feature.
But note that returning null from visitMethod does more than removing the code, it will remove the method entirely. Instead, you have to return a special visitor for the specific method which will reproduce the method but ignore the old code and create a sole return instruction (you are allowed to omit the last return statement in source code, but not the return instruction in the byte code).
private static byte[] disableMethod(Method method) {
Class<?> theClass = method.getDeclaringClass();
ClassReader cr;
try { // use resource lookup to get the class bytes
cr = new ClassReader(
theClass.getResourceAsStream(theClass.getSimpleName()+".class"));
} catch(IOException ex) {
throw new IllegalStateException(ex);
}
// passing the ClassReader to the writer allows internal optimizations
ClassWriter cw = new ClassWriter(cr, 0);
cr.accept(new MethodReplacer(
cw, method.getName(), Type.getMethodDescriptor(method)), 0);
byte[] newCode = cw.toByteArray();
return newCode;
}
static class MethodReplacer extends ClassVisitor {
private final String hotMethodName, hotMethodDesc;
MethodReplacer(ClassWriter cw, String name, String methodDescriptor) {
super(Opcodes.ASM5, cw);
hotMethodName = name;
hotMethodDesc = methodDescriptor;
}
// invoked for every method
#Override
public MethodVisitor visitMethod(
int access, String name, String desc, String signature, String[] exceptions) {
if(!name.equals(hotMethodName) || !desc.equals(hotMethodDesc))
// reproduce the methods we're not interested in, unchanged
return super.visitMethod(access, name, desc, signature, exceptions);
// alter the behavior for the specific method
return new ReplaceWithEmptyBody(
super.visitMethod(access, name, desc, signature, exceptions),
(Type.getArgumentsAndReturnSizes(desc)>>2)-1);
}
}
static class ReplaceWithEmptyBody extends MethodVisitor {
private final MethodVisitor targetWriter;
private final int newMaxLocals;
ReplaceWithEmptyBody(MethodVisitor writer, int newMaxL) {
// now, we're not passing the writer to the superclass for our radical changes
super(Opcodes.ASM5);
targetWriter = writer;
newMaxLocals = newMaxL;
}
// we're only override the minimum to create a code attribute with a sole RETURN
#Override
public void visitMaxs(int maxStack, int maxLocals) {
targetWriter.visitMaxs(0, newMaxLocals);
}
#Override
public void visitCode() {
targetWriter.visitCode();
targetWriter.visitInsn(Opcodes.RETURN);// our new code
}
#Override
public void visitEnd() {
targetWriter.visitEnd();
}
// the remaining methods just reproduce meta information,
// annotations & parameter names
#Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return targetWriter.visitAnnotation(desc, visible);
}
#Override
public void visitParameter(String name, int access) {
targetWriter.visitParameter(name, access);
}
}
The custom MethodVisitor does not get chained to the method visitor returned by the class writer. Configured this way, it will not replicate the code automatically. Instead, performing no action will be the default and only our explicit invocations on the targetWriter will produce code.
At the end of the process, you have a byte[] array containing the changed code in the class file format. So the question is, what to do with it.
The easiest, most portable thing you can do, is to create a new ClassLoader, which creates a new Class from these bytes, which has the same name (as we didn’t change the name), but is distinct from the already loaded class, because it has a different defining class loader. We can access such dynamically generated class only through Reflection:
public class BytecodeMods {
public static void main(String[] args) throws Exception {
byte[] code = disableMethod(BytecodeMods.class.getMethod("test"));
new ClassLoader() {
Class<?> get() { return defineClass(null, code, 0, code.length); }
} .get()
.getMethod("test").invoke(null);
}
public static void test() {
System.out.println("This is a test");
}
…
In order to make this example do something more notable than doing nothing, you could alter the message instead,
using the following MethodVisitor
static class ReplaceStringConstant extends MethodVisitor {
private final String matchString, replaceWith;
ReplaceStringConstant(MethodVisitor writer, String match, String replacement) {
// now passing the writer to the superclass, as most code stays unchanged
super(Opcodes.ASM5, writer);
matchString = match;
replaceWith = replacement;
}
#Override
public void visitLdcInsn(Object cst) {
super.visitLdcInsn(matchString.equals(cst)? replaceWith: cst);
}
}
by changing
return new ReplaceWithEmptyBody(
super.visitMethod(access, name, desc, signature, exceptions),
(Type.getArgumentsAndReturnSizes(desc)>>2)-1);
to
return new ReplaceStringConstant(
super.visitMethod(access, name, desc, signature, exceptions),
"This is a test", "This is a replacement");
If you want to change the code of an already loaded class or intercept it right before being loaded into the JVM, you have to use the Instrumentation API.
The byte code transformation itself doesn’t change, you’ll have to pass the source bytes into the ClassReader and get the modified bytes back from the ClassWriter. Methods like ClassFileTransformer.transform(…) will already receive the bytes representing the current form of the class (there might have been previous transformations) and return the new bytes.
The problem is, this API isn’t generally available to Java applications. It’s available for so-called Java Agents, which must have been either, started together with the JVM via startup options or get loaded dynamically in an implementation-specific way, e.g. via the Attach API.
The package documentation describes the general structure of Java Agents and the related command line options.
At the end of this answer is a program demonstrating how to use the Attach API to attach to your own JVM to load a dummy Java Agent that will give the program access to the Instrumentation API. Considering the complexity, I think, it became apparent, that the actual code transformation and turning the code into a runtime class or using it to replace a class on the fly, are two different tasks that have to collaborate, but whose code you usually want to keep separated.
The easier way is to create a MethodNode instance and replace the body with a new InsnList. First, you need the original class representation. You can get it just like #Holger suggested.
Class<?> originalClass = method.getDeclaringClass();
ClassReader classReader;
try {
cr = new ClassReader(
originalClass.getResourceAsStream(originalClass.getSimpleName()+".class"));
} catch(IOException e) {
throw new IllegalStateException(e);
}
Then create a ClassNode and replace the method body.
//Create the CLassNode
ClassNode classNode = new ClassNode();
classReader.accept(classNode,0);
//Search for the wanted method
final List<MethodNode> methods = classNode.methods;
for(MethodNode methodNode: methods){
if(methodNode.name.equals("test")){
//Replace the body with a RETURN opcode
InsnList insnList = new InsnList();
insnList.add(new InsnNode(Opcodes.RETURN));
methodNode.instructions = insnList;
}
}
Before generating the new class, you will need a ClassLoader with a public defineClass() method. Just like this.
public class GenericClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
Now you can generate the actual class.
//Generate the Class
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
classNode.accept(classWriter);
//Define the representation
GenericClassLoader classLoader = new GenericClassLoader();
Class<?> modifiedClass = classLoader.defineClass(classNode.name, classWriter.toByteArray());
How can I invoke private method using method handles ?
As far as I can see there are only two kinds of publicly accessible Lookup instances:
MethodHandles.lookup()
MethodHandles.publicLookup()
and neither allows unrestricted private access.
There is the non-public Lookup.IMPL_LOOKUP that does what I want. Is there some public way to obtain it (assuming that SecurityManager allows it) ?
Turns out it's possible with Lookup#unreflect(Method) and temporarily making method accessible (potentially introducing small security issue unless done during program initialization).
Here is modified main method from Thorben's answer:
public static void main(String[] args) {
Lookup lookup = MethodHandles.lookup();
NestedTestClass ntc = new Program().new NestedTestClass();
try {
// Grab method using normal reflection and make it accessible
Method pm = NestedTestClass.class.getDeclaredMethod("gimmeTheAnswer");
pm.setAccessible(true);
// Now convert reflected method into method handle
MethodHandle pmh = lookup.unreflect(pm);
System.out.println("reflection:" + pm.invoke(ntc));
// We can now revoke access to original method
pm.setAccessible(false);
// And yet the method handle still works!
System.out.println("handle:" + pmh.invoke(ntc));
// While reflection is now denied again (throws exception)
System.out.println("reflection:" + pm.invoke(ntc));
} catch (Throwable e) {
e.printStackTrace();
}
}
I don't know, if this is what you really want. Perhaps you could give some more information about what you want to achieve with it.
But if you want to access Lookup.IMPL_LOOKUP, you can do it like in this code sample:
public class Main {
public static void main(String[] args) {
Lookup myLookup = MethodHandles.lookup(); // the Lookup which should be trusted
NestedTestClass ntc = new Main().new NestedTestClass(); // test class instance
try {
Field impl_lookup = Lookup.class.getDeclaredField("IMPL_LOOKUP"); // get the required field via reflections
impl_lookup.setAccessible(true); // set it accessible
Lookup lutrusted = (Lookup) impl_lookup.get(myLookup); // get the value of IMPL_LOOKUP from the Lookup instance and save it in a new Lookup object
// test the trusted Lookup
MethodHandle pmh = lutrusted.findVirtual(NestedTestClass.class, "gimmeTheAnswer", MethodType.methodType(int.class));
System.out.println(pmh.invoke(ntc));
} catch (Throwable e) {
e.printStackTrace();
}
}
// nested class with private method for testing
class NestedTestClass{
#SuppressWarnings("unused")
private int gimmeTheAnswer(){
return 42;
}
}
}
It works with JDK 7, but could break in JDK 8. And be cautious! My antivirus gave an alarm when I executed it.
I think there isn't a public or clean way to do it.
I had a similar issue and finally found a solution: Access non-public (java-native) classes from JDK (7).
Here's a similiar solution which includes arguments in a private
function (I just happened to have the code lying around from a previous project):
class name:
InspectionTree.java
function signature:
private String getSamePackagePathAndName(String className, String classPath)
String firstName = "John";
String lastName = "Smith";
//call the class's constructor to set up the instance, before calling the private function
InspectionTree inspectionTree = new InspectionTree(firstName, lastName);
String privateMethodName ="getSamePackagePathAndName";
Class[] privateMethodArgClasses = new Class[] { String.class, String.class };
Method method =
inspectionTree.getClass().getDeclaredMethod(privateMethodName, privateArgClasses);
method.setAccessible(true);
String className = "Person";
String classPath = "C:\\workspace";
Object[] params = new Object[]{className, classPath};
//note the return type of function 'getSamePackagePathAndName' is a String, so we cast
//the return type here as a string
String answer= (String)method.invoke(inspectionTree, params);
method.setAccessible(false);
To check the number of interactions with a mock where the parameter in the method call is of a certain type, one can do
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(isA(FirstClass.class));
This will pass thanks to the call to isA since someMethod was called twice but only once with argument FirstClass
However, this pattern seems to not be possible when using an ArgumentCaptor, even if the Captor was created for the particular argument FirstClass
this doesn't work
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
verify(mock, times(1)).someMethod(captor.capture());
it says the mock was called more than once.
Is there any way to accomplish this verification while capturing the argument for further checking?
I recommend using Mockito's Hamcrest integration to write a good, clean matcher for it. That allows you to combine the verification with detailed checking of the passed argument:
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
verify(mock, times(1)).someMethod(argThat(personNamed("Bob")));
Matcher<Person> personNamed(final String name) {
return new TypeSafeMatcher<Person>() {
public boolean matchesSafely(Person item) {
return name.equals(item.getName());
}
public void describeTo(Description description) {
description.appendText("a Person named " + name);
}
};
}
Matchers generally lead to more readable tests and more useful test failure messages. They also tend to be very reusable, and you'll find yourself building up a library of them tailored for testing your project. Finally, you can also use them for normal test assertions using JUnit's Assert.assertThat(), so you get double use out of them.
Quoting the docs:
Note that an ArgumentCaptordon't do any type checks, it is only
there to avoid casting in your code. This might however change (type
checks could be added) in a future major release.
I wouldn't use an ArgumentCaptor for this. This class captures (literally) everything, despite what class was provided as it's .forClass argument.
To achieve what you want I suggest intercept the argument using Mockito's Answer interface:
private FirstClass lastArgument;
#Test
public void captureFirstClass() throws Exception {
doAnswer(captureLastArgument()).when(mock).someMethod(anInstanceOfFirstClass());
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(mock, times(1)).someMethod(anInstanceOfFirstClass());
//write your desired matchers against lastArgument object
}
private Answer<FirstClass> captureLastArgument() {
return new Answer<FirstClass>() {
#Override
public FirstClass answer(InvocationOnMock invocation) throws Throwable {
TestClass.this.lastArgument = (FirstClass) invocation.getArguments()[0];
return null;
}
};
}
private static Object anInstanceOfFirstClass(){
return Mockito.argThat(isA(FirstClass.class));
}
You can use the the captor for the sake of capturing, then verify the number of invocations with each argument type separately.
// given
ArgumentCaptor<AA> captor = ArgumentCaptor.forClass(AA.class);
CC cc = new CC();
// when
cut.someMethod(new AA());
cut.someMethod(new BB());
cut.someMethod(new BB());
cut.someMethod(cc);
// then
Mockito.verify(collaborator, atLeastOnce()).someMethod(captor.capture());
Mockito.verify(collaborator, times(1)).someMethod(isA(AA.class));
Mockito.verify(collaborator, times(2)).someMethod(isA(BB.class));
Mockito.verify(collaborator, times(1)).someMethod(isA(CC.class));
assertEquals(cc, captor.getValue());
Apparently the generic type of the captor reference doesn't affect anything at runtime.
I also encountered this problem today. I thought I could simply do something like
verify(mock).someMethod(and(isA(FirstClass.class), captor.capture()));
but I couldn't get it to work. I ended up with this solution:
#Test
public void Test() throws Exception {
final ArgumentCaptor<FirstClass> captor = ArgumentCaptor.forClass(FirstClass.class);
mock.someMethod(new FirstClass());
mock.someMethod(new OtherClass());
verify(eventBus, atLeastOnce()).post(captor.capture());
final List<FirstClass> capturedValues = typeCheckedValues(captor.getAllValues(), FirstClass.class);
assertThat(capturedValues.size(), is(1));
final FirstClass capturedValue = capturedValues.get(0);
// Do assertions on capturedValue
}
private static <T> List<T> typeCheckedValues(List<T> values, Class<T> clazz) {
final List<T> typeCheckedValues = new ArrayList<>();
for (final T value : values) {
if (clazz.isInstance(value)) {
typeCheckedValues.add(value);
}
}
return typeCheckedValues;
}
Note: if only one class needs to be captured in this way typeCheckedValues can be simplified into:
private static List<FirstClass> typeCheckedValues(List<FirstClass> values) {
final List<FirstClass> typeCheckedValues = new ArrayList<>();
for (final Object value : values) {
if (value instanceof FirstClass) {
typeCheckedValues.add((FirstClass) value);
}
}
return typeCheckedValues;
}
I have a large library of wicket components that are annotated with a custom annotation #ReferencedResource or another annotation #ReferencedResources, that has a ReferencedResouce[] value() parameter to allow multiple annotations.
Here is a sample code snippet:
#ReferencedResources({
#ReferencedResource(value = Libraries.MOO_TOOLS, type = ResourceType.JAVASCRIPT),
#ReferencedResource(value = "behaviors/promoteSelectOptions", type = ResourceType.JAVASCRIPT) })
public class PromoteSelectOptionsBehavior extends AbstractBehavior{
...
}
So far, I use apt to check that the referenced resources actually exist. E.g.
#ReferencedResource(value = "behaviors/promoteSelectOptions",
type = ResourceType.JAVASCRIPT)
will cause a compilation failure unless the file js/behaviors/promoteSelectOptions.js can be found on the class path. This part works nicely.
Now I am also a fan of DRY and I would like to use the same annotation to actually inject the resources into the Objects when they are created. Using AspectJ, I have implemented a part of this.
The annotated Objects are always either instances of Component or AbstractBehavior.
For components, things are easy, just match after the constructor. Here's an advice that does this:
pointcut singleAnnotation() : #within(ReferencedResource);
pointcut multiAnnotation() : #within(ReferencedResources);
after() : execution(Component+.new(..)) && (singleAnnotation() || multiAnnotation()){
final Component component = (Component) thisJoinPoint.getTarget();
final Collection<ReferencedResource> resourceAnnotations =
// gather annotations from cache
this.getResourceAnnotations(component.getClass());
for(final ReferencedResource annotation : resourceAnnotations){
// helper utility that handles the creation of statements like
// component.add(JavascriptPackageResource.getHeaderContribution(path))
this.resourceInjector.inject(component, annotation);
}
}
For behaviors however, I need to attach the resources to a response, not to the behavior itself. Here are the pointcuts I use:
pointcut renderHead(IHeaderResponse response) :
execution(* org.apache.wicket.behavior.AbstractBehavior+.renderHead(*))
&& args(response);
And here is the advice:
before(final IHeaderResponse response) :
renderHead(response) && (multiAnnotation() || singleAnnotation()) {
final Collection<ReferencedResource> resourceAnnotations =
this.getResourceAnnotations(thisJoinPoint.getTarget().getClass());
for(final ReferencedResource resource : resourceAnnotations){
this.resourceInjector.inject(response, resource);
}
}
This also works nicely if the class overrides the renderHead(response) method, but in many cases that's just not necessary because a super class already implements the base functionality while the child class only adds some configuration. So one solution would be to let these classes define a method like this:
#Override
public void renderHead(IHeaderResponse response){
super.renderHead(response);
}
I would hate this, because this is dead code, but currently this is the only working option I see, so I am looking for other solutions.
EDIT:
I have created a working solution using APT and sun javac calls. However, this leads to the next problem: Running APT and AspectJ in the same project using maven.
Anyway, as soon as I have some free time, I'll post the answer to this question (or parts of it).
Answering my own question:
Here is the relevant bit of code to insert the super call:
these fields are all initialized in init(env) or process(annotations, roundEnv):
private static Filer filer;
private static JavacProcessingEnvironment environment;
private static Messager messager;
private static Types types;
private static JavacElements elementUtils;
private Trees trees;
private TreeMaker treeMaker;
private IdentityHashMap<JCCompilationUnit, Void> compilationUnits;
private Map<String, JCCompilationUnit> typeMap;
And here is the logic that is called if a subtype of AbstractBehavior that has the annotation does not override the renderHead(response) method:
private void addMissingSuperCall(final TypeElement element){
final String className = element.getQualifiedName().toString();
final JCClassDecl classDeclaration =
// look up class declaration from a local map
this.findClassDeclarationForName(className);
if(classDeclaration == null){
this.error(element, "Can't find class declaration for " + className);
} else{
this.info(element, "Creating renderHead(response) method");
final JCTree extending = classDeclaration.extending;
if(extending != null){
final String p = extending.toString();
if(p.startsWith("com.myclient")){
// leave it alone, we'll edit the super class instead, if
// necessary
return;
} else{
// #formatter:off (turns off eclipse formatter if configured)
// define method parameter name
final com.sun.tools.javac.util.Name paramName =
elementUtils.getName("response");
// Create #Override annotation
final JCAnnotation overrideAnnotation =
this.treeMaker.Annotation(
Processor.buildTypeExpressionForClass(
this.treeMaker,
elementUtils,
Override.class
),
// with no annotation parameters
List.<JCExpression> nil()
);
// public
final JCModifiers mods =
this.treeMaker.Modifiers(Flags.PUBLIC,
List.of(overrideAnnotation));
// parameters:(final IHeaderResponse response)
final List<JCVariableDecl> params =
List.of(this.treeMaker.VarDef(this.treeMaker.Modifiers(Flags.FINAL),
paramName,
Processor.buildTypeExpressionForClass(this.treeMaker,
elementUtils,
IHeaderResponse.class),
null));
//method return type: void
final JCExpression returnType =
this.treeMaker.TypeIdent(TypeTags.VOID);
// super.renderHead(response);
final List<JCStatement> statements =
List.<JCStatement> of(
// Execute this:
this.treeMaker.Exec(
// Create a Method call:
this.treeMaker.Apply(
// (no generic type arguments)
List.<JCExpression> nil(),
// super.renderHead
this.treeMaker.Select(
this.treeMaker.Ident(
elementUtils.getName("super")
),
elementUtils.getName("renderHead")
),
// (response)
List.<JCExpression> of(this.treeMaker.Ident(paramName)))
)
);
// build code block from statements
final JCBlock body = this.treeMaker.Block(0, statements);
// build method
final JCMethodDecl methodDef =
this.treeMaker.MethodDef(
// public
mods,
// renderHead
elementUtils.getName("renderHead"),
// void
returnType,
// <no generic parameters>
List.<JCTypeParameter> nil(),
// (final IHeaderResponse response)
params,
// <no declared exceptions>
List.<JCExpression> nil(),
// super.renderHead(response);
body,
// <no default value>
null);
// add this method to the class tree
classDeclaration.defs =
classDeclaration.defs.append(methodDef);
// #formatter:on turn eclipse formatter on again
this.info(element,
"Created renderHead(response) method successfully");
}
}
}
}