com.atlassian.bamboo.specs.maven.sandbox.StackTraceUtils Maven / Gradle / Ivy
package com.atlassian.bamboo.specs.maven.sandbox;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.URISyntaxException;
import java.security.CodeSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
final class StackTraceUtils {
private static final Map CODESOURCES = new HashMap<>();
private StackTraceUtils() {
}
/**
* Returns the caller stack to a point of last call of a method on the supplied class.
* For example if A calls B that calls B that calls C, if we call the method with B, we will get A in return.
* If we call it with C, we will get A and B in return.
*/
@Nullable
static StackTraceElement[] getCallerStack(final Class>... beforeClasses) {
return getCallerStack(0, beforeClasses);
}
/**
* Returns the caller stack to a point of last call of a method on the supplied class.
* For example if A calls B that calls B that calls C, if we call the method with B, we will get A in return.
* If we call it with C, we will get A and B in return.
* @param skipTopN number of top stack element to ignore. Useful when scanning for uses of Thread, but not necessarily Thread.run() that started the current thread.
* @param beforeClasses classes calls to which look for
*/
@Nullable
static StackTraceElement[] getCallerStack(int skipTopN, final Class>... beforeClasses) {
final Set classNames = Arrays.stream(beforeClasses).map(Class::getName).collect(Collectors.toSet());
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
int classFound = -1;
for (int i = 0; i < stackTrace.length; ++i) {
if (classNames.contains(stackTrace[i].getClassName())) {
classFound = i;
} else if (classFound >= skipTopN){
break;
}
}
return classFound >= skipTopN ? Arrays.copyOfRange(stackTrace, classFound + 1, stackTrace.length - 1) : null;
}
/**
* Returns the locations from which the classes of the supplied stack traces were loaded, assuming that they were loaded
* using the supplied class loader.
*/
@NotNull
static Set getClassLocationsOnStack(final ClassLoader classLoader, final StackTraceElement[] callerStackTrace) {
final Set jars = new HashSet<>();
for (final StackTraceElement stackTraceElement : callerStackTrace) {
jars.add(classNameToJar(classLoader, stackTraceElement.getClassName()));
}
return jars;
}
private static File classNameToJar(final ClassLoader classLoader, final String className) {
return CODESOURCES.computeIfAbsent(className, classNameToJar(classLoader));
}
@NotNull
private static Function classNameToJar(final ClassLoader classLoader) {
return className -> {
try {
final Class> aClass = classLoader.loadClass(className);
final CodeSource codeSource = aClass.getProtectionDomain().getCodeSource();
if (codeSource == null) {
throw new NullPointerException("Unknown code source for " + aClass);
}
return new File(codeSource.getLocation().toURI());
} catch (final ClassNotFoundException | URISyntaxException e) {
throw new RuntimeException(e);
}
};
}
}