diff --git a/ha-trace/src/main/java/com/g2forge/habitat/trace/StackTraceAnalyzer.java b/ha-trace/src/main/java/com/g2forge/habitat/trace/AStackTraceAnalyzer.java similarity index 64% rename from ha-trace/src/main/java/com/g2forge/habitat/trace/StackTraceAnalyzer.java rename to ha-trace/src/main/java/com/g2forge/habitat/trace/AStackTraceAnalyzer.java index 18c5a65..1502b03 100644 --- a/ha-trace/src/main/java/com/g2forge/habitat/trace/StackTraceAnalyzer.java +++ b/ha-trace/src/main/java/com/g2forge/habitat/trace/AStackTraceAnalyzer.java @@ -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 elements = computeElements(); - public StackTraceAnalyzer() { - this(new Throwable()); - } - - public StackTraceAnalyzer(Object context) { - this(context, new Throwable()); - } - - public StackTraceAnalyzer(Throwable throwable) { - this(throwable, throwable); - } - protected List 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 filters) { final List elements = this.getElements(); - final List limited = new ArrayList<>(elements.subList(2, elements.size())); + final List limited = new ArrayList<>(elements.subList(getInvisibles(), elements.size())); Collections.reverse(limited); final Set prefixes = filters.stream().flatMap(filter -> Stream.of(filter.getPrefixes())).collect(Collectors.toSet()); final ISmartStackTraceElement element = limited.stream().filter(e -> { @@ -68,7 +42,7 @@ public Executable getEntrypoint(Set 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) { @@ -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(); } diff --git a/ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java b/ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java index 53a5c6d..1f9d387 100644 --- a/ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java +++ b/ha-trace/src/main/java/com/g2forge/habitat/trace/HTrace.java @@ -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; @@ -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 filters) { - return new StackTraceAnalyzer().getEntrypoint(filters); + return createSTA().getEntrypoint(filters); } /** @@ -26,10 +41,14 @@ public static Executable getEntrypoint(Set 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(); } } diff --git a/ha-trace/src/main/java/com/g2forge/habitat/trace/SmartStackTraceElement.java b/ha-trace/src/main/java/com/g2forge/habitat/trace/SmartStackTraceElement.java index 02b1e60..97c2361 100644 --- a/ha-trace/src/main/java/com/g2forge/habitat/trace/SmartStackTraceElement.java +++ b/ha-trace/src/main/java/com/g2forge/habitat/trace/SmartStackTraceElement.java @@ -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; @@ -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() { diff --git a/ha-trace/src/main/java/com/g2forge/habitat/trace/ThreadStackTraceAnalyzer.java b/ha-trace/src/main/java/com/g2forge/habitat/trace/ThreadStackTraceAnalyzer.java new file mode 100644 index 0000000..876d9e7 --- /dev/null +++ b/ha-trace/src/main/java/com/g2forge/habitat/trace/ThreadStackTraceAnalyzer.java @@ -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; + } +} diff --git a/ha-trace/src/main/java/com/g2forge/habitat/trace/ThrowableStackTraceAnalyzer.java b/ha-trace/src/main/java/com/g2forge/habitat/trace/ThrowableStackTraceAnalyzer.java new file mode 100644 index 0000000..b984553 --- /dev/null +++ b/ha-trace/src/main/java/com/g2forge/habitat/trace/ThrowableStackTraceAnalyzer.java @@ -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(); + } +} diff --git a/ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java b/ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java index 5fdf073..70c5dc9 100644 --- a/ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java +++ b/ha-trace/src/test/java/com/g2forge/habitat/trace/TestHTrace.java @@ -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; } @@ -22,7 +23,7 @@ public static class InitializerMethod { public InitializerMethod() { a = 0; - field = new StackTraceAnalyzer(this); + field = new ThrowableStackTraceAnalyzer(this); b = 0; } } @@ -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; @@ -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); } } @@ -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 @@ -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()); diff --git a/ha-trace/src/test/java/com/g2forge/habitat/trace/TestThreadStackTraceAnalyzer.java b/ha-trace/src/test/java/com/g2forge/habitat/trace/TestThreadStackTraceAnalyzer.java new file mode 100644 index 0000000..ac8c667 --- /dev/null +++ b/ha-trace/src/test/java/com/g2forge/habitat/trace/TestThreadStackTraceAnalyzer.java @@ -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()); + } +} diff --git a/ha-trace/src/test/java/com/g2forge/habitat/trace/TestThrowableStackTraceAnalyzer.java b/ha-trace/src/test/java/com/g2forge/habitat/trace/TestThrowableStackTraceAnalyzer.java new file mode 100644 index 0000000..eddedb0 --- /dev/null +++ b/ha-trace/src/test/java/com/g2forge/habitat/trace/TestThrowableStackTraceAnalyzer.java @@ -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()); + } +}