Question

The following is a program that dynamically calls all the getXXX methods on an object of CLASS, where the CLASS-name gets passed via command-line. And it works just fine.

// Program: callAllMethods.groovy

// Invoke this program as: groovy callAllMethods Date 

args.each { arg ->
    println "Methods of ${arg} ..."

    def code = """
        x = new ${arg}()
        x.class.methods.each { f ->
            if (f.name.startsWith("get")) {
                print "new ${arg}()." + f.name + ": " + f.invoke(x) 
                println ''

            }
        }  
    """
    evaluate("$code")
    println ''
}

However, when I try the simpler style of dynamic-method invocation (that does not use METHOD.invoke(OBJECT) but rather OBJECT."METHOD-NAME"()), like so,

// Program: callAllMethods.groovy

// Invoke this program as: groovy callAllMethods Date 

args.each { arg ->
    println "Methods of ${arg} ..."

    def code = """
        x = new ${arg}()
        x.class.methods.each { f ->
            if (f.name.startsWith("get")) {
                result = x."${f.name}"()
                println "new ${arg}().${f.name}: ${result}" 
            }
        }  
    """
    evaluate("$code")
    println ''
}

... I get the following error:

$ groovy callGetMethods.groovy Date
Methods of Date ...
Caught: groovy.lang.MissingPropertyException: No such property: f for class: callGetMethods
groovy.lang.MissingPropertyException: No such property: f for class: callGetMethods
    at callGetMethods$_run_closure1.doCall(callGetMethods.groovy:13)
    at callGetMethods.run(callGetMethods.groovy:10)

I cannot understand why! Groovy version that I'm using:

$ groovy -version
Groovy Version: 2.1.3 JVM: 1.6.0_43 Vendor: Sun Microsystems Inc. OS: Linux
Was it helpful?

Solution

It happens because when you use the reflection based one (the x.class.methods.each one), you concatenate and generate the code upon GString evaluation, which resolves only one variable against the current scope, which is arg, and that is ok. If you print the code, it outputs a perfectly runnable Groovy code:

x = new Date()
x.class.methods.each { f ->
    if (f.name.startsWith("get")) {
        print "new Date()." + f.name + ": " + f.invoke(x) 
        println ''
    }
}  

In your second version, the GString variables are resolved against the scope they were created, which is the script binding. So it tries to fetch the f variable from that scope, and not from the code variable. And that is why it crashes at the ${f} variable.

If you change the code variable into a plain string (single quotes), it won't resolve the variable arg, and thus you will need to tweak a bit around it to create a new class from it. Even so, it will fail, unless you pass as argument groovy callAllMethods java.util.Date, which is not groovy (pun intended).

So, to use your code in that way, the GString shouldn't be resolved in declaration time, but rather at evaluate() time. Still, the arg variable needs to be resolved in declaration time, thus, you need to concat it. Here is the result:

args.each { arg ->
    println "Methods of ${arg} ..."

    def code = '''
        x = new '''+arg+'''()
        x.class.methods.each { m ->
            if (m.name.startsWith("get")) {
                result = x."${m.name}"()
                println "new '''+arg+'''().${m.name}: ${result}"
            }
        }  
    '''
    evaluate code
    println ''
}

Which, in my box (jdk7, groovy 2.1.3), outputs:

new Date().getDay: 0
new Date().getTimezoneOffset: 180
new Date().getDate: 2
new Date().getHours: 10
new Date().getMinutes: 39
new Date().getMonth: 5
new Date().getSeconds: 56
new Date().getTime: 1370180396136
new Date().getYear: 113
new Date().getClass: class java.util.Date

If you just want to output the properties from an object, may i suggest the object.properties?

args.each { arg ->
    println "Methods of ${arg} ..."

    def code = '''
        x = new '''+arg+'''()
        x.properties.each { 
            println "new '''+arg+'''().${it.key}: ${x[it.key]}"
        }  
    '''
    evaluate code
    println ''
}

It outputs a bit more stuff for Date, though :-).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top