/** * Licensed to Cloudera, Inc. under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. Cloudera, Inc. licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.cloudera.flume.conf; import java.util.HashMap; import java.util.List; import java.util.Map; import org.antlr.runtime.tree.CommonTree; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class provides a library of pattern matching primitives over ANTLR's * CommonTree data structure. This is best used with a import static * ...PatternMatch.*; statement. See the test cases for clearer examples. * * Base patterns (wild, var, kind, tuple) are generally instantiated with the * static method. parent relation patterns are object methods that assume that * 'this' is the parent. * * Example: * * var("matchname", wild()) // match any and bind matchname to it. * * kind("DECO").child(var("sink",kind("SINK"))) // find (DECO ( SINK xxx) (xxx) * ) and bind "sink" to ( SINK xxx ) * * If there are multiple var's specified in a pattern with the same name, the * parent/outermost will win. If they are at the same level in a tuple, the left * most will win. * * NOTE: This is tied to the AST representation (CommonTree) used in the parser * generator. This is not directly tied to the actual lex/parser. */ public class PatternMatch { public static final Logger LOG = LoggerFactory.getLogger(PatternMatch.class); enum PatternType { WILD, VAR, KIND, TUPLE, PARENT, PARENT_NTH, PARENT_RECURSIVE, OR } PatternType pt; Object[] args; // pattern arguments /** * only use the static constructors to generate patterns. */ private PatternMatch(PatternType type, Object... args) { this.pt = type; this.args = args; } /** * Returns null if there is no match. Returns empty list if the tree matches. * Returns map of name->CommonTree bindings if there is a match and there were * pattern variables specified. * * For bindings, we favor parent of child and left child over right child. */ @SuppressWarnings("unchecked") public Map<String, CommonTree> match(CommonTree ct) { switch (pt) { case WILD: return new HashMap<String, CommonTree>(); case VAR: { // bind a pattern variable to the pipe String x = (String) args[0]; PatternMatch pv = (PatternMatch) args[1]; Map<String, CommonTree> m = pv.match(ct); // Fail! if (m == null) { return null; } // This is the ONLY pattern that can bind and return m.put(x, ct); return m; } case KIND: // node kind matches String k = (String) args[0]; if (ct != null && k.equals(ct.getText())) { return new HashMap<String, CommonTree>(); } return null; case TUPLE: { // parent tuple PatternMatch parent = (PatternMatch) args[0]; // recursive case if (ct == null) { return null; } List<CommonTree> ch = ct.getChildren(); if (ch == null) { return null; } if (ch.size() + 1 != args.length) { return null; // tuple cardinality is wrong } Map<String, CommonTree> mps = parent.match(ct); if (mps == null) return null; Map<String, CommonTree> ret = new HashMap<String, CommonTree>(); // This checks to see that each element in the tuple pattern matches the // corresponding element in the AST. We do this backwards to favor left // bindings for (int i = ch.size(); i >= 1; i--) { CommonTree e = ch.get(i - 1); PatternMatch p = (PatternMatch) args[i]; Map<String, CommonTree> match = p.match(e); if (match != null) { // success! ret.putAll(match); } else { // element did not match, failed. return null; } } ret.putAll(mps); // parent will mask out children. return ret; } case PARENT: { PatternMatch parent = (PatternMatch) args[0]; PatternMatch child = (PatternMatch) args[1]; // recursive case if (ct == null) { return null; } List<CommonTree> ch = ct.getChildren(); if (ch == null) { return null; } Map<String, CommonTree> ret = parent.match(ct); if (ret == null) return null; // this will just find the first match for (CommonTree e : ch) { Map<String, CommonTree> match = child.match(e); if (match != null) { // success! match.putAll(ret); return match; } } return null; } case PARENT_NTH: { PatternMatch parent = (PatternMatch) args[0]; PatternMatch child = (PatternMatch) args[1]; Integer nth = (Integer) args[2]; // recursive case List<CommonTree> ch = (List<CommonTree>) ct.getChildren(); if (ch == null) { return null; } Map<String, CommonTree> ret = parent.match(ct); if (ret == null) return null; // check the nth child to see if it matches. CommonTree chd = ch.get(nth); Map<String, CommonTree> match = child.match(chd); if (match != null) { // success! match.putAll(ret); return match; } return null; } case PARENT_RECURSIVE: { PatternMatch pat = (PatternMatch) args[0]; // base case Map<String, CommonTree> m1 = pat.match(ct); if (m1 != null) { return m1; } if (ct == null) { return null; } // recursive case List<CommonTree> ch = (List<CommonTree>) ct.getChildren(); if (ch == null) { return null; } for (CommonTree e : ch) { LOG.debug(e.toStringTree()); Map<String, CommonTree> match = this.match(e); if (match != null) { // success! return match; } } return null; } case OR: { // this will just find the first match for (PatternMatch p : (PatternMatch[]) args) { Map<String, CommonTree> match = p.match(ct); if (match != null) { // success! return match; } } return null; } default: throw new IllegalStateException("Unexpected Pattern type: " + pt); } } /** * This method creates a binding pattern, that binds if p matches. */ public static PatternMatch var(String name, PatternMatch p) { return new PatternMatch(PatternType.VAR, name, p); } /** * This method creates a wildcard pattern, a pattern that always matches */ public static PatternMatch wild() { return new PatternMatch(PatternType.WILD); } /** * This method creates a kind pattern, a pattern that matches if the node text * is the same as k. */ public static PatternMatch kind(String k) { return new PatternMatch(PatternType.KIND, k); } /** * This method creates a tuple pattern, a pattern that matches if the * cardinality of the node is the same and that each of the corresponding * patterns match. */ public static PatternMatch tuple(PatternMatch... tuple) { // The first item in tuple is the parent, the subsequent nodes are the // children in tuple order. // Cast forces tuple to be interpreted as Object... return new PatternMatch(PatternType.TUPLE, (Object[]) tuple); } /** * This method creates a child relation where 'this' is the parent, and the * specified pattern child is a child. This matches if *any* of the parent * node's children match. */ public PatternMatch child(PatternMatch child) { return new PatternMatch(PatternType.PARENT, this, child); } /** * This method creates an nth child pattern match relation. For this to match, * 'this' matches the parent, and the nth child of the parent maches the child * pattern. */ public PatternMatch nth(int n, PatternMatch child) { return new PatternMatch(PatternType.PARENT_NTH, this, child, n); } /** * This method creates a recursive pattern match relation. This will traverse * through any number of nodes to check if the child pattern matches. */ public static PatternMatch recursive(PatternMatch child) { return new PatternMatch(PatternType.PARENT_RECURSIVE, child); } /** * This method creates an 'or' disjunct match pattern. This will attempt each * disjunct in order until one matches.x */ public static PatternMatch or(PatternMatch... choice) { return new PatternMatch(PatternType.OR, (Object[]) choice); } /** * This does an inplace child replacement. This replaces the current dst nodes * children with the src nodes children. */ public static void replaceChildren(CommonTree dst, CommonTree src) { // delete all int dCount = dst.getChildCount(); for (int i = 0; i < dCount; i++) { dst.deleteChild(0); } // insert all dst.addChildren(src.getChildren()); } }