Skip to content

refactor: general key-value substitution API #87

@jbee

Description

@jbee

The ExpressionData has several Maps of data that is plugged into the evaluation.
For the caller to know what key-value pairs to insert into the maps they have to know what method to call on Exression to extract the list of specific keys, like the program variables. Then fetch and build a map of the values for the keys and provide it via ExpressionData when evaluating the expression.

This system has 2 major flaws:

  1. a caller has to know what keys (and values) may be required to evaluate an expression, making the API hard to use
  2. a caller might miss providing context values potentially producing wrong results without noticing it

To improve on this the proposal is to replace the current API with the following:

A single Map<ContextKey,ContextValue> where ContextKey and ContextValue are just marker interfaces for specific classes that have that role.
For example, a new data class ProgramRuleKey would have the role of a ContextKey holding the name of the variable, a new data class ProgramRuleValue has the role of ContextValue to hold the value that needs to be inserted into the evaluation. Different types of substitutions would use different classes, each designed for the specific case.

To find any and all required key-value pairs the Expression class would only have a single Set<ContextKey> keys() method to get all used keys in the expression. To prepare the vales a loop would take place

// create an expression from a string
Expression expr = new Expression(str);

// find all keys that need providing a value from context
for (ContextKey key : expr.keys()) {
  if (key instanceof ProgramRuleKey k) {
    // ... collect ID to a collection of what to fetch
  } else if (key instanceof OrgUnitKey k) {

   //... more cases...
  } else {
    // a failed substitution is identified here...
    throw new UnsupportedOperationException("Substituting key is not supported: "+key);
  }
}

Map<ContextKey,ContextValue> context = new HashMap<>();
// fetch stuff based on the collections the above loop has gathered
// and package that into the value class for the corresponding key 

// evaluate with the context
expr.evaluate(context);

With this missing context is identified. The specific key-value classes encode the exact information that makes a key and value for the type of substitution.

In addition to keys() the API might contain a convenience method <T> Set<T> keys(Class<T> type); only extracting a key of a single key type. Mostly this would be useful for testing as an actual caller should also use keys() to make sure they do not miss to handle a key type that does occur in the expression.
With both keys and values being wrapped into a specific class we also can identify in the evaluation that a value wasn't provided for a key since null not means "substitute with null value" but "no substitution provided" (a null value would still be wrapped in a value type container).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions