io.pipelite.expression.core.ExpressionImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pipelite-expression Show documentation
Show all versions of pipelite-expression Show documentation
Pipelite, a lightweight Java EAI framework
The newest version!
/*
* Copyright (C) 2023-2024 the original author or authors.
*
* Licensed 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
*
* https://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 io.pipelite.expression.core;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Stack;
import io.pipelite.expression.Expression;
import io.pipelite.expression.core.context.EvaluationContext;
import io.pipelite.expression.core.context.concurrent.ConcurrentEvaluationException;
import io.pipelite.expression.core.el.ShuntingYardFunction;
import io.pipelite.expression.core.el.ShuntingYardFunctionImpl;
import io.pipelite.expression.core.el.Token;
import io.pipelite.expression.core.el.TokenType;
import io.pipelite.expression.core.el.ast.AbstractLazyFunction;
import io.pipelite.expression.core.el.ast.BeanPathExpression;
import io.pipelite.expression.core.el.ast.HexLiteral;
import io.pipelite.expression.core.el.ast.LazyObject;
import io.pipelite.expression.core.el.ast.LazyParams;
import io.pipelite.expression.core.el.ast.Literal;
import io.pipelite.expression.core.el.ast.OperationResult;
import io.pipelite.expression.core.el.ast.Operator;
import io.pipelite.expression.core.el.ast.TextLiteral;
import io.pipelite.expression.core.el.ast.Type;
import io.pipelite.expression.core.el.ast.Variable;
import io.pipelite.expression.core.el.ast.impl.IfFunction;
import io.pipelite.expression.core.el.bean.ElResolver;
import io.pipelite.common.support.Preconditions;
import io.pipelite.expression.support.conversion.ConversionService;
import io.pipelite.expression.support.conversion.exception.CannotConvertValueException;
public class ExpressionImpl implements Expression {
private final String expression;
private final ConversionService conversionService;
private final ElResolver elResolver;
private ShuntingYardFunction shuntingYardAlgorithm;
private boolean evaluating;
/**
* The cached RPN (Reverse Polish Notation) of the expression.
*/
private List rpn = null;
protected final LazyObject PARAMS_START = new LazyObject() {
@Override
public Object eval() {
return null;
}
};
public ExpressionImpl(String expression, ElResolver elResolver, ConversionService conversionService) {
Preconditions.notNull(expression, "Expression is required");
Preconditions.notNull(elResolver, "ElResolver is required");
Preconditions.notNull(conversionService, "ConversionService is required");
this.expression = expression;
this.conversionService = conversionService;
this.elResolver = elResolver;
}
/*
* @Override public Expression putVariable(String name, Object value) { variableRegistry.put(name.toLowerCase(),
* value); return this; }
*
* @Override public void clearVariables() { variableRegistry.clear(); }
*/
@Override
public String asText() {
return expression;
}
/**
* Checks whether the expression is a boolean expression. An expression is considered a boolean expression, if the
* last operator or function is boolean. The IF function is handled special. If the third parameter is boolean, then
* the IF is also considered boolean, else non-boolean.
*
* @return true
if the last operator/function was a boolean.
*/
public boolean isBoolean(EvaluationContext evaluationContext) {
List rpn = getRPN(evaluationContext);
if (rpn.size() > 0) {
for (int i = rpn.size() - 1; i >= 0; i--) {
Token t = rpn.get(i);
if (t.sameTypeOf(TokenType.FUNCTION)) {
Optional functionHolder = evaluationContext
.tryResolveFunction(t.getSurface());
if (functionHolder.isPresent()) {
AbstractLazyFunction function = functionHolder.get();
if (IfFunction.class.isAssignableFrom(function.getClass())) {
/*
* The IF function is handled special. If the third parameter is boolean, then the IF is
* also considered a boolean. Just skip the IF function to check the second parameter.
*/
continue;
}
return function.isBooleanFunction();
}
}
else if (t.sameTypeOf(TokenType.OPERATOR)) {
Operator operator = evaluationContext.tryResolveOperator(t.getSurface()).get();
return operator.isBooleanOperator();
}
}
}
return false;
}
public Object evaluate(EvaluationContext evaluationContext) {
try {
if (evaluating) {
throw new ConcurrentEvaluationException();
}
evaluating = true;
return evaluateInternal(evaluationContext);
}
finally {
evaluating = false;
}
}
public T evaluateAs(Class type, EvaluationContext evaluationContext) {
Object result = evaluate(evaluationContext);
if (result == null) {
return null;
}
else if (type.isAssignableFrom(result.getClass())) {
return type.cast(result);
}
else if (conversionService.canConvert(result.getClass(), type)) {
return conversionService.convert(result, type);
}
throw new ExpressionException(String.format("Cannot evaluate expression %s as %s", expression, type));
}
@Override
public String evaluateAsText(EvaluationContext evaluationContext) {
return evaluateAs(String.class, evaluationContext);
}
private Object evaluateInternal(EvaluationContext evaluationContext) {
Stack stack = new Stack();
for (final Token token : getRPN(evaluationContext)) {
final String surface = token.getSurface();
switch (token.getType()) {
case UNARY_OPERATOR: {
final LazyObject holder = stack.pop();
Object value = holder.eval();
Operator operator = tryResolveOperator(surface, evaluationContext).get();
value = tryConvert(value, operator.getInputType());
LazyObject result = new OperationResult(operator, value, null);
stack.push(result);
break;
}
case OPERATOR:
final LazyObject holder2 = stack.pop();
final LazyObject holder1 = stack.pop();
Operator operator = tryResolveOperator(surface, evaluationContext).get();
Object value1 = tryConvert(holder1.eval(), operator.getInputType());
Object value2 = tryConvert(holder2.eval(), operator.getInputType());
stack.push(new OperationResult(operator, value1, value2));
break;
case VARIABLE:
Optional variableHolder = tryFindVariable(surface, evaluationContext);
if (!variableHolder.isPresent()) {
throw new ExpressionException("Unknown variable " + token);
}
stack.push(variableHolder.get());
break;
case BEAN_PATH_EXPRESSION:
stack.push(new BeanPathExpression(surface, evaluationContext, elResolver));
break;
case FUNCTION:
String functionName = surface.toUpperCase(Locale.ROOT);
Optional functionHolder = tryResolveFunction(functionName, evaluationContext);
if (!functionHolder.isPresent()) {
throw new IllegalArgumentException(String.format("Cannot resolve function %s", functionName));
}
AbstractLazyFunction function = functionHolder.get();
LazyParams parameters = function.numParamsVaries() ? LazyParams.variableLazyParams()
: LazyParams.ofSize(function.getNumParams());
// pop parameters off the stack until we hit the start of
// this function's parameter list
while (!stack.isEmpty() && stack.peek() != PARAMS_START) {
parameters.add(0, stack.pop());
}
if (stack.peek() == PARAMS_START) {
stack.pop();
}
LazyObject functionResult = function.lazyEval(parameters);
stack.push(functionResult);
break;
case OPEN_BRAKET:
stack.push(PARAMS_START);
break;
case LITERAL:
stack.push(new Literal(surface));
break;
case STRINGPARAM:
stack.push(new TextLiteral(surface));
break;
case HEX_LITERAL:
stack.push(new HexLiteral(surface));
break;
default:
break;
}
}
return stack.pop().eval();
}
private Object tryConvert(Object source, Type targetType) {
if (source == null) {
return null;
}
else if (targetType.getJavaType().equals(source.getClass())) {
return source;
}
else {
if (!targetType.supports(source.getClass())) {
if (conversionService.canConvert(source.getClass(), targetType.getJavaType())) {
return conversionService.convert(source, targetType.getJavaType());
}
throw new CannotConvertValueException(
String.format("Cannot convert from %s to %s", source.getClass(), targetType.getJavaType()));
}
else {
if (Number.class.isAssignableFrom(source.getClass())) {
if (conversionService.canConvert(source.getClass(), Type.NUMERIC.getJavaType())) {
return conversionService.convert(source, Type.NUMERIC.getJavaType());
}
}
return targetType.getJavaType().cast(source);
}
}
}
/**
* Cached access to the RPN notation of this expression, ensures only one calculation of the RPN per expression
* instance. If no cached instance exists, a new one will be created and put to the cache.
*
* @return The cached RPN instance.
*/
protected List getRPN(EvaluationContext evaluationContext) {
if (rpn == null) {
if (shuntingYardAlgorithm == null) {
shuntingYardAlgorithm = new ShuntingYardFunctionImpl(evaluationContext.getOperatorRegistry(),
evaluationContext.getFunctionRegistry());
}
rpn = shuntingYardAlgorithm.apply(this.expression);
validate(rpn, evaluationContext);
}
return rpn;
}
protected Optional tryFindVariable(String name, EvaluationContext evaluationContext) {
if (name != null) {
Optional