Skip to content

Commit daad6ad

Browse files
committed
Add default toString strategy
1 parent bc1c2de commit daad6ad

File tree

5 files changed

+243
-2
lines changed

5 files changed

+243
-2
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package fr.pturpin.lambdaString;
2+
3+
import com.ea.agentloader.AgentLoader;
4+
import fr.pturpin.lambdaString.LambdaTestHolder.Lambda;
5+
import fr.pturpin.lambdaString.agent.LambdaToStringAgent;
6+
import fr.pturpin.lambdaString.strategy.DefaultToStringStrategy;
7+
import org.openjdk.jmh.annotations.*;
8+
import org.openjdk.jmh.infra.Blackhole;
9+
10+
import java.util.concurrent.TimeUnit;
11+
12+
/**
13+
* Measure the time difference between getting the {@link Object#toString()} without any injection or injecting the
14+
* {@link DefaultToStringStrategy}.
15+
*/
16+
@BenchmarkMode(Mode.AverageTime)
17+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
18+
@State(Scope.Benchmark)
19+
@Warmup(iterations = 5)
20+
@Measurement(iterations = 10)
21+
@Fork(1)
22+
public class DefaultStrategyCostBenchmark {
23+
24+
@Param({ "true", "false" })
25+
public boolean isInjected;
26+
27+
@Setup
28+
public void setup() {
29+
if (isInjected) {
30+
AgentLoader.loadAgentClass(LambdaToStringAgent.class.getName(), DefaultToStringStrategy.class.getName());
31+
}
32+
}
33+
34+
@State(Scope.Benchmark)
35+
public static class Data {
36+
37+
Lambda lambda;
38+
Lambda methodRef;
39+
40+
public Data() {
41+
this.lambda = () -> {};
42+
this.methodRef = LambdaTestHolder::body;
43+
}
44+
}
45+
46+
@Benchmark
47+
public void staticLambda(Data data, Blackhole bh) {
48+
bh.consume(data.lambda.toString());
49+
}
50+
51+
@Benchmark
52+
public void staticMethodRef(Data data, Blackhole bh) {
53+
bh.consume(data.methodRef.toString());
54+
}
55+
56+
@Benchmark
57+
public void dynamicLambda(Blackhole bh) {
58+
Lambda lambda = () -> {};
59+
bh.consume(lambda.toString());
60+
}
61+
62+
@Benchmark
63+
public void dynamicMethodRef(Blackhole bh) {
64+
Lambda lambda = LambdaTestHolder::body;
65+
bh.consume(lambda.toString());
66+
}
67+
68+
}

src/main/java/fr/pturpin/lambdaString/agent/LambdaToStringAgent.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package fr.pturpin.lambdaString.agent;
22

3+
import fr.pturpin.lambdaString.strategy.DefaultToStringStrategy;
34
import fr.pturpin.lambdaString.strategy.LambdaToStringStrategy;
45
import fr.pturpin.lambdaString.transform.InnerClassLambdaMetafactoryTransformer;
56
import fr.pturpin.lambdaString.transform.LambdaToStringLinker;
@@ -17,7 +18,7 @@
1718
* to {@link java.lang.reflect.Constructor#setAccessible(boolean) set it accessible} but may failed because of a
1819
* {@link SecurityManager}.
1920
* <p>
20-
* If no class parameter is given, this agent does nothing and returns silently.
21+
* If no class parameter is given, this agent use the {@link DefaultToStringStrategy} strategy.
2122
* <p>
2223
* If an error occurs because the given strategy class name is invalid, a {@link RuntimeException} containing the
2324
* {@link LambdaToStringLinkerException} cause is thrown while loading this agent.
@@ -44,7 +45,7 @@ public static void agentmain(String agentArgs, Instrumentation inst) {
4445

4546
public static void premain(String agentArgs, Instrumentation inst) {
4647
if (agentArgs == null || agentArgs.isEmpty()) {
47-
return;
48+
agentArgs = DefaultToStringStrategy.class.getName();
4849
}
4950

5051
try {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package fr.pturpin.lambdaString.strategy;
2+
3+
import fr.pturpin.lambdaString.transform.LambdaMetaInfo;
4+
5+
import java.util.OptionalInt;
6+
7+
public class DefaultToStringStrategy implements LambdaToStringStrategy {
8+
9+
@Override
10+
public String createToString(Object lambda, LambdaMetaInfo metaInfo) throws LambdaToStringException {
11+
String className = getClassName(metaInfo);
12+
OptionalInt declarationLine = metaInfo.getDeclarationLine();
13+
14+
if (!isSynthetic(metaInfo.getModifers())) {
15+
// Seems like a method reference
16+
String methodDesc = metaInfo.getMethodDesc();
17+
String emptyArgsDesc = "()V";
18+
boolean hideArgs = declarationLine.isPresent() || emptyArgsDesc.equals(methodDesc);
19+
String toString = className + "::" + metaInfo.getMethodName() + (hideArgs ? "" : methodDesc);
20+
21+
if (declarationLine.isPresent()) {
22+
toString = toString + ":" + declarationLine.getAsInt();
23+
}
24+
return toString;
25+
}
26+
27+
if (declarationLine.isPresent()) {
28+
return className + ":" + declarationLine.getAsInt();
29+
}
30+
return className + "::" + metaInfo.getMethodName() + metaInfo.getMethodDesc();
31+
}
32+
33+
private static String getClassName(LambdaMetaInfo metaInfo) {
34+
String classFullName = metaInfo.getDeclaringClass().getName();
35+
return classFullName.substring(classFullName.lastIndexOf('.') + 1);
36+
}
37+
38+
private static boolean isSynthetic(int modifiers) {
39+
int ACC_SYNTHETIC = 0x1000; // from JVMS 4.6 (Table 4.6-A)
40+
return (modifiers & ACC_SYNTHETIC) != 0;
41+
}
42+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package fr.pturpin.lambdaString.agent;
2+
3+
import com.ea.agentloader.AgentLoader;
4+
import fr.pturpin.lambdaString.StaticLambdaHolder;
5+
import org.junit.jupiter.api.Test;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
import static org.assertj.core.api.Assertions.assertThatCode;
9+
10+
class DefaultLambdaToStringAgent {
11+
12+
@Test
13+
void agentShouldUseDefaultStrategyIfNoneIsGiven() throws Exception {
14+
assertThatCode(() -> AgentLoader.loadAgentClass(LambdaToStringAgent.class.getName(),
15+
"")).doesNotThrowAnyException();
16+
assertThat(StaticLambdaHolder.STATIC_LAMBDA.toString()).startsWith("StaticLambdaHolder");
17+
}
18+
19+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package fr.pturpin.lambdaString.strategy;
2+
3+
import com.ea.agentloader.AgentLoader;
4+
import fr.pturpin.lambdaString.StaticLambdaHolder;
5+
import fr.pturpin.lambdaString.StaticMethodRefHolder;
6+
import fr.pturpin.lambdaString.agent.LambdaToStringAgent;
7+
import org.junit.jupiter.api.BeforeAll;
8+
import org.junit.jupiter.api.Test;
9+
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
class DefaultToStringStrategyTest {
13+
14+
@BeforeAll
15+
static void beforeAll() {
16+
AgentLoader.loadAgentClass(LambdaToStringAgent.class.getName(), DefaultToStringStrategy.class.getName());
17+
}
18+
19+
@Test
20+
void testStaticLambda() throws Exception {
21+
assertThat(StaticLambdaHolder.STATIC_FINAL_LAMBDA.toString()).isEqualTo("StaticLambdaHolder:14");
22+
}
23+
24+
@Test
25+
void testLambdaInStaticInnerClass() throws Exception {
26+
assertThat(StaticLambdaHolder.LAMBDA_DECLARED_IN_CONSTRUCTOR_OF_STATIC_INNER_CLASS.toString()).isEqualTo(
27+
"StaticLambdaHolder$StaticInnerLambdaHolder:90");
28+
}
29+
30+
@Test
31+
void testLambdaInInnerClass() throws Exception {
32+
assertThat(StaticLambdaHolder.LAMBDA_DECLARED_IN_CONSTRUCTOR_OF_INNER_CLASS.toString()).isEqualTo(
33+
"StaticLambdaHolder$InnerLambdaHolder:103");
34+
}
35+
36+
@Test
37+
void testLambdaFromAnonymous() throws Exception {
38+
assertThat(StaticLambdaHolder.STATIC_LAMBDA_FROM_ANONYMOUS.toString()).isEqualTo("StaticLambdaHolder$1:59");
39+
}
40+
41+
@Test
42+
void testLambdaFromLambda() throws Exception {
43+
assertThat(StaticLambdaHolder.STATIC_LAMBDA_FROM_LAMBDA.toString()).isEqualTo("StaticLambdaHolder:69");
44+
}
45+
46+
@Test
47+
void testStaticMethodRef() throws Exception {
48+
assertThat(StaticMethodRefHolder.STATIC_METHOD_REF.toString()).isEqualTo(
49+
"StaticMethodRefHolder::staticMethod:12");
50+
}
51+
52+
@Test
53+
void testStaticMethodInStaticInnerClassFromThisStaticInnerClass() throws Exception {
54+
assertThat(StaticMethodRefHolder.STATIC_METHOD_IN_STATIC_INNER_REF_FROM_STATIC_INNER.toString()).isEqualTo(
55+
"StaticMethodRefHolder$StaticInnerHolder::staticMethod:33");
56+
}
57+
58+
@Test
59+
void testStaticMethodInStaticInnerClassFromOutsideStaticInnerClass() throws Exception {
60+
assertThat(StaticMethodRefHolder.STATIC_METHOD_IN_STATIC_INNER.toString()).isEqualTo(
61+
"StaticMethodRefHolder:116");
62+
}
63+
64+
@Test
65+
void testInstanceMethodInStaticInnerClassFromOutsideStaticInnerClass() throws Exception {
66+
assertThat(StaticMethodRefHolder.INSTANCE_METHOD_IN_STATIC_INNER.toString()).isEqualTo(
67+
"StaticMethodRefHolder:119");
68+
}
69+
70+
@Test
71+
void testStaticMethodInInterfaceFromImpl() throws Exception {
72+
assertThat(StaticMethodRefHolder.STATIC_METHOD_IN_INTERFACE_FROM_IMPL.toString()).isEqualTo(
73+
"StaticMethodRefHolder$InterfaceInnerHolder::staticMethod:51");
74+
}
75+
76+
@Test
77+
void testStaticMethodInInterface() throws Exception {
78+
assertThat(StaticMethodRefHolder.STATIC_METHOD_IN_INTERFACE.toString()).isEqualTo(
79+
"StaticMethodRefHolder$InterfaceInnerHolder::staticMethod:51");
80+
}
81+
82+
@Test
83+
void testOverriddenDefaultMethod() throws Exception {
84+
assertThat(StaticMethodRefHolder.OVERRIDDEN_DEFAULT_METHOD.toString()).isEqualTo(
85+
"StaticMethodRefHolder$OverriddenDefaultMethodHolder::defaultMethod:56");
86+
}
87+
88+
@Test
89+
void testDefaultMethodInInterface() throws Exception {
90+
assertThat(StaticMethodRefHolder.DEFAULT_METHOD_IN_INTERFACE.toString()).isEqualTo(
91+
"StaticMethodRefHolder$InterfaceInnerHolder::defaultMethod:48");
92+
}
93+
94+
@Test
95+
void testMethodRefWithArgument() throws Exception {
96+
FooInterface foo = DefaultToStringStrategyTest::foo;
97+
assertThat(foo.toString()).isEqualTo("DefaultToStringStrategyTest::foo:108");
98+
}
99+
100+
private interface FooInterface {
101+
@SuppressWarnings("unused")
102+
Object bar(int n, String s);
103+
}
104+
105+
// Position sensitive
106+
@SuppressWarnings("unused")
107+
private static Object foo(int n, String s) {
108+
return null;
109+
}
110+
111+
}

0 commit comments

Comments
 (0)