Add fflib_Formula.cls that can evaluate multiple instances of FormulaEval.FormulaInstance (#66)
This commit is contained in:
+8
-1
@@ -9,4 +9,11 @@ package.xml
|
|||||||
**/.eslintrc.json
|
**/.eslintrc.json
|
||||||
|
|
||||||
# LWC Jest
|
# LWC Jest
|
||||||
**/__tests__/**
|
**/__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-mocks
|
||||||
sfdx-source/apex-common
|
sfdx-source/apex-common
|
||||||
sfdx-source/apex-extensions-examples
|
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