1

Goal: Call a class's static filter() method from any of my @Entity classes. Returning a List<Object. Getting the right class caller type. For example, when i call filter() from the User class I want to get User as caller class (not the class owning the original static method).

This filter() method is in ORMEntity:

public static List<Object> filter(){
    System.out.println("Called filter method!");
    return Collections.emptyList();
}

I wrote this aspect:

public privileged aspect OrmAspect {

//Extends ORMEntity when class marked with @Entity
declare parents : (@Entity *) extends ORMEntity;

//Getting filter() calls from anywhere
//This pointing to ORMEntity.filter()
pointcut staticFilter() : call(* *.filter());

before() : staticFilter(){
    System.out.println(">>"+thisJoinPoint);
}

Then I can code like this from my main method:

User.filter();

User is a simple bean with @Entity annotation.

This is working like that:

  1. Compiler see the filter() method from the User class. Ok.
  2. The pointcut for filter() is only on the ORMEntity.filter() even if User extends ORMEntity (then filter() method). Not ok.

The output from the before filter() join point:

System.out.println(">>"+thisJoinPoint.getSignature().getDeclaringType());

Is ORMEntity instead of User that I expected.

How can I get the User class inherit the static filter() method ? Or have like a pointcut on a declare parent with AspectJ?

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Tokazio
  • 516
  • 2
  • 18
  • The declaring type is `ORMEntity` since that's where you declared you `filter()` method. Even if you would use a non-static method, the method itself would still be 'declared' in `ORMEntity`, but you're not even using an instance method, so you shouldn't think in terms of inheritance in case of static methods. I'm not sure what are you trying to achieve here, so it might be useful if you'd elaborate a bit more on that. – Nándor Előd Fekete Sep 26 '16 at 18:18
  • yes i understand that's ORMEntity that own filter() method. i had think that with aspectj i could inherit this method from anywhere. that's working but the caller stay ORMEntity even if i call it with User class. in fact i want to call this filter method statically from any of my @Entity classes. just to have 'nice' 'easy' code. instead of call the ORMEntity.filter(User.class); i would call User.filter(); who's calling the ORMEntity.filter(User.class); in background. that's why im searching to get the right class caller. – Tokazio Sep 26 '16 at 19:07
  • Well, some elaboration on the static method invocation: while the compiler will allow you to say `Subclass.staticMethod()` even though `staticMethod()` itself was declared in a superclass, it's only a case of the compiler being lenient on your intent. It is actually translating your method call in the background to `DeclaringClass.staticMethod()`. On the other hand, if I understand correctly, you want to know the class whose code is actually invoking the static method. That's perfectly possible with a `call(...)` type pointcut, and in fact the expression is: `thisEnclosingJoinPointStaticPart` – Nándor Előd Fekete Sep 26 '16 at 19:16
  • yes then i get the class containing the User.filter(); statement. the Main.class (assuming main method is in Main class). Not User.class – Tokazio Sep 26 '16 at 19:49
  • Well, then the answer lies in the first part of my previous comment, it's not possible. Static method is a static method, it get's wired in __statically__ (hence the name) at compile time, based on the type you're invoking it on. If you're invoking it on `Subclass`, but `Subclass` itself doesn't define a static method with that name and compatible arguments, it will be silently _'rewritten'_ by the compiler to `DeclaringClass.staticMethod()`. – Nándor Előd Fekete Sep 26 '16 at 20:03
  • i've tried many possibilities and ever getting ORMEntity or Main class instead of User as expected... thanks for your help!! – Tokazio Sep 26 '16 at 20:11

2 Answers2

1

As explained in our discussion under your own answer, I doubt that your application design really makes sense, but for what it is worth, I have prepared a solution for you using AspectJ's annotation processing capability introduced in version 1.8.2. This solution is a simplified version of what I have described for a much more complicated case in another StackOverflow answer.

Here is my Eclipse layout with two source folders and a Windows batch file performing a two-phase compilation process,

  • first compiling an APT processor responsible for creating one aspect per annotated class
  • and then actually applying this processor to your Java sources during the next AspectJ compilation step.

Here is a screenshot of my directory layout:

Eclipse directory layout

As you can see, class Application cannot be directly compiled by Eclipse, you really need to use the batch file.

Marker annotation:

Attention, this class must be stored inside src_apt in order to be visible for the annotation processor EntityProcessor later.

package de.scrum_master.app;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Entity {}

Two annotated sample entities:

package de.scrum_master.app;

@Entity
public class User {}
package de.scrum_master.app;

@Entity
public class Group {}

Driver application:

This application relies on APT doing its job before it can actually see the static methods used.

package de.scrum_master.app;

public class Application {
    public static void main(String[] args) {
        User.filter();
        Group.filter();
    }
}

Aspect printing method signatures:

This is a simpilfied version of your original aspect, only printing method signatures, not declaring any parents or static methods. This is just in order to get nicer log output later:

package de.scrum_master.aspect;

public aspect LogAspect {
    pointcut staticFilter() :
        call(public static * filter());

    before() : staticFilter(){
        System.out.println(thisJoinPoint);
    }
}

Annotation processor:

This annotation processor searches for classes annotated with @Entity and creates one aspect introducing a static method filter() for each of them.

package de.scrum_master.app;

import java.io.*;
import java.util.*;

import javax.tools.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;

@SupportedAnnotationTypes(value = { "*" })
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class EntityProcessor extends AbstractProcessor {
    private Filer filer;

    @Override
    public void init(ProcessingEnvironment env) {
        filer = env.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
        env.getElementsAnnotatedWith(Entity.class).stream()
            .filter(annotatedClass -> annotatedClass.getKind() == ElementKind.CLASS)
            .forEach(annotatedClass -> {
                String packageName = annotatedClass.getEnclosingElement().toString().substring(8);
                String className = annotatedClass.getSimpleName().toString();
                String aspectName = "ORMAspect_" + className;
                String aspectSource = createAspectSource(packageName, className,aspectName);
                writeAspectSourceToDisk(packageName, aspectName, aspectSource);
            });
        return true;
    }

    private String createAspectSource(String packageName, String className, String aspectName) {
        StringBuilder aspectSource = new StringBuilder()
            .append("package " + packageName + ";\n\n")
            .append("import java.util.Collections;\n")
            .append("import java.util.List;\n\n")
            .append("public aspect " + aspectName + " {\n")
            .append("    public static List<Object> " + className + ".filter() {\n")
            .append("        System.out.println(\"Called filter method!\");\n")
            .append("        return Collections.emptyList();\n")
            .append("    }\n")
            .append("}\n");
        return aspectSource.toString();
    }

    private void writeAspectSourceToDisk(String packageName, String aspectName, String aspectSource) {
        try {
            JavaFileObject file = filer.createSourceFile(packageName + "." + aspectName);
            file.openWriter().append(aspectSource).close();
            System.out.println("Generated aspect " + packageName + "." + aspectName);
        } catch (IOException ioe) {
            // Message "already created" can appear if processor runs more than once
            if (!ioe.getMessage().contains("already created"))
                ioe.printStackTrace();
        }
    }
}

Service descriptor for annotation processor:

This is the content of META-INF/services/javax.annotation.processing.Processor:

de.scrum_master.app.EntityProcessor

Batch file performing two-phase compilation:

This batch file does what I described at the beginning of my answer. Please make sure you adjust variables SRC_PATH and ASPECTJ_HOME to your needs.

@echo off

set SRC_PATH=C:\Users\Alexander\Documents\java-src\SO_AJ_ITDStaticMethods
set ASPECTJ_HOME=C:\Program Files\Java\AspectJ

echo Building annotation processor
cd "%SRC_PATH%"
rmdir /s /q bin
del /q processor.jar
set CLASSPATH=%ASPECTJ_HOME%\lib\aspectjrt.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src_apt -d bin
jar -cvf processor.jar -C src_apt META-INF -C bin .

echo.
echo Generating aspects and building project
rmdir /s /q bin .apt_generated
set CLASSPATH=%ASPECTJ_HOME%\lib\aspectjrt.jar;processor.jar
call "%ASPECTJ_HOME%\bin\ajc.bat" -1.8 -sourceroots src -d bin -s .apt_generated -inpath processor.jar -processor de.scrum_master.app.EntityProcessor -showWeaveInfo

echo.
echo Running de.scrum_master.app.Application
java -cp bin;"%ASPECTJ_HOME%\lib\aspectjrt.jar" de.scrum_master.app.Application

Console log when running the batch file:

C:\Users\Alexander\Documents\java-src\SO_AJ_ITDStaticMethods>compile_run.bat
Building annotation processor
Manifest wurde hinzugefügt
Eintrag META-INF/ wird ignoriert
META-INF/services/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
META-INF/services/javax.annotation.processing.Processor wird hinzugefügt(ein = 36) (aus = 38)(-5 % verkleinert)
de/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/ wird hinzugefügt(ein = 0) (aus = 0)(0 % gespeichert)
de/scrum_master/app/Entity.class wird hinzugefügt(ein = 293) (aus = 200)(31 % verkleinert)
de/scrum_master/app/EntityProcessor.class wird hinzugefügt(ein = 5679) (aus = 2476)(56 % verkleinert)

Generating aspects and building project
Generated aspect de.scrum_master.app.ORMAspect_Group
Generated aspect de.scrum_master.app.ORMAspect_User

Running de.scrum_master.app.Application
call(List de.scrum_master.app.User.filter())
Called filter method!
call(List de.scrum_master.app.Group.filter())
Called filter method!

Et voilà! The last 4 lines show what you want to see: static methods explicitly declared in your annotated target classes. Enjoy!

Community
  • 1
  • 1
kriegaex
  • 63,017
  • 15
  • 111
  • 202
0

Seems to be not possible.

The need is to hide the static ORMEntity filter() method in the User class. But static method are resolved at compile time.

The only solution i see is to generate the static filter() method in the .java file like lombok can do with getters/setters.

Tokazio
  • 516
  • 2
  • 18
  • 1
    The problem is not that it is impossible but that it just does not make sense! You want to change the way the JVM works. This is **not an AspectJ problem!!!** If you really insist you can use ITD in order to redundantly declare methods for each `@Entity` class. Then you get what you want, but it strikes me as inefficient and kinda stupid (sorry, no offense meant). Why do you want to change the (efficient) way the JVM works? – kriegaex Nov 05 '16 at 13:39
  • instead of call the ORMEntity.filter(User.class); i would call User.filter(); who's calling the ORMEntity.filter(User.class) without to write more code. ok for not 'possible' missuse. what's ITD? – Tokazio Nov 06 '16 at 08:15
  • ITD is inter-type declaration, i.e. static crosscutting like you already use it in `declare parents`. You can also declare methods for classes using AspectJ ITD, but the downside in your case is that you do not want it for a single class but for a group of them. I.e. you need to declare a static method for each single annotated class. In order to achieve that you can use AspectJ's annotation processing feature as described in the [v1.8.2 release notes](http://www.eclipse.org/aspectj/doc/released/README-182.html). But is is worth the effort? I think your application design is just flawed. – kriegaex Nov 06 '16 at 11:46
  • Oh, BTW: I already showed how to use APT + AspectJ in order to declare static methods on multiple classes [here](http://stackoverflow.com/a/29437129/1082681). I am declaring `main(..)` methods there, but you can easily change that to `filter()`. – kriegaex Nov 06 '16 at 11:48