org.apache.commons.lang3.builder.ReflectionToStringBuilder Maven / Gradle / Ivy
/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
/**
*
* 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 password
field in the returned String
:
*
*
* public String toString() {
* return (new ReflectionToStringBuilder(this) {
* protected boolean accept(Field f) {
* return super.accept(f) && !f.getName().equals("password");
* }
* }).toString();
* }
*
*
* The exact format of the 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
* @version $Id: ReflectionToStringBuilder.java 1583482 2014-03-31 22:54:57Z niallp $
*/
public class ReflectionToStringBuilder extends ToStringBuilder {
/**
*
* Builds a toString
value using the default ToStringStyle
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.
*
*
*
* 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 null
*/
public static String toString(final Object object) {
return toString(object, null, false, false, null);
}
/**
*
* 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.
*
*
*
* 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 null
, the default ToStringStyle
is used.
*
*
* @param object
* the Object to be output
* @param style
* the style of the toString
to create, may be null
* @return the String result
* @throws IllegalArgumentException
* if the Object or ToStringStyle
is null
*/
public static String toString(final Object object, final ToStringStyle style) {
return toString(object, style, false, false, null);
}
/**
*
* 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 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 null
, the default ToStringStyle
is used.
*
*
* @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
* @return the String result
* @throws IllegalArgumentException
* if the Object is null
*/
public static String toString(final Object object, final ToStringStyle style, final boolean outputTransients) {
return toString(object, style, outputTransients, false, null);
}
/**
*
* 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.
*
*
*
* Static fields will not be included. Superclass fields will be appended.
*
*
*
* If the style is null
, the default ToStringStyle
is used.
*
*
* @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 transient fields
* @return the String result
* @throws IllegalArgumentException
* if the Object is null
* @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 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 reflectUpToClass
* the superclass to reflect up to (inclusive), may be null
* @return the String result
* @throws IllegalArgumentException
* if the Object is null
* @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));
}
/**
* Converts the given Collection into an array of Strings. The returned array does not contain null
* entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
* is 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 null
.
*
* @param array
* The array to check
* @return The given array or a new array without null.
*/
static String[] toNoNullStringArray(final Object[] array) {
final List list = new ArrayList(array.length);
for (final Object e : array) {
if (e != null) {
list.add(e.toString());
}
}
return list.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
}
/**
* 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();
}
/**
* Whether or not to append static fields.
*/
private boolean appendStatics = false;
/**
* Whether or not to append transient fields.
*/
private boolean appendTransients = false;
/**
* Which field names to exclude from output. Intended for fields like "password"
.
*
* @since 3.0 this is protected instead of private
*/
protected String[] excludeFieldNames;
/**
* The last super class to stop appending fields for.
*/
private Class> upToClass = null;
/**
*
* Constructor.
*
*
*
* This constructor outputs using the default style set with setDefaultStyle
.
*
*
* @param object
* the Object to build a toString
for, must not be null
* @throws IllegalArgumentException
* if the Object passed in is null
*/
public ReflectionToStringBuilder(final Object object) {
super(object);
}
/**
*
* Constructor.
*
*
*
* If the style is null
, the default style is used.
*
*
* @param object
* the Object to build a toString
for, must not be null
* @param style
* the style of the toString
to create, may be null
* @throws IllegalArgumentException
* if the Object passed in is null
*/
public ReflectionToStringBuilder(final Object object, final ToStringStyle style) {
super(object, style);
}
/**
*
* Constructor.
*
*
*
* If the style is null
, the default style is used.
*
*
*
* If the buffer is null
, a new one is created.
*
*
* @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
* @throws IllegalArgumentException
* if the Object passed in is null
*/
public ReflectionToStringBuilder(final Object object, final ToStringStyle style, final StringBuffer buffer) {
super(object, style, buffer);
}
/**
* 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
* @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);
}
/**
* Returns whether or not to append the given Field
.
*
* - Transient fields are appended only if {@link #isAppendTransients()} returns
true
.
* - Static fields are appended only if {@link #isAppendStatics()} returns
true
.
* - Inner class fields are not appended.
*
*
* @param field
* The Field to test.
* @return Whether or not to append the given 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;
}
return true;
}
/**
*
* 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
* 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;
}
final Field[] fields = clazz.getDeclaredFields();
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);
this.append(fieldName, fieldValue);
} catch (final IllegalAccessException ex) {
//this can't happen. Would get a Security exception
// instead
//throw a runtime exception in case the impossible
// happens.
throw new InternalError("Unexpected IllegalAccessException: " + ex.getMessage());
}
}
}
}
/**
* @return Returns the excludeFieldNames.
*/
public String[] getExcludeFieldNames() {
return this.excludeFieldNames.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 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 IllegalArgumentException, 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;
}
/**
*
* Append to the toString
an Object
array.
*
*
* @param array
* the array to add to the 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 null
.
* @return this
*/
public ReflectionToStringBuilder setExcludeFieldNames(final String... excludeFieldNamesParam) {
if (excludeFieldNamesParam == null) {
this.excludeFieldNames = null;
} else {
//clone and remove nulls
this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
Arrays.sort(this.excludeFieldNames);
}
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) == false) {
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();
}
Class> clazz = this.getObject().getClass();
this.appendFieldsIn(clazz);
while (clazz.getSuperclass() != null && clazz != this.getUpToClass()) {
clazz = clazz.getSuperclass();
this.appendFieldsIn(clazz);
}
return super.toString();
}
}