Modify a method using Annotations
Asked Answered
C

5

13

How can I change what a method is doing in Java ?

I mean, I am trying to use annotations to make the following code

@Anno1(Argument = "Option1")
public class TestClass
{       
    @Anno2
    public void test()
    {
    }

}

Into

public class TestClass
{
    private static StaticReference z;

    public void test()
    {
           z.invokeToAll();
    }

}

This is a very simplified example of what I am trying to do. Anno1 will have many possible combinations, but this is not my problem so far. My problem is how to add code to method test()

I am looking for a more generic solution if possible. Eg. A way to add every kind of code in the method (not just a way to .invokeToAll())

So far I am using import javax.annotation.processing.*; and I have the following code, but I don't know how to go on from there

private void processMethodAnnotations(RoundEnvironment env)
{
    for (Element e : env.getElementsAnnotatedWith(Anno2.class))
    {
        //If it is a valid annotation over a method
        if (e.getKind() == ElementKind.METHOD) 
        {
            //What to do here :S
        }else
        {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,"Not a method!", e);               
        }           
    }
}

I have found something about Java Reflection but I have not found any source to help me with what I am doing.

Obviously I extends AbstractProcessor in my code

I have found this tutorial (http://www.zdnetasia.com/writing-and-processing-custom-annotations-part-3-39362483.htm) But this concerns creating a new class, not just changing a method. and the javax.lang.model.elements do not provide any way of editing that element (which in my case represents a Method).

I hope my question is clear and inline with the rules. If not please comment and I will clarify. Thanks.

Coronet answered 31/1, 2011 at 13:34 Comment(0)
F
13

Annotation processing is the wrong way to go for you, from Wikipedia:

When Java source code is compiled, annotations can be processed by compiler plug-ins called annotation processors. Processors can produce informational messages or create additional Java source files or resources, which in turn may be compiled and processed, but annotation processors cannot modify the annotated code itself.

People suggested to you the right way - AOP. Specifically, you can use AspectJ. "Quick result" way is (if you use Eclipse):

  1. Install AJDT (AspectJ Development Tools)

  2. Create an AspectJ project and add there your classes and annotations

  3. Create Aspect:

    public aspect Processor {

     private StaticReference z;
    
     pointcut generic()
             // intercept execution of method named test, annotated with @Anno1
             // from any class type, annotated with @Anno2
         : execution(@Anno2 * (@Anno1 *).test())
             // method takes no arguments
         && args ();
    
     // here you have written what you want the method to actually do
     void around () : generic()  {
         z.invokeToAll();
     }
    

    }

now you can execute a test and you will see that it works ;) AJDT compiles code for you automatically, so do not need any manual work to do, hope that's what you called "magic" ;)

UPDATE:

if your code in the test() method depends on the Anno1 annotation value, then inside aspect you can get class annotation for which it is executed this way:

void around () : generic()  {
    
    Annotation[] classAnnotations = thisJoinPoint.getThis().getClass().getAnnotations();
    
    String ArgumentValue = null;
    for ( Annotation annotation : classAnnotations ) {
        if ( annotation instanceof Anno1 ) {
            ArgumentValue = ((Anno1) annotation).Argument(); 
            break;
        }
    }
    
    if ( ArgumentValue != null && ArgumentValue.equals("Option1")) {
        z.invokeToAll();
    }
    
}

where thisJoinPoint is a special reference variable.

UPDATE2:

if you want to add System.out.println( this ) in your aspect, you need to write there System.out.println( thisJoinPoint.getThis() ), just tested and it works. thisJoinPoint.getThis() returns you "this" but not exactly; in fact this is Object variable and if you want to get any propery you need either to cast or to use reflection. And thisJoinPoint.getThis() does not provide access to private properties.

Well, now seems that your question is answered, but if I missed anything, or you get additional question/problems with this way - feel free to ask ;)

Firebrick answered 31/1, 2011 at 14:42 Comment(4)
You solution seems to be what I want, unfortunately due to weird reasons I may not use it. I have found a solution elsewhere (Javassist). Anyway I already gave you +1 and now an Accepted Answer for the effort and good answer. Thanks a lot!Coronet
@Muggen hm, you can tell me about "weird reasons", so I could see if they really matter; maybe there is another way to stick with AOP (it looks easier to support..)Firebrick
those weird reasons are Academic Restrictions ;). Thanks for your help though.Coronet
@Muggen, I understand, but anyway interesting what can restrict. Really, just wondering - you should use Java only (so AspectJ is treated as "different" language), or you just don't have to look for easy solutions? ;)Firebrick
S
10

It's perfectly possible to do what you ask, although there is a caveat: relying on private compiler APIs. Sounds scary, but it isn't really (compiler implementations tend to be stable).

There's a paper that explains the procedure: The Hacker's Guide to Javac.

Notably, this is used by Project Lombok to provide automatic getter/setter generation (amongst other things). The following article explains how it does it, basically re-iterating what is said the aforementioned paper.

Sides answered 19/9, 2015 at 23:11 Comment(0)
M
1

Well, you might see if the following boilerplate code will be useful:

public void magic(Object bean, String[] args) throws Exception {
    for (Method method : bean.getClass().getDeclaredMethods()) {
        if (method.isAnnotationPresent(Anno2.class)) {
            // Invoke the original method
            method.invoke(bean, args);
            // Invoke your 'z' method
            StaticReference.invokeAll();
        }
    }
}

As an alternative your might employ aspect oriented programming, for instance you have the AspectJ project.

Monseigneur answered 31/1, 2011 at 13:44 Comment(2)
Hm smart, but What if instead of z.invokeToAll(); I want for example to do something like System.out.println(this); I am looking for a more generic solution if possible.Coronet
Printing this means to invoke TestClass.toString(). You can still do that if you prefer. Moreover, with reflection, you can access all properties of your annotated classes.Foushee
H
1

I'm not sure at all if it is even possible to change the source or byte code via annotations. From what your describing it looks as if aspect oriented programming could provide a solution to your problem.

Your annotations are pretty similiar to the pointcut concept (they mark a location where code needs to be inserted) and the inserted code is close the advice concept.

Another approach would be parsing the java source file into an abstract syntax tree, modify this AST and serialize to a java compiler input.

Himes answered 31/1, 2011 at 13:49 Comment(1)
AOP is the way to go here (+1). Modifying the AST is possible (I have done it successfully), but it's a dirty hack that relies on undocumented internal APIs that are subject to changePuerperal
O
0

If your class extends a suitable interface, you could wrap it in a DynamicProxy, which delegates all calls to the original methods, except the call to test.

Obellia answered 31/1, 2011 at 14:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.