ggregory 2003/03/27 00:54:32
Modified: lang/src/java/org/apache/commons/lang/builder
ToStringStyle.java ToStringBuilder.java
Log:
Fix bug 16676: StackOverflow due to ToStringBuilder
(http://issues.apache.org/bugzilla/show_bug.cgi?id=16676)
Revision Changes Path
1.11 +49 -2
jakarta-commons/lang/src/java/org/apache/commons/lang/builder/ToStringStyle.java
Index: ToStringStyle.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/lang/src/java/org/apache/commons/lang/builder/ToStringStyle.java,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -r1.10 -r1.11
--- ToStringStyle.java 23 Mar 2003 17:54:16 -0000 1.10
+++ ToStringStyle.java 27 Mar 2003 08:54:31 -0000 1.11
@@ -54,6 +54,7 @@
package org.apache.commons.lang.builder;
import java.io.Serializable;
+import java.lang.reflect.Array;
import java.util.Collection;
import java.util.Map;
@@ -319,6 +320,8 @@
*
* <p>Either detail or summary views can be specified.</p>
*
+ * <p>If a cycle is detected, an object will be appended with the
Object.toString() format.</p>
+ *
* @param buffer the <code>StringBuffer</code> to populate
* @param fieldName the field name, typically not used as already appended
* @param value the value to add to the <code>toString</code>,
@@ -326,7 +329,12 @@
* @param detail output detail or not
*/
protected void appendInternal(StringBuffer buffer, String fieldName, Object
value, boolean detail) {
- if (value instanceof Collection) {
+ if (ToStringBuilder.isRegistered(value)
+ && !(value instanceof Number || value instanceof Boolean || value
instanceof Character)) {
+ appendAsObjectToString(buffer, value);
+
+ }
+ else if (value instanceof Collection) {
if (detail) {
appendDetail(buffer, fieldName, (Collection) value);
} else {
@@ -743,6 +751,32 @@
}
/**
+ * <p>Append to the <code>toString</code> the detail of an any array type.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param fieldName the field name, typically not used as already appended
+ * @param array the array to add to the <code>toString</code>,
+ * not <code>null</code>
+ */
+ protected void reflectionAppendArrayDetail(StringBuffer buffer, String
fieldName, Object array) {
+ buffer.append(arrayStart);
+ int length = Array.getLength(array);
+ for (int i = 0; i < length; i++) {
+ Object item = Array.get(array, i);
+ if (i > 0) {
+ buffer.append(arraySeparator);
+ }
+ if (item == null) {
+ appendNullText(buffer, fieldName);
+
+ } else {
+ appendInternal(buffer, fieldName, item, arrayContentDetail);
+ }
+ }
+ buffer.append(arrayEnd);
+ }
+
+ /**
* <p>Append to the <code>toString</code> a summary of an
* <code>Object</code> array.</p>
*
@@ -1272,6 +1306,19 @@
buffer.append('@');
buffer.append(Integer.toHexString(System.identityHashCode(object)));
}
+ }
+
+ /**
+ * <p>Appends with the same format as the default <code>Object toString()
+ * </code> method. Appends the class name followed by
+ * [EMAIL PROTECTED] System#identityHashCode(java.lang.Object)}.</p>
+ *
+ * @param buffer the <code>StringBuffer</code> to populate
+ * @param object the <code>Object</code> whose class name and id to output
+ */
+ protected void appendAsObjectToString(StringBuffer buffer, Object object) {
+ this.appendClassName(buffer, object);
+ this.appendIdentityHashCode(buffer, object);
}
/**
1.17 +119 -57
jakarta-commons/lang/src/java/org/apache/commons/lang/builder/ToStringBuilder.java
Index: ToStringBuilder.java
===================================================================
RCS file:
/home/cvs/jakarta-commons/lang/src/java/org/apache/commons/lang/builder/ToStringBuilder.java,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -r1.16 -r1.17
--- ToStringBuilder.java 23 Mar 2003 17:54:16 -0000 1.16
+++ ToStringBuilder.java 27 Mar 2003 08:54:31 -0000 1.17
@@ -54,8 +54,9 @@
package org.apache.commons.lang.builder;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
+import java.util.HashSet;
+import java.util.Set;
/**
* <p><code>ToString</code> generation routine.</p>
@@ -120,6 +121,17 @@
public class ToStringBuilder {
/**
+ * A registry of objects used by <code>reflectionToString</code> 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();
+ }
+ };
+
+ /**
* The default style of output to use
*/
private static ToStringStyle defaultStyle = ToStringStyle.DEFAULT_STYLE;
@@ -137,6 +149,40 @@
private final Object object;
/**
+ * Returns the registry of objects being traversed by the
+ * <code>reflectionToString</code> methods in the current thread.
+ * @return Set the registry of objects being traversed
+ */
+ static Set getReflectionRegistry() {
+ return (Set) reflectionRegistry.get();
+ }
+
+ /**
+ * Returns <code>true</code> if the registry contains the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ * @return boolean <code>true</code> if the registry contains the given object.
+ */
+ static boolean isRegistered(Object value) {
+ return getReflectionRegistry().contains(value);
+ }
+
+ /**
+ * Registers the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ */
+ static void register(Object value) {
+ getReflectionRegistry().add(value);
+ }
+
+ /**
+ * Unregisters the given object.
+ * Used by the reflection methods to avoid infinite loops.
+ */
+ static void unregister(Object value) {
+ getReflectionRegistry().remove(value);
+ }
+
+ /**
* <p>Constructor for <code>ToStringBuilder</code>.</p>
*
* <p>This constructor outputs using the default style set with
@@ -351,9 +397,6 @@
if (object == null) {
return style.getNullText();
}
- if (style == null) {
- style = getDefaultStyle();
- }
ToStringBuilder builder = new ToStringBuilder(object, style);
Class clazz = object.getClass();
reflectionAppend(object, clazz, builder, outputTransients);
@@ -366,7 +409,9 @@
/**
* Appends the fields and values defined by the given object of the
- * given Class.
+ * given Class. If a cycle is detected as an objects is "toString()'ed",
+ * such an object is rendered as if <code>Object.toString()</code>
+ * had been called and not implemented by the object.
*
* @param object the object to append details of
* @param clazz the class of object parameter
@@ -374,61 +419,54 @@
* @param useTransients whether to output transient fields
*/
private static void reflectionAppend(Object object, Class clazz,
ToStringBuilder builder, boolean useTransients) {
- if (clazz.isArray()) {
- reflectionAppendArray(object, clazz, builder);
+ 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;
}
- Field[] fields = clazz.getDeclaredFields();
- Field.setAccessible(fields, true);
- for (int i = 0; i < fields.length; i++) {
- Field f = fields[i];
- if ((f.getName().indexOf('$') == -1)
- && (useTransients || !Modifier.isTransient(f.getModifiers()))
- && (!Modifier.isStatic(f.getModifiers()))) {
- try {
- builder.append(f.getName(), f.get(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());
+ 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());
+ }
}
}
- }
- }
-
- /**
- * Appends the array elements in the given <code>Object</code> of the
- * given <code>Class</code> to a <code>ToStringBuilder</code>.
- *
- * @param object the array object to append details of
- * @param clazz the array class of the object parameter
- * @param builder the builder to append to
- */
- private static void reflectionAppendArray(Object object, Class clazz,
ToStringBuilder builder) {
- try {
- // A multi-dimension array invokes the append(Object) method.
- // A single-dimension array of primitive type pt invokes the
append(pt[]) method.
- builder.getClass().getDeclaredMethod("append", new Class[] {
clazz.getComponentType().isArray() ? Object.class : clazz }).invoke(
- builder,
- new Object[] { object });
- } catch (SecurityException e) {
- // "This cannot happen"
- throw new InternalError("Unexpected SecurityException: " +
e.getMessage());
- } catch (NoSuchMethodException e) {
- // "This cannot happen"
- throw new InternalError("Unexpected NoSuchMethodException: " +
e.getMessage());
- } catch (IllegalArgumentException e) {
- // Method.invoke exception
- // "This cannot happen"
- throw new InternalError("Unexpected IllegalArgumentException: " +
e.getMessage());
- } catch (IllegalAccessException e) {
- // Method.invoke exception
- // "This cannot happen"
- throw new InternalError("Unexpected IllegalAccessException: " +
e.getMessage());
- } catch (InvocationTargetException e) {
- // Method.invoke exception
- // "This cannot happen"
- throw new InternalError("Unexpected InvocationTargetException: " +
e.getMessage());
+ } finally {
+ unregister(object);
}
}
@@ -485,6 +523,18 @@
return this;
}
+ /**
+ * <p>Appends with the same format as the default <code>Object toString()
+ * </code> method. Appends the class name followed by
+ * [EMAIL PROTECTED] System#identityHashCode(java.lang.Object)}.</p>
+ *
+ * @param object the <code>Object</code> whose class name and id to output
+ */
+ public ToStringBuilder appendAsObjectToString(Object object) {
+ this.getStyle().appendAsObjectToString(this.getStringBuffer(), object);
+ return this;
+ }
+
//----------------------------------------------------------------------------
/**
@@ -754,6 +804,18 @@
*/
public ToStringBuilder append(Object[] array) {
style.append(buffer, null, array, null);
+ return this;
+ }
+
+ /**
+ * <p>Append to the <code>toString</code> an <code>Object</code>
+ * array.</p>
+ *
+ * @param array the array to add to the <code>toString</code>
+ * @return this
+ */
+ public ToStringBuilder reflectionAppendArray(Object array) {
+ style.reflectionAppendArrayDetail(buffer, null, array);
return this;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]