/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package com.espertech.esper.event.property; import com.espertech.esper.antlr.NoCaseSensitiveStream; import com.espertech.esper.client.PropertyAccessException; import com.espertech.esper.epl.generated.EsperEPL2GrammarLexer; import com.espertech.esper.epl.generated.EsperEPL2GrammarParser; import com.espertech.esper.epl.parse.ExceptionConvertor; import com.espertech.esper.type.IntValue; import com.espertech.esper.type.StringValue; import org.antlr.runtime.CharStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.antlr.runtime.tree.Tree; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * Parser for property names that can be simple, nested, mapped or a combination of these. * Uses ANTLR parser to parse. */ public class PropertyParser { private static final Log log = LogFactory.getLog(PropertyParser.class); private static Set<String> keywordCache; /** * Parse the given property name returning a Property instance for the property. * @param propertyName is the property name to parse * @param isRootedDynamic is true to indicate that the property is already rooted in a dynamic * property and therefore all child properties should be dynamic properties as well * @return Property instance for property */ public static Property parse(String propertyName, boolean isRootedDynamic) { Tree tree = parse(propertyName); if (tree.getChildCount() == 1) { return makeProperty(tree.getChild(0), isRootedDynamic); } List<Property> properties = new LinkedList<Property>(); boolean isRootedInDynamic = isRootedDynamic; for (int i = 0; i < tree.getChildCount(); i++) { Tree child = tree.getChild(i); Property property = makeProperty(child, isRootedInDynamic); if (property instanceof DynamicSimpleProperty) { isRootedInDynamic = true; } properties.add(property); } return new NestedProperty(properties); } /** * Parses a given property name returning an AST. * @param propertyName to parse * @return AST syntax tree */ public static Tree parse(String propertyName) { CharStream input; try { input = new NoCaseSensitiveStream(new StringReader(propertyName)); } catch (IOException ex) { throw new PropertyAccessException("IOException parsing property name '" + propertyName + '\'', ex); } EsperEPL2GrammarLexer lex = new EsperEPL2GrammarLexer(input); CommonTokenStream tokens = new CommonTokenStream(lex); EsperEPL2GrammarParser g = new EsperEPL2GrammarParser(tokens); EsperEPL2GrammarParser.startEventPropertyRule_return r; try { r = g.startEventPropertyRule(); } catch (RuntimeException e) { if (log.isDebugEnabled()) { log.debug("Error parsing property expression [" + propertyName + "]", e); } if (e.getCause() instanceof RecognitionException) { throw ExceptionConvertor.convertProperty((RecognitionException)e.getCause(), propertyName, true, g); } else { throw e; } } catch (RecognitionException e) { // Check for keywords and escape each, parse again String escapedPropertyName = escapeKeywords(tokens); CharStream inputEscaped; try { inputEscaped = new NoCaseSensitiveStream(new StringReader(escapedPropertyName)); } catch (IOException ex) { throw new PropertyAccessException("IOException parsing property name '" + propertyName + '\'', ex); } EsperEPL2GrammarLexer lexEscaped = new EsperEPL2GrammarLexer(inputEscaped); CommonTokenStream tokensEscaped = new CommonTokenStream(lexEscaped); EsperEPL2GrammarParser gEscaped = new EsperEPL2GrammarParser(tokensEscaped); EsperEPL2GrammarParser.startEventPropertyRule_return rEscaped; try { rEscaped = gEscaped.startEventPropertyRule(); return (Tree) rEscaped.getTree(); } catch (Exception eEscaped) { } throw ExceptionConvertor.convertProperty(e, propertyName, true, g); } return (Tree) r.getTree(); } private synchronized static String escapeKeywords(CommonTokenStream tokens) { if (keywordCache == null) { keywordCache = new HashSet<String>(); Set<String> keywords = new EsperEPL2GrammarParser(tokens).getKeywords(); for (String keyword : keywords) { if (keyword.charAt(0) == '\'' && keyword.charAt(keyword.length() - 1) == '\'') { keywordCache.add(keyword.substring(1, keyword.length() - 1)); } } } StringWriter writer = new StringWriter(); for (Object token : tokens.getTokens()) // Call getTokens first before invoking tokens.size! ANTLR problem { Token t = (Token) token; boolean isKeyword = keywordCache.contains(t.getText().toLowerCase()); if (isKeyword) { writer.append('`'); writer.append(t.getText()); writer.append('`'); } else { writer.append(t.getText()); } } return writer.toString(); } /** * Returns true if the property is a dynamic property. * @param ast property ast * @return dynamic or not */ public static boolean isPropertyDynamic(Tree ast) { for (int i = 0; i < ast.getChildCount(); i++) { int type = ast.getChild(i).getType(); if ((type == EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_SIMPLE) || (type == EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_INDEXED) || (type == EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_MAPPED)) { return true; } } return false; } private static Property makeProperty(Tree child, boolean isRootedInDynamic) { switch (child.getType()) { case EsperEPL2GrammarParser.EVENT_PROP_SIMPLE: if (!isRootedInDynamic) { return new SimpleProperty(child.getChild(0).getText()); } else { return new DynamicSimpleProperty(child.getChild(0).getText()); } case EsperEPL2GrammarParser.EVENT_PROP_MAPPED: String key = StringValue.parseString(child.getChild(1).getText()); if (!isRootedInDynamic) { return new MappedProperty(child.getChild(0).getText(), key); } else { return new DynamicMappedProperty(child.getChild(0).getText(), key); } case EsperEPL2GrammarParser.EVENT_PROP_INDEXED: int index = IntValue.parseString(child.getChild(1).getText()); if (!isRootedInDynamic) { return new IndexedProperty(child.getChild(0).getText(), index); } else { return new DynamicIndexedProperty(child.getChild(0).getText(), index); } case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_SIMPLE: return new DynamicSimpleProperty(child.getChild(0).getText()); case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_INDEXED: index = IntValue.parseString(child.getChild(1).getText()); return new DynamicIndexedProperty(child.getChild(0).getText(), index); case EsperEPL2GrammarParser.EVENT_PROP_DYNAMIC_MAPPED: key = StringValue.parseString(child.getChild(1).getText()); return new DynamicMappedProperty(child.getChild(0).getText(), key); default: throw new IllegalStateException("Event property AST node not recognized, type=" + child.getType()); } } public static String unescapeBacktick(String unescapedPropertyName) { if (unescapedPropertyName.startsWith("`") && unescapedPropertyName.endsWith("`")) { return unescapedPropertyName.substring(1, unescapedPropertyName.length() - 1); } else { return unescapedPropertyName; } } }