Skip to content

Commit 3ff675e

Browse files
authored
Merge pull request #50 from gdgib/G2-1755-Entrypoint
G2-1755 Get entrypoint executable & support for unit testing
2 parents 4257c47 + eca5cbc commit 3ff675e

File tree

7 files changed

+222
-130
lines changed

7 files changed

+222
-130
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.g2forge.habitat.trace;
2+
3+
import java.util.EnumSet;
4+
import java.util.Set;
5+
import java.util.stream.Collectors;
6+
7+
import lombok.Getter;
8+
import lombok.RequiredArgsConstructor;
9+
10+
@Getter
11+
@RequiredArgsConstructor
12+
public enum EntrypointFilter {
13+
JAVA(new String[] { "java.", "javax.", "sun.", "sunw." }, false),
14+
IBM(new String[] { "com.ibm." }, false),
15+
SUN(new String[] { "com.sun." }, false),
16+
JROCKIT(new String[] { "jrockit." }, false),
17+
OMG(new String[] { "org.omg." }, false),
18+
ECLIPSE(new String[] { "org.eclipse.jdt.launching.internal." }, false),
19+
JDK(new String[] { "jdk." }, false),
20+
JUNIT(new String[] { "org.junit." }, true),
21+
ECLIPSE_JUNIT(new String[] { "org.eclipse.jdt.internal.junit.", "org.eclipse.jdt.internal.junit4." }, true),
22+
SUREFIRE_JUNIT(new String[] { "org.apache.maven.surefire." }, true);
23+
24+
public static final Set<EntrypointFilter> ALL = EnumSet.allOf(EntrypointFilter.class);
25+
26+
public static final Set<EntrypointFilter> NONTEST = EnumSet.allOf(EntrypointFilter.class).stream().filter(ef -> !ef.isTesting()).collect(Collectors.toSet());
27+
28+
protected final String[] prefixes;
29+
30+
protected final boolean testing;
31+
}
Lines changed: 8 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
11
package com.g2forge.habitat.trace;
22

3-
import java.io.IOException;
4-
import java.io.InputStream;
5-
import java.lang.reflect.Constructor;
63
import java.lang.reflect.Executable;
7-
import java.lang.reflect.Method;
8-
import java.util.HashMap;
9-
import java.util.Map;
10-
11-
import org.objectweb.asm.ClassReader;
12-
import org.objectweb.asm.ClassVisitor;
13-
import org.objectweb.asm.Label;
14-
import org.objectweb.asm.MethodVisitor;
15-
import org.objectweb.asm.Opcodes;
4+
import java.util.Set;
165

176
import com.g2forge.alexandria.java.core.marker.Helpers;
187

@@ -21,14 +10,12 @@
2110
@UtilityClass
2211
@Helpers
2312
public class HTrace {
24-
public static final String INITIALIZER = "<init>";
25-
26-
public static Executable getMain() {
27-
return getExecutable(-1, 2);
13+
public static Executable getCaller() {
14+
return new StackTraceAnalyzer().getCaller();
2815
}
2916

30-
public static Executable getCaller() {
31-
return getExecutable(0, 2);
17+
public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
18+
return new StackTraceAnalyzer().getEntrypoint(filters);
3219
}
3320

3421
/**
@@ -39,113 +26,10 @@ public static Executable getCaller() {
3926
* @return
4027
*/
4128
public static Executable getExecutable(int offset) {
42-
return getExecutable(offset, 2);
43-
}
44-
45-
protected static Executable getExecutable(int offset, int invisible) {
46-
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
47-
48-
final int actual, pretendDepth = stackTrace.length - invisible;
49-
if (offset >= 0) {
50-
if (offset >= pretendDepth) throw new IllegalArgumentException();
51-
actual = offset + invisible;
52-
} else {
53-
if (offset < -pretendDepth) throw new IllegalArgumentException();
54-
actual = stackTrace.length + offset;
55-
}
56-
57-
return new SmartStackTraceElement(stackTrace[actual]).getExecutable();
58-
}
59-
60-
protected static String getDescriptor(Method method) {
61-
return getDescriptor(method.getParameterTypes(), method.getReturnType());
62-
}
63-
64-
protected static String getDescriptor(final Class<?>[] parameterTypes, final Class<?> returnType) {
65-
final StringBuilder retVal = new StringBuilder();
66-
retVal.append('(');
67-
for (int i = 0; i < parameterTypes.length; i++) {
68-
retVal.append(getName(parameterTypes[i]));
69-
}
70-
retVal.append(')');
71-
retVal.append(getName(returnType));
72-
return retVal.toString();
29+
return new StackTraceAnalyzer().getExecutable(offset, 2);
7330
}
7431

75-
protected static String getDescriptor(Constructor<?> constructor) {
76-
return getDescriptor(constructor.getParameterTypes(), Void.TYPE);
77-
}
78-
79-
protected static String getName(Class<?> type) {
80-
if (Void.TYPE.equals(type)) return "V";
81-
if (type.isPrimitive()) {
82-
if (Integer.TYPE.equals(type)) return "I";
83-
if (Long.TYPE.equals(type)) return "J";
84-
if (Boolean.TYPE.equals(type)) return "Z";
85-
if (Byte.TYPE.equals(type)) return "B";
86-
if (Short.TYPE.equals(type)) return "S";
87-
if (Character.TYPE.equals(type)) return "C";
88-
if (Double.TYPE.equals(type)) return "D";
89-
if (Float.TYPE.equals(type)) return "F";
90-
throw new IllegalArgumentException();
91-
}
92-
if (type.isArray()) return "[" + getName(type.getComponentType());
93-
return "L" + type.getName().replace('.', '/') + ";";
94-
}
95-
96-
/**
97-
* Get a map from source line numbers to method descriptors for all methods with the specified name. In short this lets one retrieve the descriptor for a
98-
* method and line number, which in turn can be used to get reflective access to that method.
99-
*
100-
* @param classData An input stream for the class file.
101-
* @param methodName The name of the methods to map.
102-
* @return A map from line numbers to the method descriptor.
103-
* @throws IOException There was a problem reading the class file.
104-
*/
105-
protected static Map<Integer, String> getLine2MethodMap(InputStream classData, String methodName) throws IOException {
106-
final Map<Integer, String> retVal = new HashMap<>();
107-
final ClassReader reader = new ClassReader(classData);
108-
reader.accept(new ClassVisitor(Opcodes.ASM7) {
109-
@Override
110-
public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
111-
if ((methodName != null) && !methodName.equals(name)) return null;
112-
return new MethodVisitor(Opcodes.ASM7) {
113-
@Override
114-
public void visitLineNumber(int line, Label start) {
115-
retVal.put(line, descriptor);
116-
}
117-
};
118-
}
119-
}, 0);
120-
return retVal;
121-
}
122-
123-
protected static Map<Integer, String> getLine2FieldMap(InputStream classData, String className) throws IOException {
124-
final Map<Integer, String> retVal = new HashMap<>();
125-
final ClassReader reader = new ClassReader(classData);
126-
reader.accept(new ClassVisitor(Opcodes.ASM7) {
127-
@Override
128-
public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
129-
if (!INITIALIZER.equals(name)) return null;
130-
return new MethodVisitor(Opcodes.ASM7) {
131-
protected int line = -1;
132-
133-
@Override
134-
public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) {
135-
if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
136-
if (className.equals(owner)) {
137-
retVal.put(line, name);
138-
}
139-
}
140-
}
141-
142-
@Override
143-
public void visitLineNumber(int line, Label start) {
144-
this.line = line;
145-
}
146-
};
147-
}
148-
}, 0);
149-
return retVal;
32+
public static Executable getMain() {
33+
return new StackTraceAnalyzer().getMain();
15034
}
15135
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.g2forge.habitat.trace;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.lang.reflect.Constructor;
6+
import java.lang.reflect.Method;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
10+
import org.objectweb.asm.ClassReader;
11+
import org.objectweb.asm.ClassVisitor;
12+
import org.objectweb.asm.Label;
13+
import org.objectweb.asm.MethodVisitor;
14+
import org.objectweb.asm.Opcodes;
15+
16+
import com.g2forge.alexandria.java.core.marker.Helpers;
17+
18+
import lombok.experimental.UtilityClass;
19+
20+
@UtilityClass
21+
@Helpers
22+
public class HTraceInternal {
23+
protected static final String INITIALIZER = "<init>";
24+
25+
protected static String getDescriptor(final Class<?>[] parameterTypes, final Class<?> returnType) {
26+
final StringBuilder retVal = new StringBuilder();
27+
retVal.append('(');
28+
for (int i = 0; i < parameterTypes.length; i++) {
29+
retVal.append(getName(parameterTypes[i]));
30+
}
31+
retVal.append(')');
32+
retVal.append(getName(returnType));
33+
return retVal.toString();
34+
}
35+
36+
protected static String getDescriptor(Constructor<?> constructor) {
37+
return getDescriptor(constructor.getParameterTypes(), Void.TYPE);
38+
}
39+
40+
protected static String getDescriptor(Method method) {
41+
return getDescriptor(method.getParameterTypes(), method.getReturnType());
42+
}
43+
44+
protected static Map<Integer, String> getLine2FieldMap(InputStream classData, String className) throws IOException {
45+
final Map<Integer, String> retVal = new HashMap<>();
46+
final ClassReader reader = new ClassReader(classData);
47+
reader.accept(new ClassVisitor(Opcodes.ASM7) {
48+
@Override
49+
public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
50+
if (!INITIALIZER.equals(name)) return null;
51+
return new MethodVisitor(Opcodes.ASM7) {
52+
protected int line = -1;
53+
54+
@Override
55+
public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) {
56+
if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) {
57+
if (className.equals(owner)) {
58+
retVal.put(line, name);
59+
}
60+
}
61+
}
62+
63+
@Override
64+
public void visitLineNumber(int line, Label start) {
65+
this.line = line;
66+
}
67+
};
68+
}
69+
}, 0);
70+
return retVal;
71+
}
72+
73+
/**
74+
* Get a map from source line numbers to method descriptors for all methods with the specified name. In short this lets one retrieve the descriptor for a
75+
* method and line number, which in turn can be used to get reflective access to that method.
76+
*
77+
* @param classData An input stream for the class file.
78+
* @param methodName The name of the methods to map.
79+
* @return A map from line numbers to the method descriptor.
80+
* @throws IOException There was a problem reading the class file.
81+
*/
82+
protected static Map<Integer, String> getLine2MethodMap(InputStream classData, String methodName) throws IOException {
83+
final Map<Integer, String> retVal = new HashMap<>();
84+
final ClassReader reader = new ClassReader(classData);
85+
reader.accept(new ClassVisitor(Opcodes.ASM7) {
86+
@Override
87+
public MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
88+
if ((methodName != null) && !methodName.equals(name)) return null;
89+
return new MethodVisitor(Opcodes.ASM7) {
90+
@Override
91+
public void visitLineNumber(int line, Label start) {
92+
retVal.put(line, descriptor);
93+
}
94+
};
95+
}
96+
}, 0);
97+
return retVal;
98+
}
99+
100+
protected static String getName(Class<?> type) {
101+
if (Void.TYPE.equals(type)) return "V";
102+
if (type.isPrimitive()) {
103+
if (Integer.TYPE.equals(type)) return "I";
104+
if (Long.TYPE.equals(type)) return "J";
105+
if (Boolean.TYPE.equals(type)) return "Z";
106+
if (Byte.TYPE.equals(type)) return "B";
107+
if (Short.TYPE.equals(type)) return "S";
108+
if (Character.TYPE.equals(type)) return "C";
109+
if (Double.TYPE.equals(type)) return "D";
110+
if (Float.TYPE.equals(type)) return "F";
111+
throw new IllegalArgumentException();
112+
}
113+
if (type.isArray()) return "[" + getName(type.getComponentType());
114+
return "L" + type.getName().replace('.', '/') + ";";
115+
}
116+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
package com.g2forge.habitat.trace;
22

3+
import java.lang.reflect.Executable;
34
import java.util.List;
5+
import java.util.Set;
46

57
public interface IStackTraceAnalyzer {
8+
public Executable getCaller();
9+
610
public List<? extends ISmartStackTraceElement> getElements();
11+
12+
public Executable getEntrypoint(Set<EntrypointFilter> filters);
13+
14+
public Executable getMain();
715
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,24 @@ protected Executable computeExecutable() {
5252
// Use the line number to look up the method descriptor, that way we can handle overloaded methods correctly
5353
final Map<Integer, String> lineMap;
5454
try (final InputStream classStream = getClassLoader().getResourceAsStream(getClassName().replace('.', '/') + ".class")) {
55-
lineMap = HTrace.getLine2MethodMap(classStream, element.getMethodName());
55+
lineMap = HTraceInternal.getLine2MethodMap(classStream, element.getMethodName());
5656
} catch (IOException e) {
5757
throw new IllegalArgumentException(e);
5858
}
5959
final String descriptor = lineMap.get(element.getLineNumber());
6060

6161
// Find the method with the correct name and descriptor
62-
if (HTrace.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> HTrace.getDescriptor(c).equals(descriptor)));
63-
else return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> HTrace.getDescriptor(m).equals(descriptor)));
62+
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> HTraceInternal.getDescriptor(c).equals(descriptor)));
63+
else return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> HTraceInternal.getDescriptor(m).equals(descriptor)));
6464
}
6565

6666
protected Field computeInitialized() {
67-
if (!HTrace.INITIALIZER.equals(element.getMethodName())) return null;
67+
if (!HTraceInternal.INITIALIZER.equals(element.getMethodName())) return null;
6868

6969
final Map<Integer, String> lineMap;
7070
final String className = getClassName().replace('.', '/');
7171
try (final InputStream classStream = getClassLoader().getResourceAsStream(className + ".class")) {
72-
lineMap = HTrace.getLine2FieldMap(classStream, className);
72+
lineMap = HTraceInternal.getLine2FieldMap(classStream, className);
7373
} catch (IOException e) {
7474
throw new IllegalArgumentException(e);
7575
}

0 commit comments

Comments
 (0)