From a0be4d2757a1e72f9b63eaca4c8fffdfd2ca8f61 Mon Sep 17 00:00:00 2001 From: "Gary D. Gregory" Date: Tue, 3 Jun 2003 03:51:56 +0000 Subject: [PATCH] Refactor code in ToStringBuilder.reflectionToString(...) into a new subclass called ReflectionToStringBuilder. All of the ToStringBuilder.reflectionToString(...) forward their calls to equivalent methods in ReflectionToStringBuilde. ReflectionToStringBuilder can be subclassed to provide Field or value filtering. Since the unit tests exercis ToStringBuilder.reflectionToString(...) which then forwards those calls to ReflectionToStringBuilder, and ReflectionToStringBuilder does not provide new features (yet), there are no new unit test cases (yet). git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137353 13f79535-47bb-0310-9956-ffa450edef68 --- .../builder/ReflectionToStringBuilder.java | 457 ++++++++++++++++++ .../commons/lang/builder/ToStringBuilder.java | 272 ++--------- .../commons/lang/builder/ToStringStyle.java | 7 +- 3 files changed, 489 insertions(+), 247 deletions(-) create mode 100644 src/java/org/apache/commons/lang/builder/ReflectionToStringBuilder.java diff --git a/src/java/org/apache/commons/lang/builder/ReflectionToStringBuilder.java b/src/java/org/apache/commons/lang/builder/ReflectionToStringBuilder.java new file mode 100644 index 000000000..814fb789c --- /dev/null +++ b/src/java/org/apache/commons/lang/builder/ReflectionToStringBuilder.java @@ -0,0 +1,457 @@ +package org.apache.commons.lang.builder; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.lang.ClassUtils; + +/** + *

Builds toString() values using reflection.

+ * + *

This class uses reflection to determine the fields to append. + * Because these fields are usually private, the class, + * uses Field.setAccessible to + * change the visibility of the fields. This will fail under a security manager, + * unless the appropriate permissions are set up correctly.

+ * + *

A typical invocation for this method would look like:

+ *
+ * public String toString() {
+ *   return ReflectionToStringBuilder.toString(this);
+ * }
+ * 
+ * + *

You can also use the builder to debug 3rd party objects:

+ *
+ * System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
+ * 
+ * + *

A subclass can control field output by overriding the methods: + *

+ *

+ * + *

The exact format of the toString is determined by + * the {@link ToStringStyle} passed into the constructor.

+ * + * @author Gary Gregory + * @author Stephen Colebourne + * @since 2.0 + * @version $Id: ReflectionToStringBuilder.java,v 1.1 2003/06/03 03:51:56 ggregory Exp $ + */ +public class ReflectionToStringBuilder extends ToStringBuilder { + + /** + * A registry of objects used by 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 is null + */ + 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.

+ * + * @param object the Object to be output + * @param style the style of the 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.

+ * + * @param object the Object to be output + * @param style the style of the 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.

+ * + * @param object the Object to be output + * @param style the style of the 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.

+ * + * @param object the Object to build a 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.

+ * + * @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 + * @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.

+ * + * @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 + * @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. + * + * @param field The Field to test. + * @return Whether or not to append the given 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.

+ * + * @param array the array to add to the 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.

* @@ -122,26 +117,15 @@ import java.util.Set; * @author Stephen Colebourne * @author Gary Gregory * @since 1.0 - * @version $Id: ToStringBuilder.java,v 1.21 2003/05/31 22:22:49 ggregory Exp $ + * @version $Id: ToStringBuilder.java,v 1.22 2003/06/03 03:51:56 ggregory Exp $ */ public class ToStringBuilder { - + /** * The default style of output to use */ private static ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE; - /** - * A registry of objects used by 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 is null + * @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.

+ * Forwards to ReflectionToStringBuilder. * - * @param object the Object to be output - * @param style the style of the 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.

+ * Forwards to ReflectionToStringBuilder. * - * @param object the Object to be output - * @param style the style of the 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.

+ * Forwards to ReflectionToStringBuilder. * - * @param object the Object to be output - * @param style the style of the 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.

- * - * @param array the array to add to the toString - * @return this - */ - public ToStringBuilder reflectionAppendArray(Object array) { - style.reflectionAppendArrayDetail(buffer, null, array); - return this; - } - /** *

Returns the built toString.

* @@ -1282,4 +1057,13 @@ public class ToStringBuilder { return buffer.toString(); } + /** + * Returns the object being output. + * + * @return The object being output. + */ + public Object getObject() { + return object; + } + } diff --git a/src/java/org/apache/commons/lang/builder/ToStringStyle.java b/src/java/org/apache/commons/lang/builder/ToStringStyle.java index af3837346..db558765d 100644 --- a/src/java/org/apache/commons/lang/builder/ToStringStyle.java +++ b/src/java/org/apache/commons/lang/builder/ToStringStyle.java @@ -81,8 +81,9 @@ import org.apache.commons.lang.SystemUtils; * the array length.

* * @author Stephen Colebourne + * @author Gary Gregory * @since 1.0 - * @version $Id: ToStringStyle.java,v 1.13 2003/04/18 04:57:19 bayard Exp $ + * @version $Id: ToStringStyle.java,v 1.14 2003/06/03 03:51:56 ggregory Exp $ */ public abstract class ToStringStyle implements Serializable { @@ -153,7 +154,7 @@ public abstract class ToStringStyle implements Serializable { */ private String arraySeparator = ","; /** - * The detail for array content + * The detail for array content. */ private boolean arrayContentDetail = true; /** @@ -329,7 +330,7 @@ public abstract class ToStringStyle implements Serializable { * @param detail output detail or not */ protected void appendInternal(StringBuffer buffer, String fieldName, Object value, boolean detail) { - if (ToStringBuilder.isRegistered(value) + if (ReflectionToStringBuilder.isRegistered(value) && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) { appendAsObjectToString(buffer, value);