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]

Reply via email to