Interface OutputWriter<O>

Type Parameters:
O - The type of the output objects it writes to
Functional Interface:
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

@FunctionalInterface public interface OutputWriter<O>
Sets a property on the output object. The engine uses it for the properties an action returns with ActionResult.set(java.util.Map), as a language whose expressions have no side effects does. Give an engine one with RulesEngineBuilder.outputWriter(OutputWriter); the default is beansAndMaps().

Implemented by applications, to write to an output object the default writer doesn't suit. A method added to this interface is a default method, so an existing writer keeps compiling and working.

The engine calls set(Object, String, Object, OutputConverter) on the thread running the rule, once for each property, in the order the action returned them, with an OutputConverter that converts a value as the rule's language understands it. By default that calls set(Object, String, Object) with the value as the action returned it, so a writer written as a lambda, or one that doesn't override it, never converts; one that does override it may ask the converter for a value of the type it needs. What it throws is treated as what an action throws. It fails the rule with a RuleExecutionException naming the rule and the property, which listeners receive in onError; a fatal Error, such as an OutOfMemoryError, is rethrown unchanged once they have. But when the thread was interrupted, or the run passed its deadline, by the time it threw, the run stops, as a cancelled run does, with an exception whose getRuleName() is null, which keeps what it threw as a suppressed exception; unless that is or wraps an Error, which fails the rule all the same. A run that was interrupted, or passed its deadline, while the writer set the properties without throwing stops once every one is set, and they stay set. It must be thread-safe, because runs on many threads share it.

  • Method Summary

    Modifier and Type
    Method
    Description
    static <O> OutputWriter<O>
    Returns the default writer.
    void
    set(O output, String property, @Nullable Object value)
    Sets one property on the output object.
    default void
    set(O output, String property, @Nullable Object value, OutputConverter converter)
    Sets one property on the output object, given a converter for the value from the rule's expression language.
  • Method Details

    • set

      void set(O output, String property, @Nullable Object value) throws Exception
      Sets one property on the output object.
      Parameters:
      output - The output object
      property - The property's name, not empty
      value - The value, possibly null
      Throws:
      Exception - if the property can't be set
    • set

      default void set(O output, String property, @Nullable Object value, OutputConverter converter) throws Exception
      Sets one property on the output object, given a converter for the value from the rule's expression language. By default, calls set(Object, String, Object) with the value as it is, so a writer that doesn't override this method sees the value as the action returned it, and converts nothing. A writer that needs a value of another type overrides it and asks the converter: converter.convert(value, type) returns a value the type accepts, as the language understands the value, or the value unchanged where the language has no conversion for the type, and throws an IllegalArgumentException where the language refuses to convert it. The engine calls this method, never the other one directly.
      Parameters:
      output - The output object
      property - The property's name, not empty
      value - The value, possibly null
      converter - Converts the value to the type of a setter, as the rule's language understands the value
      Throws:
      Exception - if the property can't be set
    • beansAndMaps

      static <O> OutputWriter<O> beansAndMaps()
      Returns the default writer. It sets a property with put on a Map output, and otherwise with the output's public setter whose parameter accepts the value, such as setInterestRate for interestRate. A boxed primitive, only when no setter takes it as it is, goes to a setter taking a primitive that Java widens it to, so an Integer reaches a setter taking a long or a double. No value is otherwise converted by the writer itself: never narrowed, so a setter taking an int doesn't accept a Long, and never from one wrapper to another, so a setter taking a Long doesn't accept an Integer. A varargs setter, such as setTags(String...), takes an array: a single value isn't wrapped in one. An output without such a setter fails with IllegalArgumentException, which, for a number, a character or a boolean, names the setters of the property that take a primitive or a wrapper. Of overloaded setters, it calls the most specific one that accepts the value, as Java would; when no single one is the most specific, the choice is fixed for the output class and the same on every run. A null output or property name fails with NullPointerException, and an empty property name with IllegalArgumentException, for a map output as for any other.

      Only when no setter takes a value that isn't null as it is, the writer asks the rule's language to convert it, through the OutputConverter the engine gives set(Object, String, Object, OutputConverter): for each type a setter of the property takes, the most specific setter first, until one takes what the language returns, as it is or widened. So a language whose numbers are doubles sets a setter taking an int with the Double 3.0, if it converts whole doubles (see ExpressionCompiler.convertOutput). A conversion the language refuses, with an IllegalArgumentException, doesn't stop the writer trying the property's other setters; when none takes the value, the write fails with the first refusal, or, if the language refused none, with the failure described above. Anything else the language throws fails the write at once. A language without a conversion changes nothing: the value is set only as it is, and the write fails as above.

      A setter declared with a type variable of a class, such as setContent(T), takes what the output class makes of the variable where the class has another setter of the property with a different parameter: with T a Long, beside setContent(long), a Short goes to setContent(long), as in Java, and text fails.

      Where it differs from Java: a setter declared with a type variable, with no other setter of the property, takes whatever its erased parameter does; a setter whose parameter is a supertype of what the variable is given, such as setContent(CharSequence) beside setContent(T) with T a String, is still tried first, though Java would call setContent(T); an instance of a generic class, such as new Box<Long>(), gives the variable nothing, as its type argument isn't kept at run time, so the setter takes the variable's bound; a type variable an inner class uses from its enclosing class, as in Outer<T>.Inner, isn't resolved, so a setter declared with it, or with a variable it's passed to, takes the variable's bound; and a varargs setter takes only an array.

      The setter is reached the way FactProperties reaches a getter: through a public, exported type that declares it, such as an interface the output class implements, or directly where the class's package is open to io.github.brantunger.unruly.core, which every package on the class path is. A setter it can't reach fails with IllegalStateException, saying what to export or open, rather than a less specific overload being called in its place. One limit: where a class the writer can't reach overrides a generic setter beside another public instance overload of the same name with a narrower parameter, its own or inherited, even from a class that isn't public, a value for that overload reaches the generic setter instead. It fails with an InvocationTargetException caused by a ClassCastException, or, where the overload's parameter is narrower than the generic setter's, the less specific generic setter is called.

      A bean's property whose first character is above U+00FF, such as πx, is upper-cased into its setter's name with the JDK's table of the first plane, java.lang.CharacterData00, which the JVM builds on its first use, maybe deep in a stack with no engine built. So until that table is known to be built, by such a write, by a read of such a getter through FactProperties or by the JVM's first engine build, the write first checks that the stack has room for building it, as the engine's first build does, and throws StackOverflowError if it hasn't, before the table is touched, rather than leave it unusable for the life of the JVM (#1297). After that, no write checks. A map's key, and a property whose first character is U+00FF or below, take no check.

      Type Parameters:
      O - The type of the output objects
      Returns:
      The writer