Applying a modified version of Maarten Coene's patch for #LANG-69. All unit tests pass; opinions would be very welcome though.

git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@500495 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Henri Yandell 2007-01-27 07:11:08 +00:00
parent 87ee821a5d
commit ec4a0fdfac
3 changed files with 183 additions and 197 deletions

View File

@ -95,57 +95,6 @@
* @version $Id$
*/
public class ReflectionToStringBuilder extends ToStringBuilder {
/**
* <p>
* A registry of objects used by <code>reflectionToString</code> methods to detect cyclical object references and
* avoid infinite loops.
* </p>
*/
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();
}
};
/**
* <p>
* Returns the registry of objects being traversed by the <code>reflectionToString</code> methods in the current
* thread.
* </p>
*
* @return Set the registry of objects being traversed
*/
static Set getRegistry() {
return (Set) registry.get();
}
/**
* <p>
* Returns <code>true</code> if the registry contains the given object. Used by the reflection methods to avoid
* infinite loops.
* </p>
*
* @param value
* The object to lookup in the registry.
* @return boolean <code>true</code> if the registry contains the given object.
*/
static boolean isRegistered(Object value) {
return getRegistry().contains(value);
}
/**
* <p>
* Registers the given object. Used by the reflection methods to avoid infinite loops.
* </p>
*
* @param value
* The object to register.
*/
static void register(Object value) {
getRegistry().add(value);
}
/**
* <p>
@ -462,22 +411,6 @@ public static String toStringExclude(Object object, String[] excludeFieldNames)
return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
}
/**
* <p>
* Unregisters the given object.
* </p>
*
* <p>
* Used by the reflection methods to avoid infinite loops.
* </p>
*
* @param value
* The object to unregister.
*/
static void unregister(Object value) {
getRegistry().remove(value);
}
/**
* Whether or not to append static fields.
*/
@ -657,16 +590,6 @@ protected boolean accept(Field field) {
* 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;
@ -681,37 +604,16 @@ protected void appendFieldsIn(Class clazz) {
// 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);
this.getStyle().appendFieldEnd(this.getStringBuffer(), fieldName);
// 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
//this can't happen. Would get a Security exception
// instead
// throw a runtime exception in case the impossible
//throw a runtime exception in case the impossible
// happens.
throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
}
}
}
} finally {
this.unregisterObject();
}
}
/**
@ -789,15 +691,6 @@ public ToStringBuilder reflectionAppendArray(Object array) {
return this;
}
/**
* <p>
* Registers this builder's source object to avoid infinite loops when processing circular object references.
* </p>
*/
void registerObject() {
register(this.getObject());
}
/**
* <p>
* Sets whether or not to append static fields.
@ -872,12 +765,4 @@ public String toString() {
return super.toString();
}
/**
* <p>
* Unregisters this builder's source object to avoid infinite loops when processing circular object references.
* </p>
*/
void unregisterObject() {
unregister(this.getObject());
}
}

View File

@ -19,7 +19,9 @@
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.ObjectUtils;
@ -94,6 +96,78 @@ public abstract class ToStringStyle implements Serializable {
*/
public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle();
/**
* <p>
* A registry of objects used by <code>reflectionToString</code> methods
* to detect cyclical object references and avoid infinite loops.
* </p>
*/
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();
}
};
/**
* <p>
* Returns the registry of objects being traversed by the <code>reflectionToString</code>
* methods in the current thread.
* </p>
*
* @return Set the registry of objects being traversed
*/
static Set getRegistry() {
return (Set) registry.get();
}
/**
* <p>
* Returns <code>true</code> if the registry contains the given object.
* Used by the reflection methods to avoid infinite loops.
* </p>
*
* @param value
* The object to lookup in the registry.
* @return boolean <code>true</code> if the registry contains the given
* object.
*/
static boolean isRegistered(Object value) {
return getRegistry().contains(value);
}
/**
* <p>
* Registers the given object. Used by the reflection methods to avoid
* infinite loops.
* </p>
*
* @param value
* The object to register.
*/
static void register(Object value) {
if (value != null) {
getRegistry().add(value);
}
}
/**
* <p>
* Unregisters the given object.
* </p>
*
* <p>
* Used by the reflection methods to avoid infinite loops.
* </p>
*
* @param value
* The object to unregister.
*/
static void unregister(Object value) {
getRegistry().remove(value);
}
/**
* Whether to use the field names, the default is <code>true</code>.
*/
@ -272,6 +346,7 @@ public void appendEnd(StringBuffer buffer, Object object) {
removeLastFieldSeparator(buffer);
}
appendContentEnd(buffer);
unregister(object);
}
/**
@ -343,11 +418,14 @@ public void append(StringBuffer buffer, String fieldName, Object value, Boolean
* @param detail output detail or not
*/
protected void appendInternal(StringBuffer buffer, String fieldName, Object value, boolean detail) {
if (ReflectionToStringBuilder.isRegistered(value)
if (isRegistered(value)
&& !(value instanceof Number || value instanceof Boolean || value instanceof Character)) {
ObjectUtils.appendIdentityToString(buffer, value);
} else if (value instanceof Collection) {
appendCyclicObject(buffer, fieldName, value);
return;
}
register(value);
try {
if (value instanceof Collection) {
if (detail) {
appendDetail(buffer, fieldName, (Collection) value);
} else {
@ -431,6 +509,25 @@ protected void appendInternal(StringBuffer buffer, String fieldName, Object valu
appendSummary(buffer, fieldName, value);
}
}
} finally {
unregister(value);
}
}
/**
* <p>Append to the <code>toString</code> an <code>Object</code>
* value that has been detected to participate in a cycle. This
* implementation will print the standard string value of the value.</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>,
* not <code>null</code>
*
* @since 2.2
*/
protected void appendCyclicObject(StringBuffer buffer, String fieldName, Object value) {
ObjectUtils.appendIdentityToString(buffer, value);
}
/**
@ -1301,6 +1398,7 @@ protected void appendSummary(StringBuffer buffer, String fieldName, boolean[] ar
*/
protected void appendClassName(StringBuffer buffer, Object object) {
if (useClassName && object != null) {
register(object);
if (useShortClassName) {
buffer.append(getShortClassName(object.getClass()));
} else {
@ -1317,6 +1415,7 @@ protected void appendClassName(StringBuffer buffer, Object object) {
*/
protected void appendIdentityHashCode(StringBuffer buffer, Object object) {
if (this.isUseIdentityHashCode() && object!=null) {
register(object);
buffer.append('@');
buffer.append(Integer.toHexString(System.identityHashCode(object)));
}

View File

@ -168,7 +168,7 @@ public void testReflectionObjectArray() {
assertEquals(baseStr + "[{<null>,5,{3,6}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionLongArray() {
@ -177,7 +177,7 @@ public void testReflectionLongArray() {
assertEquals(baseStr + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionIntArray() {
@ -186,7 +186,7 @@ public void testReflectionIntArray() {
assertEquals(baseStr + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionShortArray() {
@ -195,7 +195,7 @@ public void testReflectionShortArray() {
assertEquals(baseStr + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionyteArray() {
@ -204,7 +204,7 @@ public void testReflectionyteArray() {
assertEquals(baseStr + "[{1,2,-3,4}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionCharArray() {
@ -213,7 +213,7 @@ public void testReflectionCharArray() {
assertEquals(baseStr + "[{A,2,_,D}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionDoubleArray() {
@ -222,7 +222,7 @@ public void testReflectionDoubleArray() {
assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionFloatArray() {
@ -231,7 +231,7 @@ public void testReflectionFloatArray() {
assertEquals(baseStr + "[{1.0,2.9876,-3.00001,4.3}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionBooleanArray() {
@ -240,7 +240,7 @@ public void testReflectionBooleanArray() {
assertEquals(baseStr + "[{true,false,false}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
// Reflection Array Array tests
@ -251,7 +251,7 @@ public void testReflectionFloatArrayArray() {
assertEquals(baseStr + "[{{1.0,2.29686},<null>,{NaN}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
@ -261,7 +261,7 @@ public void testReflectionLongArrayArray() {
assertEquals(baseStr + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionIntArrayArray() {
@ -270,7 +270,7 @@ public void testReflectionIntArrayArray() {
assertEquals(baseStr + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionhortArrayArray() {
@ -279,7 +279,7 @@ public void testReflectionhortArrayArray() {
assertEquals(baseStr + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionByteArrayArray() {
@ -288,7 +288,7 @@ public void testReflectionByteArrayArray() {
assertEquals(baseStr + "[{{1,2},<null>,{5}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionCharArrayArray() {
@ -297,7 +297,7 @@ public void testReflectionCharArrayArray() {
assertEquals(baseStr + "[{{A,B},<null>,{p}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionDoubleArrayArray() {
@ -306,7 +306,7 @@ public void testReflectionDoubleArrayArray() {
assertEquals(baseStr + "[{{1.0,2.29686},<null>,{NaN}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionBooleanArrayArray() {
@ -316,7 +316,7 @@ public void testReflectionBooleanArrayArray() {
assertEquals(baseStr + "[{{true,false},<null>,{false}}]", ToStringBuilder.reflectionToString(array));
array = null;
assertReflectionArray("<null>", array);
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
// Reflection hierarchy tests
@ -326,7 +326,7 @@ public void testReflectionHierarchyArrayList() {
String baseStr = this.toBaseString(base);
assertEquals(baseStr + "[elementData={<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>,<null>},size=0,modCount=0]", ToStringBuilder.reflectionToString(base, null, true));
assertEquals(baseStr + "[size=0]", ToStringBuilder.reflectionToString(base, null, false));
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionHierarchy() {
@ -353,7 +353,7 @@ public void testReflectionHierarchy() {
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));
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
static class ReflectionTestFixtureA {
@ -394,7 +394,7 @@ public void testReflectionArrayCycle() throws Exception {
assertEquals(
this.toBaseString(objects) + "[{" + this.toBaseString(objects) + "}]",
ToStringBuilder.reflectionToString(objects));
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
/**
@ -411,7 +411,7 @@ public void testReflectionArrayCycleLevel2() throws Exception {
assertEquals(
this.toBaseString(objectsLevel2) + "[{{" + this.toBaseString(objectsLevel2) + "}}]",
ToStringBuilder.reflectionToString(objectsLevel2));
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
public void testReflectionArrayArrayCycle() throws Exception {
@ -433,7 +433,7 @@ public void testReflectionArrayArrayCycle() throws Exception {
+ basicToString
+ "}}]",
ToStringBuilder.reflectionToString(objects));
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
/**
@ -514,9 +514,9 @@ public String toString() {
public void testSimpleReflectionObjectCycle() throws Exception {
SimpleReflectionTestFixture simple = new SimpleReflectionTestFixture();
simple.o = simple;
assertTrue(ReflectionToStringBuilder.getRegistry().isEmpty());
assertTrue(ToStringStyle.getRegistry().isEmpty());
assertEquals(this.toBaseString(simple) + "[o=" + this.toBaseString(simple) + "]", simple.toString());
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
/**
@ -526,9 +526,9 @@ public void testSimpleReflectionObjectCycle() throws Exception {
*/
public void testSelfInstanceVarReflectionObjectCycle() throws Exception {
SelfInstanceVarReflectionTestFixture test = new SelfInstanceVarReflectionTestFixture();
assertTrue(ReflectionToStringBuilder.getRegistry().isEmpty());
assertTrue(ToStringStyle.getRegistry().isEmpty());
assertEquals(this.toBaseString(test) + "[typeIsSelf=" + this.toBaseString(test) + "]", test.toString());
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
/**
@ -539,9 +539,9 @@ public void testSelfInstanceVarReflectionObjectCycle() throws Exception {
*/
public void testSelfInstanceTwoVarsReflectionObjectCycle() throws Exception {
SelfInstanceTwoVarsReflectionTestFixture test = new SelfInstanceTwoVarsReflectionTestFixture();
assertTrue(ReflectionToStringBuilder.getRegistry().isEmpty());
assertTrue(ToStringStyle.getRegistry().isEmpty());
assertEquals(this.toBaseString(test) + "[typeIsSelf=" + this.toBaseString(test) + ",otherType=" + test.getOtherType().toString() + "]", test.toString());
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
@ -558,7 +558,7 @@ public void testReflectionObjectCycle() throws Exception {
assertEquals(
this.toBaseString(a) + "[b=" + this.toBaseString(b) + "[a=" + this.toBaseString(a) + "]]",
a.toString());
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
/**
@ -586,11 +586,15 @@ public void testReflectionArrayAndObjectCycle() throws Exception {
+ this.toBaseString(simple)
+ "}]",
ToStringBuilder.reflectionToString(simple));
this.validateEmptyReflectionRegistry();
this.validateEmptyToStringStyleRegistry();
}
void validateEmptyReflectionRegistry() {
assertTrue(ReflectionToStringBuilder.getRegistry().isEmpty());
void validateEmptyToStringStyleRegistry() {
if (!ToStringStyle.getRegistry().isEmpty()) {
System.out.println(ToStringStyle.getRegistry());
}
assertTrue(ToStringStyle.getRegistry().isEmpty());
}
// End: Reflection cycle tests
@ -831,6 +835,25 @@ public void testBooleanArrayArray() {
assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString());
}
public void testObjectCycle() {
ObjectCycle a = new ObjectCycle();
ObjectCycle b = new ObjectCycle();
a.obj = b;
b.obj = a;
String expected = toBaseString(a) + "[" + toBaseString(b) + "[" + toBaseString(a) + "]]";
assertEquals(expected, a.toString());
validateEmptyToStringStyleRegistry();
}
static class ObjectCycle {
Object obj;
public String toString() {
return new ToStringBuilder(this).append(obj).toString();
}
}
public void testSimpleReflectionStatics() {
SimpleReflectionStaticFieldsFixture instance1 = new SimpleReflectionStaticFieldsFixture();
assertEquals(
@ -948,25 +971,4 @@ public void testReflectionNull() {
assertEquals("<null>", ReflectionToStringBuilder.toString(null));
}
/* Unit test for #36061
public void testObjectCycle() {
ObjectCycle a = new ObjectCycle();
ObjectCycle b = new ObjectCycle();
a.obj = b;
b.obj = a;
String expected = toBaseString(a) + "[" + toBaseString(b) + "[" + toBaseString(a) + "]]";
assertEquals(expected, a.toString());
validateEmptyReflectionRegistry();
}
static class ObjectCycle {
Object obj;
public String toString() {
return new ToStringBuilder(this).append(obj).toString();
}
}
*/
}