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
Expand Up @@ -8,56 +8,30 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Data
@Builder(toBuilder = true)
@RequiredArgsConstructor
public class StackTraceAnalyzer implements IStackTraceAnalyzer {
protected final Object context;

protected final Throwable throwable;

public abstract class AStackTraceAnalyzer implements IStackTraceAnalyzer {
@Getter(lazy = true)
@EqualsAndHashCode.Exclude
private final List<? extends ISmartStackTraceElement> elements = computeElements();

public StackTraceAnalyzer() {
this(new Throwable());
}

public StackTraceAnalyzer(Object context) {
this(context, new Throwable());
}

public StackTraceAnalyzer(Throwable throwable) {
this(throwable, throwable);
}

protected List<? extends ISmartStackTraceElement> computeElements() {
final ClassLoader classLoader = getContextClassloader();
return Stream.of(getThrowable().getStackTrace()).map(e -> new SmartStackTraceElement(e, classLoader)).collect(Collectors.toList());
return Stream.of(getStackTrace()).map(e -> new SmartStackTraceElement(e, classLoader)).collect(Collectors.toList());
}

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

protected ClassLoader getContextClassloader() {
final ClassLoader retVal = getContext().getClass().getClassLoader();
if (retVal == null) return Thread.currentThread().getContextClassLoader();
return retVal;
return getExecutable(0, getInvisibles());
}

protected abstract ClassLoader getContextClassloader();

@Override
public Executable getEntrypoint(Set<EntrypointFilter> filters) {
final List<? extends ISmartStackTraceElement> elements = this.getElements();
final List<? extends ISmartStackTraceElement> limited = new ArrayList<>(elements.subList(2, elements.size()));
final List<? extends ISmartStackTraceElement> limited = new ArrayList<>(elements.subList(getInvisibles(), elements.size()));
Collections.reverse(limited);
final Set<String> prefixes = filters.stream().flatMap(filter -> Stream.of(filter.getPrefixes())).collect(Collectors.toSet());
final ISmartStackTraceElement element = limited.stream().filter(e -> {
Expand All @@ -68,7 +42,7 @@ public Executable getEntrypoint(Set<EntrypointFilter> filters) {
}

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

final int actual, pretendDepth = stackTrace.length - invisible;
if (offset >= 0) {
Expand All @@ -82,8 +56,12 @@ protected Executable getExecutable(int offset, int invisible) {
return new SmartStackTraceElement(stackTrace[actual], getContextClassloader()).getExecutable();
}

protected abstract int getInvisibles();

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

protected abstract StackTraceElement[] getStackTrace();
}
27 changes: 23 additions & 4 deletions ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

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

Expand All @@ -10,12 +11,26 @@
@UtilityClass
@Helpers
public class HTrace {
protected static ThrowableStackTraceAnalyzer createSTA() {
return new ThrowableStackTraceAnalyzer(new Throwable(), 2);
}

protected static Thread[] enumerateThreads() {
Thread[] threads = new Thread[(int) (Thread.activeCount() * 1.5)];
while (true) {
final int count = Thread.enumerate(threads);
if (count <= threads.length) break;
threads = new Thread[(int) (count * 1.5)];
}
return threads;
}

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

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

/**
Expand All @@ -26,10 +41,14 @@ public static Executable getEntrypoint(Set<EntrypointFilter> filters) {
* @return
*/
public static Executable getExecutable(int offset) {
return new StackTraceAnalyzer().getExecutable(offset, 2);
return createSTA().getExecutable(offset, 2);
}

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

public static Thread getMainThread() {
return Stream.of(enumerateThreads()).filter(t -> t.getId() == 1).findAny().get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
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 lombok.AccessLevel;
Expand Down Expand Up @@ -59,8 +61,14 @@ 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 -> HTraceInternal.getDescriptor(c).equals(descriptor)));
else return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredMethods()).filter(m -> element.getMethodName().equals(m.getName())).filter(m -> HTraceInternal.getDescriptor(m).equals(descriptor)));
if (HTraceInternal.INITIALIZER.equals(element.getMethodName())) return HStream.findOne(Stream.of(getDeclaringClass().getDeclaredConstructors()).filter(c -> (descriptor == null) || HTraceInternal.getDescriptor(c).equals(descriptor)));
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)));
} 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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.g2forge.habitat.trace;

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

@Data
@Builder(toBuilder = true)
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class ThreadStackTraceAnalyzer extends AStackTraceAnalyzer {
protected final ClassLoader contextClassloader;

protected final StackTraceElement[] stackTrace;

protected final int invisibles;

public ThreadStackTraceAnalyzer(Thread thread, int invisibles) {
this.contextClassloader = thread.getContextClassLoader();
this.stackTrace = thread.getStackTrace();
this.invisibles = invisibles;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.g2forge.habitat.trace;

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

@Data
@Builder(toBuilder = true)
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class ThrowableStackTraceAnalyzer extends AStackTraceAnalyzer {
protected final Object context;

protected final Throwable throwable;

protected final int invisibles;

public ThrowableStackTraceAnalyzer() {
this(new Throwable(), 1);
}

public ThrowableStackTraceAnalyzer(Object context) {
this(context, new Throwable(), 1);
}

public ThrowableStackTraceAnalyzer(Throwable throwable, int invisibles) {
this(throwable, throwable, invisibles);
}

@Override
protected ClassLoader getContextClassloader() {
final ClassLoader retVal = getContext().getClass().getClassLoader();
if (retVal == null) return Thread.currentThread().getContextClassLoader();
return retVal;
}

@Override
protected StackTraceElement[] getStackTrace() {
return getThrowable().getStackTrace();
}
}
29 changes: 22 additions & 7 deletions ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

import org.junit.Test;

import com.g2forge.alexandria.java.core.helpers.HCollection;
import com.g2forge.alexandria.test.HAssert;

public class TestHTrace {
public static class InitializerInline {
protected final int a = 0;
protected final IStackTraceAnalyzer field = new StackTraceAnalyzer(this);
protected final IStackTraceAnalyzer field = new ThrowableStackTraceAnalyzer(this);
protected final int b = 0;
}

Expand All @@ -22,7 +23,7 @@ public static class InitializerMethod {

public InitializerMethod() {
a = 0;
field = new StackTraceAnalyzer(this);
field = new ThrowableStackTraceAnalyzer(this);
b = 0;
}
}
Expand All @@ -31,7 +32,7 @@ public static class InitializerMultiline {
protected final int a = 0;
// @formatter:off
protected final IStackTraceAnalyzer field =
new StackTraceAnalyzer(
new ThrowableStackTraceAnalyzer(
this);
// @formatter:on
protected final int b;
Expand All @@ -45,11 +46,11 @@ public static class InitializerMultiple {
protected final IStackTraceAnalyzer field;

public InitializerMultiple(boolean b) {
field = new StackTraceAnalyzer(this);
field = new ThrowableStackTraceAnalyzer(this);
}

public InitializerMultiple(int x) {
field = new StackTraceAnalyzer(this);
field = new ThrowableStackTraceAnalyzer(this);
}
}

Expand All @@ -72,8 +73,9 @@ public void caller() {

@Test
public void entrypoint() {
final Executable entrypoint = HTrace.getEntrypoint(EntrypointFilter.ALL);
HAssert.assertEquals(getClass(), entrypoint.getDeclaringClass());
final Executable executable = HTrace.getEntrypoint(EntrypointFilter.ALL);
HAssert.assertEquals("entrypoint", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}

@Test
Expand Down Expand Up @@ -108,6 +110,19 @@ public void initializerMultiple() {
HAssert.assertEquals("field", field.getName());
}

@Test
public void mainThread() {
final Thread mainThread = HTrace.getMainThread();
final Thread currentThread = Thread.currentThread();
// 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());
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());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;

import org.junit.Test;

import com.g2forge.alexandria.test.HAssert;

public class TestThreadStackTraceAnalyzer {
@Test
public void caller() {
final Executable 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);
HAssert.assertEquals("entrypoint", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.g2forge.habitat.trace;

import java.lang.reflect.Executable;

import org.junit.Test;

import com.g2forge.alexandria.test.HAssert;

public class TestThrowableStackTraceAnalyzer {
@Test
public void caller() {
final Executable executable = new ThrowableStackTraceAnalyzer().getCaller();
HAssert.assertEquals("caller", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}

@Test
public void entrypoint() {
final Executable executable = new ThrowableStackTraceAnalyzer().getEntrypoint(EntrypointFilter.ALL);
HAssert.assertEquals("entrypoint", executable.getName());
HAssert.assertEquals(getClass(), executable.getDeclaringClass());
}
}