@Finalize not being applied after DSL @Mutate rule

In the following code the value of $.things.defaults.another is “Mutate Another” and not “Final Another” as is the case with $.things.thing1.another and $.things.thing2.another. Why and how do I fix this?

To be clear, the DSL @Mutate rule for ‘defaults’ which sets the value to ‘Default Mutate Value’ and another to ‘Mutate Another’ is being treated as a @Finalize rule and happening AFTER the actual @Finalize rule. If this DSL block is commented out the @Finalize rule updates the values correctly.

class testPlugin extends RuleSource {
  @Model
  void things(ModelMap<Thing> things) {}

  @Defaults
  void addDefaults(ModelMap<Thing> things) {
    things.create('defaults') {
      value = 'Default Value'
      another = 'Default Another'
    }
  }

  @Mutate
  void mutateThings(ModelMap<Thing> things) {
    things.all {
      value = 'Mutated Value'
    }
  }

  @Finalize
  void finalizeAnother(ModelMap<Thing> things) {
    things.all {
      another = 'Final Another'
    }
  }
}

@Managed
interface Thing extends Named {
  String getValue()
  void setValue(String value)
  String getAnother()
  void setAnother(String another)
}

apply plugin: testPlugin

model {
  things {
    defaults {
      value = 'Default Mutate Value'
      another = 'Mutate Another'
    }

    thing1(Thing) {
      value = 'Thing 1 Mutate Value'
      another = 'Mutate Another'
    }

    thing2(Thing) {
      value = 'Thing 2 Mutate Value'
    }
  }
}

model {
  tasks {
    print_stuff(Task) {
      doLast {
        println "Thing 1 Value: ${$.things.thing1.value}"
        println "Thing 2 Value: ${$.things.thing2.value}"
        println "Defaults Value: ${$.things.defaults.value}"
        println ""
        println "Thing 1 Another Value: ${$.things.thing1.another}"
        println "Thing 2 Another Value: ${$.things.thing2.another}"
        println "Defaults Another Value: ${$.things.defaults.another}"
      }
    }
  }
}

In case anyone else is having the same problems, my solution was a simple fix as the problem was a misunderstanding of how the @Annotations applied to ModelMaps/ModelSets. I had assumed that a @Finalize rule for a ModelMap would apply to all elements of that Map, however that is not the case, instead it is to the ModelMap collection itself. In order to update the ModelMap elements in the @Finalize rule I had to change things.all to things.afterEach. This in affect adds a new @Finalize rule to each element of the collection. So the updated @Finalize rule looks like:

@Finalize
void finalizeAnother(ModelMap<Thing> things) {
  things.afterEach {
    another = 'Final Another'
  }
}

It appears that loosely:

  • beforeEach() = @Defaults
  • all() = @Mutate
  • afterEach() = @Finalize