Advanced usage of the Any type

In our previous Introduction to the Any Type blog, we learned about the new EPL any type introduced in 10.1. In 10.2, we expanded the set of reflection capabilities on the any type. These new additions increase our ability to write even more generic and dynamic Apama applications, so let’s explore them now!

Reflection Capabilities Of the Any Type:

Reflection allows EPL to act on values of an any type object in a generic way, including altering the behavior to adapt to what fields or actions a type has. These values can be passed as an any parameter value to some common code or created via the any .newInstance method.

Fields or entries of an any value can be accessed using the following methods:

  • setEntry(any key, any value)
  • getEntry(any key) returns any
  • getEntries() returns sequence

For event types , the key should be a string containing the field name. For sequences, key is the index and should have an integer value.

The actions (including built-in methods) and constants can be obtained with the following methods on the any type:

  • getActionNames() returns sequence
  • getAction(string name) returns any
  • getConstant(string) returns any
  • getConstantNames() returns sequence

Actions may be cast to the correct action type and then invoked directly.

For actions, a list of the action’s parameter names, a dictionary mapping from the parameter name to the parameter type, and the name of the return type can be obtained with the following methods on the any type:

  • getActionParameterNames() returns sequence
  • getActionParameters() returns dictionary<string,string>
  • getActionReturnTypeName() returns string

For actions, it is also possible to use a “generic” form to call the action via the following method of the any type, even if the signature type is not known at compile time:

  • getGenericAction() returns action<sequence > returns any

A sequence of the parameter values of the correct count and types must be supplied to the generic type to execute the action.

Examples using reflection capabilities of Any type

In this example, we have written a generic map action which takes a mapper action of type any . The values of the sequence are mapped as per the mapper action provided. We have used the reflection capability “getGenericAction()” to allow callbacks to the mapper action, regardless of the signature of that action.

event Sequence
    sequence<any> values;

    //Creates a new instance of Sequence from the sequence provided
    static action create(any seq) returns Sequence {
        if not isSequence(seq) {
            throw com.apama.exceptions.Exception("Not a sequence", "IllegalArgumentException");
        }
        return Sequence(seq.getEntries());
    }

    //Checks whether the given value is referring to a sequence
    static action isSequence(any seq) returns boolean {
        return seq.getTypeName().substring(0, "sequence<".length()) = "sequence<";
    }

    //Checks whether the given value is referring to an action
    static action isAction(any act) returns boolean {
        return act.getTypeName().substring(0, "action<".length()) = "action<";
    }

    //Maps the values of the Sequence as per the mapper action provided
    action map(any mapper) returns Sequence {
        if not isAction(mapper) {
            throw com.apama.exceptions.Exception("Must provide map with an action", "IllegalArgumentException");
        }
        // Getting the number of parameters the action accepts
        integer argCount := mapper.getActionParameterNames().size();
        if argCount < 1 or argCount > 2 {
            throw com.apama.exceptions.Exception("Mapping action must have 1 arg and optionally 1 more", "IllegalArgumentException");
        }
        if mapper.getActionReturnTypeName() = "" {
            throw com.apama.exceptions.Exception("Mapping action must return a value", "IllegalArgumentException");
        }

        //Using reflection on any value to get generic action.This generic action will be called when we need to map the sequence values.
        action<sequence<any> > returns any genericMapper := mapper.getGenericAction();

        Sequence result := new Sequence;

        integer i := 0;
        any value;
        for value in values {
            if argCount = 1 {
                // Calling genericMapper action with 1 parameter after getting the number of parameters through reflection
                result.values.append(genericMapper([value]));
            } else if argCount = 2 {
                // Calling genericMapper action with 2 parameters after getting the number of parameters through reflection
                result.values.append(genericMapper([value, i]));
            }
            i := i + 1;
        }

        return result;
    }
}

monitor Test {
    action onload() {
        // Here we are applying addition operation on the sequence of values,where the addition operation is provided as an action, which gets called through reflection
        log Sequence.create([1,2,3])
        .map(add1) // Has 1 arg
        .map(addI) // Has 2 args
        .values.toString();
    }

    action add1(integer value) returns integer {
        // No casting needed!
        return value + 1;
    }

    action addI(integer value, integer i) returns integer {
        return value + i;
    }

}

In the below example, we have written a generic factory method which creates an instance of a type based on the type name, and initializes it by calling its init method with the default value for each argument.

We get the argument types using reflection on the any value which contains the event instance. Using this we can know about the type names at runtime and create their instances using the “newInstance” method on the any type.

event Cat {
    string color;
    integer weight;
    action init(string clr,integer wt) {
        color := clr;
        weight := wt;
    }
}
 
event Dog {
    string color;
    integer weight;
    float height;
    action init(string clr,integer wt,float hgt) {
        color := clr;
        weight := wt;
        height := hgt;
    }
}
 
monitor m1{
 
    action onload() {
        // Dictionary holding default values for each type.
        dictionary <string,any> defaultValues := {"string" : "", "integer" : 0, "float" : 0.0};      
        any defaultCat := factoryMethod("Cat",defaultValues);
        any defaultDog := factoryMethod("Dog",defaultValues);
 
    }
 
    // Factory method creating instance of event and calling its init method using reflection
    action factoryMethod(string name ,dictionary <string,any> values)  returns any {
        any evt := any.newInstance(name);      
        any act := evt.getAction("init");
        action <sequence <any> > returns any genAct := act.getGenericAction();
        sequence<any> actionParams := new sequence<any>;
 
        ifpresent act {
            //Getting the list of parameter names of the action
            sequence<string> paramNames := act.getActionParameterNames();
            dictionary<string,string> params := act.getActionParameters();
            string param;
            for param in paramNames {
                string typeOfParam := params.getOrDefault(param);
                //Looking for default value for a type in dictionary holding default values
                if values.hasKey(typeOfParam) {
                    actionParams.append(typeOfParam);
                }
                else {
                    actionParams.append(any.newInstance(typeOfParam));
                }
            }
        }
        any actionret := genAct(actionParams);
        return evt;
    }
}

As always, please post on our forums or leave a comment below with any questions or statements you may have; can you come up with any use that we haven’t? Thanks, and happy developing!

– Ashish