Instrumenting join tests with finders

Use finders to provide the engine with application-specific code for more efficient pattern matching on specific classes.

The finder mechanism is derived from the relation feature. This feature uses the keywords from and in for navigation through the object model.

Relations are typically used for the following reasons:

If you use relations only for an optimization purpose, you might have to write your rules in a way that is not natural and breaks the abstraction of rule-based programming.

Example of using finders

For example, suppose that the model of your application defines a Customer class with two fields, zipcode and age:

class Customer {
    public int zipcode;
    public int age;
};

The simplest way of writing a condition that matches a Customer with a given zipcode and age would be:

?c: Customer(zipcode == 80; age == 20);

But this can be very inefficient if there are many customers. The inefficiency arises because all the customers must be added and the conditions must be tested for all of them.

To address this problem, you could write Java code that retrieves a list of customers from their zip code using a hash table. You could then add a static lookup method in the Customer class like this:

class Customer {
    ...
    public static Customer[] findByZipCode (int zipcode) 
                      { <Java retrieval code> }
};

Then you could rewrite the previous condition so it uses this method:

?c: Customer(age == 20) in Customer.findByZipCode(80);

This makes the rule much more efficient because it restricts the number of objects on which the pattern matching occurs. Moreover, it is not necessary to add customers.

Unfortunately, this optimization technique is intrusive because it requires that you rewrite each rule that matched a customer’s zip code. If the optimization support provided by the application evolves, for example with the addition of new lookup methods, the rules have to be changed.

Using finders, you can achieve the same result but in a nonintrusive way. To benefit from the finder mechanism, you simply declare finder methods such as findByZipCode in a dedicated file processed by the engine.

The engine detects the declared methods and automatically uses them in rules as if the rule used an explicit in statement.

Suppose that a class ClassX of the object model contains a method with the following signature:

static ClassX[] findBySomething(type1 name1,..., typeN nameN)

where the names and types of the arguments match some fields of Class, and this method is declared as a finder. The engine then internally rewrites any condition of the form:

ClassX(name1 eq value1, .., nameN eq valueN; other tests);

as:

ClassX(other conditions)in findBySomething(value1, ..., valueN)

For example, if the definition of the class Customer contains a finder with the following signature:

class Customer {
    ...
    public static Customer[] findByZipCode(int zipcode)
                   { <Java retrieval code> }
};

then the condition:

?c: Customer(zipcode == 80; age == 20);

is internally rewritten by the engine as:

?c: Customer(age == 20) in Customer.findByZipCode(80); 

A finder method can also return a single object. In this case, the engine internally rewrites the rule using the from construct.

A finder can be declared as inaccurate. In this case, the engine considers that the finder returns a superset of the matching objects and that the equality conditions still require a verification of the objects.

The valid keywords for a finder result are:

Different types of finders

A finder can optimize a rule condition when the attributes linked to the finder arguments appear in equality tests of the condition and can be called from the condition. Other requirements depend on the type of the finder.

There are three types of finders:

Although it is always possible to call a finder in a static or an IlrContext method, an instance method requires a simple condition to exist (not, exists, and collect conditions excluded) from which the finder class is assignable. When several finders can be used to optimize a rule condition, the engine determines the best one.

The criteria involved in the finder evaluation are listed by priority:

  1. A single-element finder is preferred to a multiple-element one.

  2. An accurate finder is preferred.

  3. A constant finder is preferred.

  4. A finder is preferred when it has more arguments.

    Note:

    In the case of equivalent finders, the engine choice should be considered as nondeterministic.

The returned type of an N-ary finder may be either a Vector, an Enumeration, or an array of elements whose type is the same as the condition type. The engine assumes that in the case of Vector or Enumeration, the type returned is compatible with the condition type.