Given a stream with two lambda expressions working on it:
Stream.of(new String[]{"a", "b"})
.map(s -> s.toUpperCase())
.filter(s -> s.equals("A"))
.count();
and an AspectJ advice that matches all lambdas (taken from here) and prints out the name of the called method and the value of the lamdba's first parameter:
@Before("execution(* *..*lambda*(..))")
public void beforeLambda(JoinPoint jp) {
System.out.println("lambda called: [" + jp.getSignature() + "] "+
"with parameter [" + jp.getArgs()[0] + "]");
}
The output is:
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [B]
Is there a way to include in the output not only the lambda's parameter, but also the Stream's method that got the lambda as parameter? In other words: is it possible to know in the beforeLambda
method, if currently the map
or the filter
call is being processed?
The output I am looking for would be:
lambda called: [map] with parameter [a]
lambda called: [filter] with parameter [A]
lambda called: [map] with parameter [b]
lambda called: [filter] with parameter [B]
What I have tried so far:
- check the information in the JoinPoint. It contains the signature of the method that was created by the lambda expression. The name of the actual method is different (
lambda$0
for map andlambda$1
for filter), but as they are generated by the compiler, there is no way to use this information in the code. I could try to distinguish the two cases based on the return types, but in my real life problem, the different lambda expressions also have the same return types. - try to find a more specific pointcut expression that only matches one of the calls. Again, the problem is that there is no way to know the name of the generated method for the map or filter lambda.
- looking a the stack trace while
beforeLambda
is running. In both cases, the lowest entry in the stack trace is the stream'scount
method and the last entry beforebeforeLambda
is the generated method:
at aspectj.Starter$LambdaAspect.beforeLambda(Starter.java:25)
at aspectj.Starter.lambda$0(Starter.java:14)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
[...more from java.util, but no hint to map or filter...]
at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
at aspectj.Starter.main(Starter.java:16)
- add a second aspect to the Stream's methods, printing out which Stream method is called with which parameter (that would be in case of
map
andfilter
one of the lambdas) so that I could later replace the generated method names in the output. However the lambda's names in the Stream methods do not match the method names seen in thebeforeLambda
output:
@Before("call(* java.util.stream.Stream.*(..))")
public void beforeStream(JoinPoint jp) {
System.out.println("Stream method called: [" + jp.getSignature().getName() + "] with parameter [" + (jp.getArgs().length > 0 ? jp.getArgs()[0] : "null") + "])");
}
Stream method called: [of] with parameter [[Ljava.lang.String;@754c89eb])
Stream method called: [map] with parameter [aspectj.Starter$$Lambda$1/1112743104@512c45e7])
Stream method called: [filter] with parameter [aspectj.Starter$$Lambda$2/888074880@75e9a87])
Stream method called: [count] with parameter [null])
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [a]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [A]
lambda called: [String aspectj.Starter.lambda$0(String)] with parameter [b]
lambda called: [boolean aspectj.Starter.lambda$1(String)] with parameter [B]