From 891b71ff2c20e4846eb5569c3b1103f2243c831a Mon Sep 17 00:00:00 2001 From: Stephen Colebourne Date: Tue, 31 Dec 2002 20:17:53 +0000 Subject: [PATCH] Fix to include superclass fields in reflection toString git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137217 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/lang/builder/ToStringBuilder.java | 83 +++++++++++++++---- .../lang/builder/ToStringBuilderTest.java | 56 ++++++++++++- 2 files changed, 118 insertions(+), 21 deletions(-) diff --git a/src/java/org/apache/commons/lang/builder/ToStringBuilder.java b/src/java/org/apache/commons/lang/builder/ToStringBuilder.java index c4751095c..c58162610 100644 --- a/src/java/org/apache/commons/lang/builder/ToStringBuilder.java +++ b/src/java/org/apache/commons/lang/builder/ToStringBuilder.java @@ -98,7 +98,7 @@ * reflectionToString, 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. It is also - * slower than testing explicitly and does not handle superclasses.

+ * slower than testing explicitly.

* *

A typical invocation for this method would look like:

*
@@ -112,7 +112,7 @@
  *
  * @author Stephen Colebourne
  * @since 1.0
- * @version $Id: ToStringBuilder.java,v 1.10 2002/12/23 00:20:31 scolebourne Exp $
+ * @version $Id: ToStringBuilder.java,v 1.11 2002/12/31 20:17:53 scolebourne Exp $
  */
 public class ToStringBuilder {
     
@@ -243,14 +243,14 @@ public static void setDefaultStyle(ToStringStyle style) {
      *
      * 

Transient members will be not be included, as they are likely derived.

* - *

Static fields will be not be included.

+ *

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 reflectionToString(Object object) { - return reflectionToString(object, null, false); + return reflectionToString(object, null, false, null); } /** @@ -265,7 +265,7 @@ public static String reflectionToString(Object object) { *

Transient members will be not be included, as they are likely * derived.

* - *

Static fields will be not be included.

+ *

Static fields will not be included. Superclass fields will be appended.

* *

If the style is null, the default * ToStringStyle is used.

@@ -278,7 +278,7 @@ public static String reflectionToString(Object object) { * ToStringStyle is null */ public static String reflectionToString(Object object, ToStringStyle style) { - return reflectionToString(object, style, false); + return reflectionToString(object, style, false, null); } /** @@ -295,7 +295,7 @@ public static String reflectionToString(Object object, ToStringStyle style) { * as they are likely derived fields, and not part of the value of the * Object.

* - *

Static fields will not be tested.

+ *

Static fields will not be included. Superclass fields will be appended.

* *

* If the style is null, the default @@ -308,36 +308,85 @@ public static String reflectionToString(Object object, ToStringStyle style) { * @return the String result * @throws IllegalArgumentException if the Object is null */ - public static String reflectionToString(Object object, ToStringStyle style, - boolean outputTransients) { + public static String reflectionToString(Object object, ToStringStyle style, boolean outputTransients) { + return reflectionToString(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 reflectionToString(Object object, ToStringStyle style, boolean outputTransients, Class reflectUpToClass) { if (object == null) { throw new IllegalArgumentException("The object must not be null"); } if (style == null) { style = getDefaultStyle(); } - Field[] fields = object.getClass().getDeclaredFields(); - Field.setAccessible(fields, true); 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(); + } + + /** + * Appends the fields and values defined by the given object of the + * given Class. + * + * @param object the object to append details of + * @param clazz the class to append details of + * @param builder the builder to append to + * @param outputTransients whether to output transient fields + */ + private static void reflectionAppend(Object object, Class clazz, ToStringBuilder builder, boolean outputTransients) { + Field[] fields = clazz.getDeclaredFields(); + Field.setAccessible(fields, true); for (int i = 0; i < fields.length; ++i) { Field f = fields[i]; if (outputTransients || !Modifier.isTransient(f.getModifiers())) { if (!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"); } } } } - return builder.toString(); - } - - //---------------------------------------------------------------------------- + } + + //---------------------------------------------------------------------------- /** *

Append the toString from the superclass.

diff --git a/src/test/org/apache/commons/lang/builder/ToStringBuilderTest.java b/src/test/org/apache/commons/lang/builder/ToStringBuilderTest.java index 8cdb245c1..50ffa15ab 100644 --- a/src/test/org/apache/commons/lang/builder/ToStringBuilderTest.java +++ b/src/test/org/apache/commons/lang/builder/ToStringBuilderTest.java @@ -56,6 +56,7 @@ package org.apache.commons.lang.builder; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import junit.framework.Test; import junit.framework.TestCase; @@ -65,7 +66,7 @@ * Unit tests {@link org.apache.commons.lang.ToStringBuilder}. * * @author Stephen Colebourne - * @version $Id: ToStringBuilderTest.java,v 1.2 2002/12/08 20:48:46 scolebourne Exp $ + * @version $Id: ToStringBuilderTest.java,v 1.3 2002/12/31 20:17:53 scolebourne Exp $ */ public class ToStringBuilderTest extends TestCase { @@ -163,10 +164,57 @@ public void testBlank() { assertEquals(baseStr + "[]", new ToStringBuilder(base).toString()); } - public void testReflection() { - assertEquals(baseStr + "[value=5]", ToStringBuilder.reflectionToString(base)); + public void testReflection() { + assertEquals(baseStr + "[value=5]", ToStringBuilder.reflectionToString(base)); + } + + private String toBaseString(Object o) { + return o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o)); } - + + public void testReflectionHierarchyArrayList() { + List base = new ArrayList(); + String baseStr = this.toBaseString(base); + assertEquals(baseStr + "[elementData={,,,,,,,,,},size=0,modCount=0]", ToStringBuilder.reflectionToString(base, null, true)); + assertEquals(baseStr + "[size=0]", ToStringBuilder.reflectionToString(base, null, false)); + } + + public void testReflectionHierarchy() { + ReflectionTestFixtureA baseA = new ReflectionTestFixtureA(); + String baseStr = this.toBaseString(baseA); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA)); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA, null)); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false)); + assertEquals(baseStr + "[a=a,transientA=t]", ToStringBuilder.reflectionToString(baseA, null, true)); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, null)); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, Object.class)); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, List.class)); + assertEquals(baseStr + "[a=a]", ToStringBuilder.reflectionToString(baseA, null, false, ReflectionTestFixtureA.class)); + + ReflectionTestFixtureB baseB = new ReflectionTestFixtureB(); + baseStr = this.toBaseString(baseB); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false)); + assertEquals(baseStr + "[b=b,transientB=t,a=a,transientA=t]", ToStringBuilder.reflectionToString(baseB, null, true)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, null)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, Object.class)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, List.class)); + assertEquals(baseStr + "[b=b,a=a]", ToStringBuilder.reflectionToString(baseB, null, false, ReflectionTestFixtureA.class)); + assertEquals(baseStr + "[b=b]", ToStringBuilder.reflectionToString(baseB, null, false, ReflectionTestFixtureB.class)); + } + + static class ReflectionTestFixtureA { + private char a='a'; + private transient char transientA='t'; + } + + static class ReflectionTestFixtureB extends ReflectionTestFixtureA { + private char b='b'; + private transient char transientB='t'; + } + public void testAppendSuper() { assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString()); assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString());