Improve documentation

Among other things, document Polymorphic and PolymorphicTypes
annotations in README.
dev
Exlll 2 years ago
parent 482e9464a9
commit 268ce78163

@ -223,9 +223,9 @@ public final class UnsupportedTypes<T> {
```
**NOTE:** Even though this library does not support these types, it is still possible to serialize
them by providing a custom serializer via
[the `@SerializeWith` annotation](#the-serializewith-annotation). That serializer then has to
be applied to top-level type (i.e. `nesting` must be set to `0`, which is the default).
them by providing a custom serializer via the [`@SerializeWith`](#the-serializewith-annotation)
annotation. That serializer then has to be applied to top-level type (i.e. `nesting` must be set
to `0`, which is the default).
</details>
@ -644,6 +644,43 @@ Map<Integer, Map<String, Double>> map;
</details>
#### The `@Polymorphic` annotation
The `@Polymorphic` annotation indicates that the annotated type is polymorphic. Serializers for
polymorphic types are not selected based on the compile-time types of configuration elements, but
instead are chosen at runtime based on the actual types of their values.
This enables adding instances of subclasses / implementations of a polymorphic type to collections.
The subtypes must be valid configurations.
```java
@Polymorphic
@Configuration
static abstract class A { ... }
static final class Impl1 extends A { ... }
static final class Impl2 extends A { ... }
List<A> as = List.of(new Impl1(...), new Impl2(...), ...);
```
For correct deserialization, if an instance of polymorphic type (or one of its implementations /
subclasses) is serialized, an additional property that holds type information is added to its
serialization. By default, that type information is the Java class name of the actual type. It is
possible to provide type aliases by using the `PolymorphicTypes` annotation.
```java
@Polymorphic
@PolymorphicTypes({
@PolymorphicTypes.Type(type = Impl1.class, alias = "IMPL_1"),
@PolymorphicTypes.Type(type = Impl2.class, alias = "IMPL_2")
})
interface B { ... }
record Impl1(...) implements B { ... }
record Impl2(...) implements B { ... }
```
### Custom serializers
If you want to add support for a type that is not a Java record or whose class is not annotated
@ -691,8 +728,8 @@ Instances of the `SerializerContext` interface contain contextual information fo
serializers. A context object gives access to the configuration properties, configuration element,
and the annotated type for which the serializer was selected.
The context object can be accessed when adding a serializer factory through
the `addSerializerFactory` method:
Context objects can be obtained when adding serializer factories through the `addSerializerFactory`
method:
```java
public final class PointSerializer implements Serializer<Point, String> {

@ -162,8 +162,9 @@ class ConfigurationProperties {
* If this library already provides a serializer for the given type (e.g. {@code BigInteger},
* {@code LocalDate}, etc.) the serializer created by the factory takes precedence.
* <p>
* If a serializer is added via {@link #addSerializer(Class, Serializer)} method for the
* same type, the serializer created by the factory added by this method takes precedence.
* If a serializer is added via the {@link #addSerializer(Class, Serializer)} method
* for the same type, the serializer created by the factory that was added by this
* method takes precedence.
*
* @param serializedType the class of the type that is serialized
* @param serializerFactory the factory that creates a new serializer

@ -6,15 +6,18 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that the annotated configuration element or type should be serialized using the
* referenced serializer.
* Indicates that the annotated configuration element or type should be serialized using
* an instance of the referenced serializer. The serializer referenced by this annotation
* is selected regardless of whether the type of configuration element or annotated type
* matches the type the serializer expects.
* <p>
* If this annotation is applied to a configuration element, and that element is an array, list,
* set, or map a nesting level can be set to apply the serializer not to the top-level type but to
* its elements. For maps, the serializer is applied to the values and not the keys.
* If this annotation is applied to a configuration element, and that element is an array,
* list, set, or map a nesting level can be set to apply the serializer not to the
* top-level type but to its elements. For maps, the serializer is applied to the values
* and not the keys.
* <p>
* The following example shows how {@code nesting} can be used to apply the serializer at
* different levels.
* The following examples show how {@code nesting} can be used to apply the serializer to
* configuration elements at different levels.
*
* <pre>
* {@code
@ -43,6 +46,21 @@ import java.lang.annotation.Target;
* Map<Integer, Map<String, Double>> map;
* }
* </pre>
* <p>
* If instead the annotation is applied to a non-generic type, then, whenever that exact
* type (i.e. not a subtype or implementation) is encountered, the referenced serializer
* is selected.
* <pre>
* {@code
* @SerializeWith(serializer = MyClassSerializer.class)
* public final class MyClass { ... }
* }
* </pre>
* <p>
* Similarly, this annotation can be used as a meta-annotation on other {@code ElementType.TYPE}
* annotations. In these cases, the serializer is selected whenever a type annotated with
* the meta-annotated annotation is found. An example for this is the {@link Polymorphic}
* annotation.
*/
@Target({
ElementType.ANNOTATION_TYPE, // usage as meta-annotation
@ -62,8 +80,11 @@ public @interface SerializeWith {
/**
* Returns the nesting level at which to apply the serializer.
* <p>
* If this annotation is applied to a type or another annotation, the value
* returned by this method has no effect.
* Setting {@code nesting} to an invalid value, i.e. a negative one or one that is greater than
* the number of levels the element actually has, results in the serializer not being selected.
* <p>
* If this annotation is applied to a type or another annotation, the value returned by this
* method has no effect.
*
* @return the nesting level
*/

@ -1,17 +1,39 @@
package de.exlll.configlib;
import java.lang.reflect.AnnotatedType;
import java.util.function.Function;
/**
* Instances of this class provide contextual information for custom serializers.
* References to such instances can be obtained when adding serializer factories through
* the {@link ConfigurationProperties.Builder#addSerializerFactory(Class, Function)}
* method.
* <p>
* Custom serializers classes are allowed to declare a constructor with one parameter of
* type {@code SerializerContext}. If such a constructor exists, an instance of this class is
* passed to it when the serializer is instantiated by this library.
* Custom serializers used with {@code @SerializeWith} are allowed to declare a constructor
* with one parameter of type {@code SerializerContext}. If such a constructor exists, a
* context object is injected into it when the serializer is instantiated.
*
* <pre>
* {@code
* public final class PointSerializer implements Serializer<Point, String> {
* private final SerializerContext context;
*
* public PointSerializer(SerializerContext context) {
* this.context = context;
* }
* // implementation ...
* }
*
* YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
* .addSerializerFactory(Point.class, PointSerializer::new)
* .build();
* }
* </pre>
*/
public interface SerializerContext {
/**
* Returns the {@code ConfigurationProperties} object in use when the serializer was selected.
* Returns the {@code ConfigurationProperties} object in use when the serializer was
* selected.
*
* @return properties object in use when the serializer was selected
*/
@ -25,11 +47,11 @@ public interface SerializerContext {
ConfigurationElement<?> element();
/**
* Returns the {@code AnnotatedType} which led to the selection of the serializer. The annotated
* type returned by this method might be different from the one returned by
* {@link ConfigurationElement#annotatedType()}. Specifically, the type is different when the
* serializer is applied to a nested type via {@link SerializeWith} in which case the annotated
* type represents the type at that nesting level.
* Returns the {@code AnnotatedType} which led to the selection of the serializer. The
* annotated type returned by this method might be different from the one returned by
* {@link ConfigurationElement#annotatedType()}. Specifically, the type is different
* when the serializer is applied to a nested type via {@link SerializeWith} in which
* case the annotated type represents the type at that nesting level.
*
* @return annotated type which led to the selection of the serializer
*/

Loading…
Cancel
Save