Wyjec functions developers tutorial

for Wyjec 1.0

Introduction

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 types from Java developer's point of view

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)

Base classes for implementations of Wyjec functions

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:

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.

Declaring function parameters

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.

Reading function parameters

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);
}

Returning the result of function evaluation

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.

Function implementation example

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

Valid XHTML 1.0 Strict Viewable with Any Browser