Add fflib_Formula.cls that can evaluate multiple instances of FormulaEval.FormulaInstance (#66)
This commit is contained in:
@@ -10,3 +10,10 @@ package.xml
|
||||
|
||||
# LWC Jest
|
||||
**/__tests__/**
|
||||
|
||||
# Added by Illuminated Cloud
|
||||
/.illuminatedCloud/
|
||||
**/*.ts
|
||||
**/tsconfig*.json
|
||||
**/*.tsbuildinfo
|
||||
**/eslint.config.mjs
|
||||
@@ -53,3 +53,10 @@ temp/
|
||||
sfdx-source/apex-mocks
|
||||
sfdx-source/apex-common
|
||||
sfdx-source/apex-extensions-examples
|
||||
|
||||
# Added by Illuminated Cloud
|
||||
/out/
|
||||
target/
|
||||
/.illuminatedCloud/
|
||||
**/tsconfig*.json
|
||||
**/*.tsbuildinfo
|
||||
@@ -0,0 +1,411 @@
|
||||
/**
|
||||
* File Name: fflib_Formula
|
||||
* @description A evaluator for evaluating multiple instances of the {@link FormulaEval.FormulaInstance} class
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
public with sharing class fflib_Formula
|
||||
{
|
||||
/**
|
||||
* The structure of the formulas
|
||||
*
|
||||
* @example '(1 OR 2) AND 3'
|
||||
*/
|
||||
private final String formulaStructure;
|
||||
|
||||
private final Map<Integer, FormulaExpression> formulaByNumber;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param formulaStructure A String holding the formula structure e.g. '1 AND (2 OR 3)'
|
||||
* @param formulaByNumber A Map of the FormulaExpressions by their number
|
||||
*
|
||||
* @see fflib_Formula.FormulaExpression
|
||||
*/
|
||||
public fflib_Formula(String formulaStructure, Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
this.formulaByNumber = formulaByNumber;
|
||||
this.formulaStructure = formulaStructure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the formula structure and the given formula expressions to a boolean value
|
||||
*
|
||||
* @return Returns a boolean of the evaluated result
|
||||
*/
|
||||
public Boolean evaluate()
|
||||
{
|
||||
return parse(
|
||||
tokenizer(this.formulaStructure),
|
||||
formulaByNumber
|
||||
)
|
||||
.evaluate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the next token into an Expression
|
||||
*
|
||||
* @param tokens A list of tokens from the tokenizer
|
||||
* @param formulaByNumber A map of FormulaExpressions by their number
|
||||
*
|
||||
* @return Returns an instance of {@link Expression}
|
||||
*
|
||||
* @see fflib_Formula.Expression
|
||||
*/
|
||||
@TestVisible
|
||||
private static Expression parse(List<Token> tokens, Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
FormulaTokenType lookAhead = tokens.get(0).tokenType;
|
||||
|
||||
switch on lookAhead
|
||||
{
|
||||
when PARENTHESIS_OPEN
|
||||
{
|
||||
return parseParenthesizedExpression(tokens, formulaByNumber);
|
||||
}
|
||||
when else
|
||||
{
|
||||
return parseLogicalOrExpression(tokens, formulaByNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a parenthesized expression
|
||||
* '(' expression ')'
|
||||
*
|
||||
* @param tokens The list of tokens still to be processes
|
||||
* @param formulaByNumber A map of FormulaExpressions by their number
|
||||
*
|
||||
* @return Returns the expression within the parenthesis
|
||||
*/
|
||||
private static Expression parseParenthesizedExpression(
|
||||
List<Token> tokens,
|
||||
Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
eat(tokens, FormulaTokenType.PARENTHESIS_OPEN);
|
||||
Expression expr = parse(tokens, formulaByNumber);
|
||||
eat(tokens, FormulaTokenType.PARENTHESIS_CLOSE);
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a logical OR expression
|
||||
* expression
|
||||
* expression OR expression
|
||||
*
|
||||
* @param tokens The list of tokens still to be processes
|
||||
* @param formulaByNumber A map of FormulaExpressions by their number
|
||||
*
|
||||
* @return Returns an {@link fflib_Formula.OrExpression} or the expression itself
|
||||
*/
|
||||
private static Expression parseLogicalOrExpression(
|
||||
List<Token> tokens,
|
||||
Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
Expression left = parseLogicalAndExpression(tokens, formulaByNumber);
|
||||
|
||||
while (tokens.isEmpty() == false)
|
||||
{
|
||||
if (tokens.get(0).tokenType != FormulaTokenType.xOR) break;
|
||||
|
||||
eat(tokens, FormulaTokenType.xOR);
|
||||
Expression right = parseLogicalAndExpression(tokens, formulaByNumber);
|
||||
left = new OrExpression(left, right);
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a logic AND expression
|
||||
* expression
|
||||
* expression AND primary-expression
|
||||
*
|
||||
* @param tokens The list of tokens still to be processes
|
||||
* @param formulaByNumber A map of FormulaExpressions by their number
|
||||
*
|
||||
* @return Returns an {@link fflib_Formula.AndExpression} or the expression itself
|
||||
*/
|
||||
private static Expression parseLogicalAndExpression(
|
||||
List<fflib_Formula.Token> tokens,
|
||||
Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
Expression left = parsePrimaryExpression(tokens, formulaByNumber);
|
||||
|
||||
while (tokens.isEmpty() == false)
|
||||
{
|
||||
if (tokens.get(0).tokenType != FormulaTokenType.xAND) break;
|
||||
|
||||
eat(tokens, FormulaTokenType.xAND);
|
||||
Expression right = parsePrimaryExpression(tokens, formulaByNumber);
|
||||
left = new AndExpression(left, right);
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a primary expression
|
||||
* parenthesized-expression
|
||||
* number-expression
|
||||
*
|
||||
* @param tokens The list of tokens still to be processes
|
||||
* @param formulaByNumber A map of FormulaExpressions by their number
|
||||
*
|
||||
* @return Returns an {@link Expression} or a {@link FormulaExpression}
|
||||
*/
|
||||
private static Expression parsePrimaryExpression(
|
||||
List<fflib_Formula.Token> tokens,
|
||||
Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
Expression result;
|
||||
if (tokens.isEmpty()) throw new FormulaEvaluationException('Unexpected end of formula, expected and expression');
|
||||
switch on tokens.get(0).tokenType
|
||||
{
|
||||
when PARENTHESIS_OPEN
|
||||
{
|
||||
result = parseParenthesizedExpression(tokens, formulaByNumber);
|
||||
}
|
||||
when FORMULA_NUMBER
|
||||
{
|
||||
result = parseNumberExpression(tokens, formulaByNumber);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a number expression
|
||||
* `^\\d`
|
||||
*
|
||||
* @param tokens The list of tokens still to be processes
|
||||
* @param formulaByNumber A map of FormulaExpressions by their number
|
||||
*
|
||||
* @return Returns the corresponding {@link FormulaExpression} from the parsed number
|
||||
* which is used as key in the given formulaByNumber map
|
||||
*/
|
||||
private static Expression parseNumberExpression(
|
||||
List<fflib_Formula.Token> tokens,
|
||||
Map<Integer, FormulaExpression> formulaByNumber)
|
||||
{
|
||||
Token token = eat(tokens, FormulaTokenType.FORMULA_NUMBER);
|
||||
|
||||
Integer i = Integer.valueOf(token.literal);
|
||||
if (formulaByNumber.containsKey(i) == false) throw new FormulaEvaluationException('Unknown number ' + i);
|
||||
|
||||
return formulaByNumber.get(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes first token from the list that must match the given token type
|
||||
*
|
||||
* @param tokens The list of tokens still to be processes
|
||||
* @param tokenType The token type of the first token in the list
|
||||
*
|
||||
* @return Returns the first token of the given tokens
|
||||
*
|
||||
* @throws FormulaEvaluationException when there are no tokens left
|
||||
* or when the first token is not of the requested type
|
||||
*/
|
||||
private static Token eat(List<Token> tokens, FormulaTokenType tokenType)
|
||||
{
|
||||
if (tokens.isEmpty()) throw new FormulaEvaluationException('Unexpected end of line, expected ' + tokenType.name());
|
||||
else if (tokens.get(0).tokenType !== tokenType) throw new FormulaEvaluationException('Unexpected token \'' + tokens.get(0).literal + '\', expected ' + tokenType.name());
|
||||
return tokens.remove(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenizes the given snippet into tokens that can be read by the parser
|
||||
*
|
||||
* @param snippet A formula structure e.g. "1 AND 2 OR 3"
|
||||
*
|
||||
* @return Returns the list of tokens
|
||||
*
|
||||
* @throws FormulaEvaluationException When encountering an unknown token
|
||||
*/
|
||||
private static List<Token> tokenizer(String snippet)
|
||||
{
|
||||
List<Token> result = new List<Token>();
|
||||
|
||||
snippet = '(' + snippet + ')'; // Make sure we evaluate the whole snippet
|
||||
snippet = snippet.replaceAll('\\(', ' ( ');
|
||||
snippet = snippet.replaceAll('\\)', ' ) ');
|
||||
|
||||
List<String> strings = snippet.split('(\\b|\\s)');
|
||||
for (String literal : strings)
|
||||
{
|
||||
if (String.isBlank(literal))
|
||||
{
|
||||
continue; // ignore spaces
|
||||
}
|
||||
else if (literal.trim() == '(')
|
||||
{
|
||||
result.add(new Token(literal, FormulaTokenType.PARENTHESIS_OPEN));
|
||||
}
|
||||
else if (literal.trim() == ')')
|
||||
{
|
||||
result.add(new Token(literal, FormulaTokenType.PARENTHESIS_CLOSE));
|
||||
}
|
||||
else if (literal.toUpperCase().trim() == 'OR' || literal.trim() == '||')
|
||||
{
|
||||
result.add(new Token(literal, FormulaTokenType.xOR));
|
||||
}
|
||||
else if (literal.toUpperCase().trim() == 'AND' || literal.trim() == '&&')
|
||||
{
|
||||
result.add(new Token(literal, FormulaTokenType.xAND));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Integer.valueOf(literal);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new FormulaEvaluationException('Invalid token \'' + literal + '\'');
|
||||
}
|
||||
result.add(new Token(literal, FormulaTokenType.FORMULA_NUMBER));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@TestVisible
|
||||
private interface Expression
|
||||
{
|
||||
/**
|
||||
* Evaluate the Expression
|
||||
*
|
||||
* @return Returns a boolean value of the evaluated expression
|
||||
*/
|
||||
Boolean evaluate();
|
||||
}
|
||||
|
||||
/**
|
||||
* FormulaExpression Class
|
||||
*
|
||||
* Used to hold the {@link FormulaEval.FormulaInstance} and the object that should be evaluated
|
||||
*/
|
||||
public class FormulaExpression implements Expression
|
||||
{
|
||||
private final FormulaEval.FormulaInstance instance;
|
||||
private final Object objectToEvaluate;
|
||||
|
||||
public FormulaExpression(FormulaEval.FormulaInstance instance, Object objectToEvaluate)
|
||||
{
|
||||
this.instance = instance;
|
||||
this.objectToEvaluate = objectToEvaluate;
|
||||
}
|
||||
|
||||
public Boolean evaluate()
|
||||
{
|
||||
return (Boolean) this.instance.evaluate(this.objectToEvaluate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AndExpression Class
|
||||
*
|
||||
* Used to hold AND expressions e.g. 'expression AND expression' and is able to evaluate the expressions.
|
||||
* NOTE: the right hand side will only be evaluated if the left hand side returned true
|
||||
*/
|
||||
@TestVisible
|
||||
private class AndExpression implements Expression
|
||||
{
|
||||
@TestVisible private final Expression left;
|
||||
@TestVisible private final Expression right;
|
||||
|
||||
public AndExpression(Expression left, Expression right)
|
||||
{
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public Boolean evaluate()
|
||||
{
|
||||
return left.evaluate() && right.evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OrExpression Class
|
||||
*
|
||||
* Used to hold OR expressions e.g. 'expression OR expression' and is able to evaluate the expressions.
|
||||
* NOTE: the right hand side will only be evaluated if the left hand side returned false
|
||||
*/
|
||||
@TestVisible
|
||||
private class OrExpression implements Expression
|
||||
{
|
||||
@TestVisible private final Expression left;
|
||||
@TestVisible private final Expression right;
|
||||
|
||||
public OrExpression(Expression left, Expression right)
|
||||
{
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
}
|
||||
|
||||
public Boolean evaluate()
|
||||
{
|
||||
return left.evaluate() || right.evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Token Class
|
||||
*
|
||||
* Tokens are used by the Tokenizer to translate a formula structure into tokens the parser can understand
|
||||
*/
|
||||
@TestVisible
|
||||
private class Token
|
||||
{
|
||||
private final String literal;
|
||||
private final FormulaTokenType tokenType;
|
||||
|
||||
public Token(String literal, FormulaTokenType tokenType)
|
||||
{
|
||||
this.literal = literal;
|
||||
this.tokenType = tokenType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FormulaTokenType enum
|
||||
*
|
||||
* The available token types which can be used in a formula structure
|
||||
*/
|
||||
@TestVisible
|
||||
private enum FormulaTokenType
|
||||
{
|
||||
FORMULA_NUMBER, // e.g. '\\d'
|
||||
xAND, // e.g. 'And', '&&'
|
||||
xOR, // e.g. 'OR', '||'
|
||||
PARENTHESIS_OPEN, // e.g. '('
|
||||
PARENTHESIS_CLOSE // e.g. ')'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>64.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* File Name: fflib_FormulaTest
|
||||
* @description A test class for {@link fflib_Formula} class
|
||||
*
|
||||
* @author architect ir. Wilhelmus G.J. Velzeboer
|
||||
*
|
||||
* Copyright (c), W.G.J. Velzeboer,
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* - Redistributions of source code must retain the above author notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* - Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* - Neither the name of the author nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software without
|
||||
* specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
@IsTest
|
||||
private class fflib_FormulaTest
|
||||
{
|
||||
@IsTest
|
||||
static void itShouldTestSingleFormula()
|
||||
{
|
||||
final String formulaStructure = '1';
|
||||
fflib_Formula.FormulaExpression expression = generateAccountSiteFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expression
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
Boolean result = new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result, 'Incorrect result for evaluated formula');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldTestSingleFormula_parenthesized()
|
||||
{
|
||||
final String formulaStructure = '(1)';
|
||||
fflib_Formula.FormulaExpression expression = generateContactCountryFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expression
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
Boolean result = new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result, 'Incorrect result for evaluated formula');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldTestSingleFormula_failing()
|
||||
{
|
||||
final String formulaStructure = 'unknown';
|
||||
fflib_Formula.FormulaExpression expression = generateAccountSiteFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expression
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
try
|
||||
{
|
||||
new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Assert.fail('Expected an exception, but got none');
|
||||
}
|
||||
catch (FormulaEvaluationException e)
|
||||
{
|
||||
System.Assert.areEqual('Invalid token \'unknown\'', e.getMessage(), 'Incorrect error message');
|
||||
}
|
||||
System.Test.stopTest();
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldTestAndFormula()
|
||||
{
|
||||
final String formulaStructure = '1 AND 2';
|
||||
fflib_Formula.FormulaExpression expr1 = generateAccountSiteFormula();
|
||||
fflib_Formula.FormulaExpression expr2 = generateContactCountryFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expr1,
|
||||
2 => expr2
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
Boolean result = new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result, 'Incorrect result for evaluated formula');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldTestOrFormula()
|
||||
{
|
||||
final String formulaStructure = '1 || 2';
|
||||
fflib_Formula.FormulaExpression expr1 = generateLeadCountryFormula();
|
||||
fflib_Formula.FormulaExpression expr2 = generateContactCountryFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expr1,
|
||||
2 => expr2
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
Boolean result = new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result, 'Incorrect result for evaluated formula');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldTestOrFormula_Text()
|
||||
{
|
||||
final String formulaStructure = '1 OR 2';
|
||||
fflib_Formula.FormulaExpression expr1 = generateLeadCountryFormula();
|
||||
fflib_Formula.FormulaExpression expr2 = generateContactCountryFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expr1,
|
||||
2 => expr2
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
Boolean result = new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result, 'Incorrect result for evaluated formula');
|
||||
}
|
||||
|
||||
/**
|
||||
* OR expression
|
||||
* left = AND Expression
|
||||
* left = 1
|
||||
* right = 2
|
||||
* right = 3
|
||||
*/
|
||||
@IsTest
|
||||
static void itShouldParseAndOrFormula()
|
||||
{
|
||||
List<fflib_Formula.Token> tokens = new List<fflib_Formula.Token>
|
||||
{
|
||||
new fflib_Formula.Token('1', fflib_Formula.FormulaTokenType.FORMULA_NUMBER),
|
||||
new fflib_Formula.Token('AND', fflib_Formula.FormulaTokenType.xAND),
|
||||
new fflib_Formula.Token('2', fflib_Formula.FormulaTokenType.FORMULA_NUMBER),
|
||||
new fflib_Formula.Token('OR', fflib_Formula.FormulaTokenType.xOR),
|
||||
new fflib_Formula.Token('3', fflib_Formula.FormulaTokenType.FORMULA_NUMBER)
|
||||
};
|
||||
Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber = new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => null,
|
||||
2 => null,
|
||||
3 => null
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
fflib_Formula.Expression result = fflib_Formula.parse(tokens, formulaByNumber);
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result instanceof fflib_Formula.OrExpression, 'Expected an OR expression');
|
||||
fflib_Formula.OrExpression orExpression = (fflib_Formula.OrExpression) result;
|
||||
System.Assert.isTrue(orExpression.left instanceof fflib_Formula.AndExpression, 'Expected an AND expression');
|
||||
System.Assert.isNull(orExpression.right, 'Expected null for right hand side in OR expression');
|
||||
|
||||
fflib_Formula.AndExpression andExpression = (fflib_Formula.AndExpression) orExpression.left;
|
||||
System.Assert.isNull(andExpression.left, 'Expected null for left hand side in AND expression');
|
||||
System.Assert.isNull(andExpression.right, 'Expected null for right hand side in AND expression');
|
||||
}
|
||||
|
||||
@IsTest
|
||||
static void itShouldTestAndOrFormula()
|
||||
{
|
||||
final String formulaStructure = '1 AND (2 OR 3)';
|
||||
fflib_Formula.FormulaExpression expr1 = generateAccountSiteFormula();
|
||||
fflib_Formula.FormulaExpression expr2 = generateContactCountryFormula();
|
||||
fflib_Formula.FormulaExpression expr3 = generateLeadCountryFormula();
|
||||
|
||||
final Map<Integer, fflib_Formula.FormulaExpression> formulaByNumber =
|
||||
new Map<Integer, fflib_Formula.FormulaExpression>
|
||||
{
|
||||
1 => expr1,
|
||||
2 => expr3,
|
||||
3 => expr2
|
||||
};
|
||||
|
||||
System.Test.startTest();
|
||||
Boolean result = new fflib_Formula(formulaStructure, formulaByNumber).evaluate();
|
||||
System.Test.stopTest();
|
||||
|
||||
System.Assert.isTrue(result, 'Incorrect result for evaluated formula');
|
||||
}
|
||||
|
||||
|
||||
private static fflib_Formula.FormulaExpression generateAccountSiteFormula()
|
||||
{
|
||||
return new fflib_Formula.FormulaExpression(
|
||||
Formula.builder()
|
||||
.withReturnType(FormulaEval.FormulaReturnType.Boolean)
|
||||
.withType(Account.SObjectType)
|
||||
.withFormula('ISBLANK(Site) = FALSE')
|
||||
.build(),
|
||||
new Account(Site = 'Test')
|
||||
);
|
||||
}
|
||||
|
||||
private static fflib_Formula.FormulaExpression generateContactCountryFormula()
|
||||
{
|
||||
return new fflib_Formula.FormulaExpression(
|
||||
Formula.builder()
|
||||
.withReturnType(FormulaEval.FormulaReturnType.Boolean)
|
||||
.withType(Contact.SObjectType)
|
||||
.withFormula('MailingCountry = "Belgium"')
|
||||
.build(),
|
||||
new Contact(MailingCountry = 'Belgium')
|
||||
);
|
||||
}
|
||||
|
||||
private static fflib_Formula.FormulaExpression generateLeadCountryFormula()
|
||||
{
|
||||
return new fflib_Formula.FormulaExpression(
|
||||
Formula.builder()
|
||||
.withReturnType(FormulaEval.FormulaReturnType.Boolean)
|
||||
.withType(Lead.SObjectType)
|
||||
.withFormula('Country = "Germany"')
|
||||
.build(),
|
||||
new Lead(Country = 'Holland')
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>64.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
Reference in New Issue
Block a user