
com.alibaba.qlexpress4.Express4Runner Maven / Gradle / Ivy
package com.alibaba.qlexpress4;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import com.alibaba.qlexpress4.aparser.*;
import com.alibaba.qlexpress4.aparser.compiletimefunction.CompileTimeFunction;
import com.alibaba.qlexpress4.api.BatchAddFunctionResult;
import com.alibaba.qlexpress4.api.QLFunctionalVarargs;
import com.alibaba.qlexpress4.exception.QLException;
import com.alibaba.qlexpress4.exception.QLSyntaxException;
import com.alibaba.qlexpress4.runtime.*;
import com.alibaba.qlexpress4.runtime.context.ExpressContext;
import com.alibaba.qlexpress4.runtime.context.MapExpressContext;
import com.alibaba.qlexpress4.runtime.context.ObjectFieldExpressContext;
import com.alibaba.qlexpress4.runtime.context.QLAliasContext;
import com.alibaba.qlexpress4.runtime.function.CustomFunction;
import com.alibaba.qlexpress4.runtime.function.QMethodFunction;
import com.alibaba.qlexpress4.runtime.instruction.QLInstruction;
import com.alibaba.qlexpress4.runtime.operator.CustomBinaryOperator;
import com.alibaba.qlexpress4.runtime.operator.OperatorManager;
import com.alibaba.qlexpress4.utils.BasicUtil;
import com.alibaba.qlexpress4.utils.QLFunctionUtil;
/**
* Author: DQinYuan
*/
public class Express4Runner {
private final OperatorManager operatorManager = new OperatorManager();
private final Map> compileCache = new ConcurrentHashMap<>();
private final Map userDefineFunction = new ConcurrentHashMap<>();
private final Map compileTimeFunctions = new ConcurrentHashMap<>();
private final GeneratorScope globalScope = new GeneratorScope(null, "global", new ConcurrentHashMap<>());
private final ReflectLoader reflectLoader;
private final InitOptions initOptions;
public Express4Runner(InitOptions initOptions) {
this.initOptions = initOptions;
this.reflectLoader = new ReflectLoader(initOptions.getSecurityStrategy(), initOptions.getExtensionFunctions(),
initOptions.isAllowPrivateAccess());
SyntaxTreeFactory.warmUp();
}
public CustomFunction getFunction(String functionName) {
return userDefineFunction.get(functionName);
}
public CompileTimeFunction getCompileTimeFunction(String functionName) {
return compileTimeFunctions.get(functionName);
}
/**
* execute the script with variables set in the context, where the key corresponds to the variable name.
* @param script
* @param context
* @param qlOptions
* @return result of script
* @throws QLException
*/
public Object execute(String script, Map context, QLOptions qlOptions) throws QLException {
return execute(script, new MapExpressContext(context), qlOptions);
}
/**
* execute the script with variables set in the context, where the key corresponds to the field name of context object
* @param script
* @param context
* @param qlOptions
* @return
* @throws QLException
*/
public Object execute(String script, Object context, QLOptions qlOptions) throws QLException {
return execute(script, new ObjectFieldExpressContext(context, reflectLoader), qlOptions);
}
/**
* Execute the script using objects that have been annotated with the com.alibaba.qlexpress4.annotation.QLAlias.
* The QLAlias.value will serve as the context keys for these objects.
* Objects without the QLAlias annotation will be disregarded.
* @param script
* @param qlOptions
* @param objects
* @return
* @throws QLException
*/
public Object executeWithAliasObjects(String script, QLOptions qlOptions, Object...objects) {
return execute(script, new QLAliasContext(objects), qlOptions);
}
public Object execute(String script, ExpressContext context, QLOptions qlOptions) {
QLambda mainLambda;
if (initOptions.isDebug()) {
long start = System.currentTimeMillis();
mainLambda = parseToLambda(script, context, qlOptions);
initOptions.getDebugInfoConsumer().accept(
"Compile consume time: " + (System.currentTimeMillis() - start) + " ms"
);
} else {
mainLambda = parseToLambda(script, context, qlOptions);
}
try {
if (initOptions.isDebug()) {
long start = System.currentTimeMillis();
Object result = mainLambda.call().getResult().get();
initOptions.getDebugInfoConsumer().accept("Execute consume time: " + (System.currentTimeMillis() - start) + " ms");
return result;
} else {
return mainLambda.call().getResult().get();
}
} catch (QLException e) {
throw e;
} catch (Throwable nuKnown) {
// should not run here
throw new RuntimeException(nuKnown);
}
}
public Set getOutVarNames(String script) {
QLParser.ProgramContext programContext = parseToSyntaxTree(script);
OutVarNamesVisitor outVarNamesVisitor = new OutVarNamesVisitor();
programContext.accept(outVarNamesVisitor);
return outVarNamesVisitor.getOutVars();
}
/**
* add user defined global macro to QLExpress engine
* @param name macro name
* @param macroScript script for macro
* @return true if add macro successfully. fail if macro name already exists.
*/
public boolean addMacro(String name, String macroScript) {
QLParser.ProgramContext macroProgram = parseToSyntaxTree(macroScript);
QvmInstructionVisitor macroVisitor = new QvmInstructionVisitor(macroScript,
inheritDefaultImport(), new GeneratorScope("MACRO_" + name, globalScope),
operatorManager, QvmInstructionVisitor.Context.MACRO,
compileTimeFunctions, initOptions.getInterpolationMode());
macroProgram.accept(macroVisitor);
List macroInstructions = macroVisitor.getInstructions();
List blockStatementContexts = macroProgram.blockStatements()
.blockStatement();
boolean lastStmtExpress = !blockStatementContexts.isEmpty() &&
blockStatementContexts.get(blockStatementContexts.size() - 1) instanceof QLParser.ExpressionStatementContext;
return globalScope.defineMacroIfAbsent(name, new MacroDefine(macroInstructions, lastStmtExpress));
}
/**
* add user defined function to QLExpress engine
* @param name function name
* @param function function definition
* @return true if add function successfully. fail if function name already exists or method is not public.
*/
public boolean addFunction(String name, CustomFunction function) {
CustomFunction preFunction = userDefineFunction.putIfAbsent(name, function);
return preFunction == null;
}
public boolean addFunction(String name, Function function) {
return addFunction(name, (qContext, parameters) -> {
T t = parameters.size() > 0? (T) parameters.get(0).get(): null;
return function.apply(t);
});
}
public boolean addVarArgsFunction(String name, QLFunctionalVarargs functionalVarargs) {
return addFunction(name, (qContext, parameters) -> {
Object[] paramArr = new Object[parameters.size()];
for (int i = 0; i < paramArr.length; i++) {
paramArr[i] = parameters.get(i).get();
}
return functionalVarargs.call(paramArr);
});
}
public boolean addFunction(String name, Predicate predicate) {
return addFunction(name, (qContext, parameters) -> {
T t = parameters.size() > 0? (T) parameters.get(0).get(): null;
return predicate.test(t);
});
}
public boolean addFunction(String name, Runnable runnable) {
return addFunction(name, (qContext, parameters) -> {
runnable.run();
return null;
});
}
public boolean addFunction(String name, Consumer consumer) {
return addFunction(name, (qContext, parameters) -> {
T t = parameters.size() > 0? (T) parameters.get(0).get(): null;
consumer.accept(t);
return null;
});
}
/**
* add object member method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction} as function
* @param object object with member method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction}
* @return succ and fail functions. fail if function name already exists or method is not public
*/
public BatchAddFunctionResult addObjFunction(Object object) {
return addFunctionByAnnotation(object.getClass(), object);
}
/**
* add class static method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction} as function
* @param clazz class with static method with annotation {@link com.alibaba.qlexpress4.annotation.QLFunction}
* @return succ and fail functions. fail if function name already exists or method is not public
*/
public BatchAddFunctionResult addStaticFunction(Class> clazz) {
return addFunctionByAnnotation(clazz, null);
}
private BatchAddFunctionResult addFunctionByAnnotation(Class> clazz, Object object) {
BatchAddFunctionResult result = new BatchAddFunctionResult();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (!BasicUtil.isPublic(method)) {
result.getSucc().add(method.getName());
continue;
}
if (QLFunctionUtil.containsQLFunctionForMethod(method)) {
for (String functionName : QLFunctionUtil.getQLFunctionValue(method)) {
boolean addResult = addFunction(functionName, new QMethodFunction(object, method));
(addResult? result.getSucc(): result.getFail()).add(method.getName());
}
}
}
return result;
}
/**
* add compile time function
* @param name function name
* @param compileTimeFunction definition
* @return true if successful
*/
public boolean addCompileTimeFunction(String name, CompileTimeFunction compileTimeFunction) {
return compileTimeFunctions.putIfAbsent(name, compileTimeFunction) == null;
}
public QLParser.ProgramContext parseToSyntaxTree(String script) {
return SyntaxTreeFactory.buildTree(
script, operatorManager, initOptions.isDebug(), false,
initOptions.getDebugInfoConsumer(), initOptions.getInterpolationMode()
);
}
public QLambda parseToLambda(String script, ExpressContext context, QLOptions qlOptions) {
QLambdaDefinitionInner mainLambdaDefine = qlOptions.isCache()?
parseDefinitionWithCache(script): parseDefinition(script);
if (initOptions.isDebug()) {
initOptions.getDebugInfoConsumer().accept("\nInstructions:");
mainLambdaDefine.println(0, initOptions.getDebugInfoConsumer());
}
QvmRuntime qvmRuntime = new QvmRuntime(qlOptions.getAttachments(), reflectLoader, System.currentTimeMillis());
QvmGlobalScope globalScope = new QvmGlobalScope(context, userDefineFunction, qlOptions);
return mainLambdaDefine.toLambda(new DelegateQContext(qvmRuntime, globalScope), qlOptions, true);
}
private QLambdaDefinitionInner parseDefinitionWithCache(String script) {
try {
return getParseFuture(script).get();
} catch (Exception e) {
Throwable compileException = e.getCause();
throw compileException instanceof QLSyntaxException? (QLSyntaxException) compileException:
new RuntimeException(compileException);
}
}
private Future getParseFuture(String script) {
Future parseFuture = compileCache.get(script);
if (parseFuture != null) {
return parseFuture;
}
FutureTask parseTask = new FutureTask<>(() -> parseDefinition(script));
Future preTask = compileCache.putIfAbsent(script, parseTask);
if (preTask == null) {
parseTask.run();
}
return parseTask;
}
private QLambdaDefinitionInner parseDefinition(String script) {
QLParser.ProgramContext program = parseToSyntaxTree(script);
QvmInstructionVisitor qvmInstructionVisitor = new QvmInstructionVisitor(script, inheritDefaultImport(),
globalScope, operatorManager, compileTimeFunctions, initOptions.getInterpolationMode());
program.accept(qvmInstructionVisitor);
return new QLambdaDefinitionInner("main",
qvmInstructionVisitor.getInstructions(), Collections.emptyList(),
qvmInstructionVisitor.getMaxStackSize());
}
private ImportManager inheritDefaultImport() {
return new ImportManager(initOptions.getClassSupplier(), initOptions.getDefaultImport());
}
public boolean addOperatorBiFunction(String operator, BiFunction biFunction) {
return operatorManager.addBinaryOperator(operator, (left, right) ->
biFunction.apply((T) left.get(), (U) right.get()), QLPrecedences.MULTI
);
}
/**
* add operator with multi precedences
* @param operator operator name
* @param customBinaryOperator operator implement
* @return true if add operator successfully; false if operator already exist
*/
public boolean addOperator(String operator, CustomBinaryOperator customBinaryOperator) {
return operatorManager.addBinaryOperator(operator, customBinaryOperator, QLPrecedences.MULTI);
}
/**
* add operator
* @param operator operator name
* @param customBinaryOperator operator implement
* @param precedence precedence, see {@link QLPrecedences}
* @return true if add operator successfully; false if operator already exist
*/
public boolean addOperator(String operator, CustomBinaryOperator customBinaryOperator, int precedence) {
return operatorManager.addBinaryOperator(operator, customBinaryOperator, precedence);
}
/**
* @param operator operator name
* @param customBinaryOperator operator implement
* @return true if replace operator successfully; false if default operator not exists
*/
public boolean replaceDefaultOperator(String operator, CustomBinaryOperator customBinaryOperator) {
return operatorManager.replaceDefaultOperator(operator, customBinaryOperator);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy