Saturday, January 15, 2011

Detecting Class Innards in Groovy

When using a new language or using new features of a language that I have not used before, I like to know what fields and methods are supported by various classes and objects in that language. This has certainly been the case as I have learned and used Groovy. In this post, I look at the many ways one can find out what a Groovy class/object has to offer.


Javadoc API Documentation

One of the things I have always liked about Java is the ready availability of its standard API documentation. Fortunately, Groovy's GDK extensions and the Groovy distribution classes are similarly well documented. I find myself frequently using these generated HTML pages to discover new APIs that are available to me in the standard Groovy distribution.


Object.toString()

Groovy inherits positive Java features including the ability for object's to easily provide their state via a standard approach by explicitly overriding Object.toString(). This is a valuable tactic in Java (see Item 9 in Effective Java) and continues to be valuable in Groovy. The most significant downside of the toString() approach is that its value relies completely on the author of the class. There are several things a developer can do to make more effective toString() methods, but there's no guarantee any of those things have been done.

In fact, there's no guarantee that a toString() metho for a given class whose instance is being used even provides an explicit overridden toString() method. The next code listing is of a simple Groovy class that fails to explicitly override toString().

/**
 * Simple Groovy class used in demonstration of Groovy detecting methods without
 * explicitly overridden inspect() method, toString() method, or dump() method.
 *
 * @author Dustin
 */
class Person
{
   String lastName

   String firstName

   Person(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName
      this.firstName = newFirstName
   }
}

When an instance of this simple Groovy class has it's toString() invoked (whether implicitly or explicitly), the output is the same as for a Java class with no explicit toString() (and as defined by Object.toString() Javadoc). It will look something like this:

Person@149eb9f


Object.inspect()

Groovy's GDK Object class specifies an inspect() method for which the Javadoc states: "Inspects returns the String that matches what would be typed into a terminal to create this object." I rarely find this method very useful, but do cover it briefly here.

The inspect() method is often not implemented. When this is the case, the same object's toString() method is invoked. If the object's toString() method is not explicitly implemented, Java's Object.toString() is invoked.

This is demonstrated in the next screen snapshots for the Java Date class and for the Groovy AntBuilder class (the latter of which does not have an explicit inspect() or an explicit toString() implementation).



The Groovy Object.inspect() method suffers the same weakness as Java's (and Groovy's) Object.toString(): the method is only as good as what the author of the class implemented (which might be nothing). In the worst case, an author might not have overridden the inspect() method or the toString() method (as was the case for AntBuilder in the example above) and then very little information is provided.

The next code listing is for a simple Groovy class that implements toString() but fails to implement inspect().

PersonWithToString.groovy
/**
 * Simple Groovy class used in demonstration of Groovy detecting methods without
 * explicitly overridden inspect() method, but with an explicitly overridden
 * toString() method.
 *
 * @author Dustin
 */
class PersonWithToString
{
   String lastName

   String firstName

   PersonWithToString(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName
      this.firstName = newFirstName
   }

   @Override
   String toString()
   {
      return this.firstName + " " + this.lastName;
   }
}

If the above class is instantiated, it will return the same String whether toString() is explicitly or implicitly called or if inspect() is called. For example, suppose the class is instantiated and invoked as shown in the next code listing.

def person2 = new PersonWithToString("Rubble", "Barney")
doToString(person2)
doInspect(person2)

def doToString(obj)
{
   println "toString(): ${obj}\n"
}

def doInspect(obj)
{
   // Inspect
   println "inspect(): ${obj.inspect()}\n"
}

The output when the above is executed looks like this:

toString(): Barney Rubble

inspect(): Barney Rubble

We can explicitly override inspect() to make it useful and to meet its advertised reason for existence. An example of how this might be done is demonstrated in the next code listing.

PersonWithInspect.groovy
/**
 * Simple Groovy class used in demonstration of Groovy detecting methods with
 * explicitly overridden inspect() method but without explicitly overridden
 * dump() method.
 *
 * @author Dustin
 */
class PersonWithInspect
{
   String lastName

   String firstName

   PersonWithInspect(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName
      this.firstName = newFirstName
   }

   @Override
   String toString()
   {
      return this.firstName + " " + this.lastName
   }

   @Override
   String inspect()
   {
      return "new Person(" + this.lastName + ", " + this.firstName + ")"
   }
}

When this code has its toString() and inspect() methods called similarly to how the last Groovy class's methods were called, the output is now different:

toString(): Barney Rubble

inspect(): new Person(Rubble, Barney)

Overriding the inspect() method made it more useful and made it so that it not only provides more/different information than already available via toString(), but also helped it achieve the declared (in comments) reason for its existence: to "return the String that matches what would be typed into a terminal to create this object."


Object.dump()

The Object.dump() method often provides more details than Object.inspect(). Its Javadoc documentation states about this method: "Generates a detailed dump string of an object showing its class, hashCode and fields."

The next screen snapshot shows what the dump method displays for a new Java Date instance.


The next screen snapshot shows the AntBuilder.dump() results, which are far more descriptive than the AntBuilder.inspect() method provided.


The Object.dump() method works even when the class whose instance it is being invoked against has not even overridden the dump method. For example, suppose that the Groovy class PersonWithInspect used above had dump() called against it. Even though that class never explicitly defines such a method, calling it leads to results like this:

<PersonWithInspect@1bbd7b2 lastName=Rubble firstName=Barney>

Although no dump() method was explicitly written, calling dump() worked and returned the name of the class with its hash code and with its two attributes (lastName and firstName).

A significant advantage of Groovy's Object.dump() is that it does provide more details about an object's current state without the class author implementing or overriding any specific methods. A Groovy developer is still free to override dump() if so desired, but this is probably rarely needed or even recommended because the existing dump() method is a reasonable implementation.

The next code listing provides a simple Groovy class in which the author has chosen (perhaps poorly) to explicitly override Object.dump().

PersonWithDump.groovy
/**
 * Simple Groovy class used in demonstration of Groovy detecting methods with
 * explicitly overridden dump() method. This is typically not necessary or even
 * desirable as the dump() method is standardly supported.
 *
 * @author Dustin
 */
class PersonWithDump
{
   String lastName

   String firstName

   PersonWithDump(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName
      this.firstName = newFirstName
   }

   @Override
   String toString()
   {
      return this.firstName + " " + this.lastName
   }

   @Override
   String inspect()
   {
      return "new Person(" + this.lastName + ", " + this.firstName + ")"
   }

   @Override
   String dump()
   {
      return toString()
   }
}

When an instantiation of the above Groovy class has its toString(), inspect(), and dump() methods called, the output appears as follows.

toString(): Barney Rubble

inspect(): new Person(Rubble, Barney)

dump(): Barney Rubble

The above example demonstrates that dump() can be overridden if desired. In this exmaple, it was overridden to simply provide the result of the toString(), which is obviously less information than the default dump() implementation provides.

Before leaving discussion of the Groovy's dump() method, one might ask oneself, "How, or even does, it work for instances of Java classes used in a Groovy context?" That's a great question. The answer to it begins with the next code listing, which shows a small piece of code that instantiates a Java String, queries it for its class and attempts to dump its contents.

def string = "Dustin was here."
println string.class
doDump(string)

def doDump(obj)
{
   println "dump(): ${obj.dump()}\n"
}

The output when the above code is executed is shown next.

class java.lang.String
dump(): <java.lang.String@796944de value=Dustin was here. offset=0 count=16 hash=2036942046>

Nice! Even the instantiated class, which is unsurprisingly treated as a Java String, has support for the Groovy-provided dump() method. That is the beauty or black magic of Groovy's dynamic metadata support in action.


Direct Reflection

So far, I've looked at detection of what a particular Groovy class has to offer via some ways that are standard to Java as well (Javadoc documentation and Object.toString()) and
have also looked at two approaches that are specific to Groovy (Object.inspect() and Object.dump()). Another way to inspect a particular object in Groovy is to use Java's reflection capabilities. Groovy makes it a little easier to use Java's reflection capabilities directly. This is a powerful technique that can require more effort than some of the other approaches, but can be used programatically and can be used to find a plethora of details about a given object's underlying class, method, and attribute structure.

The next code listing demonstrates access of a Groovy class's name, methods names, and fields names via direct Java reflection on the instance of class PersonWithDump shown above.

def doReflection(obj)
{
   println("Reflection:")
   println "\tClass Name: ${obj.class.name}"
   def methods = obj.class.declaredMethods
   def methodsNames = new StringBuilder()
   methods.each
   {
      methodsNames << it.name << " "
   }
   println "\tMethods Names: ${methodsNames}"
   def fields = obj.class.declaredFields
   def fieldsNames = new StringBuilder()
   fields.each
   {
      fieldsNames << it.name << " "
   }
   println "\tFields Names: ${fieldsNames}"
}

When the above reflection-based code is executed against an instance of PersonWithDump, the output appears as shown next.

Reflection:
 Class Name: PersonWithDump
 Methods Names: invokeMethod getMetaClass setMetaClass inspect $getCallSiteArray $createCallSiteArray class$ $getStaticMetaClass $get$$class$groovy$lang$MetaClass this$dist$invoke$2 $get$$class$java$lang$String this$dist$set$2 this$dist$get$2 super$1$wait super$1$wait super$1$wait super$1$toString super$1$notify super$1$notifyAll super$1$getClass super$1$equals super$1$clone super$1$hashCode super$1$finalize getLastName setLastName getFirstName setFirstName $get$$class$PersonWithDump setProperty getProperty toString dump 
 Fields Names: lastName firstName $staticClassInfo metaClass __timeStamp __timeStamp__239_neverHappen1295139333183 $callSiteArray $class$groovy$lang$MetaClass $class$java$lang$String $class$PersonWithDump 

This example has shown that reflection can be easily applied directly in Groovy as it is applied in Java. In fact, Groovy's use of Java reflection even supports the identification of internal Groovy-only metadata methods and fields.


Object.getProperties()

Groovy offers an additional convenience method for determining information on a Groovy class's properties. The output shown next is what one can expect to see when an instance has its getProperties() method called either by calling the method explicitly or using Groovy's syntactic sugar to access it simply with .properties.

[class:class PersonWithDump, firstName:Barney, lastName:Rubble, metaClass:org.codehaus.groovy.runtime.HandleMetaClass@14dd758[groovy.lang.MetaClassImpl@14dd758[class PersonWithDump]]]

Note that no getProperties() method was explicitly provided in the PersonWithDump class, but calling person4.properties still leads to the properties information being made available in name/value pairs. Like Object.dump(), Object.getProperties() provides useful details without needing to specifically implement/override that method.

Does this nice Groovy getProperties() approach work for Java classes used in Groovy? To find out, we start with this code listing for a simple Java class called JavaPerson.

JavaPerson.java
/**
 * Simple Java class with no set methods for its data members.
 *
 * @author Dustin
 */
public class JavaPerson
{
   private final String lastName;

   private final String firstName;

   public JavaPerson(final String newLastName, final String newFirstName)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }
}

The Java class just shown does not have an explicitly overridden toString() method. Because it's not Groovy, it doesn't override inspect() either. Of course, it is not typical to override Object.dump() or Object.getProperties() in Groovy classes, so it is really not surprising that this Java class does not override those either. Now, let's suppose that some simple Groovy code invoked these methods on an instance of the Java class JavaPerson. This might look like the code shown in the next code listing.

def person5 = new JavaPerson("Rubble", "Barney")
doToString(person5)
doInspect(person5)
doReflection(person5)
doDump(person5)
doProperties(person5)

// The methods shown above with names starting with "do" have
// been defined in previous code listings in this post, but
// doProperties is first shown here.

def doProperties(obj)
{
   println ".properties: ${obj.properties}"
}

When the code is executed in Groovy, the results on the instance of the Java class are as shown next.

toString(): JavaPerson@18622f3

inspect(): JavaPerson@18622f3

dump(): <JavaPerson@18622f3 lastName=Rubble firstName=Barney>

Reflection:
 Class Name: JavaPerson
 Methods Names: getLastName getFirstName 
 Fields Names: lastName firstName 
dump(): &lr;JavaPerson@18622f3 lastName=Rubble firstName=Barney>

.properties: [class:class JavaPerson, firstName:Barney, lastName:Rubble]

As expected, toString() and inspect() are not very interesting or useful because neither is implemented/overridden. However, dump() and .properties (Groovy approaches) are useful. Not surprisingly, reflection is useful on the Java class as well.


javap

I have blogged before on the utility of the javap tool provided with the HotSpot SDK. It is easy to compile a Groovy class or script to a .class file explicitly with the groovyc command and then javap can be applied against that generated .class file.

The next screen snapshot demonstrates compiling the PersonWithInspect Groovy class shown above with groovyc and then running javap against the generated PersonWithInspect.class file.


There are numerous methods in the output that were not explicitly part of the class's definition. This javap output is a nice mechanism for peeking into the methods the Groovy class provides and the types of parameters each method expects.

As an interesting side note here, let's look at what the javap output looks like for a Java class compiled via traditional javac as compared to the javap output for a Java class compiled via groovyc. To illustrate this, the pure Java JavaPerson class will be compiled both with javac (Java compiling) and with groovyc (Groovy/Java combination compilation). The output from running the javap tool against each generated .class file is then displayed in the screen snapshots.

javap Output for javac-Generated JavaPerson

javap Output for groovyc-Generated JavaPerson

The two images just shown demonstrate quite different output. The output from javap for the javac-compiled Java class shows the methods we expected: a constructor and two "get" methods for the two attributes. We can also see that the class simply extends the Java Object class that is extended directly or indirectly by all Java classes. The output from the groovyc-compiled Java class shows that it not only extends Object, but that it also implements the groovy.lang.GroovyObject interface. We can also observe that there are many more methods available in the version compiled with groovyc, including the automatically added "set" methods and the "meta" infrastructure methods added by Groovy.


groovy.inspect.Inspector

Groovy makes its introspection capabilities accessible via a single interface encapsulated within the groovy.inspect.Inspector class. The following code snippet shows how this handy class might be used to easily access data about a particular object (whichever object is accepted in the method call under the name 'obj').

def doInspector(obj)
{
   def inspector = new groovy.inspect.Inspector(obj)
   def inspectorReport = new StringBuilder()
   inspectorReport <<<< "Object under inspection "
   inspectorReport <<<< (inspector.isGroovy() ? "IS" : "is NOT") <<<< " Groovy!\n"
   inspectorReport <<<< "METHODS\n"
   def methods = inspector.methods
   methods.each
   {
      inspectorReport <<<< "\t" <<<< it.toString() <<<< "\n"
   }
   inspectorReport <<<< "\nMETA METHODS\n"
   def metaMethods = inspector.metaMethods
   metaMethods.each
   {
      inspectorReport <<<< "\t" <<<< it.toString() <<<< "\n"
   }
   inspectorReport <<<< "\nPROPERTY INFO\n"
   def properties = inspector.propertyInfo
   properties.each
   {
      inspectorReport <<<< "\t" <<<< it.toString() <<<< "\n"
   }
   println inspectorReport
}

If the above code is invoked and an instance of PersonWithInspect is passed to it, the output looks like the following:

Object under inspection IS Groovy!
METHODS
 [JAVA, public, PersonWithInspect, Object, invokeMethod, String, Object, ]
 [JAVA, public, PersonWithInspect, MetaClass, getMetaClass, , ]
 [JAVA, public, PersonWithInspect, void, setMetaClass, MetaClass, ]
 [JAVA, public, PersonWithInspect, String, inspect, , ]
 [JAVA, public, PersonWithInspect, Object, this$dist$invoke$2, String, Object, ]
 [JAVA, public, PersonWithInspect, void, this$dist$set$2, String, Object, ]
 [JAVA, public, PersonWithInspect, Object, this$dist$get$2, String, ]
 [JAVA, public, PersonWithInspect, void, super$1$wait, long, int, ]
 [JAVA, public, PersonWithInspect, void, super$1$wait, long, ]
 [JAVA, public, PersonWithInspect, void, super$1$wait, , ]
 [JAVA, public, PersonWithInspect, String, super$1$toString, , ]
 [JAVA, public, PersonWithInspect, void, super$1$notify, , ]
 [JAVA, public, PersonWithInspect, void, super$1$notifyAll, , ]
 [JAVA, public, PersonWithInspect, Class, super$1$getClass, , ]
 [JAVA, public, PersonWithInspect, boolean, super$1$equals, Object, ]
 [JAVA, public, PersonWithInspect, Object, super$1$clone, , ]
 [JAVA, public, PersonWithInspect, int, super$1$hashCode, , ]
 [JAVA, public, PersonWithInspect, void, super$1$finalize, , ]
 [JAVA, public, PersonWithInspect, String, getLastName, , ]
 [JAVA, public, PersonWithInspect, void, setLastName, String, ]
 [JAVA, public, PersonWithInspect, String, getFirstName, , ]
 [JAVA, public, PersonWithInspect, void, setFirstName, String, ]
 [JAVA, public, PersonWithInspect, void, setProperty, String, Object, ]
 [JAVA, public, PersonWithInspect, Object, getProperty, String, ]
 [JAVA, public, PersonWithInspect, String, toString, , ]
 [JAVA, public final native, Object, void, wait, long, InterruptedException]
 [JAVA, public final, Object, void, wait, , InterruptedException]
 [JAVA, public final, Object, void, wait, long, int, InterruptedException]
 [JAVA, public, Object, boolean, equals, Object, ]
 [JAVA, public native, Object, int, hashCode, , ]
 [JAVA, public final native, Object, Class, getClass, , ]
 [JAVA, public final native, Object, void, notify, , ]
 [JAVA, public final native, Object, void, notifyAll, , ]
 [JAVA, public, PersonWithInspect, PersonWithInspect, PersonWithInspect, String, String, ]

META METHODS
 [GROOVY, public, Object, Collection, collect, Collection, Closure, n/a]
 [GROOVY, public, Object, List, getMetaPropertyValues, , n/a]
 [GROOVY, public, Object, Object, eachWithIndex, Closure, n/a]
 [GROOVY, public, Object, void, print, PrintWriter, n/a]
 [GROOVY, public, Object, void, addShutdownHook, Closure, n/a]
 [GROOVY, public, Object, Iterator, iterator, , n/a]
 [GROOVY, public static, Object, void, sleep, long, n/a]
 [GROOVY, public, Object, Object, inject, Object, Closure, n/a]
 [GROOVY, public, Object, int, findLastIndexOf, Closure, n/a]
 [GROOVY, public, Object, void, setMetaClass, MetaClass, n/a]
 [GROOVY, public, Object, boolean, any, Closure, n/a]
 [GROOVY, public, Object, Object, use, [Ljava.lang.Object;, n/a]
 [GROOVY, public, Object, boolean, every, , n/a]
 [GROOVY, public, Object, void, println, PrintWriter, n/a]
 [GROOVY, public, Object, MetaClass, getMetaClass, , n/a]
 [GROOVY, public, Object, Collection, split, Closure, n/a]
 [GROOVY, public, Object, Collection, findAll, Closure, n/a]
 [GROOVY, public, Object, List, findIndexValues, Number, Closure, n/a]
 [GROOVY, public, Object, void, printf, String, Object, n/a]
 [GROOVY, public, Object, MetaProperty, hasProperty, String, n/a]
 [GROOVY, public, Object, Object, asType, Class, n/a]
 [GROOVY, public, Object, String, sprintf, String, Object, n/a]
 [GROOVY, public, Object, Object, getAt, String, n/a]
 [GROOVY, public, Object, List, collect, Closure, n/a]
 [GROOVY, public, Object, boolean, every, Closure, n/a]
 [GROOVY, public, Object, Collection, grep, Object, n/a]
 [GROOVY, public, Object, String, dump, , n/a]
 [GROOVY, public, Object, int, findIndexOf, int, Closure, n/a]
 [GROOVY, public, Object, Object, use, Class, Closure, n/a]
 [GROOVY, public, Object, Object, identity, Closure, n/a]
 [GROOVY, public, Object, Map, getProperties, , n/a]
 [GROOVY, public, Object, void, println, , n/a]
 [GROOVY, public, Object, Object, invokeMethod, String, Object, n/a]
 [GROOVY, public, Object, List, findIndexValues, Closure, n/a]
 [GROOVY, public, Object, boolean, is, Object, n/a]
 [GROOVY, public, Object, int, findIndexOf, Closure, n/a]
 [GROOVY, public, Object, String, inspect, , n/a]
 [GROOVY, public, Object, boolean, asBoolean, , n/a]
 [GROOVY, public, Object, Object, with, Closure, n/a]
 [GROOVY, public, Object, boolean, any, , n/a]
 [GROOVY, public, Object, void, printf, String, [Ljava.lang.Object;, n/a]
 [GROOVY, public, GroovyObject, MetaClass, getMetaClass, , n/a]
 [GROOVY, public, Object, void, println, Object, n/a]
 [GROOVY, public, Object, MetaClass, metaClass, Closure, n/a]
 [GROOVY, public, Object, List, respondsTo, String, n/a]
 [GROOVY, public, Object, Object, each, Closure, n/a]
 [GROOVY, public, Object, Object, find, Closure, n/a]
 [GROOVY, public, Object, boolean, isCase, Object, n/a]
 [GROOVY, public, Object, void, print, Object, n/a]
 [GROOVY, public, Object, void, putAt, String, Object, n/a]
 [GROOVY, public static, Object, void, sleep, long, Closure, n/a]
 [GROOVY, public, Object, int, findLastIndexOf, int, Closure, n/a]
 [GROOVY, public, Object, String, toString, , n/a]
 [GROOVY, public, Object, Object, use, List, Closure, n/a]
 [GROOVY, public, Object, List, respondsTo, String, [Ljava.lang.Object;, n/a]
 [GROOVY, public, Object, String, sprintf, String, [Ljava.lang.Object;, n/a]

PROPERTY INFO
 [GROOVY, public, n/a, Class, class, class PersonWithInspect]
 [GROOVY, public, n/a, String, firstName, "Barney"]
 [GROOVY, public, n/a, String, lastName, "Rubble"]
 [GROOVY, public, n/a, MetaClass, metaClass, org.codehaus.groovy.runtime.HandleMetaClass@e53220[groovy.lang.MetaClassImpl@e53220[class PersonWithInspect]]]

There is significant data provided by Inspector. The "GROOVY" or "JAVA" portions indicate the origin of the field or method (Groovy or Java source code).

Out of curiosity, what does Inspector report for a Java class compiled with groovyc (or compiled implicitly when accessed by a Groovy script)? The following output shows what Inspector reports (including the fact that the underlying class is NOT Groovy, though there are Groovy methods added).

Object under inspection is NOT Groovy!
METHODS
 [JAVA, public, JavaPerson, String, getLastName, , ]
 [JAVA, public, JavaPerson, String, getFirstName, , ]
 [JAVA, public final native, Object, void, wait, long, InterruptedException]
 [JAVA, public final, Object, void, wait, , InterruptedException]
 [JAVA, public final, Object, void, wait, long, int, InterruptedException]
 [JAVA, public, Object, boolean, equals, Object, ]
 [JAVA, public, Object, String, toString, , ]
 [JAVA, public native, Object, int, hashCode, , ]
 [JAVA, public final native, Object, Class, getClass, , ]
 [JAVA, public final native, Object, void, notify, , ]
 [JAVA, public final native, Object, void, notifyAll, , ]
 [JAVA, public, JavaPerson, JavaPerson, JavaPerson, String, String, ]

META METHODS
 [GROOVY, public, Object, List, collect, Closure, n/a]
 [GROOVY, public, Object, Map, getProperties, , n/a]
 [GROOVY, public, Object, boolean, every, , n/a]
 [GROOVY, public, Object, void, print, Object, n/a]
 [GROOVY, public, Object, boolean, any, , n/a]
 [GROOVY, public, Object, MetaClass, metaClass, Closure, n/a]
 [GROOVY, public static, Object, void, sleep, long, Closure, n/a]
 [GROOVY, public, Object, String, inspect, , n/a]
 [GROOVY, public, Object, int, findLastIndexOf, int, Closure, n/a]
 [GROOVY, public, Object, Collection, split, Closure, n/a]
 [GROOVY, public, Object, boolean, asBoolean, , n/a]
 [GROOVY, public, Object, Object, use, Class, Closure, n/a]
 [GROOVY, public, Object, boolean, every, Closure, n/a]
 [GROOVY, public, Object, void, println, Object, n/a]
 [GROOVY, public, Object, List, getMetaPropertyValues, , n/a]
 [GROOVY, public, Object, String, sprintf, String, [Ljava.lang.Object;, n/a]
 [GROOVY, public, Object, int, findIndexOf, Closure, n/a]
 [GROOVY, public, Object, int, findLastIndexOf, Closure, n/a]
 [GROOVY, public, Object, void, println, , n/a]
 [GROOVY, public, Object, Object, identity, Closure, n/a]
 [GROOVY, public, Object, Collection, collect, Collection, Closure, n/a]
 [GROOVY, public, Object, String, toString, , n/a]
 [GROOVY, public, Object, MetaClass, getMetaClass, , n/a]
 [GROOVY, public, Object, String, dump, , n/a]
 [GROOVY, public, Object, Object, find, Closure, n/a]
 [GROOVY, public, Object, Object, each, Closure, n/a]
 [GROOVY, public, Object, MetaProperty, hasProperty, String, n/a]
 [GROOVY, public, Object, List, findIndexValues, Closure, n/a]
 [GROOVY, public, Object, Object, use, List, Closure, n/a]
 [GROOVY, public, Object, Object, inject, Object, Closure, n/a]
 [GROOVY, public, Object, Collection, grep, Object, n/a]
 [GROOVY, public, Object, void, println, PrintWriter, n/a]
 [GROOVY, public, Object, boolean, is, Object, n/a]
 [GROOVY, public, Object, List, findIndexValues, Number, Closure, n/a]
 [GROOVY, public, Object, boolean, isCase, Object, n/a]
 [GROOVY, public, Object, int, findIndexOf, int, Closure, n/a]
 [GROOVY, public, Object, Object, invokeMethod, String, Object, n/a]
 [GROOVY, public, Object, Object, asType, Class, n/a]
 [GROOVY, public static, Object, void, sleep, long, n/a]
 [GROOVY, public, Object, boolean, any, Closure, n/a]
 [GROOVY, public, Object, void, addShutdownHook, Closure, n/a]
 [GROOVY, public, Object, void, printf, String, Object, n/a]
 [GROOVY, public, Object, void, putAt, String, Object, n/a]
 [GROOVY, public, Object, void, print, PrintWriter, n/a]
 [GROOVY, public, Object, List, respondsTo, String, [Ljava.lang.Object;, n/a]
 [GROOVY, public, Object, Object, eachWithIndex, Closure, n/a]
 [GROOVY, public, Object, Collection, findAll, Closure, n/a]
 [GROOVY, public, Object, Iterator, iterator, , n/a]
 [GROOVY, public, Object, void, printf, String, [Ljava.lang.Object;, n/a]
 [GROOVY, public, Object, List, respondsTo, String, n/a]
 [GROOVY, public, Object, Object, getAt, String, n/a]
 [GROOVY, public, Object, Object, use, [Ljava.lang.Object;, n/a]
 [GROOVY, public, Object, Object, with, Closure, n/a]
 [GROOVY, public, Object, void, setMetaClass, MetaClass, n/a]
 [GROOVY, public, Object, String, sprintf, String, Object, n/a]

PROPERTY INFO
 [GROOVY, public, n/a, Class, class, class JavaPerson]
 [GROOVY, public, n/a, String, firstName, "Barney"]
 [GROOVY, public, n/a, String, lastName, "Rubble"]


ObjectBrowser

In this post, I have looked at general Java techniques (Javadoc, Object.toString(), javap) for peeking inside of Groovy objects as well as Groovy-specific approaches. I have saved an extremely useful Groovy-specific approach for last. The Groovy graphical-based ObjectBrowser is a user-friendly approach for seeing the innards of a Groovy object. It is based on Groovy's many inspection techniques including those already discussed here. In fact, it's Javadoc comments state about it: "A little GUI to show some of the Inspector capabilities." In other words, ObjectBrowser provides graphical access to the capabilities provided by groovy.inspect.Inspector.

There is more than one way to access ObjectBrowser. When using Groovy Shell, for example, one can type the "inspect" command to invoke ObjectBrowser. This is demonstrated in the next screen snapshot.


The ObjectBrowser can be invoked outside of the Groovy Shell as well. For example, the ObjectBrowser GUI is automatically started when groovy.inspect.swingui.ObjectBrowser.inspect(obj) is invoked within Groovy code (and obj is the name of the object to be evaluated).

As shown in the image above, the Groovy Object Browser provides details of the public fields and methods as well as the Groovy meta methods. It shows the name, current value, data type, modifier, and declarer for each. It also supplies the origin (Groovy or Java) of each. Advantages of this approach includes its being graphical in nature and easy to use as well as the breadth of information (including current values) that it provides.


Integrated Development Environments

Integrated Development Environments (IDEs) have lagged behind in their support for dynamic languages as compared to their support for static languages. That being stated, IDEs continue to offer improved support and plug-ins for Groovy and can be useful in determining details of what a Groovy class has to offer.


Conclusion

I have discussed and demonstrated several different Groovy-specific and Java-general approaches for determining characteristics of a Groovy object. These approaches overlap (and some even make sure of each other) and have their own advantages and disadvantages. There is no shortage of approaches for learning more about the characteristics of a given Groovy object, so which is best to use often depends on the particular situation and the developer's needs.


Further Reading

Groovy Introspection: Know What You Have

Groovy Goodness: Getting Information about Objects

Difference Between inspect() and dump()

Background on dump() and inspect()

Groovy inspect()/Eval for Externalizing Data

No comments: