From 661d16d190708a1a396d8b75ba10738e4574c11d Mon Sep 17 00:00:00 2001 From: MarkDacek Date: Sat, 18 Mar 2017 15:47:09 -0400 Subject: [PATCH] LANG-1167: Added isExcludeNullValues to ReflectionToStringBuilder and test --- .../builder/ReflectionToStringBuilder.java | 122 +++++++++++++++++- ...nToStringBuilderExcludeNullValuesTest.java | 99 ++++++++++++++ 2 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java 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 d40a838a3..0b4d7e597 100644 --- a/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java +++ b/src/main/java/org/apache/commons/lang3/builder/ReflectionToStringBuilder.java @@ -300,6 +300,64 @@ public static String toString( return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics) .toString(); } + + /** + *

+ * Builds a toString value through reflection. + *

+ * + *

+ * It uses AccessibleObject.setAccessible to gain access to private fields. This means that it will + * throw a security exception if run under a security manager, if the permissions are not set up correctly. It is + * also not as efficient as testing explicitly. + *

+ * + *

+ * If the outputTransients is true, transient fields will be output, otherwise they + * are ignored, as they are likely derived fields, and not part of the value of the Object. + *

+ * + *

+ * If the outputStatics is true, static fields will be output, otherwise they are + * ignored. + *

+ * + *

+ * 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 + * the type of the object + * @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 outputStatics + * whether to include static fields + * @param excludeNulls + * whether to exclude fields whose values are null + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @return the String result + * @throws IllegalArgumentException + * if the Object is null + * + * @see ToStringExclude + * @since 2.1 + */ + public static String toString( + final T object, final ToStringStyle style, final boolean outputTransients, + final boolean outputStatics, boolean excludeNulls, final Class reflectUpToClass) { + return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics, excludeNulls) + .toString(); + } /** * Builds a String for a toString method excluding the given field names. @@ -379,6 +437,11 @@ private static Object checkNotNull(final Object obj) { * Whether or not to append transient fields. */ private boolean appendTransients = false; + + /** + * Whether or not to append fields that are null. + */ + private boolean excludeNullValues; /** * Which field names to exclude from output. Intended for fields like "password". @@ -483,6 +546,38 @@ public ReflectionToStringBuilder( this.setAppendTransients(outputTransients); this.setAppendStatics(outputStatics); } + + /** + * Constructor. + * + * @param + * the type of the object + * @param object + * the Object to build a toString for + * @param style + * the style of the toString to create, may be null + * @param buffer + * the StringBuffer to populate, may be null + * @param reflectUpToClass + * the superclass to reflect up to (inclusive), may be null + * @param outputTransients + * whether to include transient fields + * @param outputStatics + * whether to include static fields + * @param excludeNullValues + * whether to exclude fields who value is null + * @since 2.1 + */ + public ReflectionToStringBuilder( + final T object, final ToStringStyle style, final StringBuffer buffer, + final Class reflectUpToClass, final boolean outputTransients, final boolean outputStatics, + final boolean excludeNullValues) { + super(checkNotNull(object), style, buffer); + this.setUpToClass(reflectUpToClass); + this.setAppendTransients(outputTransients); + this.setAppendStatics(outputStatics); + this.setExcludeNullValues(excludeNullValues); + } /** * Returns whether or not to append the given Field. @@ -547,7 +642,9 @@ protected void appendFieldsIn(final Class clazz) { // Warning: Field.get(Object) creates wrappers objects // for primitive types. final Object fieldValue = this.getValue(field); - this.append(fieldName, fieldValue); + if(!excludeNullValues || fieldValue != null){ + this.append(fieldName, fieldValue); + } } catch (final IllegalAccessException ex) { //this can't happen. Would get a Security exception // instead @@ -619,6 +716,17 @@ public boolean isAppendStatics() { public boolean isAppendTransients() { return this.appendTransients; } + + /** + *

+ * Gets whether or not to append fields whose values are null. + *

+ * + * @return Whether or not to append fields whose values are null. + */ + public boolean isExcludeNullValues() { + return this.excludeNullValues; + } /** *

@@ -658,6 +766,18 @@ public void setAppendStatics(final boolean appendStatics) { public void setAppendTransients(final boolean appendTransients) { this.appendTransients = appendTransients; } + + /** + *

+ * Sets whether or not to append fields whose values are null. + *

+ * + * @param excludeNullValues + * Whether or not to append fields whose values are null. + */ + public void setExcludeNullValues(final boolean excludeNullValues) { + this.excludeNullValues = excludeNullValues; + } /** * Sets the field names to exclude. diff --git a/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java new file mode 100644 index 000000000..be368d664 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/builder/ReflectionToStringBuilderExcludeNullValuesTest.java @@ -0,0 +1,99 @@ +package org.apache.commons.lang3.builder; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ReflectionToStringBuilderExcludeNullValuesTest { + + class TestFixture{ + private Integer testIntegerField; + private String testStringField; + + public TestFixture(Integer a, String b){ + this.testIntegerField = a; + this.testStringField = b; + } + } + + private static final String INTEGER_FIELD_NAME = "testIntegerField"; + private static final String STRING_FIELD_NAME = "testStringField"; + private final TestFixture BOTH_NON_NULL = new TestFixture(0, "str"); + private final TestFixture FIRST_NULL = new TestFixture(null, "str"); + private final TestFixture SECOND_NULL = new TestFixture(0, null); + private final TestFixture BOTH_NULL = new TestFixture(null, null); + + @Test + public void test_NonExclude(){ + //normal case= + String toString = ReflectionToStringBuilder.toString(BOTH_NON_NULL, null, false, false, false, null); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + //make one null + toString = ReflectionToStringBuilder.toString(FIRST_NULL, null, false, false, false, null); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + //other one null + toString = ReflectionToStringBuilder.toString(SECOND_NULL, null, false, false, false, null); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + //make the both null + toString = ReflectionToStringBuilder.toString(BOTH_NULL, null, false, false, false, null); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + } + + @Test + public void test_excludeNull(){ + + //test normal case + String toString = ReflectionToStringBuilder.toString(BOTH_NON_NULL, null, false, false, true, null); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + //make one null + toString = ReflectionToStringBuilder.toString(FIRST_NULL, null, false, false, true, null); + assertFalse(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + //other one null + toString = ReflectionToStringBuilder.toString(SECOND_NULL, null, false, false, true, null); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertFalse(toString.contains(STRING_FIELD_NAME)); + + //both null + toString = ReflectionToStringBuilder.toString(BOTH_NULL, null, false, false, true, null); + assertFalse(toString.contains(INTEGER_FIELD_NAME)); + assertFalse(toString.contains(STRING_FIELD_NAME)); + } + + @Test + public void test_ConstructorOption(){ + ReflectionToStringBuilder builder = new ReflectionToStringBuilder(BOTH_NON_NULL, null, null, null, false, false, false); + builder.setExcludeNullValues(true); + assertTrue(builder.isExcludeNullValues()); + + String toString = builder.toString(); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + builder = new ReflectionToStringBuilder(FIRST_NULL, null, null, null, false, false, true); + toString = builder.toString(); + assertFalse(toString.contains(INTEGER_FIELD_NAME)); + assertTrue(toString.contains(STRING_FIELD_NAME)); + + builder = new ReflectionToStringBuilder(SECOND_NULL, null, null, null, false, false, true); + toString = builder.toString(); + assertTrue(toString.contains(INTEGER_FIELD_NAME)); + assertFalse(toString.contains(STRING_FIELD_NAME)); + + builder = new ReflectionToStringBuilder(BOTH_NULL, null, null, null, false, false, true); + toString = builder.toString(); + assertFalse(toString.contains(INTEGER_FIELD_NAME)); + assertFalse(toString.contains(STRING_FIELD_NAME)); + } + +}