/****************************************************************************** * Copyright (c) 2002 - 2006 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *****************************************************************************/ package com.ibm.wala.cast.util; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.regex.Pattern; import com.ibm.wala.cast.tree.CAstEntity; import com.ibm.wala.cast.tree.CAstNode; import com.ibm.wala.cast.tree.CAstSourcePositionMap; import com.ibm.wala.cast.tree.visit.CAstVisitor; import com.ibm.wala.cast.tree.visit.CAstVisitor.Context; import com.ibm.wala.util.collections.HashMapFactory; import com.ibm.wala.util.collections.HashSetFactory; import com.ibm.wala.util.debug.Assertions; public class CAstPattern { private static boolean DEBUG_PARSER = false; private static boolean DEBUG_MATCH = false; private final static int CHILD_KIND = -1; private final static int CHILDREN_KIND = -2; private final static int REPEATED_PATTERN_KIND = -3; private final static int ALTERNATIVE_PATTERN_KIND = -4; private final static int OPTIONAL_PATTERN_KIND = -5; private final static int REFERENCE_PATTERN_KIND = -6; private final static int IGNORE_KIND = -99; private final String name; private final int kind; private final Object value; private final CAstPattern[] children; private final Map<String, CAstPattern> references; public static class Segments extends TreeMap<String,Object> { public CAstNode getSingle(String name) { assert containsKey(name) : name; return (CAstNode) get(name); } @SuppressWarnings("unchecked") public List<CAstNode> getMultiple(String name) { if (!containsKey(name)) { return Collections.emptyList(); } else { Object o = get(name); if (o instanceof CAstNode) { return Collections.singletonList((CAstNode)o); } else { assert o instanceof List; return (List<CAstNode>) o; } } } private void addAll(Segments other) { for (Iterator xs = other.entrySet().iterator(); xs.hasNext();) { Map.Entry e = (Map.Entry) xs.next(); String name = (String) e.getKey(); if (e.getValue() instanceof CAstNode) { add(name, (CAstNode) e.getValue()); } else { for (Iterator vs = ((List) e.getValue()).iterator(); vs.hasNext();) { add(name, (CAstNode) vs.next()); } } } } @SuppressWarnings("unchecked") private void add(String name, CAstNode result) { if (containsKey(name)) { Object o = get(name); if (o instanceof List) { ((List<CAstNode>) o).add(result); } else { assert o instanceof CAstNode; List<Object> x = new ArrayList<Object>(); x.add(o); x.add(result); put(name, x); } } else { put(name, result); } } } public CAstPattern(String name, int kind, CAstPattern[] children) { this.name = name; this.kind = kind; this.value = null; this.children = children; this.references = null; } public CAstPattern(String name, Object value) { this.name = name; this.kind = IGNORE_KIND; this.value = value; this.children = null; this.references = null; } public CAstPattern(String patternName, Map<String, CAstPattern> references) { this.name = null; this.kind = REFERENCE_PATTERN_KIND; this.value = patternName; this.references = references; this.children = null; } @Override public String toString() { StringBuffer sb = new StringBuffer(); if (name != null) { sb.append("<").append(name).append(">"); } if (value != null) { if (kind == REFERENCE_PATTERN_KIND) { sb.append("ref:").append(value); } else if (value instanceof Pattern) { sb.append("/").append(value).append("/"); } else { sb.append("literal:").append(value); } } else if (kind == CHILD_KIND) { sb.append("*"); } else if (kind == CHILDREN_KIND) { sb.append("**"); } else if (kind == REPEATED_PATTERN_KIND) { sb.append("@"); } else if (kind == ALTERNATIVE_PATTERN_KIND) { sb.append("|"); } else if (kind == OPTIONAL_PATTERN_KIND) { sb.append("?"); } else { sb.append(CAstPrinter.kindAsString(kind)); } if (children != null) { sb.append("("); for (int i = 0; i < children.length; i++) { sb.append(children[i].toString()); if (i == children.length - 1) { sb.append(")"); } else { sb.append(","); } } } return sb.toString(); } private static boolean matchChildren(CAstNode tree, int i, CAstPattern[] cs, int j, Segments s) { if (i >= tree.getChildCount() && j >= cs.length) { return true; } else if (i < tree.getChildCount() && j >= cs.length) { return false; } else if (i >= tree.getChildCount() && j < cs.length) { switch (cs[j].kind) { case CHILDREN_KIND: case OPTIONAL_PATTERN_KIND: case REPEATED_PATTERN_KIND: return matchChildren(tree, i, cs, j + 1, s); default: return false; } } else { if (cs[j].kind == CHILD_KIND) { if (DEBUG_MATCH) { System.err.println(("* matches " + CAstPrinter.print(tree.getChild(i)))); } if (s != null && cs[j].name != null) { s.add(cs[j].name, tree.getChild(i)); } return matchChildren(tree, i + 1, cs, j + 1, s); } else if (cs[j].kind == CHILDREN_KIND) { if (tryMatchChildren(tree, i, cs, j + 1, s)) { if (DEBUG_MATCH) { System.err.println("** matches nothing"); } return true; } else { if (DEBUG_MATCH) { System.err.println(("** matches " + CAstPrinter.print(tree.getChild(i)))); } if (s != null && cs[j].name != null) { s.add(cs[j].name, tree.getChild(i)); } return matchChildren(tree, i + 1, cs, j, s); } } else if (cs[j].kind == REPEATED_PATTERN_KIND) { CAstPattern repeatedPattern = cs[j].children[0]; if (repeatedPattern.tryMatch(tree.getChild(i), s)) { if (s != null && cs[j].name != null) { s.add(cs[j].name, tree.getChild(i)); } if (DEBUG_MATCH) { System.err.println((cs[j] + " matches " + CAstPrinter.print(tree.getChild(i)))); } return matchChildren(tree, i + 1, cs, j, s); } else { if (DEBUG_MATCH) { System.err.println((cs[j] + " matches nothing")); } return matchChildren(tree, i, cs, j + 1, s); } } else if (cs[j].kind == OPTIONAL_PATTERN_KIND) { if (tryMatchChildren(tree, i, cs, j + 1, s)) { if (DEBUG_MATCH) { System.err.println((cs[j] + " matches nothing")); } return true; } else { CAstPattern optionalPattern = cs[j].children[0]; if (optionalPattern.tryMatch(tree.getChild(i), s)) { if (DEBUG_MATCH) { System.err.println((cs[j] + " matches " + CAstPrinter.print(tree.getChild(i)))); } return matchChildren(tree, i + 1, cs, j + 1, s); } else { return false; } } } else { return cs[j].match(tree.getChild(i), s) && matchChildren(tree, i + 1, cs, j + 1, s); } } } public boolean match(CAstNode tree, Segments s) { if (DEBUG_MATCH) { System.err.println(("matching " + this + " against " + CAstPrinter.print(tree))); } if (kind == REFERENCE_PATTERN_KIND) { return references.get(value).match(tree, s); } else if (kind == ALTERNATIVE_PATTERN_KIND) { for (int i = 0; i < children.length; i++) { if (children[i].tryMatch(tree, s)) { if (s != null && name != null) s.add(name, tree); return true; } } if (DEBUG_MATCH) { System.err.println("match failed (a)"); } return false; } else { if ((value == null) ? tree.getKind() != kind : (tree.getKind() != CAstNode.CONSTANT || (value instanceof Pattern ? !((Pattern)value).matcher(tree.getValue().toString()).matches() : !value.equals(tree.getValue().toString())))) { if (DEBUG_MATCH) { System.err.println("match failed (b)"); } return false; } if (s != null && name != null) s.add(name, tree); if (children == null || children.length == 0) { if (DEBUG_MATCH && tree.getChildCount() != 0) { System.err.println("match failed (c)"); } return tree.getChildCount() == 0; } else { return matchChildren(tree, 0, children, 0, s); } } } private static boolean tryMatchChildren(CAstNode tree, int i, CAstPattern[] cs, int j, Segments s) { if (s == null) { return matchChildren(tree, i, cs, j, s); } else { Segments ss = new Segments(); boolean result = matchChildren(tree, i, cs, j, ss); if (result) s.addAll(ss); return result; } } private boolean tryMatch(CAstNode tree, Segments s) { if (s == null) { return match(tree, s); } else { Segments ss = new Segments(); boolean result = match(tree, ss); if (result) s.addAll(ss); return result; } } public static Segments match(CAstPattern p, CAstNode n) { Segments s = new Segments(); if (p.match(n, s)) { return s; } else { return null; } } public static CAstPattern parse(String patternString) { try { return (new Parser(patternString)).parse(); } catch (NoSuchFieldException e) { Assertions.UNREACHABLE("no such kind in pattern: " + e.getMessage()); return null; } catch (IllegalAccessException e) { Assertions.UNREACHABLE("internal error in CAstPattern" + e); return null; } } public static Collection<Segments> findAll(final CAstPattern p, final CAstEntity e) { return p.new Matcher().findAll(new Context() { @Override public CAstEntity top() { return e; } @Override public CAstSourcePositionMap getSourceMap() { return e.getSourceMap(); } }, e.getAST()); } public class Matcher extends CAstVisitor<Context> { private final Collection<Segments> result = HashSetFactory.make(); @Override public void leaveNode(CAstNode n, Context c, CAstVisitor visitor) { Segments s = match(CAstPattern.this, n); if (s != null) { result.add(s); } } public Collection<Segments> findAll(final Context c, final CAstNode top) { visit(top, c, this); return result; } } private static class Parser { private final Map<String, CAstPattern> namedPatterns = HashMapFactory.make(); private final String patternString; private int start; private int end; private Parser(String patternString) { this.patternString = patternString; } // private Parser(String patternString, int start) { // this(patternString); // this.start = start; // } private String parseName(boolean internal) { if (patternString.charAt(start) == (internal ? '{' : '<')) { int nameStart = start + 1; int nameEnd = patternString.indexOf(internal ? '}' : '>', nameStart); start = nameEnd + 1; return patternString.substring(nameStart, nameEnd); } else { return null; } } public CAstPattern parse() throws NoSuchFieldException, IllegalAccessException { if (DEBUG_PARSER) { System.err.println(("parsing " + patternString.substring(start))); } String internalName = parseName(true); String name = parseName(false); CAstPattern result; if (patternString.charAt(start) == '`') { int strEnd = patternString.indexOf('`', start + 1); end = strEnd + 1; String patternName = patternString.substring(start + 1, strEnd); assert internalName == null; result = new CAstPattern(patternName, namedPatterns); } else if (patternString.charAt(start) == '"') { int strEnd = patternString.indexOf('"', start + 1); end = strEnd + 1; result = new CAstPattern(name, patternString.substring(start + 1, strEnd)); } else if (patternString.charAt(start) == '/') { int strEnd = patternString.indexOf('/', start + 1); end = strEnd + 1; result = new CAstPattern(name, Pattern.compile(patternString.substring(start + 1, strEnd))); } else if (patternString.startsWith("**", start)) { end = start + 2; result = new CAstPattern(name, CHILDREN_KIND, null); } else if (patternString.startsWith("*", start)) { end = start + 1; result = new CAstPattern(name, CHILD_KIND, null); } else if (patternString.startsWith("|(", start)) { List<CAstPattern> alternatives = new ArrayList<CAstPattern>(); start += 2; do { alternatives.add(parse()); start = end + 2; } while (patternString.startsWith("||", end)); assert patternString.startsWith(")|", end) : patternString; end += 2; result = new CAstPattern(name, ALTERNATIVE_PATTERN_KIND, alternatives.toArray(new CAstPattern[alternatives.size()])); } else if (patternString.startsWith("@(", start)) { start += 2; CAstPattern children[] = new CAstPattern[] { parse() }; assert patternString.startsWith(")@", end); end += 2; if (DEBUG_PARSER) { System.err.println(("repeated pattern: " + children[0])); } result = new CAstPattern(name, REPEATED_PATTERN_KIND, children); } else if (patternString.startsWith("?(", start)) { start += 2; CAstPattern children[] = new CAstPattern[] { parse() }; assert patternString.startsWith(")?", end); end += 2; if (DEBUG_PARSER) { System.err.println(("optional pattern: " + children[0])); } result = new CAstPattern(name, OPTIONAL_PATTERN_KIND, children); } else { int kindEnd = patternString.indexOf('(', start); String kindStr = patternString.substring(start, kindEnd); Field kindField = CAstNode.class.getField(kindStr); int kind = kindField.getInt(null); if (patternString.charAt(kindEnd + 1) == ')') { end = kindEnd + 2; result = new CAstPattern(name, kind, null); } else { List<CAstPattern> children = new ArrayList<CAstPattern>(); start = patternString.indexOf('(', start) + 1; do { children.add(parse()); start = end + 1; if (DEBUG_PARSER) { System.err.println(("parsing children: " + patternString.substring(end))); } } while (patternString.charAt(end) == ','); assert patternString.charAt(end) == ')'; end++; result = new CAstPattern(name, kind, children.toArray(new CAstPattern[children.size()])); } } if (internalName != null) { namedPatterns.put(internalName, result); } return result; } } }