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.