ObjectSnoop |
||||||||||||||||||||
Inspecting objects in Java
|
||||||||||||||||||||
ObjectSnoop Homepage Features History Download Contact Links Manual SourceForge Project homepage Website | ObjectSnoop can be incorporated into a program to provide detailed information about the state of an object at runtime. The most likely destination of this information is a log file for examination either while the program is running, or after execution is complete. Factory methods are provided to help hide the amount of construction needed to get snooping. Before being introduced to the factory classes, each of the core classes is briefly described. This should give you enough information to get you started on using them, and hopefully give you the incentive to use the factory methods instead. Before you can use ObjectSnoop you'll need to make sure that it's installed on your system. The procedure for doing this is described in Chapter 2. If you're interested in a more detailed discussion of the structure of, and relationships between, the classes then you might want to look at Chapter 7. All of the ObjectSnoop classes are rooted under the net.sourceforge.objectsnoop [1] package, and it is intended that you need only import one package (if at all!) The conventional way of importing classes is to include an import statement in your source file. The import statement for ObjectSnoop looks like this:
This needs to be done before you've even added one line of debugging code. It can also be mildly annoying when you need to clean up and remove ObjectSnoop code: you've got to edit the file in at least two places. If you want to bypass the need for an import statement, you can qualify any ObjectSnoop classes with their full package path. Here's an example that accesses the FilterFactory class:
While there's less typing when you need to use a single class, this could become cumbersome if you want to add debugging statements into multiple parts of your code. All of the snooping is done by one class, the SnoopDirector. The director accepts objects for snooping, and the addition of custom filters for special interpretation of those objects. Snooped output from the director needs to go somewhere, and needs to be tailored to any special formatting requirements. It would be cumbersome to update the director each time a new output format was created, so instead the director needs to be prepared with, and connected to, a formatter. The director class, SnoopDirector, needs to be connected with a formatter during construction.
This needs two import statements to get this code running:
You might be looking forward to the factory methods already (Section 3.4). Snooping involves less work than creating the director, you just need to pass the object to the snoop() method.
If you want to add your own descriptive comment then try this instead:
As the snoop() method also returns a reference to the snoopDirector, we can also chain the snooping together:
Here's a simple example that gathers all this together. It creates a director and connect it to an HTML formatter that writes to a file:
Take a look at the factory methods (Section 3.4) for an easier way to do this. A formatter gives structure to the information produced by the director; structure imposed by the limitations of the medium receiving and/or displaying the data. It is unneccessary, expansive, and error prone to program the director with this knowledge. Formatters themselves need to connect to the place where they write their output. By way of example, the HTMLFormatter class accepts any class derived from java.io.Writer. This could connect the formatter to a text console, a file, or any other kind of writable data stream. Here's an earlier example showing how the HTMLFormatter object is connected to System.out (itself a data stream object) via a PrintWriter:
Each formatter conforms to the the ISnoopFormatter interface. SnoopDirector uses this interface to describe objects in a structured and guaranteed way that any implementation of this interface can rely on. All the code that you've seen so far suggests that there's quite a fair bit of preparation required to do the snooping. There is. Fortunately it's possible to identify standard combinations of formatters (and filters too, more in Section 3.5), and make them available through factory methods. A factory is, quite simply, a place where objects are manufactured for you to use. It allows the construction process to be hidden away so that you, the consumer, may use the product and then discard it. Using a factory requires only two lines of code, and even one line can be discarded with a little more typing. The class responsible for doing all the construction is SnoopFactory. You can get it to construct a director for you by calling one of the supplied static methods. Here's the import statement you need to use the class:
Now that you've got the factory imported, you can create a director that uses an HTMLFormatter. Not only can you create the director, but you can also use it at the same time by making use of the chaining we looked at earlier:
Notice that we've still got to supply the final destination for the output, but this is still much easier than importing the formatter class as well. The return value of snoop() enables method chaining, and also allows you to store the SnoopDirector object in a variable for later use. Probably only needed if you're concerned about object creation, but its there if you want to take advantage of it. When an object is from a family of interchangable classes then the implementation details are usually less important than how the object behaves when used. A developer is likely to be more interested in data returned by a set of getter methods than seeing the raw data contained in the internal representation. Filters in ObjectSnoop allow this specialized interpretation to take place. Object or interface filters can be added to an existing SnoopDirector and immediately take effect: detecting when a matching object is snooped and stepping in to provide a customized description of the object to the formatter. The list of available filters currently built into ObjectSnoop is described in some detail in Chapter 5. Once you've created your own filter you can give it to the instance of the SnoopDirector that you're using and let it get on with the job:
The filter() method actually returns the snoopDirector object, so you can do more method chaining with this too:
There's also a pre-constructed set of filters built into ObjectSnoop. They're available for use alongside your own filters, and can be identified by unique string. These filters are accessible through static methods implemented in the (appropriately named) FilterFactory class. The use of named filters is another trick that helps reduce the overhead of importing additional classes or packages into your class. Suppose you know that a filter identified by the string my_filter existed. You could add it to your director like this:
In fact there's an even easier method: SnoopDirector is aware of the existance of the FilterFactory so you can write the above code in the following way:
You can initialize the FilterFactory with your own filters as well. This needs to be done (ideally) during the initialization of your program. The following code adds your filter to the pool of those available, overriding any other filter in the pool of the same name:
Trying to use your named filter before you've added it to the FilterFactory isn't going to cause any crashes; any objects that would have matched just won't be filtered. Any filter class added to the factory must meets several stringent requirements. The most obvious is the implementation of the ISnoopFilter interface. The most important, however, is "statelessness". This means that the filter doesn't store information in class instance variables. The factory serves up the same object time and time again, so thread-like issues around state may lead to unexpected behaviour. Once you've got the filter bug you'll find you're putting multiple filters into a single director. This won't cause a problem with the director, but you do need to know the order in which they're executed. The rule for this is a simple one: The last filter added gets to check first.
This stack means that you can add filters that take precendence over any filters previously added to the director. If you find yourself using several filters together all the time, then you can create a single composite filter that contains all of them. This kind of filter is usually derived from the FilterComposite base class, but you can also use method chaining to add a filter anonymously. As you'd expect when adding filters to the composite, it is the last filter added that is given the first attempt on a snooped object. An example of a filter derived from FilterComposite:
An example of an equivalent anonymous filter using method chaining:
In both examples the filters will be checked in the order 1, 2, 3. [1] SourceForge® is a trademark of VA Software Corporation, and is used to root ObjectSnoop with permission. | |||||||||||||||||||