This is a rather complicated affair, because one of the things you need to take care of is circular references, which, if you are not careful, will cause infinite recursion resulting in a StackOverflow exception.
These are a set of methods for accomplishing what you want:
public static String reflectionToString( Object obj )
{
StringBuilder stringBuilder = new StringBuilder();
reflectionToString( stringBuilder, obj );
return stringBuilder.toString();
}
public static void reflectionToString( StringBuilder stringBuilder, Object obj )
{
reflectionToString( new ArrayList<>(), stringBuilder, obj );
}
private static void reflectionToString( ArrayList<Object> visited,
StringBuilder stringBuilder, Object obj )
{
if( obj == null )
{
stringBuilder.append( "null" );
return;
}
Class<?> objectClass = obj.getClass();
if( objectClass == String.class )
{
StaticStringHelpers.appendEscapedForJava( stringBuilder, String.valueOf( obj ), '"' );
return;
}
if( objectClass.isPrimitive() )
{
stringBuilder.append( obj );
return;
}
if( Collection.class.isAssignableFrom( objectClass ) && ((Collection<?>)obj).isEmpty() )
{
stringBuilder.append( "{}" );
return;
}
if( objectClass.isEnum() )
{
stringBuilder.append( obj );
return;
}
if( objectClass.isArray() )
{
stringBuilder.append( objectClass.getComponentType().getName() ).append( "[]" );
}
else
{
stringBuilder.append( objectClass.getName() );
}
stringBuilder.append( "@" ).append( Integer.toHexString( System.identityHashCode( obj ) ) );
if( visited.contains( obj ) )
return;
visited.add( obj );
stringBuilder.append( "={" );
if( objectClass.isArray() )
{
for( int i = 0; i < Array.getLength( obj ); i++ )
{
if( i > 0 )
stringBuilder.append( "," );
Object val = Array.get( obj, i );
reflectionToString( visited, stringBuilder, val );
}
}
else
{
boolean[] first = { true };
reflectionToString( visited, stringBuilder, first, objectClass, obj );
}
stringBuilder.append( "}" );
}
private static void reflectionToString( ArrayList<Object> visited,
StringBuilder stringBuilder, boolean[] first,
Class<?> objectClass, Object obj )
{
Class<?> superClass = objectClass.getSuperclass();
if( superClass != null )
reflectionToString( visited, stringBuilder, first, superClass, obj );
Field[] fields = objectClass.getDeclaredFields();
AccessibleObject.setAccessible( fields, true );
for( Field field : fields )
{
if( Modifier.isStatic( field.getModifiers() ) )
continue;
appendNonFirst( stringBuilder, first, "," );
stringBuilder.append( field.getName() ).append( "=" );
try
{
Object fieldValue = field.get( obj );
reflectionToString( visited, stringBuilder, fieldValue );
}
catch( Exception e )
{
assert false : e;
}
}
}
private static StringBuilder appendNonFirst( StringBuilder stringBuilder,
boolean[] first, String text )
{
if( first[0] )
first[0] = false;
else
stringBuilder.append( text );
return stringBuilder;
}
The ArrayList<Object> visited
collects all visited objects so as to avoid re-visiting them. I do not remember why it is an ArrayList and not a Set.
The StringBuilder
is where the string is collected. The toString()
method which invokes reflectionToString()
ends with a return stringBuilder.toString();
.
boolean[] first
is just a single-element array containing a boolean
, which is a hacky way to pass a single boolean by reference. It is simply used for figuring out whether a comma should be emitted.
Class<?> objectClass
is the class of the object being emitted.
Object obj
is the object being emitted.