Package space.arim.dazzleconf.backend


package space.arim.dazzleconf.backend
This package defines a framework for interactions between the library and the configuration format. It defines the structure of data passed between them.

The classes in this package each correspond to steps in the access and mutation of data, its in-memory representation, and its usage by other parts of the library:

  • Data comes from a data root as raw bytes or characters
  • It is then parsed into a DataTree by the configuration format.
  • A configuration format is represented by Backend which handles reading and writing data trees by interfacing with the data root.
  • Some configuration formats will provide a KeyMapper, an interface for making sure that Java method names (as used with this library) map to strings in accordance with the configuration format's recommended practices.

Roots and Backends

Data comes from a root source. We call this root source a 'data root.' The data root is usually a file, but it can also be a String or raw bytes. It is represented abstractly by DataRoot. Root data is just bytes or string data; it is unparsed and could be full of a bajillion errors.

The Backend represents the configuration format itself. An implementation might exist for YAML, HOCON, TOML, .ini, .properties, or any other configuration formats that are to be supported. A backend takes a data root, and it allows reading or writing to that root in the form of a data tree.

Some backends require specific roots. For example, a backend for a human-readable configuration format might expect a ReadableRoot. A backend for byte serialization, in contrast, might need to use BinaryRoot if the deserialized bytes do not comprise a valid string. Backend implementations can declare which root is necessary in their constructor.

Data trees

Data is held in memory using a DataTree. A data tree is essentially a map of keys to values, where the values themselves can be other DataTrees. Additionally, each value can have metadata like comments or line number attached, see DataEntry.

Mutability models

Both DataTree, DataList and KeyPath rely on a type-level mutability model that is designed for efficient data access, guarded mutation, and firm control over ownership.

This model makes use of subclasses which denote the mutability of the data they contain. Because mutation methods are only available in the mutable variant, modifying an immutable data tree becomes statically impossible. This is implemented through a triangle relationship: an umbrella parent type, a mutable variant, and an immutable variant.

At runtime, a value of the parent type can be one of either sub-type. For example, a DataTree could be either DataTree.Mut or a DataTree.Immut. The API, however, is intended to elide instanceof checks by making conversions between these types logical and efficient. Let's use DataTree as an example.

  • An existing DataTree can be made mutable with DataTree.intoMut(). If the receiver data tree is not already mutable, a new object is created and the data is copied to it. For efficiency, the data is actually copied lazily upon first mutation. If already mutable, the data tree yields itself.
  • An existing DataTree can be made immutable with DataTree.intoImmut(). This function moves the data into a new immutable container and poisons the old DataTree instance. Thus, it mimics a Rust-like ownership model where the caller is expected to have exclusive access to the mutable data tree, and therefore the ability to move that data to a new place. If already immutable, the data tree yields itself.
  • Thanks to lazy copying, repeated conversion using the aforementioned methods has low performance impact. Copying is performed upon first mutation of a mutable instance that was created from an immutable one.

As expected, the Mut variant is not thread safe without the presence of external synchronization.