How to do Groovy method signatures with Python-style kwargs AND default values? - groovy

I might be asking too much, but Groovy seems super flexible, so here goes...
I would like a method in a class to be defined like so:
class Foo {
Boolean y = SomeOtherClass.DEFAULT_Y
Boolean z = SomeOtherClass.DEFAULT_Z
void bar(String x = SomeOtherClass.DEFAULT_X,
Integer y = this.y, Boolean z = this.z) {
// ...
}
}
And to be able to provide only certain arguments like so:
def f = new Foo(y: 16)
f.bar(z: true) // <-- This line throws groovy.lang.MissingMethodException!
I am trying to provide an API that is both flexible and type safe, which is the problem. The given code is not flexible in that I would have to pass in (and know as the user of the API) the default value for x in order to call the method. Here are some challenges for the solution I want:
Type safety is a must--no void bar(Map) signatures unless the keys can somehow be made type safe. I realize with this I could do the type checking in the method body, but I'm trying to avoid that level of redundancy as I have many of this "kind" of method to write.
I could use a class for each method signature--something like:
class BarArgs {
String x = SomeOtherClass.DEFAULT_X
String y
String z
}
And define it like:
void bar(BarArgs barArgs) {
// ...
}
And call it using my desired way using the map constructor: f.bar(z: true), but my problem lies in the object's default on y. There's no way to handle that (that I know of) without having to specify it when calling the method as in: f.bar(y: f.y, z: true). This is fine for my little sample, but I'm looking at 20-30 optional parameters on some methods.
Any suggestions (or questions if needed) are welcome! Thank you for taking a look.

Interesting question. I've interpreted your requirements like this
The class should have a set of default properties.
Each method should have a set of default arguments.
The method defaults override the class defaults.
Each method can have additional arguments, not existing on the class.
The method arguments should not modify the class instance.
Provided arguments needs to be checked for type.
I was not sure about number 5 since it is not explicitly specified, but it
looked like that was what you wanted.
As far as I know, there is nothing built-in in groovy to support all this,
but there are several ways to make it work in a "simple-to-use" manner.
One way that comes to mind is to create specialized argument classes, but
only use maps as the arguments in the methods. With a simple super-class
or trait to verify and set the properties, it is a one-liner to get the
actual arguments for each method.
Here is a trait 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 trait is it 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 of method invocations 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

Related

Simple way to dynamically replace placeholder with value at Groovy strings

I'm trying to find laconic and efficient way to replace placeholders with values at the Groovy Strings. But I can't find convenient solution for 2 cases:
When String with the placeholder and the value are defined at different classes.
When the String is passed as argument to a method, and should be replaced with local's variable value. Here is the illustration of 2 approaches I have tried:
class A {
static def strPlaceHolder = 'token = ${tokenValue}';
static def strRefPlaceHolder = "token = ${->tokenRef}";
}
class B {
def tokenRef = "token reference as field";
void parseGString(GString str) {
println str; //fails here. No property tokenRef for class: A. Though I've expected that "this" is B
}
void parseString(String str) {
def tokenValue = "token value as local variable";
println str; //I know why it doesn't work as required. But how to make something similar
}
}
new B().parseString(A.strPlaceHolder); //token = ${tokenValue}
new B().parseGString(A.strRefPlaceHolder); //fails,
You could replace your GString fields with closures and pass those closures to your methods. e.g.:
class A {
static def strPlaceHolder = { token -> "token = ${token}" }
}
class B {
def tokenRef = "token reference as field";
void parseGString(def closure) {
println closure(tokenRef)
}
void parseString(def closure) {
def tokenValue = "token value as local variable"
println closure(tokenValue)
}
}
new B().parseString(A.strPlaceHolder);
new B().parseGString(A.strPlaceHolder);

How can I create a map from object properties?

I get an object via some 3rd party api. I use a wrapper function to get it and then return a map from its properties:
wrapperFunc() {
def myObj = someapi.getblah().getSomeObect()
return [
aaa: myObj.aaa,
bbb: myObj.bbb,
ccc: myObj.ccc
]
}
Now I could manually go through EVERY property in the object like this, but is there an elegant groovy feature to dynamically build a map from the object's properties?
You could do something like this:
class Widget {
int width
int height
static void main(args) {
def obj = new Widget(width: 7, height: 9)
List<MetaProperty> metaProperties = obj.metaClass.properties
def props = [:]
for(MetaProperty mp : metaProperties) {
props[mp.name] = mp.getProperty(obj)
}
// props will look like [width:7, class:class demo.Widget, height:9]
}
}
This is basically a variant of #jeff-scott-brown's answer.
First, create a class that contains the Object-to-Map logic that uses the Groovy MetaClass to access a type's properties. findAll filters out the "class" property, which I assume you don't care about. The collectEntries line transforms each MetaProperty object into a Map entry.
class ElegantGroovyFeature {
static Map asType(Object o, Class m) {
if (m == Map) {
o.metaClass.properties
.findAll { it.getSetter() != null }
.collectEntries { prop -> [prop.name, prop.getProperty(o)] }
} else {
o.asType(m)
}
}
}
The extension class overrides the asType method, which corresponds to the as operator, enabling you to convert arbitrary objects to Maps using obj as Map expressions:
def obj = someapi.getBlah().getSomeObject()
use (ElegantGroovyFeature) {
def mapOfProperties = obj as Map
}
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
console.log(map); // Map { foo: "bar", baenter code herez: 42 }

Groovy 'No signature of method' running Closure against delegate

I have a closure that's executed against another Groovy object as the delegate. I can do:
foo {
bar {
major = 1
}
}
but when I do:
foo {
bar {
major 1
}
}
I get an error:
> No signature of method: my.Bar.major() is applicable for argument types (java.lang.Integer) values: [1]
Possible solutions: setMajor(java.lang.Integer), getMajor(), wait(), any(), setMajor(java.lang.String), wait(long)
Bar looks something like:
class Bar {
Integer major
Integer getMajor() { return this.major }
def setMajor(Integer val) { this.major = val }
}
I thought Groovy made getters/setters transparent when dealing with property references and that referring to bar.major was the same as bar.get/setMajor(). Am I understanding this wrong, or does the meta class lookup route differ when you throw Closure delegates into the mix? The resolution strategy is DELEGATE_FIRST.
For more context: http://forums.gradle.org/gradle/topics/groovy-no-signature-of-method-running-closure-against-delegate
you would have to add also void major(Integer val). major = 1 is groovy-short for setMajor(1) while major 1 is short for major(1). (see Section Optional parenthesis)
Optional parenthesis
Method calls in Groovy can omit the parenthesis if there is at least one parameter and there is no ambiguity.
println "Hello world"
System.out.println "Nice cheese Gromit!"
E.g.:
class X {
Integer major
void major(Integer m) { major = m }
}
def x = new X()
x.major = 1 // x.setMajor(1)
assert x.major==1 // x.getMajor()==1
x.major 2 // x.major(2)
assert x.major==2
If you need this behaviour alot, you can add a methodMissing for this case. E.g.:
class X {
Integer major
def methodMissing(String name, args) {
if (this.hasProperty(name) && args.size()==1) {
this."$name" = args[0]
} else {
throw new MissingMethodException(name, this.class, args)
}
}
}
def x = new X()
x.major = 1
assert x.major==1
x.major 2
assert x.major==2

Is there a way to list arguments to a method as a collection?

If I have a method such as this:
void someMethod(int one, int two, int three)
{
log.debug("One = ${one}, two = ${two}, three = ${three}")
}
Is there a way to avoid listing each param in the debit message? I want to print out values for all the parameters without necessarily listing each one separately.
You can just use varargs:
void someMethod( int... args ) {
println "Args = $args"
}
Worried about more than 3 arguments getting used while calling method? Then make sure you only deal with args[0] till args[2] inside the method.
You may also use interceptor construct:
class Sample {
void someMethod(int one, int two, int three) {
// println("One = ${one}, two = ${two}, three = ${three}")
}
}
class SampleInterceptor implements Interceptor {
boolean doInvoke() {
true
}
Object beforeInvoke(Object obj, String name, Object[] args) {
if(name == "someMethod")
println args
}
Object afterInvoke(Object obj, String name, Object[] args, Object result) {
result
}
}
def proxy = ProxyMetaClass.getInstance(Sample)
def interceptor = new SampleInterceptor()
proxy.interceptor = interceptor
proxy.use {
def h = new Sample()
h.someMethod(1,2,3)
}
Have a look at the sample.

Groovy: Implicit call not working on instance variables inside closure

A class implements call method so that it's objects can be called as a method. This works for most of the case but not when the call is being made inside a closure on a object which is instance variable of a class.
To demonstrate the problem, in the code below I've commented the interesting lines with numbers. While most variants result in same output, only the line with comment 5 doesn't work. It throws groovy.lang.MissingMethodException: No signature of method: Client2.instanceVar() is applicable for argument types: () values: [])
Can someone help me understand the reason? Is it a bug?
class CallableObject {
def call() { println "hello" }
}
class Client {
def instanceVar = new CallableObject()
def method() {
def localVar = new CallableObject()
def closure1 = { localVar() }
def closure2 = { instanceVar.call() }
def closure3 = { instanceVar() } // doesn't work
localVar() // 1
instanceVar() // 2
closure1() // 3
closure2() // 4
closure3() // 5
}
}
new Client().method()
I guess this will make it clear.
class CallableObject {
def call() { println "hello" }
}
class Client {
def instanceVar = new CallableObject()
def getInstanceVar() {
println "Getter Called"
instanceVar
}
def method() {
def localVar = new CallableObject()
def closure1 = { localVar() }
def closure2 = { instanceVar.call() }
def closure3 = { this.#instanceVar() } //should work now
localVar() // 1
instanceVar() // 2
closure1() // 3
closure2() // 4
closure3() // 5
}
}
new Client().method()
You will see "Getter Called" printed when closure2() invoked. For a global property to be accessed in the closure inside a method, the getter in called instead. To surmount the error you get, the field instanceVar needs to be accessed directly in order to implicitly use call().

Resources