Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.g2forge.habitat.trace.executable.IExecutable;

import lombok.EqualsAndHashCode;
import lombok.Getter;

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

@Override
public Executable getCaller() {
public IExecutable getCaller() {
return getExecutable(0, getInvisibles());
}

protected abstract ClassLoader getContextClassloader();

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

protected Executable getExecutable(int offset, int invisible) {
protected IExecutable getExecutable(int offset, int invisible) {
final StackTraceElement[] stackTrace = getStackTrace();

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

@Override
public Executable getMain() {
public IExecutable getMain() {
return getExecutable(-1, getInvisibles());
}

Expand Down
10 changes: 5 additions & 5 deletions ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;
import java.util.Set;
import java.util.stream.Stream;

import com.g2forge.alexandria.java.core.marker.Helpers;
import com.g2forge.habitat.trace.executable.IExecutable;

import lombok.experimental.UtilityClass;

Expand All @@ -25,11 +25,11 @@ protected static Thread[] enumerateThreads() {
return threads;
}

public static Executable getCaller() {
public static IExecutable getCaller() {
return createSTA().getCaller();
}

public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
public static IExecutable getEntrypoint(Set<EntrypointFilter> filters) {
return createSTA().getEntrypoint(filters);
}

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

public static Executable getMain() {
public static IExecutable getMain() {
return createSTA().getMain();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;
import java.lang.reflect.Field;

import com.g2forge.habitat.trace.executable.IExecutable;

public interface ISmartStackTraceElement extends IStackTraceElement {
public Class<?> getDeclaringClass();

public Executable getExecutable();
public IExecutable getExecutable();

public Field getInitialized();
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;
import java.util.List;
import java.util.Set;

import com.g2forge.habitat.trace.executable.IExecutable;

public interface IStackTraceAnalyzer {
public Executable getCaller();
public IExecutable getCaller();

public List<? extends ISmartStackTraceElement> getElements();

public Executable getEntrypoint(Set<EntrypointFilter> filters);
public IExecutable getEntrypoint(Set<EntrypointFilter> filters);

public Executable getMain();
public IExecutable getMain();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.stream.Stream;

import com.g2forge.alexandria.java.core.helpers.HCollector;
import com.g2forge.alexandria.java.core.helpers.HStream;
import com.g2forge.habitat.trace.executable.ExecutableExecutable;
import com.g2forge.habitat.trace.executable.IExecutable;
import com.g2forge.habitat.trace.executable.StaticInitializerExecutable;

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

@Getter(lazy = true)
private final Executable executable = computeExecutable();
private final IExecutable executable = computeExecutable();

@Getter(lazy = true)
private final Field initialized = computeInitialized();
Expand All @@ -50,7 +52,7 @@ protected Class<?> computeDeclaringClass() {
}
}

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

// Find the method with the correct name and descriptor
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> (descriptor == null) || HTraceInternal.getDescriptor(c).equals(descriptor)));
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return new ExecutableExecutable(HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> (descriptor == null) || HTraceInternal.getDescriptor(c).equals(descriptor))));
else if ("<clinit>".equals(element.getMethodName())) return new StaticInitializerExecutable(getDeclaringClass());
else {
try {
return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> (descriptor == null) || HTraceInternal.getDescriptor(m).equals(descriptor)));
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))));
} catch (Throwable thowable) {
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);
}
}
}

protected Field computeInitialized() {
if (!HTraceInternal.INITIALIZER.equals(element.getMethodName())) return null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.g2forge.habitat.trace.executable;

import java.lang.reflect.Executable;

import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@Builder(toBuilder = true)
@RequiredArgsConstructor
public class ExecutableExecutable implements IExecutable {
protected final Executable executable;

@Override
public Class<?> getDeclaringClass() {
return getExecutable().getDeclaringClass();
}

@Override
public int getModifiers() {
return getExecutable().getModifiers();
}

@Override
public String getName() {
return getExecutable().getName();
}

@Override
public boolean isSynthetic() {
return getExecutable().isSynthetic();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.g2forge.habitat.trace.executable;

import java.lang.reflect.Executable;
import java.lang.reflect.Member;

import com.g2forge.alexandria.java.adt.name.IStringNamed;

public interface IExecutable extends Member, IStringNamed {
public Executable getExecutable();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.g2forge.habitat.trace.executable;

import java.lang.reflect.Executable;
import java.lang.reflect.Modifier;

import lombok.Builder;
import lombok.Data;
import lombok.RequiredArgsConstructor;

@Data
@Builder(toBuilder = true)
@RequiredArgsConstructor
public class StaticInitializerExecutable implements IExecutable {
public static final String NAME = "<clinit>";

protected final Class<?> declaringClass;

@Override
public Executable getExecutable() {
return null;
}

@Override
public int getModifiers() {
return Modifier.STATIC;
}

@Override
public String getName() {
return NAME;
}

@Override
public boolean isSynthetic() {
return false;
}
}
39 changes: 31 additions & 8 deletions ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.util.List;

import org.junit.Test;

import com.g2forge.alexandria.java.core.helpers.HCollection;
import com.g2forge.alexandria.test.HAssert;
import com.g2forge.habitat.trace.executable.IExecutable;

public class TestHTrace {
public static class InitializerInline {
Expand Down Expand Up @@ -55,25 +55,41 @@ public InitializerMultiple(int x) {
}

public static class Multiple {
public Executable m(int[] i) {
public IExecutable m(int[] i) {
return HTrace.getCaller();
}

public Executable m(Integer[] i) {
public IExecutable m(Integer[] i) {
return HTrace.getCaller();
}
}

protected static final IExecutable staticEntrypointExecutable;

protected static final Throwable staticEntrypointThrowable;

static {
IExecutable _staticEntrypointExecutable = null;
Throwable _staticEntrypointThrowable = null;
try {
_staticEntrypointExecutable = HTrace.getEntrypoint(EntrypointFilter.ALL);
} catch (Throwable throwable) {
_staticEntrypointThrowable = throwable;
}
staticEntrypointExecutable = _staticEntrypointExecutable;
staticEntrypointThrowable = _staticEntrypointThrowable;
}

@Test
public void caller() {
final Executable executable = HTrace.getCaller();
final IExecutable executable = HTrace.getCaller();
HAssert.assertEquals("caller", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}

@Test
public void entrypoint() {
final Executable executable = HTrace.getEntrypoint(EntrypointFilter.ALL);
final IExecutable executable = HTrace.getEntrypoint(EntrypointFilter.ALL);
HAssert.assertEquals("entrypoint", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}
Expand Down Expand Up @@ -117,19 +133,26 @@ public void mainThread() {
// If the main thread is current, there's nothing more to test
if (mainThread != currentThread) {
// Main thread isn't current, so make sure we're starting form Thread.run
final Executable entrypoint = new ThreadStackTraceAnalyzer(currentThread, 0).getEntrypoint(HCollection.emptySet());
final IExecutable entrypoint = new ThreadStackTraceAnalyzer(currentThread, 0).getEntrypoint(HCollection.emptySet());
HAssert.assertEquals(Thread.class, entrypoint.getDeclaringClass());
HAssert.assertEquals("run", entrypoint.getName());
}
}

@Test
public void object() {
HAssert.assertEquals(Integer.class, new Multiple().m((Integer[]) null).getParameterTypes()[0].getComponentType());
HAssert.assertEquals(Integer.class, new Multiple().m((Integer[]) null).getExecutable().getParameterTypes()[0].getComponentType());
}

@Test
public void primitive() {
HAssert.assertEquals(Integer.TYPE, new Multiple().m((int[]) null).getParameterTypes()[0].getComponentType());
HAssert.assertEquals(Integer.TYPE, new Multiple().m((int[]) null).getExecutable().getParameterTypes()[0].getComponentType());
}

@Test
public void staticEntrypoint() {
if (staticEntrypointThrowable != null) throw new AssertionError(staticEntrypointThrowable);
HAssert.assertNotNull(staticEntrypointExecutable);
HAssert.assertEquals(getClass(), staticEntrypointExecutable.getDeclaringClass());
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;

import org.junit.Test;

import com.g2forge.alexandria.test.HAssert;
import com.g2forge.habitat.trace.executable.IExecutable;

public class TestThreadStackTraceAnalyzer {
@Test
public void caller() {
final Executable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getCaller();
final IExecutable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getCaller();
HAssert.assertEquals("caller", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}

@Test
public void entrypoint() {
final Executable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getEntrypoint(EntrypointFilter.ALL);
final IExecutable executable = new ThreadStackTraceAnalyzer(Thread.currentThread(), 2).getEntrypoint(EntrypointFilter.ALL);
HAssert.assertEquals("entrypoint", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}
Expand Down
Loading