creating a Ripple library

joshsh edited this page Sep 15, 2014 · 1 revision

A library in Ripple is a collection of primitives, i.e. basic mappings together with URIs and metadata to make them embeddable in Linked Data. Libraries are loaded via a ServiceLoader provider configuration file named "net.fortytwo.ripple.model.Library" and included in the META-INF/services directory (e.g. see the default list of libraries). In order to change the set of libraries which Ripple loads into its environment, just list them there. To create and include a new library not provided with Ripple, see the following.

First, extend the abstract Library class, in particular, implementing the load(LibraryLoader.Context) method. This is where you will add all of the primitives to be included in the library. For example, see this slightly simplified implementation of MathLibrary:

public class MathLibrary extends Library
{
    // define a namespace which we will use for all primitives in the library (optional)
    // there are multiple namespaces here, each for a different version of the math: library
    public static final String
            NS_2013_03 = "http://fortytwo.net/2013/03/ripple/math#",
            NS_2008_08 = "http://fortytwo.net/2008/08/ripple/math#",
            NS_2007_08 = "http://fortytwo.net/2007/08/ripple/math#",
            NS_2007_05 = "http://fortytwo.net/2007/05/ripple/math#";

    // declare primitives to be accessed via static methods (optional)
    private static PrimitiveStackMapping
            addVal, subVal;

    // load all math primitives (required)
    public void load(final LibraryLoader.Context context)
        throws RippleException
    {
        addVal = registerPrimitive( Add.class, context );
        subVal = registerPrimitive( Sub.class, context );
    }

    // static accessors for primitives (optional)
    public static PrimitiveStackMapping getAddValue()
    {
        return addVal;
    }
    public static PrimitiveStackMapping getSubValue()
    {
        return subVal;
    }
}

The only thing absolutely required in the above are the two calls to registerPrimitive (a Library method), which add your primitives. Defining a namespace for the primitives in the library, as well as providing static accessors for certain important primitives (so they can be used in the results produced by other primitives) are common patterns in the provided Ripple libraries. Once you include your new library in the provider config file and start Ripple, the primitives you have added will be available in your environment. The local part of each primitive's URI will be a keyword to Ripple, so that you can type add or subtract rather than pasting in the full URI of the primitive (e.g. <http://fortytwo.net/2013/03/ripple/math#add>).

Implementing new primitives requires some understanding of Ripple's streaming, stack-based model of execution, but in some cases, you can get away with copying and tweaking existing primitives. Here is the Add primitive with added comments.

public class Add extends PrimitiveStackMapping
{
    // a primitive can have multiple identities, but the first id given is the default
    private static final String[] IDENTIFIERS = {
            MathLibrary.NS_2013_03 + "add",
            MathLibrary.NS_2008_08 + "add",
            MathLibrary.NS_2007_08 + "add",
            MathLibrary.NS_2007_05 + "add"};

    public String[] getIdentifiers()
    {
        return IDENTIFIERS;
    }

    public Add()
    throws RippleException
    {
        super();
    }

    // metadata describing the input parameters of the primitive
    public Parameter[] getParameters()
    {
        return new Parameter[] {
                new Parameter( "x", null, true ),
                new Parameter( "y", null, true )};
    }

    // an rdfs:comment for the primitive, describing it
    public String getComment()
    {
        return "x y  =>  x + y";
    }

    // this is how the primitive is executed.  It takes a single list as input and
    // produces any number of lists as output
    public void apply(final RippleList arg,
                      final Sink<RippleList> solutions,
                      final ModelConnection mc) throws RippleException {

        RippleList stack = arg;

        NumericValue a, b, result;

                // pop the two arguments off of the stack and cast them to numeric values
        b = mc.toNumericValue( stack.getFirst() );
        stack = stack.getRest();
        a = mc.toNumericValue( stack.getFirst() );
        stack = stack.getRest();

                // perform the addition operation
        result = a.add( b );

                // produce a single solution: the original stack after the operands have
                // been popped off and the sum has been pushed
        solutions.put(
                stack.push( result ) );
    }

    // the inverse of this primitive (optional)
    @Override
    public StackMapping getInverse() throws RippleException
    {
        return MathLibrary.getSubValue();
    }
}