org.apache.commons.lang3.builder.ReflectionToStringBuilder Maven / Gradle / Ivy
Show all versions of commons-lang3 Show documentation
/*
* 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 java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Objects;
import org.apache.commons.lang3.ArraySorter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.stream.Streams;
/**
* Assists in implementing {@link Object#toString()} methods using reflection.
*
*
* This class uses reflection to determine the fields to append. Because these fields are usually private, the class
* uses {@link java.lang.reflect.AccessibleObject#setAccessible(java.lang.reflect.AccessibleObject[], boolean)} to
* change the visibility of the fields. This will fail under a security manager, unless the appropriate permissions are
* set up correctly.
*
*
* Using reflection to access (private) fields circumvents any synchronization protection guarding access to these
* fields. If a toString method cannot safely read a field, you should exclude it from the toString method, or use
* synchronization consistent with the class' lock management around the invocation of the method. Take special care to
* exclude non-thread-safe collection classes, because these classes may throw ConcurrentModificationException if
* modified while the toString method is executing.
*
*
* A typical invocation for this method would look like:
*
*
* public String toString() {
* return ReflectionToStringBuilder.toString(this);
* }
*
*
* You can also use the builder to debug 3rd party objects:
*
*
* System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));
*
*
* A subclass can control field output by overriding the methods:
*
*
* - {@link #accept(java.lang.reflect.Field)}
* - {@link #getValue(java.lang.reflect.Field)}
*
*
* For example, this method does not include the {@code password} field in the returned {@link String}:
*
*
* public String toString() {
* return (new ReflectionToStringBuilder(this) {
* protected boolean accept(Field f) {
* return super.accept(f) && !f.getName().equals("password");
* }
* }).toString();
* }
*
*
* Alternatively the {@link ToStringExclude} annotation can be used to exclude fields from being incorporated in the
* result.
*
*
* It is also possible to use the {@link ToStringSummary} annotation to output the summary information instead of the
* detailed information of a field.
*
*
* The exact format of the {@code toString} is determined by the {@link ToStringStyle} passed into the constructor.
*
*
*
* Note: 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}.
*
*
* @since 2.0
*/
public class ReflectionToStringBuilder extends ToStringBuilder {
/**
* Converts the given Collection into an array of Strings. The returned array does not contain {@code null}
* entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
* is {@code null}.
*
* @param collection
* The collection to convert
* @return A new array of Strings.
*/
static String[] toNoNullStringArray(final Collection collection) {
if (collection == null) {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
return toNoNullStringArray(collection.toArray());
}
/**
* Returns a new array of Strings without null elements. Internal method used to normalize exclude lists
* (arrays and collections). Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException}
* if an array element is {@code null}.
*
* @param array
* The array to check
* @return The given array or a new array without null.
*/
static String[] toNoNullStringArray(final Object[] array) {
return Streams.nonNull(array).map(Objects::toString).toArray(String[]::new);
}
/**
* Builds a {@code toString} value using the default {@link ToStringStyle} through reflection.
*
*
* It uses {@code 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.
*
*
*
* Transient members will be not be included, as they are likely derived. 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 {@code null}
*
* @see ToStringExclude
* @see ToStringSummary
*/
public static String toString(final Object object) {
return toString(object, null, false, false, null);
}
/**
* Builds a {@code toString} value through reflection.
*
*
* It uses {@code 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.
*
*
*
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
* Superclass fields will be appended.
*
*
*
* If the style is {@code null}, the default {@link ToStringStyle} is used.
*
*
* @param object
* the Object to be output
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @return the String result
* @throws IllegalArgumentException
* if the Object or {@link ToStringStyle} is {@code null}
*
* @see ToStringExclude
* @see ToStringSummary
*/
public static String toString(final Object object, final ToStringStyle style) {
return toString(object, style, false, false, null);
}
/**
* Builds a {@code toString} value through reflection.
*
*
* It uses {@code 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 {@code outputTransients} is {@code 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.
*
*
*
* If the style is {@code null}, the default {@link ToStringStyle} is used.
*
*
* @param object
* the Object to be output
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param outputTransients
* whether to include transient fields
* @return the String result
* @throws IllegalArgumentException
* if the Object is {@code null}
*
* @see ToStringExclude
* @see ToStringSummary
*/
public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients) {
return toString(object, style, outputTransients, false, null);
}
/**
* Builds a {@code toString} value through reflection.
*
*
* It uses {@code 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 {@code outputTransients} is {@code 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 {@code outputStatics} is {@code true}, static fields will be output, otherwise they are
* ignored.
*
*
*
* Static fields will not be included. Superclass fields will be appended.
*
*
*
* If the style is {@code null}, the default {@link ToStringStyle} is used.
*
*
* @param object
* the Object to be output
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param outputTransients
* whether to include transient fields
* @param outputStatics
* whether to include static fields
* @return the String result
* @throws IllegalArgumentException
* if the Object is {@code null}
*
* @see ToStringExclude
* @see ToStringSummary
* @since 2.1
*/
public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients, final boolean outputStatics) {
return toString(object, style, outputTransients, outputStatics, null);
}
/**
* Builds a {@code toString} value through reflection.
*
*
* It uses {@code 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 {@code outputTransients} is {@code 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 {@code outputStatics} is {@code 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
* {@link Object}.
*
*
*
* If the style is {@code null}, the default {@link ToStringStyle} is used.
*
*
* @param
* the type of the object
* @param object
* the Object to be output
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param outputTransients
* whether to include transient fields
* @param outputStatics
* whether to include static fields
* @param excludeNullValues
* whether to exclude fields whose values are null
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be {@code null}
* @return the String result
* @throws IllegalArgumentException
* if the Object is {@code null}
*
* @see ToStringExclude
* @see ToStringSummary
* @since 3.6
*/
public static String toString(
final T object, final ToStringStyle style, final boolean outputTransients,
final boolean outputStatics, final boolean excludeNullValues, final Class super T> reflectUpToClass) {
return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics, excludeNullValues)
.toString();
}
/**
* Builds a {@code toString} value through reflection.
*
*
* It uses {@code 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 {@code outputTransients} is {@code 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 {@code outputStatics} is {@code 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
* {@link Object}.
*
*
*
* If the style is {@code null}, the default {@link ToStringStyle} is used.
*
*
* @param
* the type of the object
* @param object
* the Object to be output
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param outputTransients
* whether to include transient fields
* @param outputStatics
* whether to include static fields
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be {@code null}
* @return the String result
* @throws IllegalArgumentException
* if the Object is {@code null}
*
* @see ToStringExclude
* @see ToStringSummary
* @since 2.1
*/
public static String toString(
final T object, final ToStringStyle style, final boolean outputTransients,
final boolean outputStatics, final Class super T> reflectUpToClass) {
return new ReflectionToStringBuilder(object, style, null, reflectUpToClass, outputTransients, outputStatics)
.toString();
}
/**
* Builds a String for a toString method excluding the given field names.
*
* @param object
* The object to "toString".
* @param excludeFieldNames
* The field names to exclude. Null excludes nothing.
* @return The toString value.
*/
public static String toStringExclude(final Object object, final Collection excludeFieldNames) {
return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
}
/**
* Builds a String for a toString method excluding the given field names.
*
* @param object
* The object to "toString".
* @param excludeFieldNames
* The field names to exclude
* @return The toString value.
*/
public static String toStringExclude(final Object object, final String... excludeFieldNames) {
return new ReflectionToStringBuilder(object).setExcludeFieldNames(excludeFieldNames).toString();
}
/**
* Builds a String for a toString method including the given field names.
*
* @param object
* The object to "toString".
* @param includeFieldNames
* {@code null} or empty means all fields are included. All fields are included by default. This method will override the default behavior.
* @return The toString value.
* @since 3.13.0
*/
public static String toStringInclude(final Object object, final Collection includeFieldNames) {
return toStringInclude(object, toNoNullStringArray(includeFieldNames));
}
/**
* Builds a String for a toString method including the given field names.
*
* @param object
* The object to "toString".
* @param includeFieldNames
* The field names to include. {@code null} or empty means all fields are included. All fields are included by default. This method will override the default
* behavior.
* @return The toString value.
* @since 3.13.0
*/
public static String toStringInclude(final Object object, final String... includeFieldNames) {
return new ReflectionToStringBuilder(object).setIncludeFieldNames(includeFieldNames).toString();
}
/**
* Whether or not to append static fields.
*/
private boolean appendStatics;
/**
* Whether or not to append transient fields.
*/
private boolean appendTransients;
/**
* 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"}.
*
* @since 3.0 this is protected instead of private
*/
protected String[] excludeFieldNames;
/**
* Field names that will be included in the output. All fields are included by default.
*
* @since 3.13.0
*/
protected String[] includeFieldNames;
/**
* The last super class to stop appending fields for.
*/
private Class> upToClass;
/**
* Constructs a new instance.
*
*
* This constructor outputs using the default style set with {@code setDefaultStyle}.
*
*
* @param object
* the Object to build a {@code toString} for, must not be {@code null}
*/
public ReflectionToStringBuilder(final Object object) {
super(object);
}
/**
* Constructs a new instance.
*
*
* If the style is {@code null}, the default style is used.
*
*
* @param object
* the Object to build a {@code toString} for, must not be {@code null}
* @param style
* the style of the {@code toString} to create, may be {@code null}
*/
public ReflectionToStringBuilder(final Object object, final ToStringStyle style) {
super(object, style);
}
/**
* Constructs a new instance.
*
*
* If the style is {@code null}, the default style is used.
*
*
*
* If the buffer is {@code null}, a new one is created.
*
*
* @param object
* the Object to build a {@code toString} for
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param buffer
* the {@link StringBuffer} to populate, may be {@code null}
*/
public ReflectionToStringBuilder(final Object object, final ToStringStyle style, final StringBuffer buffer) {
super(object, style, buffer);
}
/**
* Constructs a new instance.
*
* @param
* the type of the object
* @param object
* the Object to build a {@code toString} for
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param buffer
* the {@link StringBuffer} to populate, may be {@code null}
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be {@code null}
* @param outputTransients
* whether to include transient fields
* @param outputStatics
* whether to include static fields
* @since 2.1
*/
public ReflectionToStringBuilder(
final T object, final ToStringStyle style, final StringBuffer buffer,
final Class super T> reflectUpToClass, final boolean outputTransients, final boolean outputStatics) {
super(object, style, buffer);
this.setUpToClass(reflectUpToClass);
this.setAppendTransients(outputTransients);
this.setAppendStatics(outputStatics);
}
/**
* Constructs a new instance.
*
* @param
* the type of the object
* @param object
* the Object to build a {@code toString} for
* @param style
* the style of the {@code toString} to create, may be {@code null}
* @param buffer
* the {@link StringBuffer} to populate, may be {@code null}
* @param reflectUpToClass
* the superclass to reflect up to (inclusive), may be {@code 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 3.6
*/
public 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(object, style, buffer);
this.setUpToClass(reflectUpToClass);
this.setAppendTransients(outputTransients);
this.setAppendStatics(outputStatics);
this.setExcludeNullValues(excludeNullValues);
}
/**
* Returns whether or not to append the given {@link Field}.
*
* - Transient fields are appended only if {@link #isAppendTransients()} returns {@code true}.
*
- Static fields are appended only if {@link #isAppendStatics()} returns {@code true}.
*
- Inner class fields are not appended.
*
*
* @param field
* The Field to test.
* @return Whether or not to append the given {@link Field}.
*/
protected boolean accept(final Field field) {
if (field.getName().indexOf(ClassUtils.INNER_CLASS_SEPARATOR_CHAR) != -1) {
// Reject field from inner class.
return false;
}
if (Modifier.isTransient(field.getModifiers()) && !this.isAppendTransients()) {
// Reject transient fields.
return false;
}
if (Modifier.isStatic(field.getModifiers()) && !this.isAppendStatics()) {
// Reject static fields.
return false;
}
if (this.excludeFieldNames != null
&& Arrays.binarySearch(this.excludeFieldNames, field.getName()) >= 0) {
// Reject fields from the getExcludeFieldNames list.
return false;
}
if (ArrayUtils.isNotEmpty(includeFieldNames)) {
// Accept fields from the getIncludeFieldNames list. {@code null} or empty means all fields are included. All fields are included by default.
return Arrays.binarySearch(this.includeFieldNames, field.getName()) >= 0;
}
return !field.isAnnotationPresent(ToStringExclude.class);
}
/**
* Appends the fields and values defined by the given object of the given Class.
*
*
* If a cycle is detected as an object is "toString()'ed", such an object is rendered as if
* {@code Object.toString()} had been called and not implemented by the object.
*
*
* @param clazz
* The class of object parameter
*/
protected void appendFieldsIn(final Class> clazz) {
if (clazz.isArray()) {
this.reflectionAppendArray(this.getObject());
return;
}
// The elements in the returned array are not sorted and are not in any particular order.
final Field[] fields = ArraySorter.sort(clazz.getDeclaredFields(), Comparator.comparing(Field::getName));
AccessibleObject.setAccessible(fields, true);
for (final Field field : fields) {
final String fieldName = field.getName();
if (this.accept(field)) {
try {
// Warning: Field.get(Object) creates wrappers objects
// for primitive types.
final Object fieldValue = this.getValue(field);
if (!excludeNullValues || fieldValue != null) {
this.append(fieldName, fieldValue, !field.isAnnotationPresent(ToStringSummary.class));
}
} catch (final IllegalAccessException e) {
// this can't happen. Would get a Security exception instead throw a runtime exception in case the
// impossible happens.
throw new IllegalStateException(e);
}
}
}
}
/**
* Gets the excludeFieldNames.
*
* @return the excludeFieldNames.
*/
public String[] getExcludeFieldNames() {
return this.excludeFieldNames.clone();
}
/**
* Gets the includeFieldNames
*
* @return the includeFieldNames.
* @since 3.13.0
*/
public String[] getIncludeFieldNames() {
return this.includeFieldNames.clone();
}
/**
* Gets the last super class to stop appending fields for.
*
* @return The last super class to stop appending fields for.
*/
public Class> getUpToClass() {
return this.upToClass;
}
/**
* Calls {@code java.lang.reflect.Field.get(Object)}.
*
* @param field
* The Field to query.
* @return The Object from the given Field.
*
* @throws IllegalArgumentException
* see {@link java.lang.reflect.Field#get(Object)}
* @throws IllegalAccessException
* see {@link java.lang.reflect.Field#get(Object)}
*
* @see java.lang.reflect.Field#get(Object)
*/
protected Object getValue(final Field field) throws IllegalAccessException {
return field.get(this.getObject());
}
/**
* Gets whether or not to append static fields.
*
* @return Whether or not to append static fields.
* @since 2.1
*/
public boolean isAppendStatics() {
return this.appendStatics;
}
/**
* Gets whether or not to append transient fields.
*
* @return Whether or not to append transient fields.
*/
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.
* @since 3.6
*/
public boolean isExcludeNullValues() {
return this.excludeNullValues;
}
/**
* Appends to the {@code toString} an {@link Object} array.
*
* @param array
* the array to add to the {@code toString}
* @return {@code this} instance.
*/
public ReflectionToStringBuilder reflectionAppendArray(final Object array) {
this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
return this;
}
/**
* Sets whether or not to append static fields.
*
* @param appendStatics
* Whether or not to append static fields.
* @since 2.1
*/
public void setAppendStatics(final boolean appendStatics) {
this.appendStatics = appendStatics;
}
/**
* Sets whether or not to append transient fields.
*
* @param appendTransients
* Whether or not to append transient fields.
*/
public void setAppendTransients(final boolean appendTransients) {
this.appendTransients = appendTransients;
}
/**
* Sets the field names to exclude.
*
* @param excludeFieldNamesParam
* The excludeFieldNames to excluding from toString or {@code null}.
* @return {@code this}
*/
public ReflectionToStringBuilder setExcludeFieldNames(final String... excludeFieldNamesParam) {
if (excludeFieldNamesParam == null) {
this.excludeFieldNames = null;
} else {
// clone and remove nulls
this.excludeFieldNames = ArraySorter.sort(toNoNullStringArray(excludeFieldNamesParam));
}
return this;
}
/**
* Sets whether or not to append fields whose values are null.
*
* @param excludeNullValues
* Whether or not to append fields whose values are null.
* @since 3.6
*/
public void setExcludeNullValues(final boolean excludeNullValues) {
this.excludeNullValues = excludeNullValues;
}
/**
* Sets the field names to include. {@code null} or empty means all fields are included. All fields are included by default. This method will override the default behavior.
*
* @param includeFieldNamesParam
* The includeFieldNames that must be on toString or {@code null}.
* @return {@code this}
* @since 3.13.0
*/
public ReflectionToStringBuilder setIncludeFieldNames(final String... includeFieldNamesParam) {
if (includeFieldNamesParam == null) {
this.includeFieldNames = null;
} else {
// clone and remove nulls
this.includeFieldNames = ArraySorter.sort(toNoNullStringArray(includeFieldNamesParam));
}
return this;
}
/**
* Sets the last super class to stop appending fields for.
*
* @param clazz
* The last super class to stop appending fields for.
*/
public void setUpToClass(final Class> clazz) {
if (clazz != null) {
final Object object = getObject();
if (object != null && !clazz.isInstance(object)) {
throw new IllegalArgumentException("Specified class is not a superclass of the object");
}
}
this.upToClass = clazz;
}
/**
* Gets the String built by this builder.
*
* @return the built string
*/
@Override
public String toString() {
if (this.getObject() == null) {
return this.getStyle().getNullText();
}
validate();
Class> clazz = this.getObject().getClass();
this.appendFieldsIn(clazz);
while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
clazz = clazz.getSuperclass();
this.appendFieldsIn(clazz);
}
return super.toString();
}
/**
* Validates that include and exclude names do not intersect.
*/
private void validate() {
if (ArrayUtils.containsAny(this.excludeFieldNames, (Object[]) this.includeFieldNames)) {
ToStringStyle.unregister(this.getObject());
throw new IllegalStateException("includeFieldNames and excludeFieldNames must not intersect");
}
}
}