1

In .NET we could write code like the following to return the propertyName

public static string GetName(Expression<Func<object>> exp)
{
    MemberExpression body = exp.Body as MemberExpression;

    if (body == null) {
        UnaryExpression ubody = (UnaryExpression)exp.Body;
        body = ubody.Operand as MemberExpression;
    }

     return body.Member.Name;
 }

 public class Test
 {
     public string prop1 { get; set; }
 }

Inside of properties, we usually use OnPropertyChanged(() => this.prop1) which would return the propertyName. For more info, see this post(How to raise PropertyChanged event without using string name)

I want to do something simular in groovy, but I am not sure the right way to do this.

    class TestObj
    {
        def prop1 = "value" 
        def prop2 = "value"
    }

    class Test{
        def something(){
            def t1 = new TestObj()
            def propertyName1 = getName{ t1.prop1 }
            def propertyName2 = getName{ t1.prop2 }

            assert propertyName1 == "prop1"
            assert propertyName2 == "prop2"
        }
    }

How would you implment the getName method in groovy using an expression as seen above

Community
  • 1
  • 1
NeshDev
  • 186
  • 3

2 Answers2

0

The closest I could come to delivering this capability feels completely contrived and kludgy and ad hoc to me. It relies on the Java Bean API, facilitated by the Groovy @Vetoable annotation to keep most of the ugliness outside the class code:

import groovy.beans.Vetoable

import java.beans.PropertyChangeEvent
import java.beans.PropertyVetoException

class TestObj {
    @Vetoable def prop1 = "value"
    @Vetoable def prop2 = "value"
}

def t1 = new TestObj()
def getName(def t, Closure c) {
    def name
    t.vetoableChange = { PropertyChangeEvent pce ->
        if (!pce.newValue) { throw new PropertyVetoException("", pce) }
    }
    try { c.call() }
    catch (PropertyVetoException e) { name = e.propertyChangeEvent.propertyName }
    t.vetoableChange = { it -> }
    name
}
def propertyName1 = getName(t1) { t1.prop1 = null }
def propertyName2 = getName(t1) { t1.prop2 = null }

assert propertyName1 == "prop1"
assert propertyName2 == "prop2"

YUK!

BalRog
  • 3,216
  • 23
  • 30
0

To me the most beneficial use of such a method would be at compile time, so that this kinda code breaks as early as possible and simple string-typos won't lead to bugs at runtime. In a highly dynamic language like groovy there is hardly a way to get a proper method reference to work with. So here is some code, that does this at runtime but the only thing you get out of this is an assert (in the code below), that you are controlling. (clazz.&something is not a real method reference like you would get with e.g. Clazz::getSomething in java8 or c++). So here is some clunky attempt to abuse the MethodClosure and do a trivial check, if the owning class has a getter for this at runtime.

class Clazz {
    String username
    String firstName
    def lastName
    String getFake() { return 'fake' }
    void setSomething(String something) {}
}

String getName(org.codehaus.groovy.runtime.MethodClosure it) { 
    def m = it.method.toString()
    assert it.delegate.respondsTo("get${m[0].toUpperCase()}${m[1..-1]}")
    it.method.toString()
}

def c = new Clazz()
assert getName(c.&username) == 'username'
assert getName(c.&lastName) == 'lastName'
assert getName(c.&fake) == 'fake'

That said, what I have seen in the code examples, this whole technique seems to me to help the developer on the side of firing the events to prevent errors with mistyped strings. So there might be an even groovyier solution to all of this by just using @Bindable, which turns your properties or all properties of a class into "bound properties according to the JavaBeans spec". This generates the whole property change boiler plate code for you resulting in alot less code to worry about.

cfrick
  • 35,203
  • 6
  • 56
  • 68
  • It seems that you could get a false positive with this approach if there is a method `Clazz.getWhatever(String stuff)`. In this case the method takes an argument and hence does not represent a property. However `assert getName(c.&whatever) == 'whatever'` still passes. Maybe an additional assertion inside the `getName()` method, say `assert it.parameterTypes.size() == 0`, would be in order – BalRog Nov 17 '14 at 00:13
  • @BalRog yeah one could also check the properties of the owner/delegate, if there is something there, which might even be easier after all, but needs a find{} – cfrick Nov 17 '14 at 12:14
  • True dat. One more possible bug here. I haven't checked for sure but I think that if the property name is a single character the subexpression `${m[1..-1]}` will throw some sort of "index out of bounds" exception. In the past I have had to use something ungainly like `${m[1.. – BalRog Nov 17 '14 at 19:31
  • @BalRog or in short: as yuk as ;) – cfrick Nov 17 '14 at 19:44