One, foreword

Recently, a company developed a system for us. Through the analysis of the source code, the most important modules of the whole system are the three modules of stored procedure, formula calculation and process approval. As for the formula calculation module, I have been thinking about how to realize the formula calculation. Can you configure a formula like Excel and then parse it in Java and return the result? A chance to find alibaba’s open source QLExpress on Github, it is a dynamic script engine parsing tool, it has the following features:

  • **1, thread safety, ** engine generated during the calculation of temporary variables are type ThreadLocal.
  • ** efficient execution, ** time-consuming script compilation process can be cached on the local machine, runtime temporary variable creation using buffer pool technology, similar to Groovy performance.
  • ** weakly typed scripting languages, ** and Groovy, javascript syntax is similar, although slower than the strongly typed scripting languages, but make the business much more flexible.
  • **4, security control,** can be set by setting the relevant operation parameters, to prevent infinite loop, high-risk system API call and other situations.
  • **5, the code is simple, the dependency is minimum, the jar package of ** 250K is suitable for all Java running environment, and is also widely used in the low-end POS machine of Android system.

Second, grammar related introduction

Rely on

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>QLExpress</artifactId>
  <version>3.2. 0</version>
</dependency>
Copy the code

2.1 Basic Implementation

//1. Syntax analysis and calculation
ExpressRunner runner = new ExpressRunner();
//2. Store context information
DefaultContext<String,Object> context = new DefaultContext<>();
context.put("chinese".98);
context.put("math".100);
context.put("english".78);

//3. Execute the statement Chinese + Math + English and pass in the context
Object executeResult = runner.execute("chinese+math+english", context, null.true.false);
System.out.println(executeResult);
Copy the code

2.2 Differences with Java Objects

  • Does not support the try catch {} {}
  • Java8 lambda expressions are not supported
  • For (GRCRouteLineResultDTO item: list)
  • In weakly typed languages, do not define type declarations, and do not use Templete (Map

    , etc.)
    ,list>
  • The array declaration is different
  • Min, Max, round, print and println, like, all is the key to the system default function in word, please don’t as a variable name

2.3 Extended operators :Operator

Alias the associated operator

// Syntax analysis and calculator
ExpressRunner runner = new ExpressRunner();

/** * take an alias for the symbol */
runner.addOperatorWithAlias("如果"."if".null);
runner.addOperatorWithAlias("More than".">".null);
runner.addOperatorWithAlias("则"."then".null);
runner.addOperatorWithAlias("Otherwise"."else".null);

// Calculate the formula
String exp = "If (Chinese is greater than English) {return 1; } otherwise {return 0; }";

// Add attributes
DefaultContext<String,Object> context = new DefaultContext<>();
context.put("chinese".200);
context.put("english".100);

// Execute the operation
Object execute = runner.execute(exp, context, null.true.false);
System.out.println(execute);
Copy the code

Above, let’s break down the content:

  • Define parsing and calculators
// Syntax analysis and calculator
ExpressRunner runner = new ExpressRunner();
Copy the code
  • Alias the operator
/** * take an alias for the symbol */
runner.addOperatorWithAlias("如果"."if".null);
runner.addOperatorWithAlias("More than".">".null);
runner.addOperatorWithAlias("则"."then".null);
runner.addOperatorWithAlias("Otherwise"."else".null);

Copy the code
  • Define calculation formulas and properties
// Calculate the formula
String exp = "If (Chinese is greater than English) {return 1; } otherwise {return 0; }";

// Add attributes
DefaultContext<String,Object> context = new DefaultContext<>();
context.put("chinese".200);
context.put("english".100);
Copy the code
  • Perform operations
// Execute the operation
Object execute = runner.execute(exp, context, null.true.false);
System.out.println(execute);
Copy the code

2.4 Customizing an Operator

Create a custom JoinOperator and inherit from the Operator

/** * Custom Opperator */
public class JoinOperator extends Operator {

    @Override
    public Object executeInner(Object[] list) throws Exception {
        Object opdata1 = list[0];
        Object opdata2 = list[1];
        // If it is a collection, add it directly to the list
        if(opdata1 instanceof java.util.List){
            ((java.util.List)opdata1).add(opdata2);
            return opdata1;
        }else{
            // If it is not a list, create a list to store data
            List result = new ArrayList();
            result.add(opdata1);
            result.add(opdata2);
            returnresult; }}}Copy the code

The first method, addOperator, injects a custom Operator

// Syntax analysis and calculation of the entry class
ExpressRunner runner = new ExpressRunner();
// Inject our own Operator, alias it join, and use join in the expression when calling it
runner.addOperator("join".new JoinOperator());
DefaultContext<String,Object> context = new DefaultContext<>();
/ / expression
String exp = "1 join 2 join 3";
/ / execution
Object result = runner.execute(exp, context, null.true.true);
System.out.println("result:"+result);
Copy the code
22:23:56.337 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 1
22:23:56.338 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 2
22:23:56.338 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - join(1.2)
22:23:56.339 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 3
22:23:56.339 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - join([1.2].3)
result:[1.2.3]
Copy the code

The second method: replaceOperator

// Syntax analysis and calculation of the entry class
ExpressRunner runner = new ExpressRunner();

// Replace operator
runner.replaceOperator("+".new JoinOperator());

DefaultContext<String,Object> context = new DefaultContext<>();
/ / expression
String exp = "1 + 2 + 3";
/ / execution
Object result = runner.execute(exp, context, null.true.true);
System.out.println("result:"+result);
Copy the code
22:27:29.475 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 1
22:27:29.477 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 2
22:27:29.477 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - +(1.2)
22:27:29.478 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 3
22:27:29.478 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - +([1.2].3)
result:[1.2.3]
Copy the code

The third method: addFunction

 // Syntax analysis and calculation of the entry class
 ExpressRunner runner = new ExpressRunner();

 -----> join(1,2)
 runner.addFunction("join".new JoinOperator());

 DefaultContext<String,Object> context = new DefaultContext<>();

 / / execution
 Object result = runner.execute("Join (1, 2)", context, null.true.true);
 System.out.println("result:"+result);
Copy the code
22:29:59.091 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 1
22:29:59.093 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - LoadData 2
22:29:59.093 [main] DEBUG com.ql.util.express.instruction.detail.Instruction - join(1.2)
result:[1.2]
Copy the code

2.5 Binding methods to Java classes or objects

First, you need to define a class that has two methods

  • Upper converts to uppercase
  • Equels Compares two strings
public class ClassExample {

    /** * Convert to uppercase *@paramSTR The string * to be converted@return* /
    public static String upper(String str){
        return str.toUpperCase();
    }

    /** * Determine whether two strings are equal *@param str1
     * @param str2
     * @return* /
    public boolean equals(String str1,String str2){
        returnstr1.equals(str2); }}Copy the code

Then, call the above method

  • addFunctionOfClassMethod
public void addFunctionOfClassMethod(String name, String aClassName, String aFunctionName, String[] aParameterTypes, String errorInfo)
/** * Add a class function definition, for example: math.abs (double) maps to the expression "take the absolute value (-5.0)" * Params: * Name - Function name (alias) * aClassName - Class name * aFunctionName - Method name in class * aParameterTypes - Parameter type name of method * errorInfo - If the result of the function is false, an error message */ is printed
Copy the code
  • 1. Use JDK built-in methods
ExpressRunner runner = new ExpressRunner();
DefaultContext<String,Object> context = new DefaultContext<>();

/** * Add a class function definition, for example: math.abs (double) maps to the expression "take the absolute value (-5.0)" * Params: * Name - Function name (alias) * aClassName - Class name * aFunctionName - Method name in class * aParameterTypes - Parameter type name of method * errorInfo - If the result of the function is false, an error message */ is printed
runner.addFunctionOfClassMethod("Take the absolute value.",Math.class.getName(),"abs".new String[]{"double"},null);

AddFunctionOfClassMethod is an alias defined by addFunctionOfClassMethod
Object execute = runner.execute("Take the absolute value (-100).", context, null.true.true);
System.out.println(execute);
Copy the code
  • 2. Use a custom class method, such as the equels method in the ClassExample
ExpressRunner runner = new ExpressRunner();
DefaultContext<String,Object> context = new DefaultContext<>();

/** * Add a class function definition, for example: math.abs (double) maps to the expression "take the absolute value (-5.0)" * Params: * Name - Function name (alias) * aClassName - Class name * aFunctionName - Method name in class * aParameterTypes - Parameter type name of method * errorInfo - If the result of the function is false, an error message */ is printed

New String[]{'String','String'} new String[]{'String','String'}
runner.addFunctionOfClassMethod("example",ClassExample.class.getName(),"equals".new String[]{"String"."String"},null);

// Example (yangzinan,mic)
Object execute = runner.execute("example(\"yangzinan\",\"mic\")", context, null.true.true);
System.out.println(execute);
Copy the code
  • addFunctionOfServiceMethod
public void addFunctionOfServiceMethod(String name, Object aServiceObject, String aFunctionName, String[] aParameterTypes, String errorInfo)
  
       /** * Function used to convert a user-defined object (such as a Spring object) method into an expression evaluation * Params: * name - alias * aServiceObject - Create object * aFunctionName - Method name * aParameterTypes - Parameter type * errorInfo - */
  
  
Copy the code
ExpressRunner runner = new ExpressRunner();
        DefaultContext<String,Object> context = new DefaultContext<>();

        /** * Function used to convert a user-defined object (such as a Spring object) method into an expression evaluation * Params: * name - alias * aServiceObject - Create object * aFunctionName - Method name * aParameterTypes - Parameter type * errorInfo - */
        runner.addFunctionOfServiceMethod("upper".new ClassExample(),"upper".new String[]{"String"},null);
        
        Object execute = runner.execute("upper(\"yangzinan\")", context, null.true.true);
        System.out.println(execute);
Copy the code

2.6 Macro Defines macros

ExpressRunner runner = new ExpressRunner();
/ / define macros
runner.addMacro("Calculation of average Grades"."(Chinese + Maths + English)/3.0");

/ / define macros
runner.addMacro("Is it good?"."Calculated average score >90");
// Set parameters
DefaultContext<String,Object> context = new DefaultContext<>();
context.put("Chinese".90);
context.put("Mathematics".98);
context.put("English".101);

/ / execution
Object result = runner.execute("Is it good?", context, null.true.true);
System.out.println(result);
Copy the code

2.7 Querying external variables and functions to be defined

This means that we define a quantity in an expression, but there is no property defined inside the class

  ExpressRunner runner = new ExpressRunner(true.true);
        / / expression
        String exp = "a + b * c";

        // Get a list of external variable names required for an expression
        String[] outVarNames = runner.getOutVarNames(exp);

        / / traverse
        for(String str:outVarNames){
            System.out.println("Attributes to define :"+str);
        }
Copy the code

Today to write so much, and other actual use in the back of the specific to make up some knowledge points, more API can be viewed in the official website

https://github.com/alibaba/QLExpress
Copy the code