Table of contents
- Foreword;
- Property definition
- Custom getters/setters
- Working with properties
- Read-only properties
- Convenient properties initialisation
- Declare instance/static fields
- Bound and constrained properties support
Foreword
I'm continuing my dairy of groovy exploration, here is a second part that talks about groovy syntax sugar for JavaBeans support. Feel free to check the previous topic - Exploring Groovy, part 1 - common stuff. Next article - Groovy features, part 3 - method gotchas
Property definition
It's possible to define JavaBean property for the groovy class with relaxed groovy supported-syntax. Basically, 'property' here means private field and implicitly generated and used getter/setter.
Two property types are supported - 'typed' and 'untyped'.
Here is an example that shows property definition:
package org.denis
class GroovyTestClass {
static def staticUntypedProperty;
static long staticTypedProperty;
def untypedInstanceProperty;
Integer typedInstanceProperty;
}
We can check structure of the class generated by Groovy for such a source:
$ javap org.denis.GroovyTestClass
Compiled from "GroovyTestClass.groovy"
public class org.denis.GroovyTestClass extends java.lang.Object implements groovy.lang.GroovyObject{
public static final java.lang.Class $ownClass;
public static java.lang.Long __timeStamp;
public static java.lang.Long __timeStamp__239_neverHappen1271917491545;
public org.denis.GroovyTestClass();
protected groovy.lang.MetaClass $getStaticMetaClass();
public groovy.lang.MetaClass getMetaClass();
public void setMetaClass(groovy.lang.MetaClass);
public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
public java.lang.Object getProperty(java.lang.String);
public void setProperty(java.lang.String, java.lang.Object);
static {};
public static java.lang.Object getStaticUntypedProperty();
public static void setStaticUntypedProperty(java.lang.Object);
public static long getStaticTypedProperty();
public static void setStaticTypedProperty(long);
public java.lang.Object getUntypedInstanceProperty();
public void setUntypedInstanceProperty(java.lang.Object);
public java.lang.Integer getTypedInstanceProperty();
public void setTypedInstanceProperty(java.lang.Integer);
public void super$1$wait();
public java.lang.String super$1$toString();
public void super$1$wait(long);
public void super$1$wait(long, int);
public void super$1$notify();
public void super$1$notifyAll();
public java.lang.Class super$1$getClass();
public java.lang.Object super$1$clone();
public boolean super$1$equals(java.lang.Object);
public int super$1$hashCode();
public void super$1$finalize();
static java.lang.Class class$(java.lang.String);
}
I.e. Groovy generated getters and setters for both instance and static properties.
Custom getters/setters
It's also possible to define custom getters/setters with desired visibility for particular property. The only trick here is to explicitly declare return type as 'void' for setters because methods declared via 'def' have return type 'Object' by default:
class GroovyTestClass {
int typedProperty;
def untypedProperty;
// Custom setter.
protected void setTypedProperty(value) {
typedProperty = value
}
// Custom getter
def getUntypedProperty() {
untypedProperty
}
}
Working with properties
Groovy not only generates getters and setters for properties, it also implicitly uses them during working with properties:
package org.denis
class GroovyTestClass {
int typedProperty;
def untypedProperty;
void setTypedProperty(value) {
println "'typedProperty' is set to '$value' via custom setter"
typedProperty = value
}
def getUntypedProperty() {
println "'untypedProperty' is accessed via via custom getter"
untypedProperty
}
}
class AnotherGroovyTestClass {
def static void main(args) {
def test = new GroovyTestClass()
println "Typed property: $test.typedProperty"
test.typedProperty = 1
println "Typed property: $test.typedProperty"
println "Untyped property: $test.untypedProperty"
test.untypedProperty = 2
println "Untyped property: $test.untypedProperty"
}
}
We get the following output if we run AnotherGroovyTestClass:
Typed property: 0
'typedProperty' is set to '1' via custom setter
Typed property: 1
'untypedProperty' is accessed via via custom getter
Untyped property: null
'untypedProperty' is accessed via via custom getter
Untyped property: 2
Note: direct access to the property is used every time it is accessed at compile time from the same class via explicit or implicit 'this' reference:
package org.denis
class GroovyTestClass {
def untypedProperty;
def static void main(args) {
def test = new GroovyTestClass()
println 'Direct property access from static context'
println test.untypedProperty
println ''
test.indirectPropertyAccess()
}
def indirectPropertyAccess() {
println 'Direct property access from instance context'
println untypedProperty
}
def getUntypedProperty() {
println "'untypedProperty' is accessed via via custom getter"
untypedProperty
}
}
Output:
Direct property access from static context
'untypedProperty' is accessed via via custom getter
null
Direct property access from instance context
null
Also there is a trick to access the property directly from outside the class (i.e. ignore all custom getters/setters if any). That is done via '.@' operator:
package org.denis
class GroovyTestClass {
def untypedProperty;
void setUntypedProperty(value) {
println "'untypedProperty' is set to '$value' via custom setter"
untypedProperty = value
}
def getUntypedProperty() {
println "'untypedProperty' is accessed via via custom getter"
untypedProperty
}
}
class AnotherGroovyTestClass {
def static void main(args) {
def test = new GroovyTestClass()
println 'Setting property using standard syntax'
test.untypedProperty = 1
println 'Getting property using standard syntax'
println "Property: $test.untypedProperty"
println()
println 'Setting property using non-standard syntax'
test.@untypedProperty = 'xxx'
println 'Getting property using non-standard syntax'
println "Property: ${test.@untypedProperty}"
}
}
Output:
Setting property using standard syntax
'untypedProperty' is set to '1' via custom setter
Getting property using standard syntax
'untypedProperty' is accessed via via custom getter
Property: 1
Setting property using non-standard syntax
Getting property using non-standard syntax
Property: xxx
Read-only properties
Often we need to make property read-only for external clients, i.e. define only getter for it. We can achieve that in Groovy by using 'final' keyword with the property:
package org.denis
class GroovyTestClass {
def final myProperty = 'xxx'
}
class AnotherGroovyTestClass {
def static void main(args) {
def test = new GroovyTestClass()
println 'Getting property...'
println "Property: $test.myProperty"
println 'Setting property...'
test.myProperty = 'xxx' // fails at runtime because the property is read-only
}
}
Output:
Getting property...
Property: xxx
Setting property...
Exception in thread "main" groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: myProperty for class: org.denis.GroovyTestClass
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2406)
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:3307)
at org.denis.GroovyTestClass.setProperty(GroovyTestClass.groovy)
at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:179)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:483)
at org.denis.AnotherGroovyTestClass.main(GroovyTestClass.groovy:14)
Please note that read-only property still can be accessed from the 'instance' context of the same class:
package org.denis
class GroovyTestClass {
final def myProperty = 'xxx'
def static void main(args) {
def test = new GroovyTestClass()
println "Initial read-only property value: '$test.myProperty'"
print 'Setting read-only property from instance context...'
test.setPropertyFromInstanceContext(2)
println "done. Current read-only property value: '$test.myProperty'"
println 'Trying to set read-only property value from static context...'
test.myProperty = 3 // fails at runtime because we attempt to set read-only property value from static context
}
def setPropertyFromInstanceContext(value) {
this.myProperty = value
}
}
Output:
Initial read-only property value: 'xxx'
Setting read-only property from instance context...done. Current read-only property value: '2'
Trying to set read-only property value from static context...
Exception in thread "main" groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: myProperty for class: org.denis.GroovyTestClass
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2406)
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:3307)
at org.denis.GroovyTestClass.setProperty(GroovyTestClass.groovy)
at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:179)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:483)
at org.denis.GroovyTestClass.main(GroovyTestClass.groovy:14)
That means that it's possible to define a property with, say, public getter and protected setter:
package org.denis
class GroovyTestClass {
final def myProperty = 'xxx'
def static void main(args) {
def test = new GroovyTestClass()
println "Initial property value: '$test.myProperty'"
print 'Setting property from instance context...'
test.myProperty = 2 // Allowed because of 'protected' setter visibility
println "done. Current property value: '$test.myProperty'"
}
protected void setMyProperty(value) {
this.myProperty = value
}
}
Output:
Initial property value: 'xxx'
Setting property from instance context...done. Current property value: '2'
Convenient properties initialisation
Properties may be initialised using the following convenient syntax:
package org.denis
class GroovyTestClass {
def property1
def property2
def static void main(args) {
def test = new GroovyTestClass(property1: 1, property2: 'xxx')
println "Properties are initialized: $test.property1 $test.property2"
}
}
It is executed as if new object is created and target properties setters are called on it. Here is equivalent java code for that:
test = new GroovyTestClass();
test.setProperty1(1);
test.setProperty2("xxx");
Declare instance/static fields
It's cool to have relaxed syntax for declaring and using properties but sometimes we need just fields, i.e. we don't want Groovy to generate and use getters and setters. We can achieve that simply by almost the same syntax as we used for properties. The only difference is that we explicitly define field visibility:
package org.denis
class GroovyTestClass {
public def property1
protected String property2
}
$ javap org.denis.GroovyTestClass
Compiled from "GroovyTestClass.groovy"
public class org.denis.GroovyTestClass extends java.lang.Object implements groovy.lang.GroovyObject{
public java.lang.Object property1;
protected java.lang.String property2;
public static java.lang.Long __timeStamp;
public static java.lang.Long __timeStamp__239_neverHappen1271924017378;
public org.denis.GroovyTestClass();
public java.lang.Object this$dist$invoke$2(java.lang.String, java.lang.Object);
public void this$dist$set$2(java.lang.String, java.lang.Object);
public java.lang.Object this$dist$get$2(java.lang.String);
protected groovy.lang.MetaClass $getStaticMetaClass();
public groovy.lang.MetaClass getMetaClass();
public void setMetaClass(groovy.lang.MetaClass);
public java.lang.Object invokeMethod(java.lang.String, java.lang.Object);
public java.lang.Object getProperty(java.lang.String);
public void setProperty(java.lang.String, java.lang.Object);
static {};
public void super$1$wait();
public java.lang.String super$1$toString();
public void super$1$wait(long);
public void super$1$wait(long, int);
public void super$1$notify();
public void super$1$notifyAll();
public java.lang.Class super$1$getClass();
public java.lang.Object super$1$clone();
public boolean super$1$equals(java.lang.Object);
public int super$1$hashCode();
public void super$1$finalize();
static java.lang.Class class$(java.lang.String);
}
Also note that it's still allowed to explicitly define getter/setter and Groovy automatically picks it up during field access:
package org.denis
class GroovyTestClass {
public def property1
protected String property2
def static void main(args) {
def test = new GroovyTestClass()
println "Current 'property1' field value is $test.property1"
println "Setting value to the 'property1' field..."
test.property1 = 'xxx'
println "Current 'property1' field value is $test.property1"
}
public void setProperty1(value) {
println "setProperty1() is called with value '$value'"
property1 = value
}
}
Output:
Current 'property1' field value is null
Setting value to the 'property1' field...
setProperty1() is called with value 'xxx'
Current 'property1' field value is xxx
Bound and constrained properties support
Groovy provides convenient way to define bound and constrained properties:
- bound property - property which change fires notification for interested listeners;
- constrained property - property which change may be prevented;
@Bindable and @Vetoable annotations are used for that:
package org.denis
import groovy.beans.Bindable
import groovy.beans.Vetoable
import java.beans.PropertyVetoException
class GroovyTestClass {
@Bindable def property1
@Vetoable Integer property2
def static void main(args) {
def test = new GroovyTestClass()
test.propertyChange = {
println "Got notification that '$it.propertyName' property value is set to '$it.newValue'"
}
test.vetoableChange = {
if (it.newValue <= 0) {
throw new PropertyVetoException("Detected attempt to set non-positive value ($it.newValue) to the property"
+ "'$it.propertyName'. Rejected.", it)
}
}
println "Setting 'xxx' value to the 'property1' field..."
test.property1 = 'xxx'
println "Setting '1' value to the 'property1' field..."
test.property2 = 1
println "Setting '-1' value to the 'property1' field..."
test.property2 = -1
}
}
Output:
Setting 'xxx' value to the 'property1' field...
Got notification that 'property1' property value is set to 'xxx'
Setting '1' value to the 'property1' field...
Setting '-1' value to the 'property1' field...
Exception in thread "main" java.beans.PropertyVetoException: Detected attempt to set non-positive value (-1) to the property'property2'. Rejected.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
at org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:71)
at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrap.callConstructor(ConstructorSite.java:84)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:52)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:192)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:204)
at org.denis.GroovyTestClass$_main_closure2.doCall(GroovyTestClass.groovy:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:88)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:273)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:886)
at groovy.lang.Closure.call(Closure.java:276)
at org.codehaus.groovy.runtime.ConvertedClosure.invokeCustom(ConvertedClosure.java:51)
at org.codehaus.groovy.runtime.ConversionHandler.invoke(ConversionHandler.java:79)
at $Proxy1.vetoableChange(Unknown Source)
at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:335)
at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:252)
at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:273)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:229)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:52)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:40)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:54)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
at org.denis.GroovyTestClass.fireVetoableChange(GroovyTestClass.groovy)
at org.denis.GroovyTestClass$fireVetoableChange.callCurrent(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:44)
at org.denis.GroovyTestClass$fireVetoableChange.callCurrent(Unknown Source)
at org.denis.GroovyTestClass.setProperty2(GroovyTestClass.groovy)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:88)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:2387)
at groovy.lang.MetaClassImpl.setProperty(MetaClassImpl.java:3307)
at org.denis.GroovyTestClass.setProperty(GroovyTestClass.groovy)
at org.codehaus.groovy.runtime.InvokerHelper.setProperty(InvokerHelper.java:179)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.setProperty(ScriptBytecodeAdapter.java:483)
at org.denis.GroovyTestClass.main(GroovyTestClass.groovy:33)
Note that Groovy closuers are used at the last example. We'll talk about them at the next articles.

0 comments:
Post a Comment