All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.lang3.builder.ReflectionToStringBuilder Maven / Gradle / Ivy

There is a newer version: 4.1.4
Show newest version
/*
 * 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 * {@code java.lang.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 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 * {@code java.lang.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 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} * @throws IllegalArgumentException * if the Object passed in is {@code null} */ public ReflectionToStringBuilder(final Object object) { super(Objects.requireNonNull(object, "obj")); } /** * 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} * @throws IllegalArgumentException * if the Object passed in is {@code null} */ public ReflectionToStringBuilder(final Object object, final ToStringStyle style) { super(Objects.requireNonNull(object, "obj"), 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} * @throws IllegalArgumentException * if the Object passed in is {@code null} */ public ReflectionToStringBuilder(final Object object, final ToStringStyle style, final StringBuffer buffer) { super(Objects.requireNonNull(object, "obj"), 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 reflectUpToClass, final boolean outputTransients, final boolean outputStatics) { super(Objects.requireNonNull(object, "obj"), 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 reflectUpToClass, final boolean outputTransients, final boolean outputStatics, final boolean excludeNullValues) { super(Objects.requireNonNull(object, "obj"), 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)) { // Warning: Field.get(Object) creates wrappers objects for primitive types. final Object fieldValue = Reflection.getUnchecked(field, getObject()); if (!excludeNullValues || fieldValue != null) { this.append(fieldName, fieldValue, !field.isAnnotationPresent(ToStringSummary.class)); } } } } /** * 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 this */ 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"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy