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.
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 TypeMethodDescriptionstatic <O> OutputWriter<O> Returns the default writer.voidSets one property on the output object.default voidset(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
Sets one property on the output object.- Parameters:
output- The output objectproperty- The property's name, not emptyvalue- The value, possiblynull- 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, callsset(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 anIllegalArgumentExceptionwhere the language refuses to convert it. The engine calls this method, never the other one directly.- Parameters:
output- The output objectproperty- The property's name, not emptyvalue- The value, possiblynullconverter- 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
Returns the default writer. It sets a property withputon aMapoutput, and otherwise with the output's public setter whose parameter accepts the value, such assetInterestRateforinterestRate. 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 anIntegerreaches a setter taking alongor adouble. No value is otherwise converted by the writer itself: never narrowed, so a setter taking anintdoesn't accept aLong, and never from one wrapper to another, so a setter taking aLongdoesn't accept anInteger. A varargs setter, such assetTags(String...), takes an array: a single value isn't wrapped in one. An output without such a setter fails withIllegalArgumentException, 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. Anulloutput or property name fails withNullPointerException, and an empty property name withIllegalArgumentException, for a map output as for any other.Only when no setter takes a value that isn't
nullas it is, the writer asks the rule's language to convert it, through theOutputConverterthe engine givesset(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 anintwith theDouble3.0, if it converts whole doubles (seeExpressionCompiler.convertOutput). A conversion the language refuses, with anIllegalArgumentException, 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: withTaLong, besidesetContent(long), aShortgoes tosetContent(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)besidesetContent(T)withTaString, is still tried first, though Java would callsetContent(T); an instance of a generic class, such asnew 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 inOuter<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
FactPropertiesreaches 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 toio.github.brantunger.unruly.core, which every package on the class path is. A setter it can't reach fails withIllegalStateException, 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 anInvocationTargetExceptioncaused by aClassCastException, 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 throughFactPropertiesor 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 throwsStackOverflowErrorif 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
-