diff options
Diffstat (limited to 'src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java')
-rw-r--r-- | src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java | 2555 |
1 files changed, 2555 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java new file mode 100644 index 000000000..98ecc8fb6 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/builder/ToStringStyle.java @@ -0,0 +1,2555 @@ +/* + * 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.io.Serializable; +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.WeakHashMap; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * Controls {@link String} formatting for {@link ToStringBuilder}. + * The main public interface is always via {@link ToStringBuilder}. + * + * <p>These classes are intended to be used as <em>singletons</em>. + * There is no need to instantiate a new style each time. A program + * will generally use one of the predefined constants on this class. + * Alternatively, the {@link StandardToStringStyle} class can be used + * to set the individual settings. Thus most styles can be achieved + * without subclassing.</p> + * + * <p>If required, a subclass can override as many or as few of the + * methods as it requires. Each object type (from {@code boolean} + * to {@code long} to {@link Object} to {@code int[]}) has + * its own methods to output it. Most have two versions, detail and summary. + * + * <p>For example, the detail version of the array based methods will + * output the whole array, whereas the summary method will just output + * the array length.</p> + * + * <p>If you want to format the output of certain objects, such as dates, you + * must create a subclass and override a method. + * </p> + * <pre> + * public class MyStyle extends ToStringStyle { + * protected void appendDetail(StringBuffer buffer, String fieldName, Object value) { + * if (value instanceof Date) { + * value = new SimpleDateFormat("yyyy-MM-dd").format(value); + * } + * buffer.append(value); + * } + * } + * </pre> + * + * @since 1.0 + */ +@SuppressWarnings("deprecation") // StringEscapeUtils +public abstract class ToStringStyle implements Serializable { + + /** + * Serialization version ID. + */ + private static final long serialVersionUID = -2587890625525655916L; + + /** + * The default toString style. Using the {@code Person} + * example from {@link ToStringBuilder}, the output would look like this: + * + * <pre> + * Person@182f0db[name=John Doe,age=33,smoker=false] + * </pre> + */ + public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle(); + + /** + * The multi line toString style. Using the {@code Person} + * example from {@link ToStringBuilder}, the output would look like this: + * + * <pre> + * Person@182f0db[ + * name=John Doe + * age=33 + * smoker=false + * ] + * </pre> + */ + public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle(); + + /** + * The no field names toString style. Using the + * {@code Person} example from {@link ToStringBuilder}, the output + * would look like this: + * + * <pre> + * Person@182f0db[John Doe,33,false] + * </pre> + */ + public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle(); + + /** + * The short prefix toString style. Using the {@code Person} example + * from {@link ToStringBuilder}, the output would look like this: + * + * <pre> + * Person[name=John Doe,age=33,smoker=false] + * </pre> + * + * @since 2.1 + */ + public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle(); + + /** + * The simple toString style. Using the {@code Person} + * example from {@link ToStringBuilder}, the output would look like this: + * + * <pre> + * John Doe,33,false + * </pre> + */ + public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle(); + + /** + * The no class name toString style. Using the {@code Person} + * example from {@link ToStringBuilder}, the output would look like this: + * + * <pre> + * [name=John Doe,age=33,smoker=false] + * </pre> + * + * @since 3.4 + */ + public static final ToStringStyle NO_CLASS_NAME_STYLE = new NoClassNameToStringStyle(); + + /** + * The JSON toString style. Using the {@code Person} example from + * {@link ToStringBuilder}, the output would look like this: + * + * <pre> + * {"name": "John Doe", "age": 33, "smoker": true} + * </pre> + * + * <strong>Note:</strong> Since field names are mandatory in JSON, this + * ToStringStyle will throw an {@link UnsupportedOperationException} if no + * field name is passed in while appending. Furthermore This ToStringStyle + * will only generate valid JSON if referenced objects also produce JSON + * when calling {@code toString()} on them. + * + * @since 3.4 + * @see <a href="https://www.json.org/">json.org</a> + */ + public static final ToStringStyle JSON_STYLE = new JsonToStringStyle(); + + /** + * A registry of objects used by {@code reflectionToString} methods + * to detect cyclical object references and avoid infinite loops. + * + */ + private static final ThreadLocal<WeakHashMap<Object, Object>> REGISTRY = new ThreadLocal<>(); + /* + * Note that objects of this class are generally shared between threads, so + * an instance variable would not be suitable here. + * + * In normal use the registry should always be left empty, because the caller + * should call toString() which will clean up. + * + * See LANG-792 + */ + + /** + * Returns the registry of objects being traversed by the {@code reflectionToString} + * methods in the current thread. + * + * @return Set the registry of objects being traversed + */ + public static Map<Object, Object> getRegistry() { + return REGISTRY.get(); + } + + /** + * Returns {@code true} if the registry contains the given object. + * Used by the reflection methods to avoid infinite loops. + * + * @param value + * The object to lookup in the registry. + * @return boolean {@code true} if the registry contains the given + * object. + */ + static boolean isRegistered(final Object value) { + final Map<Object, Object> m = getRegistry(); + return m != null && m.containsKey(value); + } + + /** + * Registers the given object. Used by the reflection methods to avoid + * infinite loops. + * + * @param value + * The object to register. + */ + static void register(final Object value) { + if (value != null) { + final Map<Object, Object> m = getRegistry(); + if (m == null) { + REGISTRY.set(new WeakHashMap<>()); + } + getRegistry().put(value, null); + } + } + + /** + * Unregisters the given object. + * + * <p> + * Used by the reflection methods to avoid infinite loops. + * </p> + * + * @param value + * The object to unregister. + */ + static void unregister(final Object value) { + if (value != null) { + final Map<Object, Object> m = getRegistry(); + if (m != null) { + m.remove(value); + if (m.isEmpty()) { + REGISTRY.remove(); + } + } + } + } + + /** + * Whether to use the field names, the default is {@code true}. + */ + private boolean useFieldNames = true; + + /** + * Whether to use the class name, the default is {@code true}. + */ + private boolean useClassName = true; + + /** + * Whether to use short class names, the default is {@code false}. + */ + private boolean useShortClassName; + + /** + * Whether to use the identity hash code, the default is {@code true}. + */ + private boolean useIdentityHashCode = true; + + /** + * The content start {@code '['}. + */ + private String contentStart = "["; + + /** + * The content end {@code ']'}. + */ + private String contentEnd = "]"; + + /** + * The field name value separator {@code '='}. + */ + private String fieldNameValueSeparator = "="; + + /** + * Whether the field separator should be added before any other fields. + */ + private boolean fieldSeparatorAtStart; + + /** + * Whether the field separator should be added after any other fields. + */ + private boolean fieldSeparatorAtEnd; + + /** + * The field separator {@code ','}. + */ + private String fieldSeparator = ","; + + /** + * The array start <code>'{'</code>. + */ + private String arrayStart = "{"; + + /** + * The array separator {@code ','}. + */ + private String arraySeparator = ","; + + /** + * The detail for array content. + */ + private boolean arrayContentDetail = true; + + /** + * The array end {@code '}'}. + */ + private String arrayEnd = "}"; + + /** + * The value to use when fullDetail is {@code null}, + * the default value is {@code true}. + */ + private boolean defaultFullDetail = true; + + /** + * The {@code null} text {@code '<null>'}. + */ + private String nullText = "<null>"; + + /** + * The summary size text start {@code '<size'}. + */ + private String sizeStartText = "<size="; + + /** + * The summary size text start {@code '>'}. + */ + private String sizeEndText = ">"; + + /** + * The summary object text start {@code '<'}. + */ + private String summaryObjectStartText = "<"; + + /** + * The summary object text start {@code '>'}. + */ + private String summaryObjectEndText = ">"; + + /** + * Constructor. + */ + protected ToStringStyle() { + } + + /** + * Appends to the {@code toString} the superclass toString. + * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle.</p> + * + * <p>A {@code null} {@code superToString} is ignored.</p> + * + * @param buffer the {@link StringBuffer} to populate + * @param superToString the {@code super.toString()} + * @since 2.0 + */ + public void appendSuper(final StringBuffer buffer, final String superToString) { + appendToString(buffer, superToString); + } + + /** + * Appends to the {@code toString} another toString. + * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle.</p> + * + * <p>A {@code null} {@code toString} is ignored.</p> + * + * @param buffer the {@link StringBuffer} to populate + * @param toString the additional {@code toString} + * @since 2.0 + */ + public void appendToString(final StringBuffer buffer, final String toString) { + if (toString != null) { + final int pos1 = toString.indexOf(contentStart) + contentStart.length(); + final int pos2 = toString.lastIndexOf(contentEnd); + if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) { + if (fieldSeparatorAtStart) { + removeLastFieldSeparator(buffer); + } + buffer.append(toString, pos1, pos2); + appendFieldSeparator(buffer); + } + } + } + + /** + * Appends to the {@code toString} the start of data indicator. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} to build a {@code toString} for + */ + public void appendStart(final StringBuffer buffer, final Object object) { + if (object != null) { + appendClassName(buffer, object); + appendIdentityHashCode(buffer, object); + appendContentStart(buffer); + if (fieldSeparatorAtStart) { + appendFieldSeparator(buffer); + } + } + } + + /** + * Appends to the {@code toString} the end of data indicator. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} to build a + * {@code toString} for. + */ + public void appendEnd(final StringBuffer buffer, final Object object) { + if (!this.fieldSeparatorAtEnd) { + removeLastFieldSeparator(buffer); + } + appendContentEnd(buffer); + unregister(object); + } + + /** + * Remove the last field separator from the buffer. + * + * @param buffer the {@link StringBuffer} to populate + * @since 2.0 + */ + protected void removeLastFieldSeparator(final StringBuffer buffer) { + if (StringUtils.endsWith(buffer, fieldSeparator)) { + buffer.setLength(buffer.length() - fieldSeparator.length()); + } + } + + /** + * Appends to the {@code toString} an {@link Object} + * value, printing the full {@code toString} of the + * {@link Object} passed in. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (value == null) { + appendNullText(buffer, fieldName); + + } else { + appendInternal(buffer, fieldName, value, isFullDetail(fullDetail)); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} an {@link Object}, + * correctly interpreting its type. + * + * <p>This method performs the main lookup by Class type to correctly + * route arrays, {@link Collection}s, {@link Map}s and + * {@link Objects} to the appropriate method.</p> + * + * <p>Either detail or summary views can be specified.</p> + * + * <p>If a cycle is detected, an object will be appended with the + * {@code Object.toString()} format.</p> + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString}, + * not {@code null} + * @param detail output detail or not + */ + protected void appendInternal(final StringBuffer buffer, final String fieldName, final Object value, final boolean detail) { + if (isRegistered(value) + && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) { + appendCyclicObject(buffer, fieldName, value); + return; + } + + register(value); + + try { + if (value instanceof Collection<?>) { + if (detail) { + appendDetail(buffer, fieldName, (Collection<?>) value); + } else { + appendSummarySize(buffer, fieldName, ((Collection<?>) value).size()); + } + + } else if (value instanceof Map<?, ?>) { + if (detail) { + appendDetail(buffer, fieldName, (Map<?, ?>) value); + } else { + appendSummarySize(buffer, fieldName, ((Map<?, ?>) value).size()); + } + + } else if (value instanceof long[]) { + if (detail) { + appendDetail(buffer, fieldName, (long[]) value); + } else { + appendSummary(buffer, fieldName, (long[]) value); + } + + } else if (value instanceof int[]) { + if (detail) { + appendDetail(buffer, fieldName, (int[]) value); + } else { + appendSummary(buffer, fieldName, (int[]) value); + } + + } else if (value instanceof short[]) { + if (detail) { + appendDetail(buffer, fieldName, (short[]) value); + } else { + appendSummary(buffer, fieldName, (short[]) value); + } + + } else if (value instanceof byte[]) { + if (detail) { + appendDetail(buffer, fieldName, (byte[]) value); + } else { + appendSummary(buffer, fieldName, (byte[]) value); + } + + } else if (value instanceof char[]) { + if (detail) { + appendDetail(buffer, fieldName, (char[]) value); + } else { + appendSummary(buffer, fieldName, (char[]) value); + } + + } else if (value instanceof double[]) { + if (detail) { + appendDetail(buffer, fieldName, (double[]) value); + } else { + appendSummary(buffer, fieldName, (double[]) value); + } + + } else if (value instanceof float[]) { + if (detail) { + appendDetail(buffer, fieldName, (float[]) value); + } else { + appendSummary(buffer, fieldName, (float[]) value); + } + + } else if (value instanceof boolean[]) { + if (detail) { + appendDetail(buffer, fieldName, (boolean[]) value); + } else { + appendSummary(buffer, fieldName, (boolean[]) value); + } + + } else if (ObjectUtils.isArray(value)) { + if (detail) { + appendDetail(buffer, fieldName, (Object[]) value); + } else { + appendSummary(buffer, fieldName, (Object[]) value); + } + + } else if (detail) { + appendDetail(buffer, fieldName, value); + } else { + appendSummary(buffer, fieldName, value); + } + } finally { + unregister(value); + } + } + + /** + * Appends to the {@code toString} an {@link Object} + * value that has been detected to participate in a cycle. This + * implementation will print the standard string value of the value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString}, + * not {@code null} + * + * @since 2.2 + */ + protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) { + ObjectUtils.identityToString(buffer, value); + } + + /** + * Appends to the {@code toString} an {@link Object} + * value, printing the full detail of the {@link Object}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@link Collection}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param coll the {@link Collection} to add to the + * {@code toString}, not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll) { + buffer.append(coll); + } + + /** + * Appends to the {@code toString} a {@link Map}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param map the {@link Map} to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map) { + buffer.append(map); + } + + /** + * Appends to the {@code toString} an {@link Object} + * value, printing a summary of the {@link Object}. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final Object value) { + buffer.append(summaryObjectStartText); + buffer.append(getShortClassName(value.getClass())); + buffer.append(summaryObjectEndText); + } + + /** + * <p>Appends to the {@code toString} a {@code long} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final long value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code long} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} an {@code int} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final int value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} an {@code int} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@code short} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final short value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code short} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@code byte} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final byte value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code byte} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@code char} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final char value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code char} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@code double} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final double value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code double} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@code float} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final float value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code float} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} a {@code boolean} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param value the value to add to the {@code toString} + */ + public void append(final StringBuffer buffer, final String fieldName, final boolean value) { + appendFieldStart(buffer, fieldName); + appendDetail(buffer, fieldName, value); + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} a {@code boolean} + * value. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param value the value to add to the {@code toString} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean value) { + buffer.append(value); + } + + /** + * Appends to the {@code toString} an {@link Object} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final Object[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of an + * {@link Object} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + appendDetail(buffer, fieldName, i, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} the detail of an + * {@link Object} array item. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param i the array item index to add + * @param item the array item to add + * @since 3.11 + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int i, final Object item) { + if (i > 0) { + buffer.append(arraySeparator); + } + if (item == null) { + appendNullText(buffer, fieldName); + } else { + appendInternal(buffer, fieldName, item, arrayContentDetail); + } + } + + /** + * Appends to the {@code toString} the detail of an array type. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + * @since 2.0 + */ + protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) { + buffer.append(arrayStart); + final int length = Array.getLength(array); + for (int i = 0; i < length; i++) { + appendDetail(buffer, fieldName, i, Array.get(array, i)); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of an + * {@link Object} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final Object[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code long} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final long[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code long} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code long} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final long[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} an {@code int} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final int[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of an + * {@code int} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of an + * {@code int} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final int[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code short} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final short[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code short} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code short} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final short[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code byte} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final byte[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code byte} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code byte} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final byte[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code char} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the {@code toString} + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final char[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code char} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code char} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final char[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code double} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final double[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code double} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code double} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final double[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code float} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final float[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code float} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code float} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final float[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} a {@code boolean} + * array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + * @param array the array to add to the toString + * @param fullDetail {@code true} for detail, {@code false} + * for summary info, {@code null} for style decides + */ + public void append(final StringBuffer buffer, final String fieldName, final boolean[] array, final Boolean fullDetail) { + appendFieldStart(buffer, fieldName); + + if (array == null) { + appendNullText(buffer, fieldName); + + } else if (isFullDetail(fullDetail)) { + appendDetail(buffer, fieldName, array); + + } else { + appendSummary(buffer, fieldName, array); + } + + appendFieldEnd(buffer, fieldName); + } + + /** + * Appends to the {@code toString} the detail of a + * {@code boolean} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) { + buffer.append(arrayStart); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buffer.append(arraySeparator); + } + appendDetail(buffer, fieldName, array[i]); + } + buffer.append(arrayEnd); + } + + /** + * Appends to the {@code toString} a summary of a + * {@code boolean} array. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param array the array to add to the {@code toString}, + * not {@code null} + */ + protected void appendSummary(final StringBuffer buffer, final String fieldName, final boolean[] array) { + appendSummarySize(buffer, fieldName, array.length); + } + + /** + * Appends to the {@code toString} the class name. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} whose name to output + */ + protected void appendClassName(final StringBuffer buffer, final Object object) { + if (useClassName && object != null) { + register(object); + if (useShortClassName) { + buffer.append(getShortClassName(object.getClass())); + } else { + buffer.append(object.getClass().getName()); + } + } + } + + /** + * Appends the {@link System#identityHashCode(java.lang.Object)}. + * + * @param buffer the {@link StringBuffer} to populate + * @param object the {@link Object} whose id to output + */ + protected void appendIdentityHashCode(final StringBuffer buffer, final Object object) { + if (this.isUseIdentityHashCode() && object != null) { + register(object); + buffer.append('@'); + buffer.append(ObjectUtils.identityHashCodeHex(object)); + } + } + + /** + * Appends to the {@code toString} the content start. + * + * @param buffer the {@link StringBuffer} to populate + */ + protected void appendContentStart(final StringBuffer buffer) { + buffer.append(contentStart); + } + + /** + * Appends to the {@code toString} the content end. + * + * @param buffer the {@link StringBuffer} to populate + */ + protected void appendContentEnd(final StringBuffer buffer) { + buffer.append(contentEnd); + } + + /** + * Appends to the {@code toString} an indicator for {@code null}. + * + * <p>The default indicator is {@code '<null>'}.</p> + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendNullText(final StringBuffer buffer, final String fieldName) { + buffer.append(nullText); + } + + /** + * Appends to the {@code toString} the field separator. + * + * @param buffer the {@link StringBuffer} to populate + */ + protected void appendFieldSeparator(final StringBuffer buffer) { + buffer.append(fieldSeparator); + } + + /** + * Appends to the {@code toString} the field start. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name + */ + protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { + if (useFieldNames && fieldName != null) { + buffer.append(fieldName); + buffer.append(fieldNameValueSeparator); + } + } + + /** + * Appends to the {@code toString} the field end. + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + */ + protected void appendFieldEnd(final StringBuffer buffer, final String fieldName) { + appendFieldSeparator(buffer); + } + + /** + * Appends to the {@code toString} a size summary. + * + * <p>The size summary is used to summarize the contents of + * {@link Collection}s, {@link Map}s and arrays.</p> + * + * <p>The output consists of a prefix, the passed in size + * and a suffix.</p> + * + * <p>The default format is {@code '<size=n>'}.</p> + * + * @param buffer the {@link StringBuffer} to populate + * @param fieldName the field name, typically not used as already appended + * @param size the size to append + */ + protected void appendSummarySize(final StringBuffer buffer, final String fieldName, final int size) { + buffer.append(sizeStartText); + buffer.append(size); + buffer.append(sizeEndText); + } + + /** + * Is this field to be output in full detail. + * + * <p>This method converts a detail request into a detail level. + * The calling code may request full detail ({@code true}), + * but a subclass might ignore that and always return + * {@code false}. The calling code may pass in + * {@code null} indicating that it doesn't care about + * the detail level. In this case the default detail level is + * used.</p> + * + * @param fullDetailRequest the detail level requested + * @return whether full detail is to be shown + */ + protected boolean isFullDetail(final Boolean fullDetailRequest) { + if (fullDetailRequest == null) { + return defaultFullDetail; + } + return fullDetailRequest.booleanValue(); + } + + /** + * Gets the short class name for a class. + * + * <p>The short class name is the classname excluding + * the package name.</p> + * + * @param cls the {@link Class} to get the short name of + * @return the short name + */ + protected String getShortClassName(final Class<?> cls) { + return ClassUtils.getShortClassName(cls); + } + + // Setters and getters for the customizable parts of the style + // These methods are not expected to be overridden, except to make public + // (They are not public so that immutable subclasses can be written) + /** + * Gets whether to use the class name. + * + * @return the current useClassName flag + */ + protected boolean isUseClassName() { + return useClassName; + } + + /** + * Sets whether to use the class name. + * + * @param useClassName the new useClassName flag + */ + protected void setUseClassName(final boolean useClassName) { + this.useClassName = useClassName; + } + + /** + * Gets whether to output short or long class names. + * + * @return the current useShortClassName flag + * @since 2.0 + */ + protected boolean isUseShortClassName() { + return useShortClassName; + } + + /** + * Sets whether to output short or long class names. + * + * @param useShortClassName the new useShortClassName flag + * @since 2.0 + */ + protected void setUseShortClassName(final boolean useShortClassName) { + this.useShortClassName = useShortClassName; + } + + /** + * Gets whether to use the identity hash code. + * + * @return the current useIdentityHashCode flag + */ + protected boolean isUseIdentityHashCode() { + return useIdentityHashCode; + } + + /** + * Sets whether to use the identity hash code. + * + * @param useIdentityHashCode the new useIdentityHashCode flag + */ + protected void setUseIdentityHashCode(final boolean useIdentityHashCode) { + this.useIdentityHashCode = useIdentityHashCode; + } + + /** + * Gets whether to use the field names passed in. + * + * @return the current useFieldNames flag + */ + protected boolean isUseFieldNames() { + return useFieldNames; + } + + /** + * Sets whether to use the field names passed in. + * + * @param useFieldNames the new useFieldNames flag + */ + protected void setUseFieldNames(final boolean useFieldNames) { + this.useFieldNames = useFieldNames; + } + + /** + * Gets whether to use full detail when the caller doesn't + * specify. + * + * @return the current defaultFullDetail flag + */ + protected boolean isDefaultFullDetail() { + return defaultFullDetail; + } + + /** + * Sets whether to use full detail when the caller doesn't + * specify. + * + * @param defaultFullDetail the new defaultFullDetail flag + */ + protected void setDefaultFullDetail(final boolean defaultFullDetail) { + this.defaultFullDetail = defaultFullDetail; + } + + /** + * Gets whether to output array content detail. + * + * @return the current array content detail setting + */ + protected boolean isArrayContentDetail() { + return arrayContentDetail; + } + + /** + * Sets whether to output array content detail. + * + * @param arrayContentDetail the new arrayContentDetail flag + */ + protected void setArrayContentDetail(final boolean arrayContentDetail) { + this.arrayContentDetail = arrayContentDetail; + } + + /** + * Gets the array start text. + * + * @return the current array start text + */ + protected String getArrayStart() { + return arrayStart; + } + + /** + * Sets the array start text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param arrayStart the new array start text + */ + protected void setArrayStart(String arrayStart) { + if (arrayStart == null) { + arrayStart = StringUtils.EMPTY; + } + this.arrayStart = arrayStart; + } + + /** + * Gets the array end text. + * + * @return the current array end text + */ + protected String getArrayEnd() { + return arrayEnd; + } + + /** + * Sets the array end text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param arrayEnd the new array end text + */ + protected void setArrayEnd(String arrayEnd) { + if (arrayEnd == null) { + arrayEnd = StringUtils.EMPTY; + } + this.arrayEnd = arrayEnd; + } + + /** + * Gets the array separator text. + * + * @return the current array separator text + */ + protected String getArraySeparator() { + return arraySeparator; + } + + /** + * Sets the array separator text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param arraySeparator the new array separator text + */ + protected void setArraySeparator(String arraySeparator) { + if (arraySeparator == null) { + arraySeparator = StringUtils.EMPTY; + } + this.arraySeparator = arraySeparator; + } + + /** + * Gets the content start text. + * + * @return the current content start text + */ + protected String getContentStart() { + return contentStart; + } + + /** + * Sets the content start text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param contentStart the new content start text + */ + protected void setContentStart(String contentStart) { + if (contentStart == null) { + contentStart = StringUtils.EMPTY; + } + this.contentStart = contentStart; + } + + /** + * Gets the content end text. + * + * @return the current content end text + */ + protected String getContentEnd() { + return contentEnd; + } + + /** + * Sets the content end text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param contentEnd the new content end text + */ + protected void setContentEnd(String contentEnd) { + if (contentEnd == null) { + contentEnd = StringUtils.EMPTY; + } + this.contentEnd = contentEnd; + } + + /** + * Gets the field name value separator text. + * + * @return the current field name value separator text + */ + protected String getFieldNameValueSeparator() { + return fieldNameValueSeparator; + } + + /** + * Sets the field name value separator text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param fieldNameValueSeparator the new field name value separator text + */ + protected void setFieldNameValueSeparator(String fieldNameValueSeparator) { + if (fieldNameValueSeparator == null) { + fieldNameValueSeparator = StringUtils.EMPTY; + } + this.fieldNameValueSeparator = fieldNameValueSeparator; + } + + /** + * Gets the field separator text. + * + * @return the current field separator text + */ + protected String getFieldSeparator() { + return fieldSeparator; + } + + /** + * Sets the field separator text. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param fieldSeparator the new field separator text + */ + protected void setFieldSeparator(String fieldSeparator) { + if (fieldSeparator == null) { + fieldSeparator = StringUtils.EMPTY; + } + this.fieldSeparator = fieldSeparator; + } + + /** + * Gets whether the field separator should be added at the start + * of each buffer. + * + * @return the fieldSeparatorAtStart flag + * @since 2.0 + */ + protected boolean isFieldSeparatorAtStart() { + return fieldSeparatorAtStart; + } + + /** + * Sets whether the field separator should be added at the start + * of each buffer. + * + * @param fieldSeparatorAtStart the fieldSeparatorAtStart flag + * @since 2.0 + */ + protected void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) { + this.fieldSeparatorAtStart = fieldSeparatorAtStart; + } + + /** + * Gets whether the field separator should be added at the end + * of each buffer. + * + * @return fieldSeparatorAtEnd flag + * @since 2.0 + */ + protected boolean isFieldSeparatorAtEnd() { + return fieldSeparatorAtEnd; + } + + /** + * Sets whether the field separator should be added at the end + * of each buffer. + * + * @param fieldSeparatorAtEnd the fieldSeparatorAtEnd flag + * @since 2.0 + */ + protected void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) { + this.fieldSeparatorAtEnd = fieldSeparatorAtEnd; + } + + /** + * Gets the text to output when {@code null} found. + * + * @return the current text to output when null found + */ + protected String getNullText() { + return nullText; + } + + /** + * Sets the text to output when {@code null} found. + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param nullText the new text to output when null found + */ + protected void setNullText(String nullText) { + if (nullText == null) { + nullText = StringUtils.EMPTY; + } + this.nullText = nullText; + } + + /** + * Gets the start text to output when a {@link Collection}, + * {@link Map} or array size is output. + * + * <p>This is output before the size value.</p> + * + * @return the current start of size text + */ + protected String getSizeStartText() { + return sizeStartText; + } + + /** + * Sets the start text to output when a {@link Collection}, + * {@link Map} or array size is output. + * + * <p>This is output before the size value.</p> + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param sizeStartText the new start of size text + */ + protected void setSizeStartText(String sizeStartText) { + if (sizeStartText == null) { + sizeStartText = StringUtils.EMPTY; + } + this.sizeStartText = sizeStartText; + } + + /** + * Gets the end text to output when a {@link Collection}, + * {@link Map} or array size is output. + * + * <p>This is output after the size value.</p> + * + * @return the current end of size text + */ + protected String getSizeEndText() { + return sizeEndText; + } + + /** + * Sets the end text to output when a {@link Collection}, + * {@link Map} or array size is output. + * + * <p>This is output after the size value.</p> + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param sizeEndText the new end of size text + */ + protected void setSizeEndText(String sizeEndText) { + if (sizeEndText == null) { + sizeEndText = StringUtils.EMPTY; + } + this.sizeEndText = sizeEndText; + } + + /** + * Gets the start text to output when an {@link Object} is + * output in summary mode. + * + * <p>This is output before the size value.</p> + * + * @return the current start of summary text + */ + protected String getSummaryObjectStartText() { + return summaryObjectStartText; + } + + /** + * Sets the start text to output when an {@link Object} is + * output in summary mode. + * + * <p>This is output before the size value.</p> + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param summaryObjectStartText the new start of summary text + */ + protected void setSummaryObjectStartText(String summaryObjectStartText) { + if (summaryObjectStartText == null) { + summaryObjectStartText = StringUtils.EMPTY; + } + this.summaryObjectStartText = summaryObjectStartText; + } + + /** + * Gets the end text to output when an {@link Object} is + * output in summary mode. + * + * <p>This is output after the size value.</p> + * + * @return the current end of summary text + */ + protected String getSummaryObjectEndText() { + return summaryObjectEndText; + } + + /** + * Sets the end text to output when an {@link Object} is + * output in summary mode. + * + * <p>This is output after the size value.</p> + * + * <p>{@code null} is accepted, but will be converted to + * an empty String.</p> + * + * @param summaryObjectEndText the new end of summary text + */ + protected void setSummaryObjectEndText(String summaryObjectEndText) { + if (summaryObjectEndText == null) { + summaryObjectEndText = StringUtils.EMPTY; + } + this.summaryObjectEndText = summaryObjectEndText; + } + + /** + * Default {@link ToStringStyle}. + * + * <p>This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability.</p> + */ + private static final class DefaultToStringStyle extends ToStringStyle { + + /** + * Required for serialization support. + * + * @see java.io.Serializable + */ + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * <p>Use the static constant rather than instantiating.</p> + */ + DefaultToStringStyle() { + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return DEFAULT_STYLE; + } + + } + + /** + * {@link ToStringStyle} that does not print out + * the field names. + * + * <p>This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability. + */ + private static final class NoFieldNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * <p>Use the static constant rather than instantiating.</p> + */ + NoFieldNameToStringStyle() { + this.setUseFieldNames(false); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return NO_FIELD_NAMES_STYLE; + } + + } + + /** + * {@link ToStringStyle} that prints out the short + * class name and no identity hashcode. + * + * <p>This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability.</p> + */ + private static final class ShortPrefixToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * <p>Use the static constant rather than instantiating.</p> + */ + ShortPrefixToStringStyle() { + this.setUseShortClassName(true); + this.setUseIdentityHashCode(false); + } + + /** + * Ensure <code>Singleton</ode> after serialization. + * @return the singleton + */ + private Object readResolve() { + return SHORT_PREFIX_STYLE; + } + + } + + /** + * {@link ToStringStyle} that does not print out the + * classname, identity hashcode, content start or field name. + * + * <p>This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability.</p> + */ + private static final class SimpleToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * <p>Use the static constant rather than instantiating.</p> + */ + SimpleToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + this.setUseFieldNames(false); + this.setContentStart(StringUtils.EMPTY); + this.setContentEnd(StringUtils.EMPTY); + } + + /** + * Ensure <code>Singleton</ode> after serialization. + * @return the singleton + */ + private Object readResolve() { + return SIMPLE_STYLE; + } + + } + + /** + * {@link ToStringStyle} that outputs on multiple lines. + * + * <p>This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability.</p> + */ + private static final class MultiLineToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * <p>Use the static constant rather than instantiating.</p> + */ + MultiLineToStringStyle() { + this.setContentStart("["); + this.setFieldSeparator(System.lineSeparator() + " "); + this.setFieldSeparatorAtStart(true); + this.setContentEnd(System.lineSeparator() + "]"); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return MULTI_LINE_STYLE; + } + + } + + /** + * {@link ToStringStyle} that does not print out the classname + * and identity hash code but prints content start and field names. + * + * <p>This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability.</p> + */ + private static final class NoClassNameToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + /** + * Constructor. + * + * <p>Use the static constant rather than instantiating.</p> + */ + NoClassNameToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return NO_CLASS_NAME_STYLE; + } + + } + + /** + * {@link ToStringStyle} that outputs with JSON format. + * + * <p> + * This is an inner class rather than using + * {@link StandardToStringStyle} to ensure its immutability. + * </p> + * + * @since 3.4 + * @see <a href="https://www.json.org/">json.org</a> + */ + private static final class JsonToStringStyle extends ToStringStyle { + + private static final long serialVersionUID = 1L; + + private static final String FIELD_NAME_QUOTE = "\""; + + /** + * Constructor. + * + * <p> + * Use the static constant rather than instantiating. + * </p> + */ + JsonToStringStyle() { + this.setUseClassName(false); + this.setUseIdentityHashCode(false); + + this.setContentStart("{"); + this.setContentEnd("}"); + + this.setArrayStart("["); + this.setArrayEnd("]"); + + this.setFieldSeparator(","); + this.setFieldNameValueSeparator(":"); + + this.setNullText("null"); + + this.setSummaryObjectStartText("\"<"); + this.setSummaryObjectEndText(">\""); + + this.setSizeStartText("\"<size="); + this.setSizeEndText(">\""); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final Object[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final long[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final int[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final short[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final byte[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final char[] array, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final double[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final float[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, + final boolean[] array, final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, array, fullDetail); + } + + @Override + public void append(final StringBuffer buffer, final String fieldName, final Object value, + final Boolean fullDetail) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + if (!isFullDetail(fullDetail)) { + throw new UnsupportedOperationException( + "FullDetail must be true when using JsonToStringStyle"); + } + + super.append(buffer, fieldName, value, fullDetail); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) { + appendValueAsString(buffer, String.valueOf(value)); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) { + + if (value == null) { + appendNullText(buffer, fieldName); + return; + } + + if (value instanceof String || value instanceof Character) { + appendValueAsString(buffer, value.toString()); + return; + } + + if (value instanceof Number || value instanceof Boolean) { + buffer.append(value); + return; + } + + final String valueAsString = value.toString(); + if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) { + buffer.append(value); + return; + } + + appendDetail(buffer, fieldName, valueAsString); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll) { + if (coll != null && !coll.isEmpty()) { + buffer.append(getArrayStart()); + int i = 0; + for (final Object item : coll) { + appendDetail(buffer, fieldName, i++, item); + } + buffer.append(getArrayEnd()); + return; + } + + buffer.append(coll); + } + + @Override + protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map) { + if (map != null && !map.isEmpty()) { + buffer.append(getContentStart()); + + boolean firstItem = true; + for (final Entry<?, ?> entry : map.entrySet()) { + final String keyStr = Objects.toString(entry.getKey(), null); + if (keyStr != null) { + if (firstItem) { + firstItem = false; + } else { + appendFieldEnd(buffer, keyStr); + } + appendFieldStart(buffer, keyStr); + final Object value = entry.getValue(); + if (value == null) { + appendNullText(buffer, keyStr); + } else { + appendInternal(buffer, keyStr, value, true); + } + } + } + + buffer.append(getContentEnd()); + return; + } + + buffer.append(map); + } + + private boolean isJsonArray(final String valueAsString) { + return valueAsString.startsWith(getArrayStart()) + && valueAsString.endsWith(getArrayEnd()); + } + + private boolean isJsonObject(final String valueAsString) { + return valueAsString.startsWith(getContentStart()) + && valueAsString.endsWith(getContentEnd()); + } + + /** + * Appends the given String enclosed in double-quotes to the given StringBuffer. + * + * @param buffer the StringBuffer to append the value to. + * @param value the value to append. + */ + private void appendValueAsString(final StringBuffer buffer, final String value) { + buffer.append('"').append(StringEscapeUtils.escapeJson(value)).append('"'); + } + + @Override + protected void appendFieldStart(final StringBuffer buffer, final String fieldName) { + + if (fieldName == null) { + throw new UnsupportedOperationException( + "Field names are mandatory when using JsonToStringStyle"); + } + + super.appendFieldStart(buffer, FIELD_NAME_QUOTE + StringEscapeUtils.escapeJson(fieldName) + + FIELD_NAME_QUOTE); + } + + /** + * Ensure Singleton after serialization. + * + * @return the singleton + */ + private Object readResolve() { + return JSON_STYLE; + } + + } +} |