-
Notifications
You must be signed in to change notification settings - Fork 0
Copy Strategies
Copy Strategies handle how DSL objects are copied over to existing objects (using the copyFrom method). This is mainly used when creating objects from templates, but can also be used to copy objects in other scenarios (like merging multiple script files in a 'helm' line way).
When copying an object to another, there are three distinct types of strategies: single object, collections and maps. The strategy can be configured on a class for all matching fields at once, or on a per-field basis.
Fields are marked with one of the following annotations, depending on the type:
-
@Overwrite.Singlefor single object fields -
@Overwrite.Collectionfor collection fields -
@Overwrite.Mapfor map fields
Also, fields, the class or the package can be annotated with @Overwrite to apply the strategy to all fields in the class or package.
Single object strategies handle how a single object field is copied over from a donor object. The behaviour is slightly different for dsl and pojo objects. Pojo objects are always linked into the target object, i.e. target and donor share the same object instance (which is hopefully immutable), DSL objects are copied over, i.e. a new object is created and the fields are copied over (using that object type's overwrite strategies).
The strategy is determined by checking in the following order and using the first match:
- an
@Overwrite.Singleannotation on the field itself - a nested annotation on the field (i.e. an annotation that itself is annotated with
@Overwrite.Single) - an
@Overwriteannotation (or nested annotation) on the class with the membersingleset to something other thanINHERIT - an
@Overwriteannotation (or nested annotation) on the package of the class - in turn an
@Overwriteannotation (or nested annotation) on the class or package of the class' superclass (and up through the hierarchy)
The strategy can be one of the following:
Not a strategy in itself, should no be used directly.
If the donor's field is not null, the field of the target is completely replaced by the donor's field (or a copy of the donor's field in case of a DSL object).
The target's field is replaced with the donor's field, even if the donor's field is null. This can be used to clear a field with a default value using a template. This strategy should be used only sparingly, and almost always only on specific fields as opposed to classes or packages.
The target field is on set if it was null before. This can be used to set default values in the target object if they are not already set. This can be especially useful outside of templates, for example in the Default Phase.
Merge is the default strategy. For non-DSL fields, this behaves exactly as REPLACE. For DSL fields, the donor's value is copied into the targets value (or a newly created object if the target's value is null). The nested fields are copied over using the overwrite strategies of the target's field's type.
Collection strategies handle how a collection field is copied over from a donor object. The collection itself is never linked into the target (i.e. the target and donor always have separate collections), if a collection in the target exists, it is reused (and possibly cleared depending on the strategy). This also holds true for nested collections (List of Lists).
If the collection consists of DSL objects, those objects are copied (i.e. new objects similar to those in the donor are created), simple objects are simply linked.
The strategy is determined in exactly the same way as for single object fields, but using the @Overwrite.Collection annotation instead.
The strategy can be one of the following:
Not a strategy in itself, should no be used directly.
The donor's collection is added to the target's collection. The order is determined by the collection type (e.g. List preserves order, Set does not).
The target's collection is replaced by the donor's collection's elements, but only if the donor's collection is not empty.
The target's collection is replaced by the donor's collection's elements, even if the donor's collection is empty. This can be used to clear a collection with a default value using a template. This strategy should be used only sparingly, and almost always only on specific fields as opposed to classes or packages.
Since we have no way of determining matching elements, this is not yet implemented.
Note that if the collection field of the donor is null instead of an empty collection, the target's collection is always left untouched. Also note that in order to set a collection to null, the setter syntax must be used instead of the usual methods.
MyObject.Create.With {
elements = null // "element null" would add null to the collection
}Map strategies handle how a map field is copied over from a donor object. The map itself is never linked into the target (i.e. the target and donor always have separate maps), if a map in the target exists, it is reused (and possibly cleared depending on the strategy). This also holds true for nested maps or collections (Map of Maps or Maps of Lists).
As with collections, if the values are DSL objects, those objects are copied (i.e. new objects similar to those in the donor are created), simple objects are simply linked.
The strategies are as follows:
Not a strategy in itself, should no be used directly.
The target's map is replaced by the donor's map (if not empty). This is the default strategy.
The target's map is replaced by the donor's map, even if the donor's map is empty. This can be used to clear a map with a default value using a template. This strategy should be used only sparingly, and almost always only on specific fields as opposed to classes or packages.
The target's map is replaced by the donor's map, but only if the target's map is empty.
The donor's map is added to the target's, replacing existing values with matching keys.
The donor's map is merged with the target's. New keys are added, but the values of existing keys are merged with the target's values. The merge strategy is determined by the target's field's type's overwrite strategy.
For non-DSL values, this behaves exactly as 'MERGE_KEYS', i.e. the values are replaced.
All entries in the donor's map whose keys are not present in the target's map are added to the target's map.
Nested Annotations can be used to give meaningful names to a couple of strategies. For example, the HelmOverwrite annotation is a nested annotation that sets the strategy to MERGE for single object fields, REPLACE for collections and MERGE_VALUES for maps, resembling the way helm merges value files.
It is defined as follows:
@Target({ElementType.FIELD, ElementType.TYPE, ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
@Overwrite(
singles = @Overwrite.Single(OverwriteStrategy.Single.MERGE),
collections = @Overwrite.Collection(OverwriteStrategy.Collection.ALWAYS_REPLACE),
maps = @Overwrite.Map(OverwriteStrategy.Map.MERGE_VALUES)
)
public @interface HelmOverwrite {
}Note that only one level of nesting is supported, i.e. you cannot nest annotations inside nested annotations.
The @Overwrite annotation as well as nested annotations can contain an additional member named missing of type
OverwriteStrategy.Missing, which controls the handling of fields in the donor that are not present in the target
object. This can either be FAIL (the default) or IGNORE.
Note that since this is checked on the target object, it does nothing when being put a field.