All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.espertech.esper.event.property.PropertyParser Maven / Gradle / Ivy

There is a newer version: 7.1.0
Show newest version
/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  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.client.PropertyAccessException;
import com.espertech.esper.epl.generated.EsperEPL2GrammarLexer;
import com.espertech.esper.epl.generated.EsperEPL2GrammarParser;
import com.espertech.esper.epl.parse.ASTUtil;
import com.espertech.esper.epl.parse.ExceptionConvertor;
import com.espertech.esper.epl.parse.NoCaseSensitiveStream;
import com.espertech.esper.epl.parse.ParseHelper;
import com.espertech.esper.type.IntValue;
import com.espertech.esper.type.StringValue;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Token;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.*;

/**
 * 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 Logger log = LoggerFactory.getLogger(PropertyParser.class);

    private static Set keywordCache;

    public static Property parseAndWalk(String property, boolean isRootedDynamic) {
        return walk(parse(property), isRootedDynamic);
    }

    /**
     * Parses property.
     * For cases when the property is not following the property syntax assume we act lax and assume its a simple property.
     *
     * @param property to parse
     * @return property or SimpleProperty if the property cannot be parsed
     */
    public static Property parseAndWalkLaxToSimple(String property) {
        try {
            return walk(parse(property), false);
        } catch (PropertyAccessException p) {
            return new SimpleProperty(property);
        }
    }

    /**
     * Parse the given property name returning a Property instance for the property.
     *
     * @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
     * @param tree            tree
     * @return Property instance for property
     */
    public static Property walk(EsperEPL2GrammarParser.StartEventPropertyRuleContext tree, boolean isRootedDynamic) {
        if (tree.eventProperty().eventPropertyAtomic().size() == 1) {
            return makeProperty(tree.eventProperty().eventPropertyAtomic(0), isRootedDynamic);
        }

        EsperEPL2GrammarParser.EventPropertyContext propertyRoot = tree.eventProperty();

        List properties = new LinkedList();
        boolean isRootedInDynamic = isRootedDynamic;
        for (EsperEPL2GrammarParser.EventPropertyAtomicContext atomic : propertyRoot.eventPropertyAtomic()) {
            Property property = makeProperty(atomic, 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 EsperEPL2GrammarParser.StartEventPropertyRuleContext 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 = ParseHelper.newLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lex);
        try {
            tokens.fill();
        } catch (RuntimeException e) {
            if (ParseHelper.hasControlCharacters(propertyName)) {
                throw new PropertyAccessException("Unrecognized control characters found in text");
            }
            throw new PropertyAccessException("Failed to parse text: " + e.getMessage());
        }

        EsperEPL2GrammarParser g = ParseHelper.newParser(tokens);
        EsperEPL2GrammarParser.StartEventPropertyRuleContext r;

        try {
            r = g.startEventPropertyRule();
        } catch (RecognitionException e) {
            return handleRecognitionEx(e, tokens, propertyName, g);
        } catch (RuntimeException e) {
            if (log.isDebugEnabled()) {
                log.debug("Error parsing property expression [" + propertyName + "]", e);
            }
            if (e.getCause() instanceof RecognitionException) {
                return handleRecognitionEx((RecognitionException) e.getCause(), tokens, propertyName, g);
            } else {
                throw e;
            }
        }

        return r;
    }

    private static EsperEPL2GrammarParser.StartEventPropertyRuleContext handleRecognitionEx(RecognitionException e, CommonTokenStream tokens, String propertyName, EsperEPL2GrammarParser g) {
        // 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 = ParseHelper.newLexer(inputEscaped);
        CommonTokenStream tokensEscaped = new CommonTokenStream(lexEscaped);
        EsperEPL2GrammarParser gEscaped = ParseHelper.newParser(tokensEscaped);

        try {
            return gEscaped.startEventPropertyRule();
        } catch (Exception eEscaped) {
        }

        throw ExceptionConvertor.convertProperty(e, propertyName, true, g);
    }

    private synchronized static String escapeKeywords(CommonTokenStream tokens) {

        if (keywordCache == null) {
            keywordCache = new HashSet();
            Set keywords = ParseHelper.newParser(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();
        // Call getTokens first before invoking tokens.size! ANTLR problem
        for (Object token : tokens.getTokens()) {
            Token t = (Token) token;
            if (t.getType() == EsperEPL2GrammarLexer.EOF) {
                break;
            }
            boolean isKeyword = keywordCache.contains(t.getText().toLowerCase(Locale.ENGLISH));
            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(EsperEPL2GrammarParser.StartEventPropertyRuleContext ast) {
        List ctxs = ast.eventProperty().eventPropertyAtomic();
        for (EsperEPL2GrammarParser.EventPropertyAtomicContext ctx : ctxs) {
            if (ctx.q != null || ctx.q1 != null) {
                return true;
            }
        }
        return false;
    }

    private static Property makeProperty(EsperEPL2GrammarParser.EventPropertyAtomicContext atomic, boolean isRootedInDynamic) {
        String prop = ASTUtil.unescapeDot(atomic.eventPropertyIdent().getText());
        if (prop.length() == 0) {
            throw new PropertyAccessException("Invalid zero-length string provided as an event property name");
        }
        if (atomic.lb != null) {
            int index = IntValue.parseString(atomic.ni.getText());
            if (!isRootedInDynamic && atomic.q == null) {
                return new IndexedProperty(prop, index);
            } else {
                return new DynamicIndexedProperty(prop, index);
            }
        } else if (atomic.lp != null) {
            String key = StringValue.parseString(atomic.s.getText());
            if (!isRootedInDynamic && atomic.q == null) {
                return new MappedProperty(prop, key);
            } else {
                return new DynamicMappedProperty(prop, key);
            }
        } else {
            if (!isRootedInDynamic && atomic.q1 == null) {
                return new SimpleProperty(prop);
            } else {
                return new DynamicSimpleProperty(prop);
            }
        }
    }

    public static String unescapeBacktick(String unescapedPropertyName) {
        if (unescapedPropertyName.startsWith("`") && unescapedPropertyName.endsWith("`")) {
            return unescapedPropertyName.substring(1, unescapedPropertyName.length() - 1);
        }

        if (!unescapedPropertyName.contains("`")) {
            return unescapedPropertyName;
        }

        // parse and render
        Property property = PropertyParser.parseAndWalkLaxToSimple(unescapedPropertyName);
        if (property instanceof NestedProperty) {
            StringWriter writer = new StringWriter();
            property.toPropertyEPL(writer);
            return writer.toString();
        }

        return unescapedPropertyName;
    }

    public static boolean isNestedPropertyWithNonSimpleLead(EsperEPL2GrammarParser.EventPropertyContext ctx) {
        if (ctx.eventPropertyAtomic().size() == 1) {
            return false;
        }
        EsperEPL2GrammarParser.EventPropertyAtomicContext atomic = ctx.eventPropertyAtomic().get(0);
        return atomic.lb != null || atomic.lp != null || atomic.q1 != null;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy