Converters
Converters make a Model more compact by accepting a convenient input and turning it into the field's declared value.
Field-Based Converters
Field-based converters are closures that create additional setter/adder methods.
Converter methods have the same name as regular setters/adders, but different parameters. A converter is a closure with zero or more explicit parameters that is called to create the target type (or the element type for collections/maps). For maps of simple types, a key parameter is added to the adder as well.
Examples:
(See: ConvertersDocumentaryTest#'converts timestamp input for a simple field and map entry'.)
@DSL class Foo {
@Field(converters = [
{long value -> new Date(value)},
{int date, int month, int year -> new Date(year, month, date)}
])
Date birthday
@Field(converters = [
{long value -> new Date(value)},
{int date, int month, int year -> new Date(year, month, date)}
])
Map<String, Date> payDays
}
Creates additional methods:
Date birthday(long $value) {
birthday({long value -> new Date(value)}.call($value))
}
Date birthday(int $date, int $month, int $year) {
birthday({int date, int month, int year -> new Date(year, month, date)}.call($date, $month, $year))
}
Date payDay(String $key, long $value) {
payDay($key, {long value -> new Date(value)}.call($value))
}
Date payDay(String $key, int $date, int $month, int $year) {
payDay($key, {int date, int month, int year -> new Date(year, month, date)}.call($date, $month, $year))
}
The closures must return an instance of the field (or element) type.
For a DSL Object result, a source-visible converter participates in the active Builder construction. Field converters for Simple Values are unaffected. See Behind the Curtain#builder-projection-for-custom-producers when the distinction matters to an integration.
Factory Method Converters
For non-DSL values, factory-method converters continue to return the converted value and pass it to the Builder setter or adder.
For a DSL Object relationship, a recognized static factory method can be used inside the owning Model just like a normal child creator. The same method still returns a completed object when called as a root factory:
(See: ConvertersDocumentaryTest#'uses a named factory method as an owned DSL relationship creator'.)
given: // Schema
@DSL
class Server {
Endpoint endpoint
}
@DSL
class Endpoint {
int port
static Endpoint fromPort(int port) {
Endpoint.Create.With(port: port)
}
}
when: // Model
def server = Server.Create.With {
endpoint 8443
}
then: // Assertions
assert server.endpoint.port == 8443
For the Builder-projection rules, source-visibility boundary, and diagnostics for opaque producers, see Behind the Curtain#builder-projection-for-custom-producers.
Source-visible same-compilation converters also work in Builder lifecycle methods and in nested converter bodies. For
example, storage('uri://bla/blub', 'uri://bli/blu') can select Storage.fromStrings, whose implementation calls
qualified Registry.fromString(...) converters for its owned children. Builder-phase calls use active-session Builder
twins; the same Registry.fromString(...) call outside Builder construction remains a completed-model factory.
(See: ConvertersDocumentaryTest#'uses fluent scalar converter syntax for an owned relationship'.)
A factory method is named from*, of*, or parse*; a non-DSL factory may also be named create*. Alternatively,
annotate the method with @Converter.
Avoid multiple factory methods with the same parameter signature; selection can otherwise be ambiguous.
Factory Classes
Declare additional factory classes with @Converters, either for a complete DSL class or one field.
By default, KlumAST uses public static factory methods that return the expected type or a subtype, have a recognized
prefix, or carry @Converter.
(See: ConvertersDocumentaryTest#'uses a converter factory class for convention and annotation-based inputs'.)
import java.text.SimpleDateFormat
@Converters(BarUtil)
@DSL class Foo {
Bar bar
}
class Bar {
// Regular POGO/POJO, not a DSL Object.
Date birthday
}
class BarUtil {
static Bar fromLong(long value) {
return new Bar(birthday: new Date(value))
}
@Converter
static Bar readFromString(String string) {
return new Bar(birthday: SimpleDateFormat.dateInstance.parse(string))
}
}
For a Map of simple elements, KlumAST prepends the key parameter to the converter parameters.
Customization
@Converters can customize factory discovery for third-party types: includeMethods, excludeMethods, and
excludeDefaultPrefixes change method selection, while includeConstructors exposes constructors as converters. See the
@Converters API source and Javadoc
for every option and its default.
For example, opt into constructors when a value type has a useful multi-argument constructor.
(See: ConvertersDocumentaryTest#'uses an opt-in URI constructor as a converter'.)
@Converters(includeConstructors = true)
@DSL
class RemoteService {
URI endpoint
}
def service = RemoteService.Create.With {
endpoint 'https', 'config.example.test', '/v1', 'stable'
}
assert service.endpoint == new URI('https', 'config.example.test', '/v1', 'stable')