diff options
4 files changed, 254 insertions, 1 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0defeaa68..7d7fb89a5 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,7 @@ <body> <release version="3.3" date="TBA" description="Bugfix and Feature release"> + <action issue="LANG-621" type="fix" dev="kinow" due-to="Philip Hodges, Thomas Neidhart">ReflectionToStringBuilder.toString does not debug 3rd party object fields within 3rd party object</action> <action issue="LANG-955" type="add" dev="britter" due-to="Adam Hooper">Add methods for removing all invalid characters according to XML 1.0 and XML 1.1 in an input string to StringEscapeUtils</action> <action issue="LANG-977" type="fix" dev="britter" due-to="Chris Karcher">NumericEntityEscaper incorrectly encodes supplementary characters</action> <action issue="LANG-973" type="fix" dev="sebb">Make some private fields final</action> diff --git a/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java new file mode 100644 index 000000000..f36b4bfa8 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/RecursiveToStringStyle.java @@ -0,0 +1,84 @@ +package org.apache.commons.lang3.builder; + +import java.util.Collection; + +import org.apache.commons.lang3.ClassUtils; + +/** + * <p>Works with {@link ToStringBuilder} to create a "deep" <code>toString</code>.</p> + * + * <p>To use this class write code as follows:</p> + * + * <pre> + * public class Job { + * String title; + * ... + * } + * + * public class Person { + * String name; + * int age; + * boolean smoker; + * Job job; + * + * ... + * + * public String toString() { + * return new ReflectionToStringBuilder(this, new RecursiveToStringStyle()).toString(); + * } + * } + * </pre> + * + * <p>This will produce a toString of the format: + * <code>Person@7f54[name=Stephen,age=29,smoker=false,job=Job@43cd2[title=Manager]]</code></p> + * + * @since 3.2 + * @version $Id$ + */ +public class RecursiveToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + * <p>Constructor.</p> + */ + public RecursiveToStringStyle() { + super(); + } + + @Override + public void appendDetail(StringBuffer buffer, String fieldName, Object value) { + if (!ClassUtils.isPrimitiveWrapper(value.getClass()) && + !String.class.equals(value.getClass()) && + accept(value.getClass())) { + buffer.append(ReflectionToStringBuilder.toString(value, this)); + } else { + super.appendDetail(buffer, fieldName, value); + } + } + + @Override + protected void appendDetail(StringBuffer buffer, String fieldName, Collection<?> coll) { + appendClassName(buffer, coll); + appendIdentityHashCode(buffer, coll); + appendDetail(buffer, fieldName, coll.toArray()); + } + + /** + * Returns whether or not to recursively format the given <code>Class</code>. + * By default, this method always returns {@code true}, but may be overwritten by + * sub-classes to filter specific classes. + * + * @param clazz + * The class to test. + * @return Whether or not to recursively format the given <code>Class</code>. + */ + protected boolean accept(final Class<?> clazz) { + return true; + } +} diff --git a/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java b/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java index 807fb88cd..5cd59dca0 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java @@ -81,7 +81,12 @@ import org.apache.commons.lang3.ClassUtils; * <p> * The exact format of the <code>toString</code> is determined by the {@link ToStringStyle} passed into the constructor. * </p> - * + * + * <p> + * <b>Note:</b> the default {@link ToStringStyle} will only do a "shallow" formatting, i.e. composed objects are not + * further traversed. To get "deep" formatting, use an instance of {@link RecursiveToStringStyle}. + * </p> + * * @since 2.0 * @version $Id$ */ diff --git a/src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java b/src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java new file mode 100644 index 000000000..ffaa34913 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/RecursiveToStringStyleTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3.builder; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Unit tests {@link org.apache.commons.lang3.builder.RecursiveToStringStyleTest}. + * + * @version $Id$ + */ +public class RecursiveToStringStyleTest { + + private final Integer base = Integer.valueOf(5); + private final String baseStr = base.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(base)); + + @Before + public void setUp() throws Exception { + ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE); + } + + @After + public void tearDown() throws Exception { + ToStringBuilder.setDefaultStyle(ToStringStyle.DEFAULT_STYLE); + } + + //---------------------------------------------------------------- + + @Test + public void testBlank() { + assertEquals(baseStr + "[]", new ToStringBuilder(base).toString()); + } + + @Test + public void testAppendSuper() { + assertEquals(baseStr + "[]", new ToStringBuilder(base).appendSuper("Integer@8888[]").toString()); + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").toString()); + + assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[]").append("a", "hello").toString()); + assertEquals(baseStr + "[<null>,a=hello]", new ToStringBuilder(base).appendSuper("Integer@8888[<null>]").append("a", "hello").toString()); + assertEquals(baseStr + "[a=hello]", new ToStringBuilder(base).appendSuper(null).append("a", "hello").toString()); + } + + @Test + public void testObject() { + final Integer i3 = Integer.valueOf(3); + final Integer i4 = Integer.valueOf(4); + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) null).toString()); + assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(i3).toString()); + assertEquals(baseStr + "[a=<null>]", new ToStringBuilder(base).append("a", (Object) null).toString()); + assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", i3).toString()); + assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", i3).append("b", i4).toString()); + assertEquals(baseStr + "[a=<Integer>]", new ToStringBuilder(base).append("a", i3, false).toString()); + assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new ArrayList<Object>(), false).toString()); + assertEquals(baseStr + "[a=[]]", new ToStringBuilder(base).append("a", new ArrayList<Object>(), true).toString()); + assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", new HashMap<Object, Object>(), false).toString()); + assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", new HashMap<Object, Object>(), true).toString()); + assertEquals(baseStr + "[a=<size=0>]", new ToStringBuilder(base).append("a", (Object) new String[0], false).toString()); + assertEquals(baseStr + "[a={}]", new ToStringBuilder(base).append("a", (Object) new String[0], true).toString()); + } + + @Test + public void testPerson() { + final Person p = new Person(); + p.name = "John Doe"; + p.age = 33; + p.smoker = false; + p.job = new Job(); + p.job.title = "Manager"; + final String pBaseStr = p.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p)); + final String pJobStr = p.job.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(p.job)); + assertEquals(pBaseStr + "[name=John Doe,age=33,smoker=false,job=" + pJobStr + "[title=Manager]]", + new ReflectionToStringBuilder(p, new RecursiveToStringStyle()).toString()); + } + + @Test + public void testLong() { + assertEquals(baseStr + "[3]", new ToStringBuilder(base).append(3L).toString()); + assertEquals(baseStr + "[a=3]", new ToStringBuilder(base).append("a", 3L).toString()); + assertEquals(baseStr + "[a=3,b=4]", new ToStringBuilder(base).append("a", 3L).append("b", 4L).toString()); + } + + @Test + public void testObjectArray() { + Object[] array = new Object[] {null, base, new int[] {3, 6}}; + assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append(array).toString()); + assertEquals(baseStr + "[{<null>,5,{3,6}}]", new ToStringBuilder(base).append((Object) array).toString()); + array = null; + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString()); + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString()); + } + + @Test + public void testLongArray() { + long[] array = new long[] {1, 2, -3, 4}; + assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append(array).toString()); + assertEquals(baseStr + "[{1,2,-3,4}]", new ToStringBuilder(base).append((Object) array).toString()); + array = null; + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString()); + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString()); + } + + @Test + public void testLongArrayArray() { + long[][] array = new long[][] {{1, 2}, null, {5}}; + assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append(array).toString()); + assertEquals(baseStr + "[{{1,2},<null>,{5}}]", new ToStringBuilder(base).append((Object) array).toString()); + array = null; + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append(array).toString()); + assertEquals(baseStr + "[<null>]", new ToStringBuilder(base).append((Object) array).toString()); + } + + static class Person { + /** + * Test String field. + */ + String name; + + /** + * Test integer field. + */ + int age; + + /** + * Test boolean field. + */ + boolean smoker; + + /** + * Test Object field. + */ + Job job; + } + + static class Job { + /** + * Test String field. + */ + String title; + } + +} |