Skip to main content
Skip table of contents

Create plugin functions

In addition to creating new input, output, and transform tools that appear on the palette, you can also use the RedPoint Data Management (RPDM) SDK to create new functions. These functions automatically inject themselves into the expression language used by the Calculate and Filter tools, and by the Expression step. They are "first class" functions: they appear in the function insert menu and the expression editor's auto-completion lists.

Writing a new function to plug into RPDM is like writing a method in Java, with a few restrictions and annotations. To create a new plugin function, follow these steps:

The fundamentals

  • Create a new public class, extending either FunctionInstance or FunctionSingleton from the net.redpoint.function package.

  • Implement the getCategory() function to return the category (or submenu) under which the functions will be listed in the expression-builder menu.

  • Create static methods in a FunctionSingleton subclass, or non-static methods in a FunctionInstance subclass.

  • Plugin functions must be public.

  • Plugin functions must return a value, they cannot be void.

  • Plugin functions must have the @Function annotation, from the net.redpoint.function.annotations package.

  • Make a resource under META-INF.services listing your classes.

FunctionSingleton vs FunctionInstance

RPDM provides two classes that you can extend to create new plugin functions, and you must choose which one to extend. These classes serve distinct and different purposes.

FunctionSingleton is appropriate for functions that don't need an object instance per useā€”and most functions don't. So you will usually extend FunctionSingleton. The following code demonstrates a function that divides two numbers:

CODE
import net.redpoint.function.FunctionSingleton;
import net.redpoint.function.annotations.Function;
public class MyFunctions extends FunctionSingleton {
    public String getCategory() { return "Math"; }
    @Function
    public static double divide(double numerator, double denominator) { return numerator/denominator; }
}

Because the divide() function doesn't need any state or objects, it can be implemented in a subclass of FunctionSingleton. Some things to note about creating functions in a subclass of FunctionSingleton:

  • Methods must be static.

  • No instance of your class is created when your function is used; however, an instance will be created as part of inserting it into the expression language. 

  • You may use static fields in your class.

  • If you use static fields in your class, your methods should have the synchronized keyword to serialize access to the fields, because the same object will be used for all instances of the expression.

It is perfectly fine to use FunctionSingleton even if your function maintains state, provided that state can be shared and access to it synchronized:

CODE
public class MyFunctions extends FunctionSingleton {
    private static int counter = 0;
    public String getCategory() { return "Math"; }
    @Function
    public static synchronized int count() { return ++counter; }
}

You should only extend FunctionInstance (rather than FunctionSingleton) if your function is both of these things:

  • Expensive to execute, and

  • Uses fields of your class.

For example, suppose that you are writing a plugin function that uses a library to carry out its work. You could implement this by extending FunctionSingleton:

CODE
public class ExpensiveFunctions extends FunctionSingleton {
    static private LibraryThing thing = new LibraryThing(...);
    public String getCategory() { return "ExpensiveThings"; }
    @Function
    public static synchronized double expensiveFunction(double value) { return thing.doSomething(value); }
}

Note that we have to add the synchronized keyword to the method to serialized access to "thing". But this means that multiple instances of this function used in separate methods will have to wait for access to the singleton thing.

To improve performance, extend FunctionInstance instead. This will cause a separate instance of your class to be created for every function used in an expression, and there will be no penalty for parallelism:

CODE
public class ExpensiveFunctions extends FunctionInstance {
    private LibraryThing thing = new LibraryThing(...);
    public String getCategory() { return "ExpensiveThings"; }
    @Function
    public double expensiveFunction(double value) { return thing.doSomething(value); }
}

Supported Types

RPDM can process only certain data types. Use only the following types for arguments and return values in your methods:

  • java.lang.Boolean, boolean

  • java.lang.Byte, byte

  • java.lang.Short, short

  • java.lang.Integer, int

  • java.lang.Long, long

  • java.math.BigDecimal

  • java.lang.Float, float

  • java.lang.Double, double

  • org.joda.time.LocalDateTime

  • org.joda.time.LocalDate

  • org.joda.time.LocalTime

  • java.sql.Timestamp

  • java.sql.Date

  • java.sql.Time

  • java.lang.String

  • byte[]

@Function annotation

You must precede your method definition with an @Function annotation. RPDM will ignore methods that lack this annotation.

Argument names

RPDM uses Java reflection to determine the signatures of the plugin functions. However, while argument types are returned by reflection, argument names are not. Use the argName property of the @Function annotation to make RPDM aware of the argument names. The property shown below contains a comma-separated list of names. If this is missing, RPDM will make up some names for you, but they will not be very helpful.

CODE
@Function(argNames="numerator,denominator")
public static double divide(double numerator, double denominator) { 
    return numerator/denominator; 
}

META-INF.services resource

RPDM uses the Java ServiceLoader to list classes that contain plugin functions. In order to "publish" your function class, you'll need to create a text-file resource in the META-INF.services package whose name matches the base class(es) being extended. For example, in the NetBeans IDE, functions implemented by extending FunctionInstance will have a resource that resembles this:

Figure 6

That resource file contains a simple list of all the classes you want to publish. For example, RPDM uses this mechanism internally to create and publish its own set of "Hmac" cryptography functions. Its resource file looks like this:

CODE
#Definition of functions provided by RPDM
net.redpoint.internal.function.HMacFunctions

Conversions, error values, and null values 

RPDM will automatically convert types where possible. For example, if your function takes a String, but you pass it an Integer, RPDM will format the integer into a string and pass it to your function. However, not all conversions are possible. For example RPDM will not convert numbers into Date, Time, or DateTime values. If a non-convertible value is passed to a function, RPDM will return the special "Error" value on behalf of the function. If a null value is passed to your function, it will be turned into a "default" value instead, such as 0 for integers or Jan 1 1600 for Dates.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.