Add comparing to a formula string
This commit is contained in:
@@ -24,6 +24,8 @@ Add an equal to criteria comparing a fields value to a given value
|
|||||||
Add an equal to criteria comparing a related fields value to a given value
|
Add an equal to criteria comparing a related fields value to a given value
|
||||||
- <<evaluate, evaluate(Object record)>> +
|
- <<evaluate, evaluate(Object record)>> +
|
||||||
Evaluates the stored criteria based on the given SObject
|
Evaluates the stored criteria based on the given SObject
|
||||||
|
- <<formula, formulaCriteria(String formula)>> +
|
||||||
|
Adds a criteria based on a formula
|
||||||
- <<greaterOrEqualTo, greaterOrEqualTo(Schema.SObjectField field, Object value)>> +
|
- <<greaterOrEqualTo, greaterOrEqualTo(Schema.SObjectField field, Object value)>> +
|
||||||
Compares the fields values to be greater or equal to the given value
|
Compares the fields values to be greater or equal to the given value
|
||||||
- <<greaterOrEqualTo2, greaterOrEqualTo(String relatedField, Object value)>> +
|
- <<greaterOrEqualTo2, greaterOrEqualTo(String relatedField, Object value)>> +
|
||||||
@@ -247,6 +249,45 @@ System.assert(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[[formula]]
|
||||||
|
=== formulaCriteria(String formula)(Object record)
|
||||||
|
Adds a criteria based on a formula that can dynamically compare criteria with complex AND or OR structure.
|
||||||
|
|
||||||
|
==== _Signature_
|
||||||
|
```java
|
||||||
|
public virtual fflib_Criteria formulaCriteria(String formula)
|
||||||
|
```
|
||||||
|
==== _Parameters_
|
||||||
|
|
||||||
|
_**formula**_ +
|
||||||
|
The condition formula to evaluate
|
||||||
|
|
||||||
|
==== _Returns_
|
||||||
|
The Boolean result of the evaluated criteria
|
||||||
|
|
||||||
|
==== _Example_
|
||||||
|
```java
|
||||||
|
Contact record = new Contact(
|
||||||
|
LastName = 'Smith',
|
||||||
|
FirstName = 'John',
|
||||||
|
Department = 'MIB'
|
||||||
|
);
|
||||||
|
|
||||||
|
new fflib_Criteria()
|
||||||
|
.FormulaCriteria('(1 AND 2) OR (1 AND 3)')
|
||||||
|
.equalTo(Contact.LastName, 'Smith')
|
||||||
|
.equalTo(Contact.FirstName, 'John')
|
||||||
|
.equalTo(Contact.Department, 'MIB')
|
||||||
|
.evaluate(record); // true
|
||||||
|
|
||||||
|
new fflib_Criteria()
|
||||||
|
.FormulaCriteria('(1 AND 2) OR 3')
|
||||||
|
.equalTo(Contact.LastName, 'Smith')
|
||||||
|
.equalTo(Contact.FirstName, 'John')
|
||||||
|
.equalTo(Contact.Department, 'MIB')
|
||||||
|
.toSOQL(); // "(LastName='Smith' OR FirstName='John') AND Department='MIB'"
|
||||||
|
```
|
||||||
|
|
||||||
___
|
___
|
||||||
[[greaterOrEqualTo]]
|
[[greaterOrEqualTo]]
|
||||||
=== greaterOrEqualTo(Schema.SObjectField field, Object value)
|
=== greaterOrEqualTo(Schema.SObjectField field, Object value)
|
||||||
|
|||||||
@@ -31,14 +31,20 @@
|
|||||||
public virtual with sharing class fflib_Criteria
|
public virtual with sharing class fflib_Criteria
|
||||||
implements Evaluator
|
implements Evaluator
|
||||||
{
|
{
|
||||||
|
private static final String OPEN_BRACKET = '(';
|
||||||
|
private static final String CLOSE_BRACKET = ')';
|
||||||
|
private static final String FORMULA = 'FORMULA';
|
||||||
|
|
||||||
// Add brace when generated to SQL
|
// Add brace when generated to SQL
|
||||||
private Boolean embraced = false;
|
private Boolean embraced = false;
|
||||||
|
|
||||||
// List to hold all evaluators
|
// List to hold all evaluators
|
||||||
private List<Evaluator> evaluators;
|
private List<Evaluator> evaluators;
|
||||||
|
|
||||||
// The type of comparator NONE | AND | OR
|
// The type of comparator NONE | AND | OR | FORMULA
|
||||||
private String type;
|
private String type;
|
||||||
|
private IFormulaEvaluator formulaEvaluator;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,6 +151,154 @@ public virtual with sharing class fflib_Criteria
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param formula Formula string
|
||||||
|
*
|
||||||
|
* @return An instance of itself to enable method chaining
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* new fflib_Criteria()
|
||||||
|
* .formulaCriteria('(1 OR 2) AND 3')
|
||||||
|
* .equalTo(Account.AccountNumber, '0001')
|
||||||
|
* .equalTo(Account.AccountNumber, '0002')
|
||||||
|
* .equalTo(Account.ShippingCountry, 'USA'))
|
||||||
|
*
|
||||||
|
* Evaluates:
|
||||||
|
* (AccountNumber = '0001' OR AccountNumber = '0001') AND ShippingCountry = 'USA'
|
||||||
|
*/
|
||||||
|
public virtual fflib_Criteria formulaCriteria(String formula)
|
||||||
|
{
|
||||||
|
this.type = 'FORMULA';
|
||||||
|
this.formulaEvaluator = new FormulaEvaluator(formula.toUpperCase());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface IFormulaEvaluator
|
||||||
|
{
|
||||||
|
Boolean evaluate(Object record, Map<Integer, Boolean> evaluatedResultByNumber);
|
||||||
|
String toSOQL(Map<Integer, String> queryByNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FormulaEvaluator implements IFormulaEvaluator
|
||||||
|
{
|
||||||
|
private String formula;
|
||||||
|
private String type
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (type != null && type != value)
|
||||||
|
throw new CriteriaException('Can not mix AND and OR, use brackets');
|
||||||
|
type = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IFormulaEvaluator> formulaParts = new List<IFormulaEvaluator>();
|
||||||
|
|
||||||
|
public FormulaEvaluator(String formula)
|
||||||
|
{
|
||||||
|
this.formula = formula;
|
||||||
|
String expression = String.valueOf(formula);
|
||||||
|
while(expression.length() > 0)
|
||||||
|
{
|
||||||
|
if (expression.startsWith(' '))
|
||||||
|
{
|
||||||
|
expression = expression.removeStart(' ');
|
||||||
|
}
|
||||||
|
else if (expression.startsWith(OPEN_BRACKET))
|
||||||
|
{
|
||||||
|
String subFormula = fflib_StringUtils.getBetweenBrackets(OPEN_BRACKET, CLOSE_BRACKET, expression, 0);
|
||||||
|
if (subFormula == null)
|
||||||
|
throw new CriteriaException('Missing closing bracket');
|
||||||
|
|
||||||
|
formulaParts.add(new FormulaEvaluator(subFormula));
|
||||||
|
expression = expression.substring(subFormula.length() + 2);
|
||||||
|
}
|
||||||
|
else if (expression.startsWith('OR'))
|
||||||
|
{
|
||||||
|
type = 'OR';
|
||||||
|
expression = expression.removeStart('OR');
|
||||||
|
}
|
||||||
|
else if (expression.startsWith('AND'))
|
||||||
|
{
|
||||||
|
type = 'AND';
|
||||||
|
expression = expression.removeStart('AND');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String numberValue;
|
||||||
|
if (expression.indexOf(' ') == -1) // at the end of string
|
||||||
|
{
|
||||||
|
numberValue = expression;
|
||||||
|
expression = '';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
numberValue = expression.substring(0, expression.indexOf(' '));
|
||||||
|
expression = expression.substring(expression.indexOf(' '));
|
||||||
|
}
|
||||||
|
formulaParts.add(new FormulaNumberEvaluator(Integer.valueOf(numberValue)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean evaluate(Object record, Map<Integer, Boolean> evaluatedResultByNumber)
|
||||||
|
{
|
||||||
|
Boolean result = !(this.type == 'OR');
|
||||||
|
for (IFormulaEvaluator evaluator : formulaParts)
|
||||||
|
{
|
||||||
|
Boolean evaluateResult = evaluator.evaluate(record, evaluatedResultByNumber);
|
||||||
|
if (evaluateResult && (this.type == 'OR'))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (!evaluateResult && this.type == 'AND')
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toSOQL(Map<Integer, String> queryByNumber)
|
||||||
|
{
|
||||||
|
String result = '';
|
||||||
|
for (IFormulaEvaluator evaluator : formulaParts)
|
||||||
|
{
|
||||||
|
if (result != '')
|
||||||
|
result += ' ' + this.type + ' ';
|
||||||
|
result += evaluator.toSOQL(queryByNumber);
|
||||||
|
}
|
||||||
|
return OPEN_BRACKET + result + CLOSE_BRACKET;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FormulaNumberEvaluator implements IFormulaEvaluator
|
||||||
|
{
|
||||||
|
private Integer expressionNumber;
|
||||||
|
|
||||||
|
public FormulaNumberEvaluator(Integer expressionNumber)
|
||||||
|
{
|
||||||
|
this.expressionNumber = expressionNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean evaluate(Object record, Map<Integer, Boolean> evaluatedResultByNumber)
|
||||||
|
{
|
||||||
|
if (!evaluatedResultByNumber.containsKey(expressionNumber - 1))
|
||||||
|
throw new CriteriaException('Expected a formula expression with number: ' + expressionNumber);
|
||||||
|
|
||||||
|
return evaluatedResultByNumber.get(expressionNumber - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toSOQL(Map<Integer, String> queryByNumber)
|
||||||
|
{
|
||||||
|
if (!queryByNumber.containsKey(expressionNumber - 1))
|
||||||
|
throw new CriteriaException('Expected a formula expression with number: ' + expressionNumber);
|
||||||
|
|
||||||
|
return queryByNumber.get(expressionNumber - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an equal to criteria comparing a fields value to a given value
|
* Add an equal to criteria comparing a fields value to a given value
|
||||||
*
|
*
|
||||||
@@ -210,6 +364,8 @@ public virtual with sharing class fflib_Criteria
|
|||||||
{
|
{
|
||||||
if (this.evaluators.isEmpty()) return true;
|
if (this.evaluators.isEmpty()) return true;
|
||||||
|
|
||||||
|
if (this.type == FORMULA) return evaluateFormula(record);
|
||||||
|
|
||||||
Boolean result = !(this.type == 'OR');
|
Boolean result = !(this.type == 'OR');
|
||||||
for (Evaluator evaluator : evaluators)
|
for (Evaluator evaluator : evaluators)
|
||||||
{
|
{
|
||||||
@@ -226,6 +382,16 @@ public virtual with sharing class fflib_Criteria
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean evaluateFormula(Object record)
|
||||||
|
{
|
||||||
|
Map<Integer, Boolean> evaluatedResultByNumber = new Map<Integer, Boolean>();
|
||||||
|
for (Integer i = 0; i < evaluators.size(); i++)
|
||||||
|
{
|
||||||
|
evaluatedResultByNumber.put(i, evaluators.get(i).evaluate(record));
|
||||||
|
}
|
||||||
|
return formulaEvaluator.evaluate(record, evaluatedResultByNumber);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares the fields values to be greater or equal to the given value
|
* Compares the fields values to be greater or equal to the given value
|
||||||
* Evaluates to false if the field value is null
|
* Evaluates to false if the field value is null
|
||||||
@@ -491,6 +657,8 @@ public virtual with sharing class fflib_Criteria
|
|||||||
{
|
{
|
||||||
if (this.evaluators.isEmpty()) return '';
|
if (this.evaluators.isEmpty()) return '';
|
||||||
|
|
||||||
|
if (this.type == FORMULA) return formulaToSOQL();
|
||||||
|
|
||||||
String result = '';
|
String result = '';
|
||||||
for (Evaluator evaluator : this.evaluators)
|
for (Evaluator evaluator : this.evaluators)
|
||||||
{
|
{
|
||||||
@@ -506,6 +674,19 @@ public virtual with sharing class fflib_Criteria
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formulaToSOQL()
|
||||||
|
{
|
||||||
|
Map<Integer, String> queryByNumber = new Map<Integer, String>();
|
||||||
|
for (Integer i = 0; i < this.evaluators.size(); i++)
|
||||||
|
{
|
||||||
|
queryByNumber.put(i, evaluators.get(i).toSOQL());
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = this.formulaEvaluator
|
||||||
|
.toSOQL(queryByNumber);
|
||||||
|
return result.substring(1, result.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
private static String operatorToString(fflib_Operator operator)
|
private static String operatorToString(fflib_Operator operator)
|
||||||
{
|
{
|
||||||
if (operator == fflib_Operator.EQUALS)
|
if (operator == fflib_Operator.EQUALS)
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* File Name: fflib_StringUtils
|
||||||
|
*
|
||||||
|
* @author : architect ir. Wilhelmus G.J. Velzeboer
|
||||||
|
*/
|
||||||
|
public with sharing class fflib_StringUtils
|
||||||
|
{
|
||||||
|
public static String getBetweenBrackets(String open, String close, String expression, Integer index)
|
||||||
|
{
|
||||||
|
Integer openChar = open.charAt(0);
|
||||||
|
Integer closeChar = close.charAt(0);
|
||||||
|
|
||||||
|
// If index given is invalid and is
|
||||||
|
// not an opening bracket.
|
||||||
|
if (expression.charAt(index) != openChar)
|
||||||
|
new StringException('Expected char \'' + open + '\' as position ' + index);
|
||||||
|
|
||||||
|
// Stack to store opening brackets.
|
||||||
|
List<Integer> stack = new List<Integer>();
|
||||||
|
|
||||||
|
// Traverse through string starting from
|
||||||
|
// given index.
|
||||||
|
for (Integer i = index; i < expression.length(); i++)
|
||||||
|
{
|
||||||
|
|
||||||
|
// If current character is an opening bracket push it in stack.
|
||||||
|
if (expression.charAt(i) == openChar)
|
||||||
|
{
|
||||||
|
stack.add(expression.charAt(i));
|
||||||
|
}
|
||||||
|
// If current character is a closing bracket, pop from stack.
|
||||||
|
// If stack is empty, then this closing bracket is required bracket.
|
||||||
|
else if (expression.charAt(i) == closeChar)
|
||||||
|
{
|
||||||
|
stack.remove(stack.size()-1);
|
||||||
|
if (stack.isEmpty())
|
||||||
|
{
|
||||||
|
return expression.substring(index + 1, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StringException extends Exception {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||||
|
<apiVersion>51.0</apiVersion>
|
||||||
|
<status>Active</status>
|
||||||
|
</ApexClass>
|
||||||
@@ -569,4 +569,33 @@ private with sharing class fflib_CriteriaTest
|
|||||||
.evaluate(record)
|
.evaluate(record)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IsTest
|
||||||
|
static void itShouldEvaluateFormula()
|
||||||
|
{
|
||||||
|
// GIVEN a record
|
||||||
|
Contact record = new Contact(
|
||||||
|
LastName = 'Smith',
|
||||||
|
FirstName = 'John',
|
||||||
|
Department = 'MIB1'
|
||||||
|
);
|
||||||
|
|
||||||
|
// WHEN we process a formula criteria it should evaluate the formula
|
||||||
|
System.assert(
|
||||||
|
new fflib_Criteria()
|
||||||
|
.FormulaCriteria('(1 AND 2) OR (1 AND 3)')
|
||||||
|
.equalTo(Contact.LastName, 'Smith')
|
||||||
|
.equalTo(Contact.FirstName, 'John')
|
||||||
|
.equalTo(Contact.Department, 'MIB')
|
||||||
|
.evaluate(record)
|
||||||
|
);
|
||||||
|
System.assertEquals(false,
|
||||||
|
new fflib_Criteria()
|
||||||
|
.FormulaCriteria('(1 AND 2) OR (1 AND 3)')
|
||||||
|
.equalTo(Contact.LastName, 'Ford')
|
||||||
|
.equalTo(Contact.FirstName, 'John')
|
||||||
|
.equalTo(Contact.Department, 'MIB')
|
||||||
|
.evaluate(record)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user