When using Wyjec in your application, sooner or later you will want to extend it with your own domain-specific functions. There is nothing special about it, Wyjec is designed exactly for such use cases. Actually, standard Wyjec functions are treated equally with the ones provided by you, so you can implement their full replacements.
Wyjec language has a few types with names similar to or the same as the ones used in the Java language, but they are not directly related. Each Wyjec type is mapped into a Java primitive or reference type according to the following table:
Wyjec type | Java type |
---|---|
integer | long |
boolean | boolean |
string | RawString (reference type defined inside the Wyjec implementation) |
float | double |
date | long (interpreted as the number of milliseconds since 1970.01.01 00:00:00 GMT) |
Every Wyjec function is implemented as a single Java class that is a subclass of pl.chyla.wyjec.core.Function. Classes implementing Wyjec functions do not inherit directly from Function
class. Instead, each concrete implementation of a function inherits from one of abstract subclasses of Function
from pl.chyla.wyjec.core.functions package. The type of values returned by a function determines the base class for a class that implements the function, as presented in the table below.
Return type | Base class |
---|---|
boolean |
BooleanFunction |
integer |
IntegerFunction |
string |
StringFunction |
float |
FloatFunction |
date |
DateFunction |
A class that implements Wyjec function must provide three methods which are declared abstract
in its superclass. Two of them are common for all function classes:
private
) static final
field to store the appropriate FunctionSignature instance and simply return a reference to it.
The third method to be implemented by the class is named execute
and its signature depends on the function's base class (i.e. one of classes presented in the table). I'll describe it later in detail.
Writing declarations of parameters should be one of the first steps when implementing a function. A class implementing the function should define single static final
field for every parameter required by the function and single static final
field representing all optional parameters in case the function supports them. Objects representing required parameters must be instances of classes that inherit from Parameter and object representing optional parameters must be an instance of a class that inherits from OptParameters. Additionally, the class should define single field that represents the function's signature. It must be an instance of FunctionSignature that groups all objects representing required and optional parameters.
The actual types of fields that represent required or optional parameters depend on Wyjec types of these parameters. The table below summarises classes used in all cases.
Wyjec type | Class for required parameter | Class for optional parameters |
---|---|---|
integer | IntegerParameter | IntegerOptParameters |
boolean | BooleanParameter | BooleanOptParameters |
string | StringParameter | StringOptParameters |
float | FloatParameter | FloatOptParameters |
date | DateParameter | DateOptParameters |
For example, if a function has two required parameters with types integer
and boolean
respectively and accepts optional string
parameters, it should define the following fields that represent them.
private static final IntegerParameter pIntParam = new IntegerParameter();
private static final BooleanParameter pBoolParam = new BooleanParameter();
private static final StringOptParameters pStrParams = new StringOptParameters();
Note:
By convention, fields that represent parameters have a "p" prefix. You don't have to follow it, but it's constantly used in this document and in the implementation of standard Wyjec functions.
The function's signature should be created by passing all the above objects to its constructor: an array of objects representing required parameters and the object representing optional parameters. In case of functions that don't accept optional parameters, the second parameter of the constructor should be set to null
.
private static final FunctionSignature signature = new FunctionSignature(new Parameter[] {pIntParam, pBoolParam}, pStrParams);
Important note:
When defining fields that represent required or optional parameters, always initialise them by using class instance creation expression (e.g. new IntegerParameter()
). Do not try to share Parameter
or OptParameters
instances for different parameters, even if they have the same type. Similar rule applies to signature objects, with one exception: when implementing a function that doesn't accept any parameters, you may use the existing FunctionSignature.SIGNATURE_NO_PARAMETERS instance instead of creating a new one.
Parameter and OptParameters instances described in the previous section are shared between all instances of a function implementation class, so they can't contain the actual parameters of a function. Instead, each of these instances is used as a parameter's declaration and it can be used to specify a parameter which you refer to. A class that implements a function may read values of the actual parameters by passing a declaration of the appropriate parameter (Parameter
or OptParameters
instance) to one of getParameter
methods derived from the Function class.
The following table summarises Function
's methods that provide access to values of actual parameters that are declared as required ones.
Method | Returned value |
---|---|
getParameter(BooleanParameter param) | boolean parameter specified with param object |
getParameter(IntegerParameter param) | integer parameter specified with param object |
getParameter(StringParameter param) | string parameter specified with param object |
getParameter(FloatParameter param) | float parameter specified with param object |
getParameter(DateParameter param) | date parameter specified with param object |
When reading value of a parameter that is declared as optional one, you must specify an OptParameters
instance that declares a function's optional parameters and a non-negative index of the parameter. The index specifies position of the parameter in the group of all optional parameters. First optional parameter should be always referred using 0
index, no matter how many required parameters are placed before the optional ones.
The following table summarises Function
's methods that provide access to values of actual parameters that are declared as optional ones.
Method | Returned value |
---|---|
getParameter(BooleanOptParameters optParams, int index) | boolean parameter specified by its index in a list of optional parameters represented by optParams |
getParameter(IntegerOptParameters optParams, int index) | integer parameter specified by its index in a list of optional parameters represented by optParams |
getParameter(StringOptParameters optParams, int index) | string parameter specified by its index in a list of optional parameters represented by optParams |
getParameter(FloatOptParameters optParams, int index) | float parameter specified by its index in a list of optional parameters represented by optParams |
getParameter(DateOptParameters optParams, int index) | date parameter specified by its index in a list of optional parameters represented by optParams |
Before reading any optional parameters you should use getOptParametersCount(OptParameters optParams) method to find out how many of them were passed to the function. Only nonnegative numbers less than a value returned by the getOptParametersCount
are valid as index
parameter for getParameter
methods.
Important note:
A function instance may read its parameters only by using Parameter and OptParameters instances that were passed to a constructor of its own FunctionSignature instance, i.e. the one that the function's getSignature() method returns.
For example, if a function contains declarations of fields pIntParam
, pBoolParam
, pStrParams
and signature
as shown in the previous section, you can use the following code to print values of all parameters to the standard output:
PrintStream out = System.out;
long intParam = getParameter(pIntParam);
out.println("int: " + intParam);
boolean boolParam = getParameter(pBoolParam);
out.println("bool: " + boolParam);
for (int i = 0; i < getOptParametersCount(pStrParams); i++) {
ReadableRawString strParam = getParameter(pStrParams, i);
out.println("str #" + i + ": " + strParam);
}
As I already mentioned at the beginning of this document, every class that implements a function must contain a method named execute
. A signature of this function is different for every function type, but there's one simple rule: for all types of functions except for string function the execute
method takes no parameters and returns a value of type that corresponds to Wyjec type of function result. Implementations of these methods should simply return the result of function evaluation as the method's result.
Method | Description |
---|---|
boolean execute() | evaluates the result of boolean function |
long execute() | evaluates the result of integer function |
double execute() | evaluates the result of float function |
long execute() | evaluates the result of date function |
For string functions a signature of execute
method is different:
void execute(RawString result)
An implementation of this method should return the result of function evaluation by appending it to result
object given as a parameter. It's guaranteed that the parameter contains an empty string when the method is called, so you don't have to do anything if the result of evaluation is an empty string. I strongly recommend building the result by calling append
methods on result
parameter. Consult RawString API documentation to find out more on this topic.
In order to demonstrate information from previous sections in practice I prepared an implementation of very simple Wyjec function that is shown below. The function accepts single integer parameter and returns a string value that consists of specified number of dots. For negative numbers the function returns an empty string.
The function has the following signature:
dots (dotsCount : integer) : string
public class FnDots extends StringFunction {
private static final IntegerParameter pDotsCount = new IntegerParameter();
private static final FunctionSignature signature =
new FunctionSignature(new Parameter[] {pDotsCount}, null);
public FunctionSignature getSignature() {
return signature;
}
public String getName() {
return "dots";
}
protected void execute(RawString result) {
for (int i = 0; i < getParameter(pDotsCount); i++) {
result.append('.');
}
}
}
© Copyright by Zbigniew Chyla 2005