
hm.binkley.util.StackTraceFocuser Maven / Gradle / Ivy
package hm.binkley.util;
import com.google.common.collect.Lists;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static hm.binkley.util.LinkedIterable.over;
import static java.util.Arrays.asList;
import static java.util.regex.Pattern.compile;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;
/**
* {@code StackTraceFocuser} narrows exception stack traces, discarding uniteresting frames as an
* aid to logging or debugging.
*
* It does not modify exception messages or suppressed exceptions, only an exception and its causal
* chain.
*
* @param the exception type
*
* @author B. K. Oxley (binkley)
*/
public final class StackTraceFocuser
implements Function {
private static final List defaultClassNameIgnores = asList(compile("^java\\."),
compile("^javax\\."), compile("^sun\\."), compile("^com\\.sun\\."));
private final Predicate ignore;
/**
* Creates a new {@code StackTraceFocuser} for the given list of classNameIgnores
* regexen.
*
* @param classNameIgnores the patterns to ignore, never missing
* @param the exception type
*
* @return thew new {@code StackTraceFocuser}, never missing
*/
@Nonnull
public static StackTraceFocuser ignoreClassNames(
@Nonnull final List classNameIgnores) {
return new StackTraceFocuser<>(classNameIgnores.stream().
map(StackTraceFocuser::ignoreClassName).
collect(toList()));
}
/**
* Creates a new, default {@code StackTraceFocuser} ignoring frames from the JDK.
*
* @param the exception type
*
* @return thew new {@code StackTraceFocuser}, never missing
*/
@Nonnull
public static StackTraceFocuser ignoreJavaClasses() {
return ignoreClassNames(defaultClassNameIgnores);
}
/**
* Constructs a new {@code StackTraceFocuser} for the given ignores predicates. All
* predicates are or'ed when checking if a frame should be ignored.
*
* @param ignores the predicates of frames to ignore, never missing
*/
public StackTraceFocuser(@Nonnull final Iterable> ignores) {
ignore = stream(ignores.spliterator(), false).
reduce(Predicate::or).
orElse(frame -> false).
negate();
}
/**
* Constructs a new {@code StackTraceFocuser} for the given ignores predicates.
*
* @param first the first predicate of frames to ignore, never missing
* @param rest the optional remaining predicates of frames to ignore
*/
@SafeVarargs
public StackTraceFocuser(@Nonnull final Predicate first,
final Predicate... rest) {
this(Lists.asList(first, rest));
}
@Override
public E apply(final E e) {
stream(over(e, Objects::isNull, Throwable::getCause).spliterator(), true).
forEach(x -> {
final List found = asList(x.getStackTrace()).stream().
filter(ignore).
collect(toList());
x.setStackTrace(found.toArray(new StackTraceElement[found.size()]));
});
return e;
}
@Nonnull
public static Predicate ignoreClassName(@Nonnull final Pattern className) {
return frame -> className.matcher(frame.getClassName()).find();
}
@Nonnull
public static Predicate ignoreMethodName(@Nonnull final Pattern methodName) {
return frame -> methodName.matcher(frame.getMethodName()).find();
}
@Nonnull
public static Predicate ignoreFileName(@Nonnull final Pattern fileName) {
return frame -> fileName.matcher(frame.getFileName()).find();
}
@Nonnull
public static Predicate ignoreLineNumber(@Nonnull final Pattern lineNumber) {
return frame -> lineNumber.matcher(String.valueOf(frame.getLineNumber())).find();
}
}