Lorenz 0.6: Mapping Containers Proposal

As Lorenz has grown so has the duplication throughout the codebase, mapping containers present a solution that reduces the duplication and provides maximum convenience coverage.

Jamie Mansfield
Jamie Mansfield

Lorenz 0.6 is going to be developed over a longer period of time than with usual releases - we have decided to focus on the long-term maintainability of the project, alongside working to cover more bases for newer consumers of our libraries (Nocturne and Symphony are large drivers currently).

An issue that has existed for some time, but has begun to really resonate as the library has grown - is duplication rampant throughout the mapping model, alongside dodgy convenience coverage (expanded get, getOrCreate, etc class for signatures). My proposal is to introduce containers for children mappings, moving the storage of children away from the model itself - standardising the access of children mappings, and simplifying implementation.

This would look something like the following:

MappingContainer<Mapping, Signature>
+ Mapping get(Signature)
+ Mapping getOrCreate(Signature)
+ Set<Mapping> getAll()

With implementations for field mappings (FieldContainer), method mappings (MethodContainer), and class mappings (ClassContainer) - this is where relvant convenience methods will reside.

Mapping containers should be able to resolve the following:

  1. Increase code reuse through the model.
  2. Separate convenience functionality away, to make a clear distinction 1.
  3. Eliminate the necessity for the mapping model to be separate from its own implementation 2.
  4. Clean-up redundant code introduced in previous versions, and not identified.

Aside from the benefits, there is one MAJOR drawback to making this change - specifically, nearly all existing use of Lorenz will be broken in some form 3. The following is a tentative example of how this change would pan out:

final MappingSet mappings = new MappingSet();
final TopLevelClassMapping a = mappings.getOrCreate("a")
        .fields()
            .getOrCreate("a").setDeobfName("name").getParent()
            .getOrCreate("b").setDeobfName("age").getParent()
            .getParent()
        .methods()
            .getOrCreate("a", "()V").setDeobfName("run").getParent()
            .getParent();

This change would have no implications to the usage of therecently introduced Groovy DSL and upcoming Kotlin DSL.

To assess the viability of this solution (ensuring it actually achieves the set out goals), and the damage it causes to downstream projects - I have started a pull request. Any input on the new model design in welcomed there, and as always on our IRC channel (#cadix on EsperNet).


  1. Currently the “canonical” functionality (what would be in MappingContainer) exists alongside the convenience functionality, and this can make it difficult to find the core functions at time, and can hinder maintainence. ↩︎

  2. This is somewhat of a complicated topic - the mapping model was split into API and implementation for 2 reasons. Most importantly as Lorenz was growing rapidly, it was awkward to have both in the same file - and we can more clearly have an inheritance structure with interfaces. Secondly, it was originally seen as an approach to resolve systems such as Nocturne needing to be aware of changes to the mapping model - something that we are resolving through introducing “observable” functions throughout Lorenz. ↩︎

  3. We could maintain a compatibility layer for the lifespan of 0.6.0 to allow dependants to have a staged approach to moving over to the new system. However, this would be a large undertaking itself. At this time, I do not intend to write such layer, however I have yet to see how large the downstream carnage is. ↩︎