/*
***************************************************************************************
* 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.epl.parse;
import com.espertech.esper.client.ConfigurationInformation;
import com.espertech.esper.client.ConfigurationPlugInAggregationMultiFunction;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.context.util.ContextDescriptor;
import com.espertech.esper.epl.core.EngineImportException;
import com.espertech.esper.epl.core.EngineImportService;
import com.espertech.esper.epl.core.EngineImportSingleRowDesc;
import com.espertech.esper.epl.core.EngineImportUndefinedException;
import com.espertech.esper.epl.declexpr.ExprDeclaredHelper;
import com.espertech.esper.epl.declexpr.ExprDeclaredNodeImpl;
import com.espertech.esper.epl.declexpr.ExprDeclaredService;
import com.espertech.esper.epl.enummethod.dot.ExprLambdaGoesNode;
import com.espertech.esper.epl.expression.baseagg.ExprAggregateNodeUtil;
import com.espertech.esper.epl.expression.core.ExprChainedSpec;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.epl.expression.dot.ExprDotNode;
import com.espertech.esper.epl.expression.dot.ExprDotNodeImpl;
import com.espertech.esper.epl.expression.funcs.ExprMinMaxRowNode;
import com.espertech.esper.epl.expression.funcs.ExprPlugInSingleRowNode;
import com.espertech.esper.epl.expression.methodagg.ExprMinMaxAggrNode;
import com.espertech.esper.epl.expression.table.ExprTableAccessNode;
import com.espertech.esper.epl.generated.EsperEPL2GrammarParser;
import com.espertech.esper.epl.script.ExprNodeScript;
import com.espertech.esper.epl.spec.ExpressionDeclDesc;
import com.espertech.esper.epl.spec.ExpressionScriptProvided;
import com.espertech.esper.epl.spec.StatementSpecRaw;
import com.espertech.esper.epl.table.mgmt.TableService;
import com.espertech.esper.epl.variable.VariableService;
import com.espertech.esper.plugin.PlugInAggregationMultiFunctionFactory;
import com.espertech.esper.type.MinMaxTypeEnum;
import com.espertech.esper.util.LazyAllocatedMap;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.Tree;
import java.io.StringWriter;
import java.util.*;
public class ASTLibFunctionHelper {
public static List<ExprChainedSpec> getLibFuncChain(List<EsperEPL2GrammarParser.LibFunctionNoClassContext> ctxs, Map<Tree, ExprNode> astExprNodeMap) {
List<ExprChainedSpec> chained = new ArrayList<ExprChainedSpec>(ctxs.size());
for (EsperEPL2GrammarParser.LibFunctionNoClassContext ctx : ctxs) {
ExprChainedSpec chainSpec = ASTLibFunctionHelper.getLibFunctionChainSpec(ctx, astExprNodeMap);
chained.add(chainSpec);
}
return chained;
}
public static ExprChainedSpec getLibFunctionChainSpec(EsperEPL2GrammarParser.LibFunctionNoClassContext ctx, Map<Tree, ExprNode> astExprNodeMap) {
String methodName = ASTConstantHelper.removeTicks(ctx.funcIdentChained().getText());
List<ExprNode> parameters = getExprNodesLibFunc(ctx.libFunctionArgs(), astExprNodeMap);
boolean property = ctx.l == null;
return new ExprChainedSpec(methodName, parameters, property);
}
public static List<ExprNode> getExprNodesLibFunc(EsperEPL2GrammarParser.LibFunctionArgsContext ctx, Map<Tree, ExprNode> astExprNodeMap) {
if (ctx == null) {
return Collections.emptyList();
}
List<EsperEPL2GrammarParser.LibFunctionArgItemContext> args = ctx.libFunctionArgItem();
if (args == null || args.isEmpty()) {
return Collections.emptyList();
}
List<ExprNode> parameters = new ArrayList<ExprNode>(args.size());
for (EsperEPL2GrammarParser.LibFunctionArgItemContext arg : args) {
if (arg.expressionLambdaDecl() != null) {
List<String> lambdaparams = getLambdaGoesParams(arg.expressionLambdaDecl());
ExprLambdaGoesNode goes = new ExprLambdaGoesNode(lambdaparams);
ExprNode lambdaExpr = ASTExprHelper.exprCollectSubNodes(arg.expressionWithNamed(), 0, astExprNodeMap).get(0);
goes.addChildNode(lambdaExpr);
parameters.add(goes);
} else {
ExprNode parameter = ASTExprHelper.exprCollectSubNodes(arg.expressionWithNamed(), 0, astExprNodeMap).get(0);
parameters.add(parameter);
}
}
return parameters;
}
protected static List<String> getLambdaGoesParams(EsperEPL2GrammarParser.ExpressionLambdaDeclContext ctx) {
List<String> parameters;
if (ctx.i != null) {
parameters = new ArrayList<String>(1);
parameters.add(ctx.i.getText());
} else {
parameters = ASTUtil.getIdentList(ctx.columnList());
}
return parameters;
}
public static void handleLibFunc(CommonTokenStream tokenStream,
EsperEPL2GrammarParser.LibFunctionContext ctx,
ConfigurationInformation configurationInformation,
EngineImportService engineImportService,
Map<Tree, ExprNode> astExprNodeMap,
LazyAllocatedMap<ConfigurationPlugInAggregationMultiFunction, PlugInAggregationMultiFunctionFactory> plugInAggregations,
String engineURI,
ExpressionDeclDesc expressionDeclarations,
ExprDeclaredService exprDeclaredService,
List<ExpressionScriptProvided> scriptExpressions,
ContextDescriptor contextDescriptor,
TableService tableService,
StatementSpecRaw statementSpec,
VariableService variableService) {
ASTLibModel model = getModel(ctx, tokenStream);
boolean duckType = configurationInformation.getEngineDefaults().getExpression().isDuckTyping();
boolean udfCache = configurationInformation.getEngineDefaults().getExpression().isUdfCache();
// handle "some.xyz(...)" or "some.other.xyz(...)"
if (model.chainElements.size() == 1 &&
model.optionalClassIdent != null &&
ASTTableExprHelper.checkTableNameGetExprForProperty(tableService, model.optionalClassIdent) == null) {
ExprChainedSpec chainSpec = getLibFunctionChainSpec(model.chainElements.get(0), astExprNodeMap);
ExprDeclaredNodeImpl declaredNode = ExprDeclaredHelper.getExistsDeclaredExpr(model.optionalClassIdent, Collections.<ExprNode>emptyList(), expressionDeclarations.getExpressions(), exprDeclaredService, contextDescriptor);
if (declaredNode != null) {
ExprNode exprNode = new ExprDotNodeImpl(Collections.singletonList(chainSpec), duckType, udfCache);
exprNode.addChildNode(declaredNode);
ASTExprHelper.exprCollectAddSubNodesAddParentNode(exprNode, ctx, astExprNodeMap);
return;
}
List<ExprChainedSpec> chain = new ArrayList<ExprChainedSpec>(2);
chain.add(new ExprChainedSpec(model.getOptionalClassIdent(), Collections.<ExprNode>emptyList(), true));
chain.add(chainSpec);
ExprDotNode dotNode = new ExprDotNodeImpl(chain, configurationInformation.getEngineDefaults().getExpression().isDuckTyping(), configurationInformation.getEngineDefaults().getExpression().isUdfCache());
if (dotNode.isVariableOpGetName(variableService) != null) {
statementSpec.setHasVariables(true);
}
ASTExprHelper.exprCollectAddSubNodesAddParentNode(dotNode, ctx, astExprNodeMap);
return;
}
// try additional built-in single-row function
ExprNode singleRowExtNode = engineImportService.resolveSingleRowExtendedBuiltin(model.getChainElements().get(0).getFuncName());
if (singleRowExtNode != null) {
if (model.chainElements.size() == 1) {
ASTExprHelper.exprCollectAddSubNodesAddParentNode(singleRowExtNode, ctx, astExprNodeMap);
return;
}
List<ExprChainedSpec> spec = new ArrayList<ExprChainedSpec>();
EsperEPL2GrammarParser.LibFunctionArgsContext firstArgs = model.getChainElements().get(0).getArgs();
List<ExprNode> childExpressions = ASTLibFunctionHelper.getExprNodesLibFunc(firstArgs, astExprNodeMap);
singleRowExtNode.addChildNodes(childExpressions);
addChainRemainderFromOffset(model.getChainElements(), 1, spec, astExprNodeMap);
ExprDotNode dotNode = new ExprDotNodeImpl(spec, configurationInformation.getEngineDefaults().getExpression().isDuckTyping(), configurationInformation.getEngineDefaults().getExpression().isUdfCache());
dotNode.addChildNode(singleRowExtNode);
ASTExprHelper.exprCollectAddSubNodesAddParentNode(dotNode, ctx, astExprNodeMap);
return;
}
// try plug-in single-row function
try {
String firstFunction = model.getChainElements().get(0).getFuncName();
boolean firstFunctionIsProperty = !model.getChainElements().get(0).isHasLeftParen();
Pair<Class, EngineImportSingleRowDesc> classMethodPair = engineImportService.resolveSingleRow(firstFunction);
List<ExprChainedSpec> spec = new ArrayList<ExprChainedSpec>();
EsperEPL2GrammarParser.LibFunctionArgsContext firstArgs = model.getChainElements().get(0).getArgs();
List<ExprNode> childExpressions = ASTLibFunctionHelper.getExprNodesLibFunc(firstArgs, astExprNodeMap);
spec.add(new ExprChainedSpec(classMethodPair.getSecond().getMethodName(), childExpressions, firstFunctionIsProperty));
addChainRemainderFromOffset(model.getChainElements(), 1, spec, astExprNodeMap);
ExprPlugInSingleRowNode plugin = new ExprPlugInSingleRowNode(firstFunction, classMethodPair.getFirst(), spec, classMethodPair.getSecond());
ASTExprHelper.exprCollectAddSubNodesAddParentNode(plugin, ctx, astExprNodeMap);
return;
} catch (EngineImportUndefinedException e) {
// Not an single-row function
} catch (EngineImportException e) {
throw new IllegalStateException("Error resolving single-row function: " + e.getMessage(), e);
}
// special case for min,max
String firstFunction = model.getChainElements().get(0).getFuncName();
if ((firstFunction.toLowerCase(Locale.ENGLISH).equals("max")) || (firstFunction.toLowerCase(Locale.ENGLISH).equals("min")) ||
(firstFunction.toLowerCase(Locale.ENGLISH).equals("fmax")) || (firstFunction.toLowerCase(Locale.ENGLISH).equals("fmin"))) {
EsperEPL2GrammarParser.LibFunctionArgsContext firstArgs = model.getChainElements().get(0).getArgs();
handleMinMax(firstFunction, firstArgs, astExprNodeMap);
return;
}
// obtain chain with actual expressions
List<ExprChainedSpec> chain = new ArrayList<ExprChainedSpec>();
addChainRemainderFromOffset(model.getChainElements(), 0, chain, astExprNodeMap);
// add chain element for class info, if any
boolean distinct = model.getChainElements().get(0).getArgs() != null && model.getChainElements().get(0).getArgs().DISTINCT() != null;
if (model.getOptionalClassIdent() != null) {
chain.add(0, new ExprChainedSpec(model.getOptionalClassIdent(), Collections.<ExprNode>emptyList(), true));
distinct = false;
}
firstFunction = chain.get(0).getName();
// try plug-in aggregation function
ExprNode aggregationNode = ASTAggregationHelper.tryResolveAsAggregation(engineImportService, distinct, firstFunction, plugInAggregations, engineURI);
if (aggregationNode != null) {
ExprChainedSpec firstSpec = chain.remove(0);
aggregationNode.addChildNodes(firstSpec.getParameters());
ExprNode exprNode;
if (chain.isEmpty()) {
exprNode = aggregationNode;
} else {
exprNode = new ExprDotNodeImpl(chain, duckType, udfCache);
exprNode.addChildNode(aggregationNode);
}
ASTExprHelper.exprCollectAddSubNodesAddParentNode(exprNode, ctx, astExprNodeMap);
return;
}
// try declared or alias expression
ExprDeclaredNodeImpl declaredNode = ExprDeclaredHelper.getExistsDeclaredExpr(firstFunction, chain.get(0).getParameters(), expressionDeclarations.getExpressions(), exprDeclaredService, contextDescriptor);
if (declaredNode != null) {
chain.remove(0);
ExprNode exprNode;
if (chain.isEmpty()) {
exprNode = declaredNode;
} else {
exprNode = new ExprDotNodeImpl(chain, duckType, udfCache);
exprNode.addChildNode(declaredNode);
}
ASTExprHelper.exprCollectAddSubNodesAddParentNode(exprNode, ctx, astExprNodeMap);
return;
}
// try script
ExprNodeScript scriptNode = ExprDeclaredHelper.getExistsScript(configurationInformation.getEngineDefaults().getScripts().getDefaultDialect(), chain.get(0).getName(), chain.get(0).getParameters(), scriptExpressions, exprDeclaredService);
if (scriptNode != null) {
chain.remove(0);
ExprNode exprNode;
if (chain.isEmpty()) {
exprNode = scriptNode;
} else {
exprNode = new ExprDotNodeImpl(chain, duckType, udfCache);
exprNode.addChildNode(scriptNode);
}
ASTExprHelper.exprCollectAddSubNodesAddParentNode(exprNode, ctx, astExprNodeMap);
return;
}
// try table
Pair<ExprTableAccessNode, List<ExprChainedSpec>> tableInfo = ASTTableExprHelper.checkTableNameGetLibFunc(tableService, engineImportService, plugInAggregations, engineURI, firstFunction, chain);
if (tableInfo != null) {
ASTTableExprHelper.addTableExpressionReference(statementSpec, tableInfo.getFirst());
chain = tableInfo.getSecond();
ExprNode exprNode;
if (chain.isEmpty()) {
exprNode = tableInfo.getFirst();
} else {
exprNode = new ExprDotNodeImpl(chain, duckType, udfCache);
exprNode.addChildNode(tableInfo.getFirst());
}
ASTExprHelper.exprCollectAddSubNodesAddParentNode(exprNode, ctx, astExprNodeMap);
return;
}
// Could be a mapped property with an expression-parameter "mapped(expr)" or array property with an expression-parameter "array(expr)".
ExprDotNode dotNode;
if (chain.size() == 1) {
dotNode = new ExprDotNodeImpl(chain, false, false);
} else {
dotNode = new ExprDotNodeImpl(chain, duckType, udfCache);
}
ASTExprHelper.exprCollectAddSubNodesAddParentNode(dotNode, ctx, astExprNodeMap);
}
private static void addChainRemainderFromOffset(List<ASTLibModelChainElement> chainElements, int offset, List<ExprChainedSpec> specList, Map<Tree, ExprNode> astExprNodeMap) {
for (int i = offset; i < chainElements.size(); i++) {
ExprChainedSpec spec = getLibFunctionChainSpec(chainElements.get(i), astExprNodeMap);
specList.add(spec);
}
}
private static ExprChainedSpec getLibFunctionChainSpec(ASTLibModelChainElement element, Map<Tree, ExprNode> astExprNodeMap) {
String methodName = ASTConstantHelper.removeTicks(element.getFuncName());
List<ExprNode> parameters = ASTLibFunctionHelper.getExprNodesLibFunc(element.getArgs(), astExprNodeMap);
return new ExprChainedSpec(methodName, parameters, !element.isHasLeftParen());
}
private static ASTLibModel getModel(EsperEPL2GrammarParser.LibFunctionContext ctx, CommonTokenStream tokenStream) {
EsperEPL2GrammarParser.LibFunctionWithClassContext root = ctx.libFunctionWithClass();
List<EsperEPL2GrammarParser.LibFunctionNoClassContext> ctxElements = ctx.libFunctionNoClass();
// there are no additional methods
if (ctxElements == null || ctxElements.isEmpty()) {
String classIdent = root.classIdentifier() == null ? null : ASTUtil.unescapeClassIdent(root.classIdentifier());
ASTLibModelChainElement ele = fromRoot(root);
return new ASTLibModel(classIdent, Collections.singletonList(ele));
}
// add root and chain to just a list of elements
List<ASTLibModelChainElement> chainElements = new ArrayList<ASTLibModelChainElement>(ctxElements.size() + 1);
ASTLibModelChainElement rootElement = fromRoot(root);
chainElements.add(rootElement);
for (EsperEPL2GrammarParser.LibFunctionNoClassContext chainedCtx : ctxElements) {
ASTLibModelChainElement chainedElement = new ASTLibModelChainElement(chainedCtx.funcIdentChained().getText(), chainedCtx.libFunctionArgs(), chainedCtx.l != null);
chainElements.add(chainedElement);
}
// determine/remove the list of chain elements, from the start and uninterrupted, that don't have parameters (no parenthesis 'l')
List<ASTLibModelChainElement> chainElementsNoArgs = new ArrayList<ASTLibModelChainElement>(chainElements.size());
Iterator<ASTLibModelChainElement> iterator = chainElements.iterator();
for (; iterator.hasNext(); ) {
ASTLibModelChainElement element = iterator.next();
if (!element.isHasLeftParen()) { // has no parenthesis, therefore part of class identifier
chainElementsNoArgs.add(element);
iterator.remove(); //
} else { // else stop here
break;
}
}
// write the class identifier including the no-arg chain elements
StringWriter classIdentBuf = new StringWriter();
String delimiter = "";
if (root.classIdentifier() != null) {
classIdentBuf.append(ASTUtil.unescapeClassIdent(root.classIdentifier()));
delimiter = ".";
}
for (ASTLibModelChainElement noarg : chainElementsNoArgs) {
classIdentBuf.append(delimiter);
classIdentBuf.append(noarg.getFuncName());
delimiter = ".";
}
if (chainElements.isEmpty()) {
// would this be an event property, but then that is handled greedily by parser
throw ASTWalkException.from("Encountered unrecognized lib function call", tokenStream, ctx);
}
// class ident can be null if empty
String classIdentifierString = classIdentBuf.toString();
String classIdentifier = classIdentifierString.length() > 0 ? classIdentifierString : null;
return new ASTLibModel(classIdentifier, chainElements);
}
public static ASTLibModelChainElement fromRoot(EsperEPL2GrammarParser.LibFunctionWithClassContext root) {
if (root.funcIdentTop() != null) {
return new ASTLibModelChainElement(root.funcIdentTop().getText(), root.libFunctionArgs(), root.l != null);
} else {
return new ASTLibModelChainElement(root.funcIdentInner().getText(), root.libFunctionArgs(), root.l != null);
}
}
// Min/Max nodes can be either an aggregate or a per-row function depending on the number or arguments
private static void handleMinMax(String ident, EsperEPL2GrammarParser.LibFunctionArgsContext ctxArgs, Map<Tree, ExprNode> astExprNodeMap) {
// Determine min or max
String childNodeText = ident;
MinMaxTypeEnum minMaxTypeEnum;
boolean filtered = childNodeText.startsWith("f");
if (childNodeText.toLowerCase(Locale.ENGLISH).equals("min") || childNodeText.toLowerCase(Locale.ENGLISH).equals("fmin")) {
minMaxTypeEnum = MinMaxTypeEnum.MIN;
} else if (childNodeText.toLowerCase(Locale.ENGLISH).equals("max") || childNodeText.toLowerCase(Locale.ENGLISH).equals("fmax")) {
minMaxTypeEnum = MinMaxTypeEnum.MAX;
} else {
throw ASTWalkException.from("Uncountered unrecognized min or max node '" + ident + "'");
}
List<ExprNode> args = Collections.emptyList();
if (ctxArgs != null && ctxArgs.libFunctionArgItem() != null) {
args = ASTExprHelper.exprCollectSubNodes(ctxArgs, 0, astExprNodeMap);
}
int numArgsPositional = ExprAggregateNodeUtil.countPositionalArgs(args);
boolean isDistinct = ctxArgs != null && ctxArgs.DISTINCT() != null;
if (numArgsPositional > 1 && isDistinct && !filtered) {
throw ASTWalkException.from("The distinct keyword is not valid in per-row min and max " +
"functions with multiple sub-expressions");
}
ExprNode minMaxNode;
if (!isDistinct && numArgsPositional > 1 && !filtered) {
// use the row function
minMaxNode = new ExprMinMaxRowNode(minMaxTypeEnum);
} else {
// use the aggregation function
minMaxNode = new ExprMinMaxAggrNode(isDistinct, minMaxTypeEnum, filtered, false);
}
minMaxNode.addChildNodes(args);
astExprNodeMap.put(ctxArgs, minMaxNode);
}
public static class ASTLibModel {
private final String optionalClassIdent;
private final List<ASTLibModelChainElement> chainElements;
public ASTLibModel(String optionalClassIdent, List<ASTLibModelChainElement> chainElements) {
this.optionalClassIdent = optionalClassIdent;
this.chainElements = chainElements;
}
public String getOptionalClassIdent() {
return optionalClassIdent;
}
public List<ASTLibModelChainElement> getChainElements() {
return chainElements;
}
}
public static class ASTLibModelChainElement {
private final String funcName;
private final EsperEPL2GrammarParser.LibFunctionArgsContext args;
private final boolean hasLeftParen;
public ASTLibModelChainElement(String funcName, EsperEPL2GrammarParser.LibFunctionArgsContext args, boolean hasLeftParen) {
this.funcName = funcName;
this.args = args;
this.hasLeftParen = hasLeftParen;
}
public String getFuncName() {
return funcName;
}
public EsperEPL2GrammarParser.LibFunctionArgsContext getArgs() {
return args;
}
public boolean isHasLeftParen() {
return hasLeftParen;
}
}
}