Type Operations
Whilst operators are used as an identifier to allow the developer to perform operations on two operands, a type operation is the class which defines the action that operator has and the types that are supported. Let's look at an example of a simple type operation class:
public class BooleanOperation extends TypeOperation {
public BooleanOperation() {
super("BooleanOp");
}
/**
* See {@link TypeOperation#canHandle(Token, OperatorHandler, Token) canHandle}
*/
@Override
public boolean canHandle(Token<?> first, OperatorHandler<?> operator, Token<?> second) {
return first.is(Boolean.class) && second.is(Boolean.class);
}
/**
* See {@link TypeOperation#process(LARFConfig, LARFContext, Token, OperatorHandler, Token,
* UnhandledEvent) process}
*/
@Override
public Token<?> process(LARFConfig config, LARFContext context, Token<?> first,
OperatorHandler<?> operator, Token<?> second,
UnhandledEvent unhandledEvent) {
boolean result = false;
if (operator instanceof ComparisonOperator) {
switch (((ComparisonOperator)operator).getOpType()) {
case EQUAL: result = first.getValue(Boolean.class).equals(
second.getValue(Boolean.class));
break;
case NOT_EQUAL: result = !first.getValue(Boolean.class).equals(
second.getValue(Boolean.class));
break;
default: return unhandledEvent.trigger();
}
} else if (operator instanceof AssignmentOperator) {
switch (((AssignmentOperator)operator).getOpType()) {
case ASSIGN: return first.set(context, second);
default: return unhandledEvent.trigger();
}
}
return new BooleanToken(result);
}
}
This class represents all the operations that can happen with booleans. Breaking this down we can see that there
are two required methods which are canHandle
and process
. The former determines the type compatibility
we want to enforce against the two operands. In this case both have to be of type Boolean, but in other scenarios you
may want to be more flexible. Let's take a closer look at the definition of the process
method. It has six
parameters which are the following:
- The configuration object
- The context object
- The first operand e.g. <operand> <operator> <operand> (1 + 2)
- The operator e.g. <operand> <operator> <operand> (1 + 2)
- The second operand e.g. <operand> <operator> <operand> (1 + 2)
- A lambda method which is run when the operator / operand combination is not handled
Next we'll take a look at what the process
method is doing in this situation. It is checking the type of the
operator against each of our defined operator types. There are several sets defined (for more information on this
and you can even create your own. For more information on this please see the Operators) section.
Let's take a closer look at one section:
if (operator instanceof ComparisonOperator) {
switch (((ComparisonOperator)operator).getOpType()) {
case EQUAL: result = first.getValue(Boolean.class).equals(
second.getValue(Boolean.class));
break;
case NOT_EQUAL: result = !first.getValue(Boolean.class).equals(
second.getValue(Boolean.class));
break;
default: return unhandledEvent.trigger();
}
}
Here we are checking that the operator matches the type comparison. If it is then we compare the operators OpType
against the enum value defined for each set of operators e.g. Arithmetic, Bitwise, Comparison etc. In each case
we'll perform the operation and set the result to a value. If no cases are matched then the default always calls
the standard unhandledEvent.trigger()
method. For the cases, we could return the result directly in a BooleanToken,
but taking this approach is useful should we want to do logging or debugging.
To handle different sets of operators, it's simply a case of checking the operator against each set for a match. To
register this class, simply add it to the initTypeOperations
methods in your configuration class implementation:
@Override
protected TypeOperation initTypeOperations() {
addTypeOperation(new DateOperation());
addTypeOperation(new EnumOperation());
addTypeOperation(new StringOperation());
addTypeOperation(new NumericOperation());
addTypeOperation(new BooleanOperation());
addTypeOperation(new ArrayOperation());
addTypeOperation(new MapOperation());
addTypeOperation(new NullOperation());
//Fallback
return new ObjectOperation();
}