Interest Ask. I interpreted your requirements like this
- The class must have a default set of properties.
- Each method must have a set of default arguments.
- The default method overrides the default values ββof the class.
- Each method may have additional arguments that do not exist in the class.
- The arguments to the method should not modify the class instance.
- The arguments provided must be checked for the type.
I was not sure about number 5, as it is not explicitly listed, but it looks like this is what you wanted.
As far as I know, groovy has no built-in firmware to support all this, but there are several ways to make it work in an βeasy to useβ way.
One of the ways that comes to mind is to create specialized argument classes, but use maps only as arguments in methods. With a simple superclass or tag to check and set properties, it is single-line to get the actual arguments for each method.
Here is a dash and some examples that can be used as a starting point:
trait DefaultArgs { void setArgs(Map args, DefaultArgs defaultArgs) { if (defaultArgs) { setArgs(defaultArgs.toArgsMap()) } setArgs(args) } void setArgs(Map args) { MetaClass thisMetaClass = getMetaClass() args.each { name, value -> assert name instanceof String MetaProperty metaProperty = thisMetaClass.getMetaProperty(name) assert name && metaProperty != null if (value != null) { assert metaProperty.type.isAssignableFrom(value.class) } thisMetaClass.setProperty(this, name, value) } } Map toArgsMap() { def properties = getProperties() properties.remove('class') return properties } }
With this attribute, it is easy to create specialized argument classes.
@ToString(includePackage = false, includeNames = true) class FooArgs implements DefaultArgs { String a = 'a' Boolean b = true Integer i = 42 FooArgs(Map args = [:], DefaultArgs defaultArgs = null) { setArgs(args, defaultArgs) } } @ToString(includePackage = false, includeNames = true, includeSuper = true) class BarArgs extends FooArgs { Long l = 10 BarArgs(Map args = [:], FooArgs defaultArgs = null) { setArgs(args, defaultArgs) } }
And a class that uses these arguments:
class Foo { FooArgs defaultArgs Foo(Map args = [:]) { defaultArgs = new FooArgs(args) } void foo(Map args = [:]) { FooArgs fooArgs = new FooArgs(args, defaultArgs) println fooArgs } void bar(Map args = [:]) { BarArgs barArgs = new BarArgs(args, defaultArgs) println barArgs } }
Finally, a simple test script; output method calls in comments
def foo = new Foo() foo.foo() // FooArgs(a:a, b:true, i:42) foo.foo(a:'A') // FooArgs(a:A, b:true, i:42) foo.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:42)) foo.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000)) foo.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:42)) def foo2 = new Foo(i:16) foo2.foo() // FooArgs(a:a, b:true, i:16) foo2.foo(a:'A') // FooArgs(a:A, b:true, i:16) foo2.bar() // BarArgs(l:10, super:FooArgs(a:a, b:true, i:16)) foo2.bar(i:1000, a:'H') // BarArgs(l:10, super:FooArgs(a:H, b:true, i:1000)) foo2.bar(l:50L) // BarArgs(l:50, super:FooArgs(a:a, b:true, i:16)) def verifyError(Class thrownClass, Closure closure) { try { closure() assert "Expected thrown: $thrownClass" && false } catch (Throwable e) { assert e.class == thrownClass } } // Test exceptions on wrong type verifyError(PowerAssertionError) { foo.foo(a:5) } verifyError(PowerAssertionError) { foo.foo(b:'true') } verifyError(PowerAssertionError) { foo.bar(i:10L) } // long instead of integer verifyError(PowerAssertionError) { foo.bar(l:10) } // integer instead of long // Test exceptions on missing properties verifyError(PowerAssertionError) { foo.foo(nonExisting: 'hello') } verifyError(PowerAssertionError) { foo.bar(nonExisting: 'hello') } verifyError(PowerAssertionError) { foo.foo(l: 50L) } // 'l' does not exist on foo