bb.util
Class ObjectState

java.lang.Object
  extended by bb.util.ObjectState

public class ObjectState
extends Object

Determines the state of some other object.

The constructor finds the complete state: every Field in object's Class, all of its superclasses, and its complete interface hierarchy. (Recall: interfaces can declare static final fields, altho this is not a good idea).

By default, the constructor stores all this state, however, an optional number of ObjectState.Filters may be provided to restrict the state that is stored. Many users will find the pre-defined immediateState or immediateInstanceState fields convenient.

The state of this other object is stored as a Class --> Field[] map. Its accessor is getClassToFields.

This class offers convenience toString() and toStringLabeled() methods, which return formatted Strings and make it trivial for any class to write a generic toString/toStringLabeled methods. For example,


        @Override public String toString() {
                return new ObjectState( this, ObjectState.immediateInstanceState ).toString();
        }
 
or

        public String toStringLabeled() {
                return new ObjectState( this ).toStringLabeled();
        }
 
might work. Even more convenient for the casual user is the static describe(java.lang.Object) method.

If this generic toString/toStringLabeled approach is inadequate, generateToStringCode/generateToStringLabeledCode may be used to automatically generate java source code which can then be customized. For example, a call like:


        System.out.println( new ObjectState( this, immediateState ).generateToStringCode() );
 
possibly followed by some moderate hand editing might be all that is required.

This class is useful in debugging, and is heavily used by the p and g classes.

This class uses reflection to get the Field data. Since reflection is much slower than direct field access, care should be taken in using this class if high performance is needed.

This class is not multithread safe.

Author:
Brent Boyer

Nested Class Summary
static class ObjectState.AcceptOnlyImmediateClass
          Accepts a Class only if it is object's class.
static class ObjectState.AcceptOnlyPublicProtectedFields
          Accepts a Field only if it is public or protected.
static class ObjectState.ClassFilterAbstract
          Base class for Filters which care only about Classes.
static class ObjectState.FieldFilterAbstract
          Base class for Filters which care only about Fields.
static interface ObjectState.Filter
          Interface for types which accept/reject Classes/Fields.
static class ObjectState.RejectInterfaces
          Rejects a Class if it is an interface.
static class ObjectState.RejectObjectClass
          Rejects a Class if it is Object.class.
static class ObjectState.RejectStaticFields
          Rejects a Field if it is static.
static class ObjectState.UnitTest
          See the Overview page of the project's javadocs for a general description of this unit test class.
 
Field Summary
private  Map<Class,Field[]> classToFields
          For each Class in object's hierarchy (i.e. object's direct Class, any superclasses, any interface hierarchy), stores a mapping from the Class to an array of every declared Field of that class.
private  Set<ObjectState.Filter> filters
          Stores all the ObjectState.Filters that this instance uses.
static Set<ObjectState.Filter> immediateInstanceState
          A Set of ObjectState.Filters that acts to only accept this state: fields from the immediate object under consideration (i.e. reject fields from superclasses/interfaces) instance fields (i.e. reject static fields) Contract: is never null.
static Set<ObjectState.Filter> immediateState
          A Set of ObjectState.Filters that acts to only accept fields from the immediate object under consideration (i.e. reject fields from superclasses/interfaces); both static and instance fields are accepted.
private  Object object
          Object that this instance is concerned with.
private static ThreadLocal<Map<Object,Integer>> threadToObjCount
          The toStringLabeled(String, String) method can result in recursive calls because of its use of toStringSmart(java.lang.Object, java.lang.String, java.lang.String, java.util.Set).
 
Constructor Summary
ObjectState(Object object, ObjectState.Filter... filters)
          Calls the fundamental constructor after converting filters into a non-null Set.
ObjectState(Object object, Set<ObjectState.Filter> filters)
          Constructor.
 
Method Summary
private  void appendCodeForClass(Class c, StringBuilder sb, AtomicBoolean first, boolean labeled)
           
private  void appendField(Field field, StringBuilder sb, String prefix, String indent)
           
private  void appendFields(Class c, StringBuilder sb, String prefix, String indent)
           
private  boolean classAccepted(Class c, Object object, Set<ObjectState.Filter> filters)
           
private static void decrementObjectGraph(Object obj)
           
static String describe(Object obj)
          Returns describe( obj, null).
static String describe(Object obj, String prefix)
          Returns new ObjectState( obj, immediateInstanceState ).
private  Field[] extractFields(Class c, Object object, Set<ObjectState.Filter> filters)
           
private  boolean fieldAccepted(Field field, Object object, Set<ObjectState.Filter> filters)
           
private  String fieldErrors(Field field)
           
private  String fieldWarnings(Field field)
           
private  String generateCode(boolean labeled)
           
 String generateToStringCode()
          Returns a String which is valid java source code for a toString implementation for object.
 String generateToStringLabeledCode()
          Returns a String which is valid java source code for a toStringLabeled implementation for object.
 Map<Class,Field[]> getClassToFields()
          Accessor for classToFields.
 Set<ObjectState.Filter> getFilters()
          Accessor for filters.
 int getHashCode()
          Returns 0 if getObject() returns null.
 int getNumberOfFields()
          Returns the total number of fields stored in all the values of classToFields.
 Object getObject()
          Accessor for object.
 String getType()
          Returns getObject's class name.
private  void handleClass(Class c, Object object, Set<ObjectState.Filter> filters, Map<Class,Field[]> map)
           
private static void incrementObjectGraph(Object obj)
           
private static boolean isInObjectGraph(Object obj)
           
private static boolean isObjectGraphEmpty()
           
private  String lineStart(AtomicBoolean first, boolean labeled)
           
 String toString()
          Returns toString("\t").
 String toString(String separator)
          Returns a simple String which barely describes the data in this class compared to toStringLabeled.
 String toStringLabeled()
          Returns toStringLabeled(0).
 String toStringLabeled(int indentLevel)
          Returns toStringLabeled( StringUtil.getTabs.
 String toStringLabeled(String prefix, String indent)
          Returns a complex String which more fully describes the data in this class compared to toString(String).
static String toStringSmart(Object obj, String prefix, String indent, Set<ObjectState.Filter> filters)
          Returns a String which describes the state of obj.
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
 

Field Detail

threadToObjCount

private static final ThreadLocal<Map<Object,Integer>> threadToObjCount
The toStringLabeled(String, String) method can result in recursive calls because of its use of toStringSmart(java.lang.Object, java.lang.String, java.lang.String, java.util.Set). This means that infinite loops can result when applied to certain object graphs with circular reference chains, just as with object serialization. One way to prevent this is to keep track of objects encountered so far, and use a placeholder if ever encounter a known object and stop the recursion there. That is what this field does: it allows the thread executing toStringLabeled to know what objects it has encountered so far. It is a Map from an encountered Object to the number of times that it has been encountered.

Contract: is never null. The Map implementation used in the values must use reference equality, not object-equality; see IdentityHashMap. A given thread's Map must be empty when the topmost toStringLabeled call finally returns.

Note that this field must be static and not instance based, because toStringSmart can create new ObjectState intances, therefore the tracking has to be done beyond the instance level.


immediateState

public static final Set<ObjectState.Filter> immediateState
A Set of ObjectState.Filters that acts to only accept fields from the immediate object under consideration (i.e. reject fields from superclasses/interfaces); both static and instance fields are accepted.

Contract: is never null. Furthermore, is unmodifiable in order to preserve encapsulation. It will never be changed after class initialization, so users may safely iterate over it with no fear of ConcurrentModificationException.


immediateInstanceState

public static final Set<ObjectState.Filter> immediateInstanceState
A Set of ObjectState.Filters that acts to only accept this state:
  1. fields from the immediate object under consideration (i.e. reject fields from superclasses/interfaces)
  2. instance fields (i.e. reject static fields)

Contract: is never null. Furthermore, is unmodifiable in order to preserve encapsulation. It will never be changed after class initialization, so users may safely iterate over it with no fear of ConcurrentModificationException.


object

private final Object object
Object that this instance is concerned with.

Contract: none; may even be null.


filters

private final Set<ObjectState.Filter> filters
Stores all the ObjectState.Filters that this instance uses.

If multiple elements are present, then each is connected by an implicit AND (i.e. a Class/Field must be accepted by every Filter in order to be be present in classToFields).

Contract: is never null, but may be empty. Furthermore, is unmodifiable in order to preserve encapsulation. It will never be changed after construction, so users may safely iterate over it with no fear of ConcurrentModificationException.


classToFields

private final Map<Class,Field[]> classToFields
For each Class in object's hierarchy (i.e. object's direct Class, any superclasses, any interface hierarchy), stores a mapping from the Class to an array of every declared Field of that class.

Contract:

  1. is never null
  2. is empty if and only if object is null
  3. iteration returns Classes in the following order: first the Class for object itself, then the Class hierarchy for each interface that is directly implemented by object. This process is then recursively repeated for each superclass above object. If an interface appears multiple times in the interface hierarchy, then it will obviously only appear once as a key in this field, and its iteration order is determined by its first appearance.
  4. each Field[] that is mapped to will a) never be null but b) will be zero length if the Class/Interface has no fields and c) is sorted using a ReflectUtil.FieldComparator.
  5. each element of the Field[] that is mapped to is guaranteed to be accessible. If any element's isAccessible method originally returned false, then a call to field.setAccessible(true) is made during construction. This means that all code in this class can count on these elements being accessible and should never susequently call setAccessible. It also means that no code in this class should ever assume that an element's isAccessible result says anything about the original accessibility of the field.
  6. is unmodifiable in order to preserve encapsulation. It will never be changed after construction, so users may safely iterate over it with no fear of ConcurrentModificationException.

Constructor Detail

ObjectState

public ObjectState(Object object,
                   ObjectState.Filter... filters)
            throws SecurityException
Calls the fundamental constructor after converting filters into a non-null Set.

Throws:
SecurityException

ObjectState

public ObjectState(Object object,
                   Set<ObjectState.Filter> filters)
            throws SecurityException
Constructor. Determines object's state as restricted by filters, and places the result into classToFields.

Parameters:
object - assigned to object, so must satisfy the contract
filters - assigned to filters, so must satisfy the contract
Throws:
SecurityException - if Class.getDeclaredFields has an isssue
Method Detail

isInObjectGraph

private static boolean isInObjectGraph(Object obj)

isObjectGraphEmpty

private static boolean isObjectGraphEmpty()

incrementObjectGraph

private static void incrementObjectGraph(Object obj)

decrementObjectGraph

private static void decrementObjectGraph(Object obj)

describe

public static String describe(Object obj)
                       throws RuntimeException
Returns describe( obj, null).

Throws:
RuntimeException

describe

public static String describe(Object obj,
                              String prefix)
                       throws RuntimeException
Returns new ObjectState( obj, immediateInstanceState ).toStringLabeled()( prefix, "\t" ). Is simply a convenience method that covers a conmmon usage scenario.

Parameters:
obj - the object to get a report on its immediate instance state
Throws:
RuntimeException - (or some subclass) if any problem occurs; this RuntimeException may wrap some underlying Throwable

toStringSmart

public static String toStringSmart(Object obj,
                                   String prefix,
                                   String indent,
                                   Set<ObjectState.Filter> filters)
                            throws RuntimeException
Returns a String which describes the state of obj. This method is noteworthy because of how it tries to intelligently handle certain scenarios for obj in a way that many users will find convenient.

The following special cases are checked in the order listed:

  1. if obj is null, then "<NULL>" is returned
  2. if obj is an array of any primitive type, then a comma separated list containing each element is returned
  3. if obj is an Object array, Collection, or Map, then each element is printed on its own indented line (altho if it is empty, then the text "<EMPTY (XXX has no elements)>" is returned, where XXX is either array, Collection, or Map as appropriate)
  4. if obj is in the java.lang package and/or is an instance of CharSequence, then obj.toString() is returned (the idea here being that the toString method for these types is likely to produce a simple 1 line result)
For all other classes, this method returns a newline followed by new ObjectState( obj, getFilters() ).toStringLabeled(prefix, indent) (i.e. a recursive call).

Throws:
RuntimeException - (or some subclass) if any problem occurs; this RuntimeException may wrap some underlying Throwable, so be sure to inspect its cause

handleClass

private void handleClass(Class c,
                         Object object,
                         Set<ObjectState.Filter> filters,
                         Map<Class,Field[]> map)

classAccepted

private boolean classAccepted(Class c,
                              Object object,
                              Set<ObjectState.Filter> filters)

extractFields

private Field[] extractFields(Class c,
                              Object object,
                              Set<ObjectState.Filter> filters)
                       throws SecurityException
Throws:
SecurityException

fieldAccepted

private boolean fieldAccepted(Field field,
                              Object object,
                              Set<ObjectState.Filter> filters)

getObject

public Object getObject()
Accessor for object.


getFilters

public Set<ObjectState.Filter> getFilters()
Accessor for filters.


getClassToFields

public Map<Class,Field[]> getClassToFields()
Accessor for classToFields.


getType

public String getType()
Returns getObject's class name. In general, the result is getObject's class name, however, the following are special cases:
  1. if getObject returns null, then the String "<NO TYPE: null Object reference>" is returned
  2. if getObject returns an array, then the result is formatted just like it would be declared in source code (i.e. as the element type followed by braces, e.g. "byte[]"; this contrasts with the class name produced by the JVM for a byte array, which is the cryptic "[B")


getHashCode

public int getHashCode()
Returns 0 if getObject() returns null. Else returns getObject().hashCode().


getNumberOfFields

public int getNumberOfFields()
                      throws IllegalStateException
Returns the total number of fields stored in all the values of classToFields.

Throws:
IllegalStateException - if getClassToFields raises it

toString

public String toString()
                throws RuntimeException
Returns toString("\t").

Overrides:
toString in class Object
Throws:
RuntimeException

toString

public String toString(String separator)
                throws RuntimeException
Returns a simple String which barely describes the data in this class compared to toStringLabeled.

The result always consists of but a single line which has all the fields of object recorded by this instance. These fields appear only as their values (e.g. no field name labels are present), with the value's toString method (from whatever class it belongs to) being used to generate the String form of the value. If a null reference is ever encountered, it is represented by the text "null".

The result is likely to be very confusing unless filters is very restrictive (e.g. is something like immediateInstanceState). In contrast, toStringLabeled can handle any scenario, since it labels its result.

Contract: the result is never blank and only ends with a newline if that newline is from the last field's value.

Parameters:
separator - text used to separate the fields inthe result; may not be null
Throws:
RuntimeException - (or some subclass) if any problem occurs; this RuntimeException may wrap some underlying Throwable

toStringLabeled

public String toStringLabeled()
                       throws RuntimeException
Returns toStringLabeled(0).

Throws:
RuntimeException

toStringLabeled

public String toStringLabeled(int indentLevel)
                       throws RuntimeException
Returns toStringLabeled( StringUtil.getTabs.(indentLevel), "\t" ).

Throws:
RuntimeException

toStringLabeled

public String toStringLabeled(String prefix,
                              String indent)
                       throws RuntimeException
Returns a complex String which more fully describes the data in this class compared to toString(String).

The result always consists of multiple lines, with a given line dedicated to each piece of information. The first line describes the calling thread. The second line reports getHashCode(). Finally, the remaining lines list all the fields of object recorded by this instance. These fields appear in the form "name = value", with toStringSmart(java.lang.Object, java.lang.String, java.lang.String, java.util.Set) being used to generate the String form of the value. The class that a field was declared in actually appears first on its own line before its declared fields are listed.

Contract: the result is never blank and always ends with a newline.

Parameters:
prefix - optional text that appears at the start of every line in the result; may be null
indent - text used to indent by one level; may not be null
Throws:
RuntimeException - (or some subclass) if any problem occurs; this RuntimeException may wrap some underlying Throwable, so be sure to inspect its cause

appendFields

private void appendFields(Class c,
                          StringBuilder sb,
                          String prefix,
                          String indent)
                   throws Exception
Throws:
Exception

appendField

private void appendField(Field field,
                         StringBuilder sb,
                         String prefix,
                         String indent)
                  throws Exception
Throws:
Exception

generateToStringCode

public String generateToStringCode()
Returns a String which is valid java source code for a toString implementation for object.

The result contains every field present in classToFields. Each such field is given its own line in the source code, but the result that the source code produces has every field in a single tab delimited line.

If a field's access level is such that its direct access will cause an IllegalAccessException, then the line for that field is actually commented out and a diagnostic error line is printed beneath it which explains the issue and possibly offers resolution (the programmer will have to manually intervene).

If a field is a static, then a warning comment is placed at the end of its line suggesting that the line be removed.

The programer is highly likely to need to hand edit the result before using it in an actual class: the warnings and errors mentioned above should be dealt with, custom decisions may need be made about how to handle individual fields, etc. This method merely helps automate the process of writing a custom toString method for someone who does not want to use the generic toString (e.g. to obtain more customization or performance).

Contract: the result is never blank and always ends with a newline.


generateToStringLabeledCode

public String generateToStringLabeledCode()
Returns a String which is valid java source code for a toStringLabeled implementation for object.

The result contains every field present in classToFields. Each such field is given its own line in the result, both in the source code as well as the result that the source code produces.

If a field's access level is such that its direct access will cause an IllegalAccessException, then the line for that field is actually commented out and a diagnostic error line is printed beneath it which explains the issue and possibly offers resolution (the programmer will have to manually intervene).

If a field is a static, then a warning comment is placed at the end of its line suggesting that the line be removed.

The programer is highly likely to need to hand edit the result before using it in an actual class: the warnings and errors mentioned above should be dealt with, custom decisions may need be made about how to handle individual fields, etc. This method merely helps automate the process of writing a custom toStringLabeled method for someone who does not want to use the generic toStringLabeled (e.g. to obtain more customization or performance).

Contract: the result is never blank and always ends with a newline.


generateCode

private String generateCode(boolean labeled)

appendCodeForClass

private void appendCodeForClass(Class c,
                                StringBuilder sb,
                                AtomicBoolean first,
                                boolean labeled)

lineStart

private String lineStart(AtomicBoolean first,
                         boolean labeled)

fieldErrors

private String fieldErrors(Field field)

fieldWarnings

private String fieldWarnings(Field field)