LANG-1167: Added isExcludeNullValues to ReflectionToStringBuilder and test

This commit is contained in:
MarkDacek 2017-03-18 15:47:09 -04:00
parent 40b8ecd3fa
commit 661d16d190
2 changed files with 220 additions and 1 deletions

View File

@ -301,6 +301,64 @@ public static <T> String toString(
.toString(); .toString();
} }
/**
* <p>
* Builds a <code>toString</code> value through reflection.
* </p>
*
* <p>
* It uses <code>AccessibleObject.setAccessible</code> 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.
* </p>
*
* <p>
* If the <code>outputTransients</code> is <code>true</code>, transient fields will be output, otherwise they
* are ignored, as they are likely derived fields, and not part of the value of the Object.
* </p>
*
* <p>
* If the <code>outputStatics</code> is <code>true</code>, static fields will be output, otherwise they are
* ignored.
* </p>
*
* <p>
* Superclass fields will be appended up to and including the specified superclass. A null superclass is treated as
* <code>java.lang.Object</code>.
* </p>
*
* <p>
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
* </p>
*
* @param <T>
* the type of the object
* @param object
* the Object to be output
* @param style
* the style of the <code>toString</code> to create, may be <code>null</code>
* @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 <code>null</code>
* @return the String result
* @throws IllegalArgumentException
* if the Object is <code>null</code>
*
* @see ToStringExclude
* @since 2.1
*/
public static <T> String toString(
final T object, final ToStringStyle style, final boolean outputTransients,
final boolean outputStatics, boolean excludeNulls, final Class<? super T> reflectUpToClass) {
return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics, excludeNulls)
.toString();
}
/** /**
* Builds a String for a toString method excluding the given field names. * Builds a String for a toString method excluding the given field names.
* *
@ -380,6 +438,11 @@ private static Object checkNotNull(final Object obj) {
*/ */
private boolean appendTransients = false; 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 <code>"password"</code>. * Which field names to exclude from output. Intended for fields like <code>"password"</code>.
* *
@ -484,6 +547,38 @@ public <T> ReflectionToStringBuilder(
this.setAppendStatics(outputStatics); this.setAppendStatics(outputStatics);
} }
/**
* Constructor.
*
* @param <T>
* the type of the object
* @param object
* the Object to build a <code>toString</code> for
* @param style
* the style of the <code>toString</code> to create, may be <code>null</code>
* @param buffer
* the <code>StringBuffer</code> to populate, may be <code>null</code>
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be <code>null</code>
* @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 <T> ReflectionToStringBuilder(
final T object, final ToStringStyle style, final StringBuffer buffer,
final Class<? super T> 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 <code>Field</code>. * Returns whether or not to append the given <code>Field</code>.
* <ul> * <ul>
@ -547,7 +642,9 @@ protected void appendFieldsIn(final Class<?> clazz) {
// Warning: Field.get(Object) creates wrappers objects // Warning: Field.get(Object) creates wrappers objects
// for primitive types. // for primitive types.
final Object fieldValue = this.getValue(field); final Object fieldValue = this.getValue(field);
if(!excludeNullValues || fieldValue != null){
this.append(fieldName, fieldValue); this.append(fieldName, fieldValue);
}
} catch (final IllegalAccessException ex) { } catch (final IllegalAccessException ex) {
//this can't happen. Would get a Security exception //this can't happen. Would get a Security exception
// instead // instead
@ -620,6 +717,17 @@ public boolean isAppendTransients() {
return this.appendTransients; return this.appendTransients;
} }
/**
* <p>
* Gets whether or not to append fields whose values are null.
* </p>
*
* @return Whether or not to append fields whose values are null.
*/
public boolean isExcludeNullValues() {
return this.excludeNullValues;
}
/** /**
* <p> * <p>
* Append to the <code>toString</code> an <code>Object</code> array. * Append to the <code>toString</code> an <code>Object</code> array.
@ -659,6 +767,18 @@ public void setAppendTransients(final boolean appendTransients) {
this.appendTransients = appendTransients; this.appendTransients = appendTransients;
} }
/**
* <p>
* Sets whether or not to append fields whose values are null.
* </p>
*
* @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. * Sets the field names to exclude.
* *

View File

@ -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));
}
}