package org.gridkit.jvmtool.stacktrace.analytics; import java.lang.Thread.State; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.gridkit.jvmtool.stacktrace.CounterCollection; import org.gridkit.jvmtool.stacktrace.StackFrame; import org.gridkit.jvmtool.stacktrace.StackFrameList; import org.gridkit.jvmtool.stacktrace.ThreadSnapshot; import org.gridkit.jvmtool.stacktrace.analytics.ClassificatorAST.AndCombinatorFilter; import org.gridkit.jvmtool.stacktrace.analytics.ClassificatorAST.AnyOfFrameMatcher; import org.gridkit.jvmtool.stacktrace.analytics.ClassificatorAST.LastFollowedFilter; import org.gridkit.jvmtool.stacktrace.analytics.ClassificatorAST.LastNotFollowedFilter; import org.gridkit.jvmtool.stacktrace.analytics.ClassificatorAST.OrCombinatorFilter; import org.gridkit.jvmtool.stacktrace.analytics.ClassificatorAST.PatternFilter; /** * Default implementation of factory is producing thread safe filter * implementations. * <br/> * See {@link CachingFilterFactory} for optimized single threaded version. * * @author Alexey Ragozin (alexey.ragozin@gmail.com) */ public class BasicFilterFactory { public ThreadSnapshotFilter build(ClassificatorAST.Filter filter) { if (filter instanceof AndCombinatorFilter) { List<ThreadSnapshotFilter> list = new ArrayList<ThreadSnapshotFilter>(); for(ClassificatorAST.Filter f: ((AndCombinatorFilter)filter).subfilters) { if (!(f instanceof ClassificatorAST.TrueFilter)) { if (f instanceof ClassificatorAST.FalseFilter) { return falseFilter(); } list.add(build(f)); } } if (list.isEmpty()) { return trueFilter(); } else { return disjunction(list); } } else if (filter instanceof OrCombinatorFilter) { List<ThreadSnapshotFilter> list = new ArrayList<ThreadSnapshotFilter>(); for(ClassificatorAST.Filter f: ((OrCombinatorFilter)filter).subfilters) { if (!(f instanceof ClassificatorAST.FalseFilter)) { if (f instanceof ClassificatorAST.TrueFilter) { return trueFilter(); } list.add(build(f)); } } if (list.isEmpty()) { return falseFilter(); } else { return conjunction(list); } } else if (filter instanceof ClassificatorAST.TrueFilter) { return trueFilter(); } else if (filter instanceof ClassificatorAST.FalseFilter) { return falseFilter(); } else if (filter instanceof ClassificatorAST.LastFollowedFilter) { LastFollowedFilter lff = (LastFollowedFilter) filter; return followed(lastFrame(build(lff.snippet)), build(lff.followFilter)); } else if (filter instanceof ClassificatorAST.LastNotFollowedFilter) { LastNotFollowedFilter lff = (LastNotFollowedFilter) filter; return followed(lastFrame(build(lff.snippet)), not(build(lff.followFilter))); } else if (filter instanceof ClassificatorAST.PatternFilter) { PatternFilter pf = (PatternFilter) filter; return frameFilter(patternFrameMatcher(pf.patterns)); } else { throw new IllegalArgumentException("Unknow AST node: " + filter); } } public StackFrameMatcher build(ClassificatorAST.FrameMatcher matcher) { if (matcher instanceof ClassificatorAST.PatternFilter) { PatternFilter pf = (PatternFilter) matcher; return patternFrameMatcher(pf.patterns); } else if (matcher instanceof ClassificatorAST.FalseFilter) { return falseFrameMatcher(); } else if (matcher instanceof ClassificatorAST.AnyOfFrameMatcher) { AnyOfFrameMatcher any = (AnyOfFrameMatcher) matcher; List<String> patterns = new ArrayList<String>(); List<StackFrameMatcher> list = new ArrayList<StackFrameMatcher>(); for(ClassificatorAST.FrameMatcher m: any.submatchers) { if (m instanceof ClassificatorAST.FalseFilter) { continue; } else if (m instanceof ClassificatorAST.PatternFilter) { patterns.addAll(((PatternFilter)m).patterns); } else { list.add(build(m)); } } if (!patterns.isEmpty()) { list.add(patternFrameMatcher(patterns)); } if (list.isEmpty()) { return falseFrameMatcher(); } else if (list.size() == 1) { return list.get(0); } else { return matcherConjunction(list); } } else { throw new IllegalArgumentException("Unknown ASt node: " + matcher); } } public ThreadSnapshotFilter disjunction(ThreadSnapshotFilter... subfilters) { return disjunction(Arrays.asList(subfilters)); } public ThreadSnapshotFilter disjunction(Collection<ThreadSnapshotFilter> subfilters) { if (subfilters.isEmpty()) { return trueFilter(); } return new DisjunctionFilter(subfilters.toArray(new ThreadSnapshotFilter[0])); } public ThreadSnapshotFilter conjunction(ThreadSnapshotFilter... subfilters) { return conjunction(Arrays.asList(subfilters)); } public ThreadSnapshotFilter conjunction(Collection<ThreadSnapshotFilter> subfilters) { if (subfilters.isEmpty()) { return falseFilter(); } return new ConjunctionFilter(subfilters.toArray(new ThreadSnapshotFilter[0])); } public StackFrameMatcher matcherConjunction(StackFrameMatcher... subfilters) { return matcherConjunction(Arrays.asList(subfilters)); } public StackFrameMatcher matcherConjunction(Collection<StackFrameMatcher> subfilters) { if (subfilters.isEmpty()) { return falseFrameMatcher(); } return new ConjunctionMatcher(subfilters.toArray(new StackFrameMatcher[0])); } public ThreadSnapshotFilter not(final ThreadSnapshotFilter filter) { return new ThreadSnapshotFilter() { @Override public boolean evaluate(ThreadSnapshot snapshot) { return !filter.evaluate(snapshot); } }; } public ThreadSnapshotFilter followed(PositionalStackMatcher matcher, ThreadSnapshotFilter filter) { return new FollowedPredicate(matcher, filter); } public ThreadSnapshotFilter frameFilter(final StackFrameMatcher matcher) { return new ThreadSnapshotFilter() { @Override public boolean evaluate(ThreadSnapshot snapshot) { for(StackFrame frame: snapshot.stackTrace()) { if (matcher.evaluate(frame)) { return true; } } return false; } }; } public ThreadSnapshotFilter falseFilter() { return new FalseFilter(); } public StackFrameMatcher falseFrameMatcher() { return new FalseMatcher(); } public ThreadSnapshotFilter trueFilter() { return new TrueFilter(); } public StackFrameMatcher patternFrameMatcher(String... patterns) { return patternFrameMatcher(Arrays.asList(patterns)); } public StackFrameMatcher patternFrameMatcher(Collection<String> patterns) { if (patterns.isEmpty()) { throw new IllegalArgumentException("Pattern list is empty"); } return new PatternFrameMatcher(patterns); } public PositionalStackMatcher lastFrame(StackFrameMatcher matcher) { return new LastFrameMatcher(matcher); } public PositionalStackMatcher firstFrame(StackFrameMatcher matcher) { return new FirstFrameMatcher(matcher); } protected final class TrueFilter implements ThreadSnapshotFilter { @Override public boolean evaluate(ThreadSnapshot snapshot) { return true; } } protected final class FalseMatcher implements StackFrameMatcher { @Override public boolean evaluate(StackFrame frame) { return false; } } protected final class FalseFilter implements ThreadSnapshotFilter { @Override public boolean evaluate(ThreadSnapshot snapshot) { return false; } } protected class LastFrameMatcher implements PositionalStackMatcher { private final StackFrameMatcher matcher; public LastFrameMatcher(StackFrameMatcher matcher) { this.matcher = matcher; } @Override public int matchNext(ThreadSnapshot snap, int matchFrom) { StackFrameList trace = snap.stackTrace(); if (matchFrom > 0) { // assume that match have been found already return -1; } for(int i = matchFrom; i < trace.depth(); ++i) { if (matcher.evaluate(trace.frameAt(i))) { return i; } } return -1; } } protected class FirstFrameMatcher implements PositionalStackMatcher { private final StackFrameMatcher matcher; public FirstFrameMatcher(StackFrameMatcher matcher) { this.matcher = matcher; } @Override public int matchNext(ThreadSnapshot snap, int matchFrom) { StackFrameList trace = snap.stackTrace(); if (matchFrom > 0) { // assume that match have been found already return -1; } for(int i = trace.depth(); i > 0; --i) { if (matcher.evaluate(trace.frameAt(i - 1))) { return i; } } return -1; } } protected static class FollowedPredicate implements ThreadSnapshotFilter, PositionalStackMatcher { private final PositionalStackMatcher matcher; private final ThreadSnapshotFilter tailFilter; public FollowedPredicate(PositionalStackMatcher matcher, ThreadSnapshotFilter tailFilter) { this.matcher = matcher; this.tailFilter = tailFilter; } @Override public boolean evaluate(ThreadSnapshot snapshot) { int n = -1; while(true) { int m = matcher.matchNext(snapshot, n + 1); if (m < 0) { break; } n = m; } if (n >= 0) { StackFrameList remained = snapshot.stackTrace(); remained = remained.fragment(0, n); return tailFilter.evaluate(new ThreadSnapProxy(snapshot, remained)); } else { return false; } } @Override public int matchNext(ThreadSnapshot snap, int matchFrom) { int n = matchFrom - 1; while(true) { int m = matcher.matchNext(snap, n + 1); if (m < 0) { if (n >= matchFrom) { StackFrameList remained = snap.stackTrace(); remained = remained.fragment(0, n); if (tailFilter.evaluate(new ThreadSnapProxy(snap, remained))) { return n; } } return -1; } n = m; } } } protected static class DisjunctionFilter implements ThreadSnapshotFilter { private final ThreadSnapshotFilter[] filters; public DisjunctionFilter(ThreadSnapshotFilter[] filters) { this.filters = filters; } @Override public boolean evaluate(ThreadSnapshot snapshot) { for(ThreadSnapshotFilter f: filters) { if (!f.evaluate(snapshot)) { return false; } } return true; } } protected static class ConjunctionFilter implements ThreadSnapshotFilter { private final ThreadSnapshotFilter[] filters; public ConjunctionFilter(ThreadSnapshotFilter[] filters) { this.filters = filters; } @Override public boolean evaluate(ThreadSnapshot snapshot) { for(ThreadSnapshotFilter f: filters) { if (f.evaluate(snapshot)) { return true; } } return false; } } protected static class ConjunctionMatcher implements StackFrameMatcher { private final StackFrameMatcher[] matchers; public ConjunctionMatcher(StackFrameMatcher[] matcher) { this.matchers = matcher; } @Override public boolean evaluate(StackFrame frame) { for(StackFrameMatcher f: matchers) { if (f.evaluate(frame)) { return true; } } return false; } } protected static class PatternFrameMatcher implements StackFrameMatcher { private final Pattern regEx; PatternFrameMatcher(Collection<String> patterns) { StringBuilder sb = new StringBuilder(); sb.append('('); for(String pattern: patterns) { sb.append(wildCardTranslate(pattern)); sb.append('|'); } sb.setCharAt(sb.length() - 1, ')'); regEx = Pattern.compile(sb.toString()); } @Override public boolean evaluate(StackFrame frame) { return regEx.matcher(frame).lookingAt(); } } protected static class ThreadSnapProxy implements ThreadSnapshot { ThreadSnapshot snap; StackFrameList stack; public ThreadSnapProxy(ThreadSnapshot snap, StackFrameList stack) { this.snap = snap; this.stack = stack; } public long threadId() { return snap.threadId(); } public String threadName() { return snap.threadName(); } public long timestamp() { return snap.timestamp(); } public StackFrameList stackTrace() { return stack != null ? stack : snap.stackTrace(); } public State threadState() { return snap.threadState(); } @Override public CounterCollection counters() { return snap.counters(); } } /** * GLOB pattern supports *, ** and ? wild cards. * Leading and trailing ** have special meaning, consecutive separator become optional. */ protected static String wildCardTranslate(String pattern) { String separator = "."; StringBuffer sb = new StringBuffer(); String es = escape(separator); // special starter Matcher ss = Pattern.compile("^([*][*][" + es + "]).*").matcher(pattern); if (ss.matches()) { pattern = pattern.substring(ss.group(1).length()); // make leading sep optional sb.append("(.*[" + es + "])?"); } // special trailer Matcher st = Pattern.compile(".*([" + es + "][*][*])$").matcher(pattern); boolean useSt = false; if (st.matches()) { pattern = pattern.substring(0, st.start(1)); useSt = true; } for(int i = 0; i != pattern.length(); ++i) { char c = pattern.charAt(i); if (c == '?') { sb.append("[^" + es + "]"); } else if (c == '*') { if (i + 1 < pattern.length() && pattern.charAt(i+1) == '*') { i++; // ** sb.append(".*"); } else { sb.append("[^" + es + "]*"); } } else { if (c == '$') { sb.append("\\$"); } else if (Character.isJavaIdentifierPart(c) || Character.isWhitespace(c)) { sb.append(c); } else { sb.append('\\').append(c); } } } if (useSt) { sb.append("([" + es + "].*)?"); } return sb.toString(); } private static String escape(String separator) { StringBuffer sb = new StringBuffer(); for(int i = 0; i != separator.length(); ++i) { char c = separator.charAt(i); if ("\\[]&-".indexOf(c) >= 0){ sb.append('\\').append(c); } else { sb.append(c); } } return sb.toString(); } }