Many development tools have very poor support for introspection of objects
in debug mode. Very often when reaching a breakpoint in debug mode it is
necessary to find out more about the current internal state of some objects.
Most tools have some support for that, but it is often a pain to use them.
However, JOI is not an extension of the debugger or using the debugging API.
It is just available on the level of System.out.println()
!
The usage of JOI is made similar to the Inspector in Smalltalk environments. I guess most Smalltalkers are really missing this little tool in Java IDEs. So JOI is here to fill that gap.
To see the latest changes goto Release Notes
The general idea is to use the object inspctor at places in the code where you
would normally add a System.out.println( ... )
to learn more about
an object's internal state.
Using an inspector is very easy !
The only method you have to know is:
Add the archive pf-joi-full.jar to your classpath.
and put the inspect-statements
wherever you want to know more obout the current values of an object's attributes
( instance variables ).
Just give the object you are interested in to the inspect() method.
This will open a window with a tree on the left side and an information view on
the right side. In the tree is a row for the object itself ( named this )
and a row for each instance variable.
The type of the selected attribute and its value are displayed in the right info view.
Try the following example: Example1.java
All internal fields of the opened Frame are listed and can be inspected as well.
ATTENTION:
Initially, attributes declared as static ( class variables ), final ( constants )
or transient are not shown !
Only real instance variables, regardless if they are defined as public,
protected, private or default (package) are visible !
However, that can be changed in the menu 'Show'.
For a look at the MapInspector try the second example: Example2.java.
As an alternative you can also execute: java -jar pf-joi-full.jar
System properties with the specialized MapInspector and PropertiesRenderer after
a sort was applied.
In this second example you also can see a variant of the inspect() method .
This method opens the inspector window and stops the current thread until all inspector windows are closed. Sometimes this is necessary to avoid the change of the inspected object while it is in the inspector window. Another good reason can be seen in the example itself - the program would terminate immediately after the inspector window was opened.
For the other available public methods see the Inspector API.
Extensibility
Object Rendering
By default the value of an object or an attribute is shown as the result
of its toString() method. If a different, self-created string representation
of own objects is desired, the corresponding class can implement the method
inspectString() which will be used by the inspector instead of
toString() to display the objects's value.
There are two ways to do this:
Another alternative is to provide an object renderer that is able to provide the string
representation for the instances of a specific class.
Let's assume the value of a java.awt.Color object should be #000000 rather than
the default java.awt.Color[r=0,g=0,b=0] from the toString() method.
Of course this class can't be extended with a inspectString() method.
So the solution is to write a class that implements org.pf.joi.ObjectRenderer
and tell JOI to use it to render all instances of java.awt.Color.
(ColorRenderer.java)
Telling JOI about an object renderer is quite simple. Just put the mapping of the classes into
a file 'META-INF/joi.renderer' and ensure that this file is in the classpath.
Here's an example of such a file:
# # Defines renderer classes for specific object classes # The key is the class name of objects that are inspected. # The value is the name or the renderer class to be used # to display these inspected objects. # java.awt.Color=org.pf.joi.test.ColorRenderer java.lang.Integer[]=org.pf.joi.test.IntegerArrayRenderer
Since V2.1 it is also possible to provide a visual component renderer in addition to
the string representation. Such a renderer must implement the interface
org.pf.joi.ObjectRenderer2. That is an extension of
org.pf.joi.ObjectRenderer which simply adds the method
public Component inspectComponent( Object obj ).
As an example the PropertiesRenderer is delivered with JOI. It will
be used automatically whenever a Properties object is inspected. Then it is rendered to a JTable
containing the key/value pairs (see Example2.java).
Specialized Inspectors
From the very beginning JOI was designed in a way that enables programmers
to extend it with more specialized inspectors. Now here's a short guide
how to do that.
JOI comes with a basic inspector ( class BasicInspector ), that shows all instance variables
of any object it was opened on. However, for debugging sometimes it is more convenient to see
a slightly different representation of an object's internals. For example Hashtable instances
are much better to understand, if we can see the keys and their associated values rather than
all the internal variables. Therefore a special inspector class named MapInspector is
registered in the JOI framework to handle all instances of classes that implement the java.util.Map
interface (e.g. java.util.Hashtable).
The following entry in 'META-INF/joi.inspector' installs that special inspector:
java.util.Map=org.pf.joi.MapInspectorOf course everybody can create and install his/her own inspectors for classes and/or interfaces. Just put the appropriate class mapping in a 'META-INF/joi.inspector' file and ensure that it will be found in the classpath. The best is to just deliver the inspector classes and their corresponding 'META-INF/joi.inspector' file together in a JAR.
Example for a inspector mapping:
com.company.business.RootObject=com.company.debug.RootObjectInspector
The key on the left is the class or interface the inspector should be used for. The value on the right is the associated inspector class wich must be a subclass of org.pf.joi.BasicInspector.
Before that really works, you have to programm the new inspector class. Here are the steps you have
to follow, using the implementation of StringInspector as example.
public StringSpy( Object obj ) throws SecurityException { super( obj ) ; } // StringSpy()
protected String getString() { return (String)this.getObject() ; } // getString()
protected void addAllElements() throws SecurityException { ElementSpy elementSpy = null ; Object element = null ; String str = null ; int index = 0 ; str = this.getString() ; for ( index = 0 ; index < str.length() ; index++ ) { elementSpy = new ArrayElementSpy( this, index, new Character( str.charAt( index ) ), Character.TYPE ) ; this.getElementHolders().add( elementSpy ) ; } } // addAllElements()
protected AbstractObjectSpy objectSpyFor( Object obj ) { return ( new StringSpy( obj ) ) ; } // objectSpyFor()
protected String getInspectorId() { return "StringInspector" ; } // getInspectorId()
java.lang.String=org.pf.joi.StringInspector
So to keep an object's state persistent it is possible to write your own class that must implement org.pf.joi.ExportProvider. For details see ExportProvider Javadoc
Interface/Superclass | Properties file for plug-in definition |
BasicInspector | META-INF/joi.inspector |
ObjectRenderer | META-INF/joi.renderer |
ExportProvider | META-INF/joi.exporter |
java.lang.Sring[]=com.mycode.joi.StringArrayRenderer
So the sequence for the rendering now is: