Skip to content

Commit d9d375f

Browse files
authored
Merge pull request #58 from gdgib/G2-1782-StaticInitializers
2 parents e310a08 + 2129472 commit d9d375f

File tree

11 files changed

+146
-38
lines changed

11 files changed

+146
-38
lines changed

ha-trace/src/main/java/com/g2forge/habitat/trace/AStackTraceAnalyzer.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.lang.reflect.Executable;
43
import java.util.ArrayList;
54
import java.util.Collections;
65
import java.util.List;
76
import java.util.Set;
87
import java.util.stream.Collectors;
98
import java.util.stream.Stream;
109

10+
import com.g2forge.habitat.trace.executable.IExecutable;
11+
1112
import lombok.EqualsAndHashCode;
1213
import lombok.Getter;
1314

@@ -22,14 +23,14 @@ protected List<? extends ISmartStackTraceElement> computeElements() {
2223
}
2324

2425
@Override
25-
public Executable getCaller() {
26+
public IExecutable getCaller() {
2627
return getExecutable(0, getInvisibles());
2728
}
2829

2930
protected abstract ClassLoader getContextClassloader();
3031

3132
@Override
32-
public Executable getEntrypoint(Set<EntrypointFilter> filters) {
33+
public IExecutable getEntrypoint(Set<EntrypointFilter> filters) {
3334
final List<? extends ISmartStackTraceElement> elements = this.getElements();
3435
final List<? extends ISmartStackTraceElement> limited = new ArrayList<>(elements.subList(getInvisibles(), elements.size()));
3536
Collections.reverse(limited);
@@ -41,7 +42,7 @@ public Executable getEntrypoint(Set<EntrypointFilter> filters) {
4142
return (element != null) ? element.getExecutable() : null;
4243
}
4344

44-
protected Executable getExecutable(int offset, int invisible) {
45+
protected IExecutable getExecutable(int offset, int invisible) {
4546
final StackTraceElement[] stackTrace = getStackTrace();
4647

4748
final int actual, pretendDepth = stackTrace.length - invisible;
@@ -59,7 +60,7 @@ protected Executable getExecutable(int offset, int invisible) {
5960
protected abstract int getInvisibles();
6061

6162
@Override
62-
public Executable getMain() {
63+
public IExecutable getMain() {
6364
return getExecutable(-1, getInvisibles());
6465
}
6566

ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.lang.reflect.Executable;
43
import java.util.Set;
54
import java.util.stream.Stream;
65

76
import com.g2forge.alexandria.java.core.marker.Helpers;
7+
import com.g2forge.habitat.trace.executable.IExecutable;
88

99
import lombok.experimental.UtilityClass;
1010

@@ -25,11 +25,11 @@ protected static Thread[] enumerateThreads() {
2525
return threads;
2626
}
2727

28-
public static Executable getCaller() {
28+
public static IExecutable getCaller() {
2929
return createSTA().getCaller();
3030
}
3131

32-
public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
32+
public static IExecutable getEntrypoint(Set<EntrypointFilter> filters) {
3333
return createSTA().getEntrypoint(filters);
3434
}
3535

@@ -40,11 +40,11 @@ public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
4040
* @param offset The offset relative to the caller of this method.
4141
* @return
4242
*/
43-
public static Executable getExecutable(int offset) {
43+
public static IExecutable getExecutable(int offset) {
4444
return createSTA().getExecutable(offset, 2);
4545
}
4646

47-
public static Executable getMain() {
47+
public static IExecutable getMain() {
4848
return createSTA().getMain();
4949
}
5050

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.lang.reflect.Executable;
43
import java.lang.reflect.Field;
54

5+
import com.g2forge.habitat.trace.executable.IExecutable;
6+
67
public interface ISmartStackTraceElement extends IStackTraceElement {
78
public Class<?> getDeclaringClass();
89

9-
public Executable getExecutable();
10+
public IExecutable getExecutable();
1011

1112
public Field getInitialized();
1213
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.lang.reflect.Executable;
43
import java.util.List;
54
import java.util.Set;
65

6+
import com.g2forge.habitat.trace.executable.IExecutable;
7+
78
public interface IStackTraceAnalyzer {
8-
public Executable getCaller();
9+
public IExecutable getCaller();
910

1011
public List<? extends ISmartStackTraceElement> getElements();
1112

12-
public Executable getEntrypoint(Set<EntrypointFilter> filters);
13+
public IExecutable getEntrypoint(Set<EntrypointFilter> filters);
1314

14-
public Executable getMain();
15+
public IExecutable getMain();
1516
}

ha-trace/src/main/java/com/g2forge/habitat/trace/SmartStackTraceElement.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22

33
import java.io.IOException;
44
import java.io.InputStream;
5-
import java.lang.reflect.Executable;
65
import java.lang.reflect.Field;
76
import java.lang.reflect.Method;
87
import java.util.Map;
98
import java.util.stream.Stream;
109

1110
import com.g2forge.alexandria.java.core.helpers.HCollector;
1211
import com.g2forge.alexandria.java.core.helpers.HStream;
12+
import com.g2forge.habitat.trace.executable.ExecutableExecutable;
13+
import com.g2forge.habitat.trace.executable.IExecutable;
14+
import com.g2forge.habitat.trace.executable.StaticInitializerExecutable;
1315

1416
import lombok.AccessLevel;
1517
import lombok.Builder;
@@ -32,7 +34,7 @@ public class SmartStackTraceElement implements ISmartStackTraceElement {
3234
private final Class<?> declaringClass = computeDeclaringClass();
3335

3436
@Getter(lazy = true)
35-
private final Executable executable = computeExecutable();
37+
private final IExecutable executable = computeExecutable();
3638

3739
@Getter(lazy = true)
3840
private final Field initialized = computeInitialized();
@@ -50,7 +52,7 @@ protected Class<?> computeDeclaringClass() {
5052
}
5153
}
5254

53-
protected Executable computeExecutable() {
55+
protected IExecutable computeExecutable() {
5456
// Use the line number to look up the method descriptor, that way we can handle overloaded methods correctly
5557
final Map<Integer, String> lineMap;
5658
try (final InputStream classStream = getClassLoader().getResourceAsStream(getClassName().replace('.', '/') + ".class")) {
@@ -61,16 +63,17 @@ protected Executable computeExecutable() {
6163
final String descriptor = lineMap.get(element.getLineNumber());
6264

6365
// Find the method with the correct name and descriptor
64-
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> (descriptor == null) || HTraceInternal.getDescriptor(c).equals(descriptor)));
66+
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return new ExecutableExecutable(HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> (descriptor == null) || HTraceInternal.getDescriptor(c).equals(descriptor))));
67+
else if ("<clinit>".equals(element.getMethodName())) return new StaticInitializerExecutable(getDeclaringClass());
6568
else {
6669
try {
67-
return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> (descriptor == null) || HTraceInternal.getDescriptor(m).equals(descriptor)));
70+
return new ExecutableExecutable(HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> (descriptor == null) || HTraceInternal.getDescriptor(m).equals(descriptor))));
6871
} catch (Throwable thowable) {
6972
throw new RuntimeException(String.format("Failed to find method %1$s.%2$s with descriptor %3$s, possible methods are: %4$s", getDeclaringClass().getName(), element.getMethodName(), descriptor, Stream.of(getDeclaringClass().getDeclaredMethods()).map(Method::getName).collect(HCollector.joining(", ", ", & "))), thowable);
7073
}
7174
}
7275
}
73-
76+
7477
protected Field computeInitialized() {
7578
if (!HTraceInternal.INITIALIZER.equals(element.getMethodName())) return null;
7679

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.g2forge.habitat.trace.executable;
2+
3+
import java.lang.reflect.Executable;
4+
5+
import lombok.Builder;
6+
import lombok.Data;
7+
import lombok.RequiredArgsConstructor;
8+
9+
@Data
10+
@Builder(toBuilder = true)
11+
@RequiredArgsConstructor
12+
public class ExecutableExecutable implements IExecutable {
13+
protected final Executable executable;
14+
15+
@Override
16+
public Class<?> getDeclaringClass() {
17+
return getExecutable().getDeclaringClass();
18+
}
19+
20+
@Override
21+
public int getModifiers() {
22+
return getExecutable().getModifiers();
23+
}
24+
25+
@Override
26+
public String getName() {
27+
return getExecutable().getName();
28+
}
29+
30+
@Override
31+
public boolean isSynthetic() {
32+
return getExecutable().isSynthetic();
33+
}
34+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.g2forge.habitat.trace.executable;
2+
3+
import java.lang.reflect.Executable;
4+
import java.lang.reflect.Member;
5+
6+
import com.g2forge.alexandria.java.adt.name.IStringNamed;
7+
8+
public interface IExecutable extends Member, IStringNamed {
9+
public Executable getExecutable();
10+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.g2forge.habitat.trace.executable;
2+
3+
import java.lang.reflect.Executable;
4+
import java.lang.reflect.Modifier;
5+
6+
import lombok.Builder;
7+
import lombok.Data;
8+
import lombok.RequiredArgsConstructor;
9+
10+
@Data
11+
@Builder(toBuilder = true)
12+
@RequiredArgsConstructor
13+
public class StaticInitializerExecutable implements IExecutable {
14+
public static final String NAME = "<clinit>";
15+
16+
protected final Class<?> declaringClass;
17+
18+
@Override
19+
public Executable getExecutable() {
20+
return null;
21+
}
22+
23+
@Override
24+
public int getModifiers() {
25+
return Modifier.STATIC;
26+
}
27+
28+
@Override
29+
public String getName() {
30+
return NAME;
31+
}
32+
33+
@Override
34+
public boolean isSynthetic() {
35+
return false;
36+
}
37+
}

ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.lang.reflect.Executable;
43
import java.lang.reflect.Field;
54
import java.util.List;
65

76
import org.junit.Test;
87

98
import com.g2forge.alexandria.java.core.helpers.HCollection;
109
import com.g2forge.alexandria.test.HAssert;
10+
import com.g2forge.habitat.trace.executable.IExecutable;
1111

1212
public class TestHTrace {
1313
public static class InitializerInline {
@@ -55,25 +55,41 @@ public InitializerMultiple(int x) {
5555
}
5656

5757
public static class Multiple {
58-
public Executable m(int[] i) {
58+
public IExecutable m(int[] i) {
5959
return HTrace.getCaller();
6060
}
6161

62-
public Executable m(Integer[] i) {
62+
public IExecutable m(Integer[] i) {
6363
return HTrace.getCaller();
6464
}
6565
}
6666

67+
protected static final IExecutable staticEntrypointExecutable;
68+
69+
protected static final Throwable staticEntrypointThrowable;
70+
71+
static {
72+
IExecutable _staticEntrypointExecutable = null;
73+
Throwable _staticEntrypointThrowable = null;
74+
try {
75+
_staticEntrypointExecutable = HTrace.getEntrypoint(EntrypointFilter.ALL);
76+
} catch (Throwable throwable) {
77+
_staticEntrypointThrowable = throwable;
78+
}
79+
staticEntrypointExecutable = _staticEntrypointExecutable;
80+
staticEntrypointThrowable = _staticEntrypointThrowable;
81+
}
82+
6783
@Test
6884
public void caller() {
69-
final Executable executable = HTrace.getCaller();
85+
final IExecutable executable = HTrace.getCaller();
7086
HAssert.assertEquals("caller", executable.getName());
7187
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
7288
}
7389

7490
@Test
7591
public void entrypoint() {
76-
final Executable executable = HTrace.getEntrypoint(EntrypointFilter.ALL);
92+
final IExecutable executable = HTrace.getEntrypoint(EntrypointFilter.ALL);
7793
HAssert.assertEquals("entrypoint", executable.getName());
7894
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
7995
}
@@ -117,19 +133,26 @@ public void mainThread() {
117133
// If the main thread is current, there's nothing more to test
118134
if (mainThread != currentThread) {
119135
// Main thread isn't current, so make sure we're starting form Thread.run
120-
final Executable entrypoint = new ThreadStackTraceAnalyzer(currentThread, 0).getEntrypoint(HCollection.emptySet());
136+
final IExecutable entrypoint = new ThreadStackTraceAnalyzer(currentThread, 0).getEntrypoint(HCollection.emptySet());
121137
HAssert.assertEquals(Thread.class, entrypoint.getDeclaringClass());
122138
HAssert.assertEquals("run", entrypoint.getName());
123139
}
124140
}
125141

126142
@Test
127143
public void object() {
128-
HAssert.assertEquals(Integer.class, new Multiple().m((Integer[]) null).getParameterTypes()[0].getComponentType());
144+
HAssert.assertEquals(Integer.class, new Multiple().m((Integer[]) null).getExecutable().getParameterTypes()[0].getComponentType());
129145
}
130146

131147
@Test
132148
public void primitive() {
133-
HAssert.assertEquals(Integer.TYPE, new Multiple().m((int[]) null).getParameterTypes()[0].getComponentType());
149+
HAssert.assertEquals(Integer.TYPE, new Multiple().m((int[]) null).getExecutable().getParameterTypes()[0].getComponentType());
150+
}
151+
152+
@Test
153+
public void staticEntrypoint() {
154+
if (staticEntrypointThrowable != null) throw new AssertionError(staticEntrypointThrowable);
155+
HAssert.assertNotNull(staticEntrypointExecutable);
156+
HAssert.assertEquals(getClass(), staticEntrypointExecutable.getDeclaringClass());
134157
}
135158
}

ha-trace/src/test/java/com/g2forge/habitat/trace/TestThreadStackTraceAnalyzer.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.lang.reflect.Executable;
4-
53
import org.junit.Test;
64

75
import com.g2forge.alexandria.test.HAssert;
6+
import com.g2forge.habitat.trace.executable.IExecutable;
87

98
public class TestThreadStackTraceAnalyzer {
109
@Test
1110
public void caller() {
12-
final Executable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getCaller();
11+
final IExecutable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getCaller();
1312
HAssert.assertEquals("caller", executable.getName());
1413
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
1514
}
1615

1716
@Test
1817
public void entrypoint() {
19-
final Executable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getEntrypoint(EntrypointFilter.ALL);
18+
final IExecutable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getEntrypoint(EntrypointFilter.ALL);
2019
HAssert.assertEquals("entrypoint", executable.getName());
2120
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
2221
}

0 commit comments

Comments
 (0)