About Me

My photo
Author of Groovy modules: GBench, GProf, Co-author of a Java book パーフェクトJava, Game Developer at GREE

Tuesday, December 28, 2010

Generic toString() in Groovy

Let me suppose that you implement toString() returns class and property information
in format "${class-name}(${property-name}:${property-value}, …)".


First, I will give you an example in Java for comparison.


Java version:
----
class AnObject {
    // property definitions


    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName()).append("(");
        try {
            PropertyDescriptor[] pds =
                Introspector.getBeanInfo(this.getClass()).getPropertyDescriptors();
            for (int i = 0, c = 0, sz = pds.length; i < sz; i++) {
                PropertyDescriptor pd = pds[i];
                if (!"class".equals(pd.getDisplayName())) {
                    Object v = pd.getReadMethod().invoke(this);
                    if (c != 0) {
                        sb.append(", ");
                    }
                    sb.append(pd.getDisplayName()).append(":").append(v);
                    c++;
                }
            }
        } catch (Exception e) {}
        sb.append(")");
        return sb.toString();
    }
}
----
I shortened the code as much as possible without losing readability, but it requires you to hit keys about 800 times.


Let’s rewrite this in Groovy.


Groovy (using java.beans) version:
----
class AnObject {
    // property definitions 


    String toString() {
        """${ this.class.name }(${
           try {
               Introspector.getBeanInfo(this.class).propertyDescriptors
                   .findAll{ pd ->
                       "class" != pd.displayName && "metaClass" != pd.displayName }
                   .collect{ pd ->
                       "${ pd.displayName }:${ pd.readMethod.invoke(this) }" }
                   .join(", ")
              } catch(e) {}
         })"""
    }
}
----
Now it's much shorter.


You can make it a little more shorter by using MetaClass.


Groovy (using MetaClass) version:
----
class AnObject {
    // property definitions 


    String toString() {
        """${ this.class.name }(${
            properties
                .findAll{ p -> "metaClass" != p.key && "class" != p.key }
                .collect{ p -> "${ p.key }:${ p.value }" }
                .join(", ")
        })"""
    }
}
----
* MetaClass does not keep order of property definitions because it has metadata of properties in hash map.


Actually you can also make it just 1 line by using dump() method If you won't need a custom format.


Groovy (using dump() method) version:
----
class AnObject {
    // property definitions 

    String toString() {
        dump()
    }
}
----
In this case, the format will be "<${class-name}@${hash-code} ${property-name}=${property-value} ...>". 


MetaClass and dump() method are also provided by the GDK that extends the JDK.

   

2 comments:

  1. It'd be great to see this one applied at the beginning of a grails application (in BootStrap maybe) to all domain classes where toString has not been overriden. This way whenever a new class is being created it will always have a proper toString method right out of the box.

    Btw. this will be obsolete starting with Groovy 1.8 where the @ToString annotation will be available taking care of the problem.

    ReplyDelete
  2. Thank you Matthias, I will try Groovy 1.8.

    ReplyDelete