简体   繁体   中英

Dynamic direct field access in Groovy?

How does something like XmlSlurper 's parsed objects direct field access ( groovy.xml.slurpersupport.NodeChild.@someAttributeName ) work? Consider an input file foobar.xml :

<root foo="bar">
    <foobar>Hi</foobar>
</root>

and the Groovy script:

import groovy.xml.XmlSlurper

def xml = new XmlSlurper().parse new File('./foobar.xml')

println xml.foobar
println xml.@foo

Outputs:

Hi
bar

As far as I understand, xml.foobar (a nonexistent property) can be handled by using the metaprogramming method propertyMissing() (similar to the methodMissing() for nonexistent methods). However, I can't seem to find a dynamic analog for direct access fields like foo . How could I implement something similar? Ie, I could create a class that dynamically handles property/method accesses (eg, along with a backing map) and the metaprogramming methods above, but there doesn't seem to be the equivalent for fields, eg:

class DynamicTest {
    def propertyMissing(String propertyName) {
        println "Hit missing property $propertyName"
    }
    def methodMissing(String methodName, def args) {
        println "Hit missing method $methodName"
    }

    // nothing like this exists?
    def fieldMissing(String fieldName) {
        println 'Hit missing field $fieldName'
    }
}

def obj = new DynamicTest()
obj.test1()    // Hit missing method test1
obj.test2      // Hit missing property test2
obj.@test3     // Caught: groovy.lang.MissingFieldException: No such field: test3 for class: DynamicTest

Note that I have a day's worth of experience with Groovy and metaprogramming, so I'm not too sure if I'm using the correct language here. My understanding that xml.foobar is a Groovy-type metaprogramming "field" (which can be also be accessed using xml.getProperty('foobar') , xml['foobar'] , and xml.getAt('foobar') ), and that xml.@foo is an ordinary, Java-like field. Please let me know if there are any inherent misconceptions with the question above.

So, you can go and have a look at the source code for Node over here

The magic is in the static initializer block on lines 55-58 , which calls setMetaclass with a new metaclass that catches attribute getting and setting

Converting to Groovy you end up with something like this:

class Example {

    static {
        setMetaClass(GroovySystem.metaClassRegistry.getMetaClass(Example), Example)
    }

    def get(name) {
        println "Getting $name"
    }

    def set(name, value) {
        println "Setting $name to $value"
    }

    protected static void setMetaClass(final MetaClass metaClass, Class nodeClass) {
        final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) {

            @Override
            def getAttribute(object, String attribute) {
                object.get("@$attribute")
            }

            @Override
            void setAttribute(object, String attribute, newValue) {
                object.set("@$attribute", newValue)
            }

        };
        GroovySystem.metaClassRegistry.setMetaClass(nodeClass, newMetaClass);
    }    
}

def e = new Example()
e.@woo            // prints "Getting @woo"
e.@woo = 'yay'    // prints "Setting @woo to yay"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM