/*
* 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;
/**
* <p>
* Assists in implementing {@link Object#toString()} methods using reflection.
* </p>
*
* <p>
* 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.
* </p>
*
* <p>
* A typical invocation for this method would look like:
* </p>
*
* <pre>
* public String toString() {
* return ReflectionToStringBuilder.toString(this);
* }</pre>
*
*
*
* <p>
* You can also use the builder to debug 3rd party objects:
* </p>
*
* <pre>
* System.out.println("An object: " + ReflectionToStringBuilder.toString(anObject));</pre>
*
*
*
* <p>
* A subclass can control field output by overriding the methods:
* <ul>
* <li>{@link #accept(java.lang.reflect.Field)}</li>
* <li>{@link #getValue(java.lang.reflect.Field)}</li>
* </ul>
* </p>
* <p>
* For example, this method does <i>not</i> include the <code>password</code> field in the returned
* <code>String</code>:
* </p>
*
* <pre>
* public String toString() {
* return (new ReflectionToStringBuilder(this) {
* protected boolean accept(Field f) {
* return super.accept(f) && !f.getName().equals("password");
* }
* }).toString();
* }</pre>
*
*
*
* <p>
* The exact format of the <code>toString</code> is determined by the {@link ToStringStyle} passed into the
* constructor.
* </p>
*
* @since 2.0
* @version $Id: ReflectionToStringBuilder.java 1090821 2011-04-10 15:59:07Z mbenson $
*/
public class ReflectionToStringBuilder extends ToStringBuilder {
/**
* <p>
* Builds a <code>toString</code> value using the default <code>ToStringStyle</code> 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>
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
* Superclass fields will be appended.
* </p>
*
* @param object
* the Object to be output
* @return the String result
* @throws IllegalArgumentException
* if the Object is <code>null</code>
*/
public static String toString(Object object) {
return toString(object, null, false, false, null);
}
/**
* <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>
* Transient members will be not be included, as they are likely derived. Static fields will not be included.
* Superclass fields will be appended.
* </p>
*
* <p>
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
* </p>
*
* @param object
* the Object to be output
* @param style
* the style of the <code>toString</code> to create, may be <code>null</code>
* @return the String result
* @throws IllegalArgumentException
* if the Object or <code>ToStringStyle</code> is <code>null</code>
*/
public static String toString(Object object, ToStringStyle style) {
return toString(object, style, false, false, null);
}
/**
* <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 members will be output, otherwise they
* are ignored, as they are likely derived fields, and not part of the value of the Object.
* </p>
*
* <p>
* Static fields will not be included. Superclass fields will be appended.
* </p>
*
* <p>
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
* </p>
*
* @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
* @return the String result
* @throws IllegalArgumentException
* if the Object is <code>null</code>
*/
public static String toString(Object object, ToStringStyle style, boolean outputTransients) {
return toString(object, style, outputTransients, false, null);
}
/**
* <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>
* Static fields will not be included. Superclass fields will be appended.
* </p>
*
* <p>
* If the style is <code>null</code>, the default <code>ToStringStyle</code> is used.
* </p>
*
* @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 transient fields
* @return the String result
* @throws IllegalArgumentException
* if the Object is <code>null</code>
* @since 2.1
*/
public static String toString(Object object, ToStringStyle style, boolean outputTransients, boolean outputStatics) {
return toString(object, style, outputTransients, outputStatics, null);
}
/**
* <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 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>
* @since 2.1
*/
public static <T> String toString(
T object, ToStringStyle style, boolean outputTransients,
boolean outputStatics, 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(Object object, Collection<String> excludeFieldNames) {
return toStringExclude(object, toNoNullStringArray(excludeFieldNames));
}
/**
* Converts the given Collection into an array of Strings. The returned array does not contain <code>null</code>
* entries. Note that {@link Arrays#sort(Object[])} will throw an {@link NullPointerException} if an array element
* is <code>null</code>.
*
* @param collection
* The collection to convert
* @return A new array of Strings.
*/
static String[] toNoNullStringArray(Collection<String> 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</code>.
*
* @param array
* The array to check
* @return The given array or a new array without null.
*/
static String[] toNoNullStringArray(Object[] array) {
List<String> list = new ArrayList<String>(array.length);
for (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(Object object, 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 <code>"password"</code>.
*
* @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;
/**
* <p>
* Constructor.
* </p>
*
* <p>
* This constructor outputs using the default style set with <code>setDefaultStyle</code>.
* </p>
*
* @param object
* the Object to build a <code>toString</code> for, must not be <code>null</code>
* @throws IllegalArgumentException
* if the Object passed in is <code>null</code>
*/
public ReflectionToStringBuilder(Object object) {
super(object);
}
/**
* <p>
* Constructor.
* </p>
*
* <p>
* If the style is <code>null</code>, the default style is used.
* </p>
*
* @param object
* the Object to build a <code>toString</code> for, must not be <code>null</code>
* @param style
* the style of the <code>toString</code> to create, may be <code>null</code>
* @throws IllegalArgumentException
* if the Object passed in is <code>null</code>
*/
public ReflectionToStringBuilder(Object object, ToStringStyle style) {
super(object, style);
}
/**
* <p>
* Constructor.
* </p>
*
* <p>
* If the style is <code>null</code>, the default style is used.
* </p>
*
* <p>
* If the buffer is <code>null</code>, a new one is created.
* </p>
*
* @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>
* @throws IllegalArgumentException
* if the Object passed in is <code>null</code>
*/
public ReflectionToStringBuilder(Object object, ToStringStyle style, StringBuffer buffer) {
super(object, style, buffer);
}
/**
* 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
* @since 2.1
*/
public <T> ReflectionToStringBuilder(
T object, ToStringStyle style, StringBuffer buffer,
Class<? super T> reflectUpToClass, boolean outputTransients, boolean outputStatics) {
super(object, style, buffer);
this.setUpToClass(reflectUpToClass);
this.setAppendTransients(outputTransients);
this.setAppendStatics(outputStatics);
}
/**
* Returns whether or not to append the given <code>Field</code>.
* <ul>
* <li>Transient fields are appended only if {@link #isAppendTransients()} returns <code>true</code>.
* <li>Static fields are appended only if {@link #isAppendStatics()} returns <code>true</code>.
* <li>Inner class fields are not appened.</li>
* </ul>
*
* @param field
* The Field to test.
* @return Whether or not to append the given <code>Field</code>.
*/
protected boolean accept(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;
}
/**
* <p>
* Appends the fields and values defined by the given object of the given Class.
* </p>
*
* <p>
* If a cycle is detected as an object is "toString()'ed", such an object is rendered as if
* <code>Object.toString()</code> had been called and not implemented by the object.
* </p>
*
* @param clazz
* The class of object parameter
*/
protected void appendFieldsIn(Class<?> clazz) {
if (clazz.isArray()) {
this.reflectionAppendArray(this.getObject());
return;
}
Field[] fields = clazz.getDeclaredFields();
AccessibleObject.setAccessible(fields, true);
for (Field field : fields) {
String fieldName = field.getName();
if (this.accept(field)) {
try {
// Warning: Field.get(Object) creates wrappers objects
// for primitive types.
Object fieldValue = this.getValue(field);
this.append(fieldName, fieldValue);
} catch (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();
}
/**
* <p>
* Gets the last super class to stop appending fields for.
* </p>
*
* @return The last super class to stop appending fields for.
*/
public Class<?> getUpToClass() {
return this.upToClass;
}
/**
* <p>
* Calls <code>java.lang.reflect.Field.get(Object)</code>.
* </p>
*
* @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(Field field) throws IllegalArgumentException, IllegalAccessException {
return field.get(this.getObject());
}
/**
* <p>
* Gets whether or not to append static fields.
* </p>
*
* @return Whether or not to append static fields.
* @since 2.1
*/
public boolean isAppendStatics() {
return this.appendStatics;
}
/**
* <p>
* Gets whether or not to append transient fields.
* </p>
*
* @return Whether or not to append transient fields.
*/
public boolean isAppendTransients() {
return this.appendTransients;
}
/**
* <p>
* Append to the <code>toString</code> an <code>Object</code> array.
* </p>
*
* @param array
* the array to add to the <code>toString</code>
* @return this
*/
public ReflectionToStringBuilder reflectionAppendArray(Object array) {
this.getStyle().reflectionAppendArrayDetail(this.getStringBuffer(), null, array);
return this;
}
/**
* <p>
* Sets whether or not to append static fields.
* </p>
*
* @param appendStatics
* Whether or not to append static fields.
* @since 2.1
*/
public void setAppendStatics(boolean appendStatics) {
this.appendStatics = appendStatics;
}
/**
* <p>
* Sets whether or not to append transient fields.
* </p>
*
* @param appendTransients
* Whether or not to append transient fields.
*/
public void setAppendTransients(boolean appendTransients) {
this.appendTransients = appendTransients;
}
/**
* Sets the field names to exclude.
*
* @param excludeFieldNamesParam
* The excludeFieldNames to excluding from toString or <code>null</code>.
* @return <code>this</code>
*/
public ReflectionToStringBuilder setExcludeFieldNames(String... excludeFieldNamesParam) {
if (excludeFieldNamesParam == null) {
this.excludeFieldNames = null;
} else {
//clone and remove nulls
this.excludeFieldNames = toNoNullStringArray(excludeFieldNamesParam);
Arrays.sort(this.excludeFieldNames);
}
return this;
}
/**
* <p>
* Sets the last super class to stop appending fields for.
* </p>
*
* @param clazz
* The last super class to stop appending fields for.
*/
public void setUpToClass(Class<?> clazz) {
if (clazz != null) {
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;
}
/**
* <p>
* Gets the String built by this builder.
* </p>
*
* @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();
}
}
|