From a0be4d2757a1e72f9b63eaca4c8fffdfd2ca8f61 Mon Sep 17 00:00:00 2001
From: "Gary D. Gregory" Builds This class uses reflection to determine the fields to append.
+ * Because these fields are usually private, the class,
+ * uses A typical invocation for this method would look like: You can also use the builder to debug 3rd party objects: A subclass can control field output by overriding the methods:
+ * toString()
values using reflection.Field.setAccessible
to
+ * change the visibility of the fields. This will fail under a security manager,
+ * unless the appropriate permissions are set up correctly.
+ * public String toString() {
+ * return ReflectionToStringBuilder.toString(this);
+ * }
+ *
+ *
+ *
+ * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
+ *
+ *
+ *
+ *
+ *
The exact format of the toString
is determined by
+ * the {@link ToStringStyle} passed into the constructor.
reflectionToString
methods to detect cyclical object references
+ * and avoid infinite loops.
+ */
+ private static ThreadLocal registry = new ThreadLocal() {
+ protected synchronized Object initialValue() {
+ // The HashSet implementation is not synchronized,
+ // which is just what we need here.
+ return new HashSet();
+ }
+ };
+
+ /**
+ * Returns the registry of objects being traversed by the
+ * reflectionToString
methods in the current thread.
+ * @return Set the registry of objects being traversed
+ */
+ static Set getRegistry() {
+ return (Set) registry.get();
+ }
+
+ /**
+ * Returns true
if the registry contains the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value The object to lookup in the registry.
+ * @return boolean true
if the registry contains the given object.
+ */
+ static boolean isRegistered(Object value) {
+ return getRegistry().contains(value);
+ }
+
+ /**
+ * Registers the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value The object to register.
+ */
+ static void register(Object value) {
+ getRegistry().add(value);
+ }
+
+ /**
+ * This method uses reflection to build a suitable
+ * toString
using the default ToStringStyle
.
+ *
+ *
It uses Field.setAccessible
to gain access to private
+ * fields. This means that it will throw a security exception if run
+ * under a security manger, if the permissions are not set up correctly.
+ * It is also not as efficient as testing explicitly.
Transient members will be not be included, as they are likely derived. + * Static fields will not be included. Superclass fields will be appended.
+ * + * @param object the Object to be output + * @return the String result + * @throws IllegalArgumentException if the Object isnull
+ */
+ public static String toString(Object object) {
+ return toString(object, null, false, null);
+ }
+
+ /**
+ * This method uses reflection to build a suitable
+ * toString
.
It uses Field.setAccessible
to gain access to private
+ * fields. This means that it will throw a security exception if run
+ * under a security manger, if the permissions are not set up correctly.
+ * It is also not as efficient as testing explicitly.
Transient members will be not be included, as they are likely derived. + * Static fields will not be included. Superclass fields will be appended.
+ * + *If the style is null
, the default
+ * ToStringStyle
is used.
toString
to create,
+ * may be null
+ * @return the String result
+ * @throws IllegalArgumentException if the Object or
+ * ToStringStyle
is null
+ */
+ public static String toString(Object object, ToStringStyle style) {
+ return toString(object, style, false, null);
+ }
+
+ /**
+ * This method uses reflection to build a suitable
+ * toString
.
It uses Field.setAccessible
to gain access to private
+ * fields. This means that it will throw a security exception if run
+ * under a security manger, if the permissions are not set up correctly.
+ * It is also not as efficient as testing explicitly.
If the outputTransients
is true
,
+ * transient members will be output, otherwise they are ignored,
+ * as they are likely derived fields, and not part of the value of the
+ * Object.
Static fields will not be included. Superclass fields will be appended.
+ * + *
+ * If the style is null
, the default
+ * ToStringStyle
is used.
toString
to create,
+ * may be null
+ * @param outputTransients whether to include transient fields
+ * @return the String result
+ * @throws IllegalArgumentException if the Object is null
+ */
+ public static String toString(Object object, ToStringStyle style, boolean outputTransients) {
+ return toString(object, style, outputTransients, null);
+ }
+
+ /**
+ * This method uses reflection to build a suitable
+ * toString
.
It uses Field.setAccessible
to gain access to private
+ * fields. This means that it will throw a security exception if run
+ * under a security manger, if the permissions are not set up correctly.
+ * It is also not as efficient as testing explicitly.
If the outputTransients
is true
,
+ * transient members will be output, otherwise they are ignored,
+ * as they are likely derived fields, and not part of the value of the
+ * Object.
Static fields will not be included. Superclass fields will be appended
+ * up to and including the specified superclass. A null superclass is treated
+ * as java.lang.Object
.
If the style is null
, the default
+ * ToStringStyle
is used.
toString
to create,
+ * may be null
+ * @param outputTransients whether to include transient fields
+ * @param reflectUpToClass the superclass to reflect up to (inclusive), may be null
+ * @return the String result
+ * @throws IllegalArgumentException if the Object is null
+ */
+ public static String toString(
+ Object object,
+ ToStringStyle style,
+ boolean outputTransients,
+ Class reflectUpToClass) {
+ return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients).toString();
+ }
+
+ /**
+ * Unregisters the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ *
+ * @param value The object to unregister.
+ */
+ static void unregister(Object value) {
+ getRegistry().remove(value);
+ }
+
+ /**
+ * Whether or not to append transient fields.
+ */
+ private boolean appendTransients = false;
+
+ /**
+ * The last super class to stop appending fields for.
+ */
+ private Class upToClass = null;
+
+ /**
+ * Constructs a new instance.
+ * + *This constructor outputs using the default style set with
+ * setDefaultStyle
.
toString
for,
+ * must not be null
+ * @throws IllegalArgumentException if the Object passed in is
+ * null
+ */
+ public ReflectionToStringBuilder(Object object) {
+ super(object);
+ }
+
+ /**
+ * Constructor specifying the output style.
+ * + *If the style is null
, the default style is used.
toString
for,
+ * must not be null
+ * @param style the style of the toString
to create,
+ * may be null
+ * @throws IllegalArgumentException if the Object passed in is
+ * null
+ */
+ public ReflectionToStringBuilder(Object object, ToStringStyle style) {
+ super(object, style);
+ }
+
+ /**
+ * Constructors a new instance.
+ * + *If the style is null
, the default style is used.
If the buffer is null
, a new one is created.
toString
for,
+ * must not be null
+ * @param style the style of the toString
to create,
+ * may be null
+ * @param buffer the StringBuffer
to populate, may be
+ * null
+ * @throws IllegalArgumentException if the Object passed in is
+ * null
+ */
+ public ReflectionToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
+ super(object, style, buffer);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param object the Object to build a toString
for,
+ * must not be null
+ * @param style the style of the toString
to create,
+ * may be null
+ * @param buffer the StringBuffer
to populate, may be
+ * null
+ */
+ public ReflectionToStringBuilder(
+ Object object,
+ ToStringStyle style,
+ StringBuffer buffer,
+ Class reflectUpToClass,
+ boolean outputTransients) {
+ super(object, style, buffer);
+ this.setUpToClass(reflectUpToClass);
+ this.setAppendTransients(outputTransients);
+ }
+
+ /**
+ * Returns whether or not to append the given Field
.
+ * true
.
+ * Field
.
+ */
+ protected boolean accept(Field field) {
+ String fieldName = field.getName();
+ return (fieldName.indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) == -1)
+ && (this.isAppendTransients() || !Modifier.isTransient(field.getModifiers()))
+ && (!Modifier.isStatic(field.getModifiers()));
+ }
+
+ /**
+ * Appends the fields and values defined by the given object of the
+ * given Class. If a cycle is detected as an objects is "toString()'ed",
+ * such an object is rendered as if Object.toString()
+ * had been called and not implemented by the object.
+ *
+ * @param clazz The class of object parameter
+ */
+ protected void appendFieldsIn(Class clazz) {
+ if (isRegistered(this.getObject())) {
+ // The object has already been appended, therefore we have an object cycle.
+ // Append a simple Object.toString style string. The field name is already appended at this point.
+ this.appendAsObjectToString(this.getObject());
+ return;
+ }
+ try {
+ this.registerObject();
+ if (clazz.isArray()) {
+ this.reflectionAppendArray(this.getObject());
+ return;
+ }
+ Field[] fields = clazz.getDeclaredFields();
+ Field.setAccessible(fields, true);
+ for (int i = 0; i < fields.length; i++) {
+ Field field = fields[i];
+ String fieldName = field.getName();
+ if (this.accept(field)) {
+ try {
+ // Warning: Field.get(Object) creates wrappers objects for primitive types.
+ Object fieldValue = this.getValue(field);
+ if (isRegistered(fieldValue) && !field.getType().isPrimitive()) {
+ // A known field value has already been appended, therefore we have an object cycle,
+ // append a simple Object.toString style string.
+ this.getStyle().appendFieldStart(this.getStringBuffer(), fieldName);
+ this.appendAsObjectToString(fieldValue);
+ // The recursion out of
+ // builder.append(fieldName, fieldValue);
+ // below will append the field
+ // end marker.
+ } else {
+ try {
+ this.registerObject();
+ this.append(fieldName, fieldValue);
+ } finally {
+ this.unregisterObject();
+ }
+ }
+ } catch (IllegalAccessException ex) {
+ //this can't happen. Would get a Security exception instead
+ //throw a runtime exception in case the impossible happens.
+ throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
+ }
+ }
+ }
+ } finally {
+ this.unregisterObject();
+ }
+ }
+
+ /**
+ * Gets the last super class to stop appending fields for.
+ *
+ * @return The last super class to stop appending fields for.
+ */
+ public Class getUpToClass() {
+ return this.upToClass;
+ }
+
+ /**
+ * Calls java.lang.reflect.Field.get(Object)
+ * @see java.lang.reflect.Field#get(Object)
+ * @throws IllegalArgumentException
+ * @throws IllegalAccessException
+ */
+ protected Object getValue(Field field) throws IllegalArgumentException, IllegalAccessException {
+ return field.get(this.getObject());
+ }
+
+ /**
+ * Returns whether or not to append transient fields.
+ *
+ * @return Whether or not to append transient fields.
+ */
+ public boolean isAppendTransients() {
+ return this.appendTransients;
+ }
+
+ /**
+ * Append to the toString
an Object
+ * array.
toString
+ * @return this
+ */
+ public ToStringBuilder reflectionAppendArray(Object array) {
+ this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
+ return this;
+ }
+
+ /**
+ * Registers this builder's source object to avoid infinite loops processing circular object references.
+ */
+ void registerObject() {
+ register(this.getObject());
+ }
+
+ /**
+ * Sets whether or not to append transient fields.
+ *
+ * @param appendTransients Whether or not to append transient fields.
+ */
+ public void setAppendTransients(boolean appendTransients) {
+ this.appendTransients = appendTransients;
+ }
+
+ /**
+ * Sets the last super class to stop appending fields for.
+ *
+ * @param clazz The last super class to stop appending fields for.
+ */
+ public void setUpToClass(Class clazz) {
+ this.upToClass = clazz;
+ }
+
+ public String toString() {
+ if (this.getObject() == null) {
+ return this.getStyle().getNullText();
+ }
+ Class clazz = this.getObject().getClass();
+ this.appendFieldsIn(clazz);
+ while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
+ clazz = clazz.getSuperclass();
+ this.appendFieldsIn(clazz);
+ }
+ return super.toString();
+ }
+
+ /**
+ * Unegisters this builder's source object to avoid infinite loops processing circular object references.
+ */
+ void unregisterObject() {
+ unregister(this.getObject());
+ }
+
+}
diff --git a/src/java/org/apache/commons/lang/builder/ToStringBuilder.java b/src/java/org/apache/commons/lang/builder/ToStringBuilder.java
index 6898d1a86..48601699e 100644
--- a/src/java/org/apache/commons/lang/builder/ToStringBuilder.java
+++ b/src/java/org/apache/commons/lang/builder/ToStringBuilder.java
@@ -53,11 +53,6 @@
*/
package org.apache.commons.lang.builder;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.HashSet;
-import java.util.Set;
-
/**
* Builds toString()
values.
reflectionToString
methods to detect cyclical object references
- * and avoid infinite loops.
- */
- private static ThreadLocal reflectionRegistry = new ThreadLocal() {
- protected synchronized Object initialValue() {
- // The HashSet implementation is not synchronized, which is just what we need here.
- return new HashSet();
- }
- };
-
//----------------------------------------------------------------------------
/**
@@ -161,230 +145,43 @@ public class ToStringBuilder {
}
/**
- * Returns the registry of objects being traversed by the
- * reflectionToString
methods in the current thread.
- * @return Set the registry of objects being traversed
- */
- static Set getReflectionRegistry() {
- return (Set) reflectionRegistry.get();
- }
-
- /**
- * Returns true
if the registry contains the given object.
- * Used by the reflection methods to avoid infinite loops.
+ * Forwards to ReflectionToStringBuilder.
*
- * @param value The object to lookup in the registry.
- * @return boolean true
if the registry contains the given object.
- */
- static boolean isRegistered(Object value) {
- return getReflectionRegistry().contains(value);
- }
-
- /**
- * Appends the fields and values defined by the given object of the
- * given Class. If a cycle is detected as an objects is "toString()'ed",
- * such an object is rendered as if Object.toString()
- * had been called and not implemented by the object.
- *
- * @param object the object to append details of
- * @param clazz the class of object parameter
- * @param builder the builder to append to
- * @param useTransients whether to output transient fields
- */
- private static void reflectionAppend(Object object, Class clazz, ToStringBuilder builder, boolean useTransients) {
- if (isRegistered(object)) {
- // The object has already been appended, therefore we have an object cycle.
- // Append a simple Object.toString style string. The field name is already appended at this point.
- builder.appendAsObjectToString(object);
- return;
- }
- try {
- register(object);
- if (clazz.isArray()) {
- builder.reflectionAppendArray(object);
- return;
- }
- Field[] fields = clazz.getDeclaredFields();
- Field.setAccessible(fields, true);
- for (int i = 0; i < fields.length; i++) {
- Field f = fields[i];
- String fieldName = f.getName();
- if ((fieldName.indexOf('$') == -1)
- && (useTransients || !Modifier.isTransient(f.getModifiers()))
- && (!Modifier.isStatic(f.getModifiers()))) {
- try {
- // Warning: Field.get(Object) creates wrappers objects for primitive types.
- Object fieldValue = f.get(object);
- if (isRegistered(fieldValue)
- && !f.getType().isPrimitive()) {
- // A known field value has already been appended, therefore we have an object cycle,
- // append a simple Object.toString style string.
- builder.getStyle().appendFieldStart(builder.getStringBuffer(), fieldName);
- builder.appendAsObjectToString(fieldValue);
- // The recursion out of
- // builder.append(fieldName, fieldValue);
- // below will append the field
- // end marker.
- } else {
- try {
- register(object);
- builder.append(fieldName, fieldValue);
- } finally {
- unregister(object);
- }
- }
- } catch (IllegalAccessException ex) {
- //this can't happen. Would get a Security exception instead
- //throw a runtime exception in case the impossible happens.
- throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
- }
- }
- }
- } finally {
- unregister(object);
- }
- }
-
- //-------------------------------------------------------------------------
-
- /**
- * This method uses reflection to build a suitable
- * toString
using the default ToStringStyle
.
- *
- *
It uses Field.setAccessible
to gain access to private
- * fields. This means that it will throw a security exception if run
- * under a security manger, if the permissions are not set up correctly.
- * It is also not as efficient as testing explicitly.
Transient members will be not be included, as they are likely derived.
- * - *Static fields will not be included. Superclass fields will be appended.
- * - * @param object the Object to be output - * @return the String result - * @throws IllegalArgumentException if the Object isnull
+ * @see ReflectionToStringBuilder#toString(Object)
*/
public static String reflectionToString(Object object) {
- return reflectionToString(object, null, false, null);
+ return ReflectionToStringBuilder.toString(object);
}
/**
- * This method uses reflection to build a suitable
- * toString
.
It uses Field.setAccessible
to gain access to private
- * fields. This means that it will throw a security exception if run
- * under a security manger, if the permissions are not set up correctly.
- * It is also not as efficient as testing explicitly.
Transient members will be not be included, as they are likely - * derived.
- * - *Static fields will not be included. Superclass fields will be appended.
- * - *If the style is null
, the default
- * ToStringStyle
is used.
toString
to create,
- * may be null
- * @return the String result
- * @throws IllegalArgumentException if the Object or
- * ToStringStyle
is null
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle)
*/
public static String reflectionToString(Object object, ToStringStyle style) {
- return reflectionToString(object, style, false, null);
+ return ReflectionToStringBuilder.toString(object, style);
}
/**
- * This method uses reflection to build a suitable
- * toString
.
It uses Field.setAccessible
to gain access to private
- * fields. This means that it will throw a security exception if run
- * under a security manger, if the permissions are not set up correctly.
- * It is also not as efficient as testing explicitly.
If the outputTransients
is true
,
- * transient members will be output, otherwise they are ignored,
- * as they are likely derived fields, and not part of the value of the
- * Object.
Static fields will not be included. Superclass fields will be appended.
- * - *
- * If the style is null
, the default
- * ToStringStyle
is used.
toString
to create,
- * may be null
- * @param outputTransients whether to include transient fields
- * @return the String result
- * @throws IllegalArgumentException if the Object is null
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean)
*/
public static String reflectionToString(Object object, ToStringStyle style, boolean outputTransients) {
- return reflectionToString(object, style, outputTransients, null);
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, null);
}
/**
- * This method uses reflection to build a suitable
- * toString
.
It uses Field.setAccessible
to gain access to private
- * fields. This means that it will throw a security exception if run
- * under a security manger, if the permissions are not set up correctly.
- * It is also not as efficient as testing explicitly.
If the outputTransients
is true
,
- * transient members will be output, otherwise they are ignored,
- * as they are likely derived fields, and not part of the value of the
- * Object.
Static fields will not be included. Superclass fields will be appended - * up to and including the specified superclass. A null superclass is treated - * as java.lang.Object.
- * - *If the style is null
, the default
- * ToStringStyle
is used.
toString
to create,
- * may be null
- * @param outputTransients whether to include transient fields
- * @param reflectUpToClass the superclass to reflect up to (inclusive), may be null
- * @return the String result
- * @throws IllegalArgumentException if the Object is null
+ * @see ReflectionToStringBuilder#toString(Object,ToStringStyle,boolean,Class)
*/
public static String reflectionToString(
Object object,
ToStringStyle style,
boolean outputTransients,
Class reflectUpToClass) {
- if (style == null) {
- style = getDefaultStyle();
- }
- if (object == null) {
- return style.getNullText();
- }
- ToStringBuilder builder = new ToStringBuilder(object, style);
- Class clazz = object.getClass();
- reflectionAppend(object, clazz, builder, outputTransients);
- while (clazz.getSuperclass() != null && clazz != reflectUpToClass) {
- clazz = clazz.getSuperclass();
- reflectionAppend(object, clazz, builder, outputTransients);
- }
- return builder.toString();
- }
-
- /**
- * Registers the given object.
- * Used by the reflection methods to avoid infinite loops.
- *
- * @param value The object to register.
- */
- static void register(Object value) {
- getReflectionRegistry().add(value);
+ return ReflectionToStringBuilder.toString(object, style, outputTransients, reflectUpToClass);
}
/**
@@ -401,27 +198,17 @@ public class ToStringBuilder {
}
/**
- * Unregisters the given object.
- * Used by the reflection methods to avoid infinite loops.
- *
- * @param value The object to unregister.
- */
- static void unregister(Object value) {
- getReflectionRegistry().remove(value);
- }
-
- /**
- * Current toString buffer
+ * Current toString buffer.
*/
private final StringBuffer buffer;
-
+
/**
- * The object being output
+ * The object being output.
*/
private final Object object;
-
+
/**
- * The style of output to use
+ * The style of output to use.
*/
private final ToStringStyle style;
@@ -1257,18 +1044,6 @@ public class ToStringBuilder {
return style;
}
- /**
- * Append to the toString
an Object
- * array.
toString
- * @return this
- */
- public ToStringBuilder reflectionAppendArray(Object array) {
- style.reflectionAppendArrayDetail(buffer, null, array);
- return this;
- }
-
/**
* Returns the built toString
.