Reputation: 942
In my groovy class, I have 3 properties below:
class Story{
@JsonUnwrapped
Object collections;
String dateformat;
@JsonUnwrapped
Object custom;
public String getDateformat(){
println "getDateformat - ${dateformat}";
return this.dateformat;
}
public void setDateformat(String str){
this.dateformat = str;
println "setDateformat - ${dateformat}";
}
public void setCollections(Object obj)
{
println "setCollections";
}
public void setCustom(Object obj){
println "setCustom: ${this.dateformat}";
}
}
On class instanciation, the setters are automatically called is this order:
setCustom: null
setCollections
setDateformat - yyyy/MM/dd HH:mm:ss.SSS Z
getDateformat - yyyy/MM/dd HH:mm:ss.SSS Z
The fields can not be renamed. I would like dateformat property to be available in setCustom setter method. Is there any way I could achieve that without creating a second custom constructor?
Upvotes: 1
Views: 291
Reputation: 9885
To my surprise, yes!
And it's super simple. I excluded the JSON annotations since they are irrelevant, but here's an example:
class Story{
Object collections
String dateformat
Object custom
public String getDateformat(){
println "getDateformat - ${dateformat}";
return this.dateformat;
}
public void setDateformat(String str){
this.dateformat = str;
println "setDateformat - ${dateformat}";
}
public void setCollections(Object obj)
{
println "setCollections";
}
public void setCustom(Object obj){
println "setCustom: ${this.dateformat}";
}
}
def s = new Story(collections: new Object(), dateformat: 'yyyy/MM/dd HH:mm:ss.SSS Z', custom: new Object())
The output looks like this:
setCollections
setDateformat - yyyy/MM/dd HH:mm:ss.SSS Z
setCustom: yyyy/MM/dd HH:mm:ss.SSS Z
The trick is to call the Map-
based constructor with the properties defined in the proper order.
The default Map
-based constructor provided by Groovy is actually handled by the runtime. More specifically, MetaClassImpl. It first creates an instance with the no-argument constructor, and then sets each of the properties in the Map
. The important thing here is how the properties are set:
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
String key = entry.getKey().toString();
Object value = entry.getValue();
setProperty(bean, key, value);
}
As you can see, it iterates through the keys in the Map
and applies each one. When using the constructor syntax in my example, you're declaring a Groovy Map
literal, which ends up being an instance of LinkedHashMap. LinkedHashMap
preserves the order in which the keys are defined, allowing you to control the order in which the properties are set.
Having said all of that, having such an unintuitive dependency on the order in which the properties are set will make the Story
class quite fragile. Instead, you can add a new method, lets call it validate()
, which will take into account the interdependencies of the properties and ensure everything is good to go before you use the instance.
class Story{
Object collections
String dateformat
Object custom
boolean validate() {
/*
* Do whatever the hell you want with
* dateformat and custom.
* Return whether it's all good or not.
* Or, throw an Exception.
*/
}
}
Upvotes: 2