package org.gridkit.jvmtool.stacktrace.analytics; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; public class TraceFilterPredicateParser { private static final String REG_PAR = "[()]"; private static final String REG_PATTERN = "[\\w\\d.:*$]+"; private static final String REG_COMMA = "[,]"; private static final String REG_PLUS = "[+]"; private static final String REG_EXCL = "[!]"; private static final String REG_SLASH_PLUS = "[/][+]"; private static final String REG_SLASH_EXCL = "[/][!]"; private static final String REG_SLASH_UP_PLUS = "[/]\\^[+]"; private static final String REG_SLASH_UP_EXCL = "[/]\\^[!]"; static final Pattern TOKENIZER; static { String pattern = "(" + "(" + REG_PAR + ")|" + "(" + REG_PATTERN + ")|" + "(" + REG_COMMA + ")|" + "(" + REG_PLUS + ")|" + "(" + REG_EXCL + ")|" + "(" + REG_SLASH_PLUS + ")|" + "(" + REG_SLASH_EXCL + ")|" + "(" + REG_SLASH_UP_PLUS + ")|" + "(" + REG_SLASH_UP_EXCL + ")|" + "\\s+)"; TOKENIZER = Pattern.compile(pattern); } public static ThreadSnapshotFilter parseFilter(String source, BasicFilterFactory factory) throws ParserException { FilterParser parser = new FilterParser(factory, source); return parser.parse(); } public static PositionalStackMatcher parsePositionMatcher(String source, BasicFilterFactory factory) throws ParserException { FilterParser parser = new FilterParser(factory, source); return parser.parsePositionalMatcher(); } private static class FilterParser { List<List<Op>> stackStash = new ArrayList<List<Op>>(); List<Op> stack = new ArrayList<Op>(); String text; Matcher matcher; int offset; BasicFilterFactory filterFactory; public FilterParser(BasicFilterFactory factory, String text) { this.text = text; this.filterFactory = factory; matcher = TOKENIZER.matcher(text); } public ThreadSnapshotFilter parse() { parseText(); Op root = collapse(); return produceFilter(root); } public PositionalStackMatcher parsePositionalMatcher() { parseText(); Op root = collapse(); return producePosFilter(root); } protected void parseText() { while(true) { if (matcher.lookingAt()) { offset = matcher.start(); if (matcher.group(2) != null) { processPar(); } else if (matcher.group(3) != null) { processPattern(); } else if (matcher.group(4) != null) { processOp(TokenType.COMMA, 3); } else if (matcher.group(5) != null) { processOp(TokenType.PLUS, 1); } else if (matcher.group(6) != null) { processOp(TokenType.EXCL, 1); } else if (matcher.group(7) != null) { processOp(TokenType.SLASH_PLUS, 2); } else if (matcher.group(8) != null) { processOp(TokenType.SLASH_EXCL, 2); } else if (matcher.group(9) != null) { processOp(TokenType.SLASH_UP_PLUS, 2); } else if (matcher.group(10) != null) { processOp(TokenType.SLASH_UP_EXCL, 2); } } else { throw error(matcher.regionStart(), "cannot parse"); } if (matcher.end() == text.length()) { break; } else { matcher.region(matcher.end(), text.length()); } } } private void processOp(TokenType tt, int rank) { Op op = new Op(); op.toc = tt; op.rank = rank; op.body = matcher.group(1); op.offset = matcher.start(); pushToken(op); } private void processPattern() { String pattern = matcher.group(1); Op op = new Op(); op.toc = TokenType.PATTERN; op.rank = -1; op.body = pattern; op.offset = matcher.start(); pushToken(op); } private void processPar() { if (text.charAt(matcher.start()) == '(') { // opening paren stashStack(); } else { if (stackStash.isEmpty()) { error(offset, "No mathcing paranthesis"); } // closing paren Op op = collapse(); op.rank = -1; unstashStack(); pushToken(op); } } private Op collapse() { if (stack.isEmpty()) { error(offset, "Empty expression"); } if (stack.get(stack.size() - 1).rank > 0) { error(offset, "Incomplete operator"); } while(stack.size() > 1) { mergeLastOp(); } return stack.get(0); } private void unstashStack() { if (stackStash.size() < 0) { throw new RuntimeException("Nothing on stack"); } stack = stackStash.remove(stackStash.size() - 1); } private void stashStack() { stackStash.add(stack); stack = new ArrayList<TraceFilterPredicateParser.Op>(); } private Op last() { return stack.get(stack.size() - 1); } private void pushToken(Op op) { if (op.rank < 0) { if (stack.isEmpty()) { stack.add(op); } else { if (last().rank < 0) { error(op.offset, " operator expected"); } else { stack.add(op); } } } else if (op.rank >= 0) { if (stack.isEmpty()) { error(op.offset, " operator expected"); } while(true) { int lor = lastOpRank(); if (lor < 0 || lor < op.rank) { stack.add(op); break; } else { mergeLastOp(); continue; } } } } private int lastOpRank() { if (stack.size() == 0) { return -1; } else { int s = stack.size(); Op op = stack.get(s - 1); if (op.rank >= 0) { return op.rank; } else { if (stack.size() < 2) { return -1; } return stack.get(s - 2).rank; } } } private void mergeLastOp() { int s = stack.size(); Op b = stack.remove(s - 1); Op o = stack.remove(s - 2); Op a = stack.remove(s - 3); if (o.rank < 0) { throw new RuntimeException("Op already collapsed"); } o.left = a; o.right = b; o.rank = -1; stack.add(o); } private RuntimeException error(int offs, String message) { throw new ParserException(text, offs, message); } private ThreadSnapshotFilter produceFilter(Op node) { switch(node.toc) { case PATTERN: return filterFactory.frameFilter(filterFactory.patternFrameMatcher(refinePattern(node.body))); case COMMA: return produceConjunctionFilter(node); case PLUS: return filterFactory.disjunction(produceFilter(node.left), produceFilter(node.right)); case EXCL: return filterFactory.disjunction(produceFilter(node.left), filterFactory.not(produceFilter(node.right))); case SLASH_PLUS: return filterFactory.followed(filterFactory.lastFrame(produceMatcher(node.left)), produceFilter(node.right)); case SLASH_EXCL: return filterFactory.followed(filterFactory.lastFrame(produceMatcher(node.left)), filterFactory.not(produceFilter(node.right))); case SLASH_UP_PLUS: return filterFactory.followed(filterFactory.firstFrame(produceMatcher(node.left)), produceFilter(node.right)); case SLASH_UP_EXCL: return filterFactory.followed(filterFactory.firstFrame(produceMatcher(node.left)), filterFactory.not(produceFilter(node.right))); case UNIVERSE: return filterFactory.trueFilter(); default: throw new RuntimeException("Unknown node"); } } private PositionalStackMatcher producePosFilter(Op node) { switch(node.toc) { case PATTERN: case COMMA: return (PositionalStackMatcher)filterFactory.followed(filterFactory.firstFrame(produceMatcher(node)), filterFactory.trueFilter()); case SLASH_PLUS: return (PositionalStackMatcher)filterFactory.followed(filterFactory.lastFrame(produceMatcher(node.left)), produceFilter(node.right)); case SLASH_EXCL: return (PositionalStackMatcher)filterFactory.followed(filterFactory.lastFrame(produceMatcher(node.left)), filterFactory.not(produceFilter(node.right))); case SLASH_UP_PLUS: return (PositionalStackMatcher)filterFactory.followed(filterFactory.firstFrame(produceMatcher(node.left)), produceFilter(node.right)); case SLASH_UP_EXCL: return (PositionalStackMatcher)filterFactory.followed(filterFactory.firstFrame(produceMatcher(node.left)), filterFactory.not(produceFilter(node.right))); default: throw new RuntimeException("Positional operator required"); } } /** * Optimize conjunction into single frame matcher where possible. */ private ThreadSnapshotFilter produceConjunctionFilter(Op node) { List<String> pattern = new ArrayList<String>(); while(node.toc == TokenType.COMMA && node.right.toc == TokenType.PATTERN) { pattern.add(refinePattern(node.right.body)); node = node.left; } if (pattern.isEmpty()) { return filterFactory.conjunction(produceFilter(node.left), produceFilter(node.right)); } else { if (node.toc == TokenType.PATTERN) { pattern.add(refinePattern(node.body)); node = null; } ThreadSnapshotFilter f = filterFactory.frameFilter(filterFactory.patternFrameMatcher(pattern)); return node == null ? f : filterFactory.conjunction(f, produceFilter(node)); } } /** * Optimize conjunction into single frame matcher where possible. */ private StackFrameMatcher produceConjunctionMatcher(Op node) { List<String> pattern = new ArrayList<String>(); while(node.toc == TokenType.COMMA && node.right.toc == TokenType.PATTERN) { pattern.add(refinePattern(node.right.body)); node = node.left; } if (pattern.isEmpty()) { return filterFactory.matcherConjunction(produceMatcher(node), produceMatcher(node.right)); } else { if (node.toc == TokenType.PATTERN) { pattern.add(refinePattern(node.body)); node = null; } StackFrameMatcher f = filterFactory.patternFrameMatcher(pattern); return node == null ? f : filterFactory.matcherConjunction(f, produceMatcher(node)); } } private String refinePattern(String body) { // in stack trace ':' may appear only after filename.java // replace '*:' with '*.*:' to simplify line number patterns int c = body.lastIndexOf(':'); if (c > 0) { if (c > 2) { if (body.charAt(c - 1) == '*' && body.charAt(c - 2) == '*') { // **: - is ok return body; } } if (c > 1 && body.charAt(c - 1) == '*') { return body.substring(0, c - 1) + "*.*:" + body.substring(c + 1); } } return body; } private StackFrameMatcher produceMatcher(Op node) { switch(node.toc) { case PATTERN: return filterFactory.patternFrameMatcher(refinePattern(node.body)); case COMMA: return produceConjunctionMatcher(node); case PLUS: throw error(node.offset, "Unsupported for frame predicate"); case EXCL: throw error(node.offset, "Unsupported for frame predicate"); case SLASH_PLUS: throw error(node.offset, "Unsupported for frame predicate"); case SLASH_EXCL: throw error(node.offset, "Unsupported for frame predicate"); case SLASH_UP_PLUS: throw error(node.offset, "Unsupported for frame predicate"); case SLASH_UP_EXCL: throw error(node.offset, "Unsupported for frame predicate"); case UNIVERSE: return filterFactory.patternFrameMatcher("**"); default: throw new RuntimeException("Unknown node"); } } } private static enum TokenType { UNIVERSE, PATTERN, COMMA, PLUS, EXCL, SLASH_PLUS, SLASH_EXCL, SLASH_UP_PLUS, SLASH_UP_EXCL, } private static class Op { TokenType toc; int rank; int offset; String body; Op left; Op right; public String toString() { if (left == null && right == null) { return "'" + body + "'"; } else { return "'" + body + "'" + "(" + left + ", " + right + ")"; } } } }