GuardExpression.java
/*******************************************************************************
* Copyright (c) 2026 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer - initial API and implementation
*******************************************************************************/
package org.sandbox.jdt.triggerpattern.api;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* AST model for guard expressions using a sealed interface pattern.
*
* <p>Guard expressions are used to constrain pattern matches. They support
* function calls, logical operators ({@code &&}, {@code ||}, {@code !}),
* and parenthesized sub-expressions.</p>
*
* <p>Examples:</p>
* <ul>
* <li>{@code sourceVersionGE(11)}</li>
* <li>{@code $x instanceof String}</li>
* <li>{@code $x instanceof String && sourceVersionGE(11)}</li>
* <li>{@code !isStatic($x)}</li>
* </ul>
*
* @since 1.3.2
*/
public sealed interface GuardExpression
permits GuardExpression.FunctionCall, GuardExpression.And, GuardExpression.Or, GuardExpression.Not {
/**
* Sets the guard function resolver used by {@link FunctionCall} to look up
* guard functions by name.
*
* @param resolver a function that maps guard function names to implementations
*/
static void setGuardFunctionResolver(Function<String, GuardFunction> resolver) {
GuardFunctionResolverHolder.setResolver(resolver);
}
/**
* Returns the current guard function resolver.
*
* @return the resolver, or {@code null} if not set
*/
static Function<String, GuardFunction> getGuardFunctionResolver() {
return GuardFunctionResolverHolder.getResolver();
}
/**
* Evaluates this guard expression against the given context.
*
* @param ctx the guard context
* @return {@code true} if the guard condition is satisfied
*/
boolean evaluate(GuardContext ctx);
/**
* A function call guard expression (e.g., {@code sourceVersionGE(11)},
* {@code $x instanceof String}).
*
* <p>The {@code instanceof} expression is modeled as a function call with the name
* {@code "instanceof"}, the placeholder name as the first argument, and the type
* name as the second argument.</p>
*
* @param name the function name
* @param args the function arguments
*/
record FunctionCall(String name, List<String> args) implements GuardExpression {
/**
* Creates a function call guard expression.
*
* @param name the function name
* @param args the function arguments
*/
public FunctionCall {
Objects.requireNonNull(name, "Function name cannot be null"); //$NON-NLS-1$
args = List.copyOf(args);
}
@Override
public boolean evaluate(GuardContext ctx) {
Function<String, GuardFunction> resolver = GuardFunctionResolverHolder.getResolver();
GuardFunction fn = resolver != null ? resolver.apply(name) : null;
if (fn == null) {
throw new IllegalStateException("Unknown guard function: " + name); //$NON-NLS-1$
}
return fn.evaluate(ctx, args.toArray());
}
}
/**
* Logical AND of two guard expressions.
*
* @param left the left operand
* @param right the right operand
*/
record And(GuardExpression left, GuardExpression right) implements GuardExpression {
/**
* Creates a logical AND guard expression.
*
* @param left the left operand
* @param right the right operand
*/
public And {
Objects.requireNonNull(left, "Left operand cannot be null"); //$NON-NLS-1$
Objects.requireNonNull(right, "Right operand cannot be null"); //$NON-NLS-1$
}
@Override
public boolean evaluate(GuardContext ctx) {
return left.evaluate(ctx) && right.evaluate(ctx);
}
}
/**
* Logical OR of two guard expressions.
*
* @param left the left operand
* @param right the right operand
*/
record Or(GuardExpression left, GuardExpression right) implements GuardExpression {
/**
* Creates a logical OR guard expression.
*
* @param left the left operand
* @param right the right operand
*/
public Or {
Objects.requireNonNull(left, "Left operand cannot be null"); //$NON-NLS-1$
Objects.requireNonNull(right, "Right operand cannot be null"); //$NON-NLS-1$
}
@Override
public boolean evaluate(GuardContext ctx) {
return left.evaluate(ctx) || right.evaluate(ctx);
}
}
/**
* Logical NOT of a guard expression.
*
* @param operand the operand to negate
*/
record Not(GuardExpression operand) implements GuardExpression {
/**
* Creates a logical NOT guard expression.
*
* @param operand the operand to negate
*/
public Not {
Objects.requireNonNull(operand, "Operand cannot be null"); //$NON-NLS-1$
}
@Override
public boolean evaluate(GuardContext ctx) {
return !operand.evaluate(ctx);
}
}
}