Skip to main content

Working with Functions

Expresso provides a rich set of built-in functions and allows you to define custom functions to extend its capabilities.

Built-in Functions​

String Functions​

FunctionDescriptionExampleResult
upperCase(str)Converts a string to uppercaseupperCase("hello")"HELLO"
lowerCase(str)Converts a string to lowercaselowerCase("HELLO")"hello"
trim(str)Removes whitespace from both endstrim(" hello ")"hello"
length(str)Returns the string lengthlength("hello")5
substring(str, start, end)Extracts part of a stringsubstring("hello", 1, 3)"el"
replace(str, old, new)Replaces text in a stringreplace("hello", "l", "x")"hexxo"
contains(str, substr)Checks if a string contains a substringcontains("hello", "el")true
startsWith(str, prefix)Checks if a string starts with a prefixstartsWith("hello", "he")true
endsWith(str, suffix)Checks if a string ends with a suffixendsWith("hello", "lo")true
concat(str1, str2, ...)Concatenates multiple stringsconcat("a", "b", "c")"abc"

Numeric Functions​

FunctionDescriptionExampleResult
min(a, b, ...)Returns the smallest valuemin(5, 3, 7)3
max(a, b, ...)Returns the largest valuemax(5, 3, 7)7
abs(num)Returns the absolute valueabs(-5)5
round(num, places)Rounds to specified decimal placesround(3.14159, 2)3.14
floor(num)Rounds down to the nearest integerfloor(3.7)3
ceil(num)Rounds up to the nearest integerceil(3.2)4
sqrt(num)Returns the square rootsqrt(16)4
pow(base, exponent)Raises a number to a powerpow(2, 3)8
random()Returns a random number between 0 and 1random()0.7231...

Collection Functions​

FunctionDescriptionExampleResult
size(collection)Returns the collection sizesize([1, 2, 3])3
sum(collection)Returns the sum of all elementssum([1, 2, 3])6
avg(collection)Returns the average of all elementsavg([1, 2, 3])2.0
join(collection, delimiter)Joins elements into a stringjoin(['a', 'b', 'c'], ",")"a,b,c"
contains(collection, element)Checks if collection contains an elementcontains([1, 2, 3], 2)true
filter(collection, expression)Filters elements based on a predicatefilter($users, $item.age > 18)[adult users]
map(collection, expression)Transforms each elementmap($users, $item.name)[names]
sort(collection)Sorts elements in ascending ordersort([3, 1, 2])[1, 2, 3]
reverse(collection)Reverses the order of elementsreverse([1, 2, 3])[3, 2, 1]

Date Functions​

FunctionDescriptionExampleResult
now()Returns the current date and timenow()2023-05-20T14:30:15
dateFormat(date, pattern)Formats a datedateFormat(now(), "yyyy-MM-dd")"2023-05-20"
dateParse(str, pattern)Parses a string to a datedateParse("2023-05-20", "yyyy-MM-dd")date object
dateAdd(date, amount, unit)Adds time to a datedateAdd(now(), 1, "day")tomorrow
dateDiff(date1, date2, unit)Returns the difference between datesdateDiff(now(), $futureDate, "days")days between

Date and Time Support Examples​

Expresso provides comprehensive date and time functionality:

// Get the current date
Object today = evaluator.evaluate("currentDate()", context);
// Returns java.time.LocalDate.now()

// Get the current time
Object now = evaluator.evaluate("currentTime()", context);
// Returns java.time.LocalTime.now()

// Get the current date and time
Object dateTime = evaluator.evaluate("currentDateTime()", context);
// Returns java.time.LocalDateTime.now()

// Parse a date from string
Object parsedDate = evaluator.evaluate("parseDate('2023-05-15')", context);
// Returns LocalDate for May 15, 2023

// Parse with custom format
Object customDate = evaluator.evaluate("parseDate('15/05/2023', 'dd/MM/yyyy')", context);
// Returns LocalDate for May 15, 2023

// Parse date and time
Object parsedDateTime = evaluator.evaluate("parseDateTime('2023-05-15T14:30:00')", context);
// Returns LocalDateTime

// Add days to a date
Object futureDate = evaluator.evaluate("addDays(currentDate(), 10)", context);
// Returns current date + 10 days

// Add months to a date
Object nextMonth = evaluator.evaluate("addMonths(currentDate(), 1)", context);
// Returns current date + 1 month

// Add years to a date
Object nextYear = evaluator.evaluate("addYears(currentDate(), 1)", context);
// Returns current date + 1 year

// Get days between dates
Object dayDiff = evaluator.evaluate(
"daysBetween(parseDate('2023-01-01'), parseDate('2023-01-15'))",
context
);
// Returns 14 (number of days between the dates)

// Get day of month
Object day = evaluator.evaluate("getDayOfMonth(currentDate())", context);
// Returns current day of month (1-31)

// Get month
Object month = evaluator.evaluate("getMonth(currentDate())", context);
// Returns current month (1-12)

// Get year
Object year = evaluator.evaluate("getYear(currentDate())", context);
// Returns current year

// Check if a date is before another
Object isBefore = evaluator.evaluate(
"isDateBefore(parseDate('2023-01-01'), parseDate('2023-02-01'))",
context
);
// Returns true

// Check if a date is after another
Object isAfter = evaluator.evaluate(
"isDateAfter(parseDate('2023-02-01'), parseDate('2023-01-01'))",
context
);
// Returns true

// Format a date using a pattern
Object formattedDate = evaluator.evaluate(
"formatDate(currentDate(), 'MMMM dd, yyyy')",
context
);
// Returns something like "May 15, 2023"

Logical Functions​

FunctionDescriptionExampleResult
if(condition, trueVal, falseVal)Returns value based on conditionif($age >= 18, "Adult", "Minor")"Adult" or "Minor"
coalesce(val1, val2, ...)Returns first non-null valuecoalesce($name, "Unknown")$name or "Unknown"
isNull(value)Checks if a value is nullisNull($name)true or false
isNumber(value)Checks if a value is a numberisNumber($age)true or false
isString(value)Checks if a value is a stringisString($name)true or false
isBoolean(value)Checks if a value is a booleanisBoolean($active)true or false
toString(value)Converts a value to stringtoString(123)"123"
toNumber(value)Converts a value to numbertoNumber("123")123
toBoolean(value)Converts a value to booleantoBoolean("true")true

Custom Functions​

You can extend Expresso's capabilities by registering custom functions:

// Create the evaluator
ExpressionEvaluator evaluator = new ExpressionEvaluator();

// Register a simple function
evaluator.registerFunction("double", args -> {
double value = ((Number) args[0]).doubleValue();
return value * 2;
});

// Use the custom function
Double result = (Double) evaluator.evaluate("double(5)", context); // 10.0

// Register a function with multiple parameters
evaluator.registerFunction("rectangle_area", args -> {
double width = ((Number) args[0]).doubleValue();
double height = ((Number) args[1]).doubleValue();
return width * height;
});

// Use the multi-parameter function
Double area = (Double) evaluator.evaluate("rectangle_area(5, 3)", context); // 15.0

// Register a function with variable arguments
evaluator.registerFunction("average", args -> {
if (args.length == 0) return 0.0;

double sum = 0;
for (Object arg : args) {
sum += ((Number) arg).doubleValue();
}
return sum / args.length;
});

// Use the variable argument function
Double avg = (Double) evaluator.evaluate("average(10, 20, 30, 40)", context); // 25.0

Function Registration Best Practices​

  1. Type Safety: Ensure your functions handle different input types appropriately
  2. Null Handling: Handle null inputs gracefully
  3. Error Handling: Provide meaningful error messages for invalid inputs
  4. Documentation: Document your functions for other developers
  5. Security: Be careful with functions that could expose sensitive operations
// Example of a well-implemented custom function
evaluator.registerFunction("safeDiv", args -> {
if (args.length != 2) {
throw new IllegalArgumentException("safeDiv requires exactly 2 arguments");
}

// Handle null inputs
if (args[0] == null || args[1] == null) {
return null;
}

try {
double numerator = ((Number) args[0]).doubleValue();
double denominator = ((Number) args[1]).doubleValue();

// Handle division by zero
if (denominator == 0) {
return null; // or throw an exception, or return a default value
}

return numerator / denominator;
} catch (ClassCastException e) {
throw new IllegalArgumentException("safeDiv requires numeric arguments");
}
});

Advanced Function Registration​

For more complex use cases, you can create a function registry:

public class CustomFunctionRegistry {
private final ExpressionEvaluator evaluator;

public CustomFunctionRegistry(ExpressionEvaluator evaluator) {
this.evaluator = evaluator;
registerAllFunctions();
}

private void registerAllFunctions() {
// Math functions
registerMathFunctions();

// Date functions
registerDateFunctions();

// String functions
registerStringFunctions();

// Business functions
registerBusinessFunctions();
}

private void registerMathFunctions() {
evaluator.registerFunction("percentage", args -> {
double value = ((Number) args[0]).doubleValue();
double percent = ((Number) args[1]).doubleValue();
return value * (percent / 100);
});

// More math functions...
}

private void registerDateFunctions() {
// Date-related functions...
}

private void registerStringFunctions() {
// String-related functions...
}

private void registerBusinessFunctions() {
// Business-specific functions...
}
}

// Usage
ExpressionEvaluator evaluator = new ExpressionEvaluator();
CustomFunctionRegistry registry = new CustomFunctionRegistry(evaluator);

// Now all registered functions are available
Double percent = (Double) evaluator.evaluate("percentage(200, 15)", context); // 30.0

This organized approach to function registration makes your code more maintainable as you add more custom functions.