(FALSE AND ((TRUE OR FALSE) AND (TRUE OR FALSE)))
Wondering how to evaluate this expression which is a string using Apex?
Following class can be used to evaluate:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*************************************************************************************************** | |
* Class Name : BooleanExpression | |
* Created Date : July 4, 2018 | |
* Author : Vijaya Sankar N | |
* Description : Class to evaluate a boolean expression | |
* Example : (TRUE AND (FALSE OR TRUE)) is TRUE | |
****************************************************************************************************/ | |
public class BooleanExpression { | |
static Map<String, String> logicTypes = new Map<String, String>(); | |
static Map<String, Map<String, String>> expressionLogic = new Map<String, Map<String, String>>(); | |
/** | |
* Evaluate a boolean expreassion | |
* | |
*/ | |
public static Boolean eval(String expression) { | |
// If expression contains all TRUE or FALSE | |
if(expression.containsNone('FALSE')) { return TRUE; } | |
if(expression.containsNone('TRUE')) { return FALSE; } | |
fillLogic(); | |
return Boolean.valueOf(evaluateExpression(expression.toUpperCase())); | |
} | |
/** | |
* Evaluate the expression | |
* | |
*/ | |
public static String evaluateExpression(String expression) { | |
for(String logicType : logicTypes.keySet()) { | |
if(expression.contains(logicType)) { | |
expression = simplifyExpression(expression, logicTypes.get(logicType)); | |
} | |
} | |
if(expression.contains('AND') || expression.contains('OR') || expression.contains('(')) { | |
expression = evaluateExpression(expression); | |
} | |
return expression; | |
} | |
/** | |
* Simplify the expression | |
* | |
*/ | |
public static string simplifyExpression(String expression, String LogicType){ | |
Map<String, String> Logic = new Map<String, String>(expressionLogic.get(LogicType)); | |
for(String key : Logic.keySet()) { | |
expression = expression.replace(key, Logic.get(key)); | |
} | |
return expression; | |
} | |
/** | |
* Fill AND and OR Logic | |
* | |
*/ | |
public static void fillLogic() { | |
Map<String, String> ANDLogic = new Map<String, String>(); | |
Map<String, String> ORLogic = new Map<String, String>(); | |
Map<String, String> BRACELogic = new Map<String, String>(); | |
logicTypes.put('AND', 'AND'); | |
logicTypes.put('OR', 'OR'); | |
logicTypes.put('(', 'BRACES'); | |
// AND Logic | |
ANDLogic.put('TRUE AND TRUE', 'TRUE'); | |
ANDLogic.put('TRUE AND FALSE', 'FALSE'); | |
ANDLogic.put('FALSE AND TRUE', 'FALSE'); | |
ANDLogic.put('FALSE AND FALSE', 'FALSE'); | |
expressionLogic.put('AND', ANDLogic); | |
// OR Logic | |
ORLogic.put('TRUE OR TRUE', 'TRUE'); | |
ORLogic.put('TRUE OR FALSE', 'TRUE'); | |
ORLogic.put('FALSE OR TRUE', 'TRUE'); | |
ORLogic.put('FALSE OR FALSE', 'FALSE'); | |
expressionLogic.put('OR', ORLogic); | |
// Braces Logic | |
BRACELogic.put('(TRUE)', 'TRUE'); | |
BRACELogic.put('(FALSE)', 'FALSE'); | |
expressionLogic.put('BRACES', BRACELogic); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@isTest | |
public class BooleanExpression_Test { | |
@isTest static void eval_test() { | |
System.assert(BooleanExpression.eval('TRUE')); | |
System.assert(BooleanExpression.eval('TRUE OR FALSE')); | |
System.assert(BooleanExpression.eval('TRUE OR TRUE')); | |
System.assert(BooleanExpression.eval('TRUE OR (TRUE AND FALSE)')); | |
System.assert(BooleanExpression.eval('TRUE OR (TRUE AND FALSE AND TRUE OR TRUE)')); | |
System.assert(BooleanExpression.eval('TRUE OR (TRUE AND FALSE AND (TRUE OR FALSE))')); | |
System.assert(BooleanExpression.eval('TRUE OR (TRUE OR (FALSE AND (TRUE OR FALSE)))')); | |
System.assert(BooleanExpression.eval('(FALSE OR ((TRUE OR FALSE) AND (TRUE OR FALSE)))')); | |
System.assert(!BooleanExpression.eval('FALSE')); | |
System.assert(!BooleanExpression.eval('TRUE AND FALSE')); | |
System.assert(!BooleanExpression.eval('FALSE AND FALSE')); | |
System.assert(!BooleanExpression.eval('TRUE AND (TRUE AND FALSE)')); | |
System.assert(!BooleanExpression.eval('FALSE AND (TRUE AND FALSE AND TRUE OR TRUE)')); | |
System.assert(!BooleanExpression.eval('TRUE AND (TRUE AND FALSE AND (TRUE OR FALSE))')); | |
System.assert(!BooleanExpression.eval('TRUE AND (TRUE AND (FALSE AND (TRUE OR FALSE)))')); | |
System.assert(!BooleanExpression.eval('(FALSE AND ((TRUE OR FALSE) AND (TRUE OR FALSE)))')); | |
} | |
} |
How to use?
This static method can be called as:
BooleanExpression.eval('(FALSE AND ((TRUE OR FALSE) AND (TRUE OR FALSE)))');
How it evolved?
While replicating the logic of Assignment Rule in Salesforce lightning, needed a way to implement Filter Logic like, 1 AND ( 2 OR 3 )
. Added few more to the above to achieve.
What is added to replicate Filter Logic in Assignment Rule?
Added a method:
/** * Substitue numbers in expression with values based on the index * */ public static String substituteValues(String expression, Boolean[] values) { String[] arguments = expression.split(' '); for(String arg : arguments) { try { Integer index = Integer.valueOf(arg.replaceAll('[^0-9]', '')); expression = expression.replaceAll(String.valueOf(index), String.valueOf(values[index - 1])); } catch(Exception e) {} } return expression; }
Call the method in eval()
, and added another argument for accepting values as list
. Updated eval()
method would be:
/** * Evaluate a boolean expreassion * */ public static Boolean eval(String expression, Boolean[] values) { expression = substituteValues(expression.toUpperCase(), values); // If expression contains all true or false if(expression.containsNone('false')) { return true; } if(expression.containsNone('true')) { return false; } return Boolean.valueOf(evaluateExpression(expression)); }
To invoke,
List values = new List { true, false, true }; BooleanExpession.eval('1 AND (2 OR 4)', values);
Hi,
This post really awesome!
Thanks,
Chetan
LikeLiked by 1 person
Thanks, glad to here, it helped you!
LikeLike
Hi Vijaya,
Thanks for the post. It is helpful.
I found a couple of bugs in the code. So wanted to bring it to your notice.
1) ‘TRUE’.containsNone(‘FALSE’); // evaluates to false because of the character ‘E’. So, I believe the first two statements of the eval method need to be updated as
if(expression.containsNone(‘F’) && expression.containsNone(‘f’)) { return TRUE; }
if(expression.containsNone(‘T’) && expression.containsNone(‘t’)) { return FALSE; }
2) Also, the logic is failing if additional spaces are added in the Boolean expression. When you try evaluating TRUE OR ( TRUE AND FALSE ) in this way with a space between ‘(‘ and ‘T’ and space between ‘E’ and ‘)’ logic is throwing an exception.
Thanks,
Prudhvi
LikeLike
Thanks for this great article and prudhvikonda for the bug resolving.
LikeLike
Thanks for the following post
But the code has a major bug.
The following statements are wrong in terms of logic:
if(expression.containsNone(‘FALSE’)) { return TRUE; }
if(expression.containsNone(‘TRUE’)) { return FALSE; }
Because .containsNone() will deal with characters individually and not the string as a whole
Instead please replace them with:
Integer falseCount = expression.countMatches(‘FALSE’);
Integer trueCount = expression.countMatches(‘TRUE’);
if(falseCount == 0 && trueCount == 0) { return true; }
if(falseCount == 0 && trueCount > 0 ) { return TRUE; }
if(trueCount == 0 && falseCount > 0) { return FALSE; }
LikeLike
Thanks for this. This along with bugs resolved for the char E worked like charm(thsnkd prudhvikonda ), Since I am cleansing the Logic string in page before sending in to the controller , the other bugs didn’t bother me ! cheers.
LikeLike