├── .classpath
├── .project
├── .settings
├── org.eclipse.jdt.core.prefs
└── org.maven.ide.eclipse.prefs
├── .travis.yml
├── README.md
├── libs
└── com
│ └── stst
│ └── core
│ └── expression-evaluator
│ └── 0.0.1
│ ├── expression-evaluator-0.0.1.jar
│ └── expression-evaluator-0.0.1.pom
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── stst
│ └── core
│ └── expression
│ └── demo
│ ├── CustomKeywords.java
│ └── ExpressionsFactory.java
└── test
└── java
└── com
└── stst
└── core
└── expression
└── demo
├── ConditionsExpressionTest.java
├── CustomKeywordsTest.java
├── DateEpxressionsTest.java
├── FormatExpressionsTest.java
├── MathematicalExpressionsTest.java
└── StringExpressionsTest.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | expression-evaluator-demo
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.maven.ide.eclipse.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.maven.ide.eclipse.maven2Nature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | #Tue Dec 30 15:02:12 IST 2014
2 | eclipse.preferences.version=1
3 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
4 |
--------------------------------------------------------------------------------
/.settings/org.maven.ide.eclipse.prefs:
--------------------------------------------------------------------------------
1 | #Tue Dec 30 14:30:23 IST 2014
2 | activeProfiles=
3 | eclipse.preferences.version=1
4 | fullBuildGoals=process-test-resources
5 | includeModules=false
6 | resolveWorkspaceProjects=true
7 | resourceFilterGoals=process-resources resources\:testResources
8 | skipCompilerPlugin=true
9 | version=1
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Expression Evaluator (Demo)
2 | ===========================
3 |
4 | [](https://travis-ci.org/Shy-Ta/expression-evaluator-demo)
5 |
6 | A general purpose java library which offers excel or Google spreadsheet like formula/ expression support. The library also allows easy addition of new functions.
7 |
8 | The project contains examples\ tests to demonstrate what is already on offer and also give some examples to extend or add new functionality. The sequence of topics is as below:
9 |
10 | * Arithmetic Functions
11 | * String Functions
12 | * Date Functions
13 | * Format Functions
14 | * Condition/ Operator based Functions
15 | * User Defined Functions (Ability to customize or add new functions)
16 | * Performance Considerations
17 |
18 |
19 | #####Arithmetic Functions
20 |
21 | ```
22 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");
23 | assertEquals(BigDecimal.valueOf(11), evalExpr.eval());
24 | //mathematical functions
25 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("tan(toRadians(45.0)) + sqrt(9.0)");
26 | assertEquals(Double.valueOf(4), evalExpr.eval());
27 | //variable driven functions
28 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("log10(var1)");
29 | Map variables = new HashMap();
30 | variables.put("var1", 100);
31 | assertEquals(Double.valueOf(2), evalExpr.eval(variables));
32 | ```
33 |
34 | #####String Functions
35 |
36 | ```
37 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("LEFT('New York', 3)");
38 | assertEquals("New", evalExpr.eval());
39 | //variable driven String function
40 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("LEFT(City, 3)");
41 | Map variables = new HashMap();
42 | variables.put("City", "New York");
43 | assertEquals("New", evalExpr.eval(variables));
44 | //composite String function
45 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("CONCATENATE(LEFT('New York', 3), ' Hampshire')");
46 | assertEquals("New Hampshire", evalExpr.eval());
47 | ```
48 |
49 | Some of the included functions are LEFT, RIGHT, CONCATENATE, LEN, TRIM, UPPER, LOWER, FIND, REPLACE and a few more. New functions can be added easily as explained later in the blog.
50 |
51 | #####Date Functions
52 |
53 | The library internally uses the joda-time library; however it exposes the date as java.util in order to ensure compatibility with standard jdk distribution.
54 |
55 | ```
56 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("DATE()");
57 | assertEquals(new Date().toString(), evalExpr.eval().toString());
58 | //date representation of a custom date
59 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("ASDATE('20141229','yyyyMMdd')");
60 | Calendar calendar = Calendar.getInstance();
61 | calendar.set(2014, Calendar.DECEMBER, 29, 0, 0, 0);
62 | assertEquals(calendar.getTime().toString(), evalExpr.eval().toString());
63 | ```
64 |
65 | The keyword DATESTR returns the string representation of a date while DATETIME returns the string representation of both the date as well as the time.
66 |
67 | #####Format Functions for Date or Number
68 |
69 | ```
70 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("FORMAT(ASDATE('20141229','yyyyMMdd'),'MMM-dd-yyyy')");
71 | assertEquals("Dec-29-2014", evalExpr.eval());
72 | //number formatting
73 | assertEquals("9,876.54", ExpressionsFactory.create("FORMAT(9876.543, '#,##0.00')").eval());
74 | assertEquals("9877", ExpressionsFactory.create("FORMAT(9876.543, '#')").eval());
75 | assertEquals("54%", ExpressionsFactory.create("FORMAT(0.543, '0%')").eval());
76 | ```
77 |
78 | #####Logical/ Conditional Operators (AND, OR, IF)
79 |
80 | ```
81 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("IF(AND(Score>0,Score<=40), 'Fail', IF(AND(Score>40, Score<=65), 'Grade II','Grade I'))");
82 | Map variables = new HashMap();
83 | variables.put("Score", 90);
84 | assertEquals("Grade I", evalExpr.eval(variables));
85 | variables.put("Score", 50);
86 | assertEquals("Grade II", evalExpr.eval(variables));
87 | ```
88 |
89 | The above is a great example of how business logic can be made configuration driven rather than code driven; this makes it easier to just change the expressions and get the updated result rather than changing the code and going through the release-deploy cycle.
90 |
91 | #####Customize (Add user defined functions)
92 |
93 | A sample implementation for EXACT function in excel which compares the equality of two string can be done by creating a class CustomKeywords with the method below
94 |
95 | ```
96 | public static boolean EXACT(String str1, String str2) {
97 | return str1.equals(str2);
98 | }
99 | ```
100 | and then an example would look like
101 | ```
102 | ExpressionsEvaluator evalExpr = new ExpressionsBuilder("EXACT(\"Hello\", inputStr)").withImports(Arrays.asList(new String[] { "static com.stst.core.expression.demo.CustomKeywords.*" })).build();
103 | Map variables = new HashMap();
104 | variables.put("inputStr", "Helo");
105 | assertEquals(false, evalExpr.eval(variables));
106 | variables.put("inputStr", "Hello");
107 | assertEquals(true, evalExpr.eval(variables));
108 | ```
109 |
110 | #####Performance
111 |
112 | The performance of the expression engine is comparable to the existing solutions like jexl, embedded javascript engine in jdk et-all. This is based on a benchmark that I had done previously but I would recommend running your own benchmarks and analyze the pros and cons. The following tricks should definitely be included for the performance analysis
113 | * Minimize new creation of GroovyShell (ExpressionsBuilder allows groovy shell re-use). In a multi-threaded environment use a thread local as groovy shell isn't thread safe.
114 | * Minimize creation of expression evaluator - as it has one time cost of running the parser. An expression once created can be cached and then run for different set of variables.
115 |
116 | A new class is loaded for every new instance of expression - this leads to a slight increase in perm gen memory. This shouldn't be a concern in the majority of cases. However, even in cases where this is a concern the gc option to support class unloading should take care of it. Do reach out in case you need any help with this.
117 |
118 | #####Conclusion
119 |
120 | As you may have guessed all the functions can be used in conjunction so feel free to explore. All the tests mentioned in the examples above can be found in this maven project.
121 |
122 | Do let me know your experience and any questions or feedback you may have.
123 |
--------------------------------------------------------------------------------
/libs/com/stst/core/expression-evaluator/0.0.1/expression-evaluator-0.0.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shy-Ta/expression-evaluator-demo/1751caa3316863802b132d27fb53314bd3cdabf9/libs/com/stst/core/expression-evaluator/0.0.1/expression-evaluator-0.0.1.jar
--------------------------------------------------------------------------------
/libs/com/stst/core/expression-evaluator/0.0.1/expression-evaluator-0.0.1.pom:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.stst.core
6 | expression-evaluator
7 | 0.0.1
8 | POM was created from install:install-file
9 |
10 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.stst.core
6 | expression-evaluator-demo
7 | 0.0.1-SNAPSHOT
8 | jar
9 |
10 | expression-evaluator-demo
11 | http://maven.apache.org
12 |
13 |
14 | UTF-8
15 |
16 |
17 |
18 | localrepo
19 | file://${project.basedir}/libs
20 |
21 |
22 |
23 |
24 | com.stst.core
25 | expression-evaluator
26 | 0.0.1
27 |
28 |
29 | org.codehaus.groovy
30 | groovy-all
31 | 2.0.0
32 |
33 |
34 | joda-time
35 | joda-time
36 | 2.5
37 |
38 |
39 | org.apache.commons
40 | commons-lang3
41 | 3.1
42 |
43 |
44 | junit
45 | junit
46 | 4.11
47 | test
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/src/main/java/com/stst/core/expression/demo/CustomKeywords.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | public class CustomKeywords {
4 |
5 | public static boolean EXACT(String str1, String str2) {
6 | return str1.equals(str2);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/stst/core/expression/demo/ExpressionsFactory.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | import com.stst.expression.core.ExpressionsBuilder;
4 | import com.stst.expression.core.ExpressionsEvaluator;
5 |
6 | public class ExpressionsFactory {
7 |
8 | public static ExpressionsEvaluator create(String expression) {
9 | return new ExpressionsBuilder(expression).build();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/com/stst/core/expression/demo/ConditionsExpressionTest.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import org.junit.Test;
9 |
10 | import com.stst.core.expression.demo.ExpressionsFactory;
11 | import com.stst.expression.core.ExpressionsEvaluator;
12 |
13 | public class ConditionsExpressionTest {
14 |
15 | @Test
16 | public void testIfWithAndOperator() {
17 | ExpressionsEvaluator evalExpr = ExpressionsFactory
18 | .create("IF(AND(Score>0,Score<=40), 'Fail', IF(AND(Score>40, Score<=65), 'Grade II','Grade I'))");
19 | Map variables = new HashMap();
20 | variables.put("Score", 90);
21 | assertEquals("Grade I", evalExpr.eval(variables));
22 | variables.put("Score", 50);
23 | assertEquals("Grade II", evalExpr.eval(variables));
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/test/java/com/stst/core/expression/demo/CustomKeywordsTest.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 | import static org.junit.Assert.assertEquals;
3 | import java.util.Arrays;
4 | import java.util.HashMap;
5 | import java.util.Map;
6 |
7 | import org.junit.Test;
8 |
9 | import com.stst.expression.core.ExpressionsBuilder;
10 | import com.stst.expression.core.ExpressionsEvaluator;
11 |
12 | public class CustomKeywordsTest {
13 |
14 | @Test
15 | public void testCustomExactFunction() {
16 | ExpressionsEvaluator evalExpr = new ExpressionsBuilder("EXACT(\"Hello\", inputStr)").withImports(
17 | Arrays.asList(new String[] { "static com.stst.core.expression.demo.CustomKeywords.*" })).build();
18 | Map variables = new HashMap();
19 | variables.put("inputStr", "Helo");
20 | assertEquals(false, evalExpr.eval(variables));
21 | variables.put("inputStr", "Hello");
22 | assertEquals(true, evalExpr.eval(variables));
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/com/stst/core/expression/demo/DateEpxressionsTest.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.util.Calendar;
6 | import java.util.Date;
7 |
8 | import org.junit.Test;
9 |
10 | import com.stst.core.expression.demo.ExpressionsFactory;
11 | import com.stst.expression.core.ExpressionsEvaluator;
12 |
13 | public class DateEpxressionsTest {
14 |
15 | @Test
16 | public void testDate() {
17 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("DATE()");
18 | assertEquals(new Date().toString(), evalExpr.eval().toString());
19 | }
20 |
21 | @Test
22 | public void testDateBasedOnAPattern() {
23 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("ASDATE('20141229','yyyyMMdd')");
24 | Calendar calendar = Calendar.getInstance();
25 | calendar.set(2014, Calendar.DECEMBER, 29, 0, 0, 0);
26 | assertEquals(calendar.getTime().toString(), evalExpr.eval().toString());
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/test/java/com/stst/core/expression/demo/FormatExpressionsTest.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import org.junit.Test;
6 |
7 | import com.stst.core.expression.demo.ExpressionsFactory;
8 | import com.stst.expression.core.ExpressionsEvaluator;
9 |
10 | public class FormatExpressionsTest {
11 |
12 | @Test
13 | public void testDateFormat() {
14 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("FORMAT(ASDATE('20141229','yyyyMMdd'),'MMM-dd-yyyy')");
15 | assertEquals("Dec-29-2014", evalExpr.eval());
16 | }
17 |
18 | @Test
19 | public void testNumberFormat() {
20 | assertEquals("9,876.54", ExpressionsFactory.create("FORMAT(9876.543, '#,##0.00')").eval());
21 | assertEquals("9877", ExpressionsFactory.create("FORMAT(9876.543, '#')").eval());
22 | assertEquals("54%", ExpressionsFactory.create("FORMAT(0.543, '0%')").eval());
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/com/stst/core/expression/demo/MathematicalExpressionsTest.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.math.BigDecimal;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | import org.junit.Test;
10 |
11 | import com.stst.core.expression.demo.ExpressionsFactory;
12 | import com.stst.expression.core.ExpressionsEvaluator;
13 |
14 | public class MathematicalExpressionsTest {
15 |
16 | @Test
17 | public void testArithmetic() {
18 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("2+3*4-6/2");
19 | assertEquals(BigDecimal.valueOf(11), evalExpr.eval());
20 | }
21 |
22 | @Test
23 | public void testMathFunctions() {
24 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("tan(toRadians(45.0)) + sqrt(9.0)");
25 | assertEquals(Double.valueOf(4), evalExpr.eval());
26 | }
27 |
28 | @Test
29 | public void testVariableDrivenFunctions() {
30 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("log10(var1)");
31 | Map variables = new HashMap();
32 | variables.put("var1", 100);
33 | assertEquals(Double.valueOf(2), evalExpr.eval(variables));
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/com/stst/core/expression/demo/StringExpressionsTest.java:
--------------------------------------------------------------------------------
1 | package com.stst.core.expression.demo;
2 |
3 | import static org.junit.Assert.assertEquals;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import org.junit.Test;
9 |
10 | import com.stst.core.expression.demo.ExpressionsFactory;
11 | import com.stst.expression.core.ExpressionsEvaluator;
12 |
13 | public class StringExpressionsTest {
14 |
15 | @Test
16 | public void testSingleStringFunction() {
17 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("LEFT('New York', 3)");
18 | assertEquals("New", evalExpr.eval());
19 | }
20 |
21 | @Test
22 | public void testSingleStringFunctionWithVariable() {
23 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("LEFT(City, 3)");
24 | Map variables = new HashMap();
25 | variables.put("City", "New York");
26 | assertEquals("New", evalExpr.eval(variables));
27 | }
28 |
29 | @Test
30 | public void testCompositeStringFunctionWithVariable() {
31 | ExpressionsEvaluator evalExpr = ExpressionsFactory.create("CONCATENATE(LEFT('New York', 3), ' Hampshire')");
32 | assertEquals("New Hampshire", evalExpr.eval());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------