    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3.builder;
    018
    019import java.io.Serializable;
    020import java.lang.reflect.Array;
    021import java.util.Collection;
    022import java.util.Map;
    023import java.util.Map.Entry;
    024import java.util.Objects;
    025import java.util.WeakHashMap;
    026
    027import org.apache.commons.lang3.ClassUtils;
    028import org.apache.commons.lang3.ObjectUtils;
    029import org.apache.commons.lang3.StringEscapeUtils;
    030import org.apache.commons.lang3.StringUtils;
    031
    032/**
    033 * Controls {@link String} formatting for {@link ToStringBuilder}.
    034 * The main public interface is always via {@link ToStringBuilder}.
    035 *
    036 * <p>These classes are intended to be used as <em>singletons</em>.
    037 * There is no need to instantiate a new style each time. A program
    038 * will generally use one of the predefined constants on this class.
    039 * Alternatively, the {@link StandardToStringStyle} class can be used
    040 * to set the individual settings. Thus most styles can be achieved
    041 * without subclassing.</p>
    042 *
    043 * <p>If required, a subclass can override as many or as few of the
    044 * methods as it requires. Each object type (from {@code boolean}
    045 * to {@code long} to {@link Object} to {@code int[]}) has
    046 * its own methods to output it. Most have two versions, detail and summary.
    047 *
    048 * <p>For example, the detail version of the array based methods will
    049 * output the whole array, whereas the summary method will just output
    050 * the array length.</p>
    051 *
    052 * <p>If you want to format the output of certain objects, such as dates, you
    053 * must create a subclass and override a method.
    054 * </p>
    055 * <pre>
    056 * public class MyStyle extends ToStringStyle {
    057 *   protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
    058 *     if (value instanceof Date) {
    059 *       value = new SimpleDateFormat("yyyy-MM-dd").format(value);
    060 *     }
    061 *     buffer.append(value);
    062 *   }
    063 * }
    064 * </pre>
    065 *
    066 * @since 1.0
    067 */
    068@SuppressWarnings("deprecation") // StringEscapeUtils
    069public abstract class ToStringStyle implements Serializable {
    070
    071    /**
    072     * Serialization version ID.
    073     */
    074    private static final long serialVersionUID = -2587890625525655916L;
    075
    076    /**
    077     * The default toString style. Using the {@code Person}
    078     * example from {@link ToStringBuilder}, the output would look like this:
    079     *
    080     * <pre>
    081     * Person@182f0db[name=John Doe,age=33,smoker=false]
    082     * </pre>
    083     */
    084    public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle();
    085
    086    /**
    087     * The multi line toString style. Using the {@code Person}
    088     * example from {@link ToStringBuilder}, the output would look like this:
    089     *
    090     * <pre>
    091     * Person@182f0db[
    092     *   name=John Doe
    093     *   age=33
    094     *   smoker=false
    095     * ]
    096     * </pre>
    097     */
    098    public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle();
    099
    100    /**
    101     * The no field names toString style. Using the
    102     * {@code Person} example from {@link ToStringBuilder}, the output
    103     * would look like this:
    104     *
    105     * <pre>
    106     * Person@182f0db[John Doe,33,false]
    107     * </pre>
    108     */
    109    public static final ToStringStyle NO_FIELD_NAMES_STYLE = new NoFieldNameToStringStyle();
    110
    111    /**
    112     * The short prefix toString style. Using the {@code Person} example
    113     * from {@link ToStringBuilder}, the output would look like this:
    114     *
    115     * <pre>
    116     * Person[name=John Doe,age=33,smoker=false]
    117     * </pre>
    118     *
    119     * @since 2.1
    120     */
    121    public static final ToStringStyle SHORT_PREFIX_STYLE = new ShortPrefixToStringStyle();
    122
    123    /**
    124     * The simple toString style. Using the {@code Person}
    125     * example from {@link ToStringBuilder}, the output would look like this:
    126     *
    127     * <pre>
    128     * John Doe,33,false
    129     * </pre>
    130     */
    131    public static final ToStringStyle SIMPLE_STYLE = new SimpleToStringStyle();
    132
    133    /**
    134     * The no class name toString style. Using the {@code Person}
    135     * example from {@link ToStringBuilder}, the output would look like this:
    136     *
    137     * <pre>
    138     * [name=John Doe,age=33,smoker=false]
    139     * </pre>
    140     *
    141     * @since 3.4
    142     */
    143    public static final ToStringStyle NO_CLASS_NAME_STYLE = new NoClassNameToStringStyle();
    144
    145    /**
    146     * The JSON toString style. Using the {@code Person} example from
    147     * {@link ToStringBuilder}, the output would look like this:
    148     *
    149     * <pre>
    150     * {"name": "John Doe", "age": 33, "smoker": true}
    151     * </pre>
    152     *
    153     * <strong>Note:</strong> Since field names are mandatory in JSON, this
    154     * ToStringStyle will throw an {@link UnsupportedOperationException} if no
    155     * field name is passed in while appending. Furthermore This ToStringStyle
    156     * will only generate valid JSON if referenced objects also produce JSON
    157     * when calling {@code toString()} on them.
    158     *
    159     * @since 3.4
    160     * @see <a href="https://www.json.org/">json.org</a>
    161     */
    162    public static final ToStringStyle JSON_STYLE = new JsonToStringStyle();
    163
    164    /**
    165     * A registry of objects used by {@code reflectionToString} methods
    166     * to detect cyclical object references and avoid infinite loops.
    167     *
    168     */
    169    private static final ThreadLocal<WeakHashMap<Object, Object>> REGISTRY = new ThreadLocal<>();
    170    /*
    171     * Note that objects of this class are generally shared between threads, so
    172     * an instance variable would not be suitable here.
    173     *
    174     * In normal use the registry should always be left empty, because the caller
    175     * should call toString() which will clean up.
    176     *
    177     * See LANG-792
    178     */
    179
    180    /**
    181     * Returns the registry of objects being traversed by the {@code reflectionToString}
    182     * methods in the current thread.
    183     *
    184     * @return Set the registry of objects being traversed
    185     */
    186    public static Map<Object, Object> getRegistry() {
    187        return REGISTRY.get();
    188    }
    189
    190    /**
    191     * Returns {@code true} if the registry contains the given object.
    192     * Used by the reflection methods to avoid infinite loops.
    193     *
    194     * @param value
    195     *                  The object to lookup in the registry.
    196     * @return boolean {@code true} if the registry contains the given
    197     *             object.
    198     */
    199    static boolean isRegistered(final Object value) {
    200        final Map<Object, Object> m = getRegistry();
    201        return m != null && m.containsKey(value);
    202    }
    203
    204    /**
    205     * Registers the given object. Used by the reflection methods to avoid
    206     * infinite loops.
    207     *
    208     * @param value
    209     *                  The object to register.
    210     */
    211    static void register(final Object value) {
    212        if (value != null) {
    213            final Map<Object, Object> m = getRegistry();
    214            if (m == null) {
    215                REGISTRY.set(new WeakHashMap<>());
    216            }
    217            getRegistry().put(value, null);
    218        }
    219    }
    220
    221    /**
    222     * Unregisters the given object.
    223     *
    224     * <p>
    225     * Used by the reflection methods to avoid infinite loops.
    226     * </p>
    227     *
    228     * @param value
    229     *                  The object to unregister.
    230     */
    231    static void unregister(final Object value) {
    232        if (value != null) {
    233            final Map<Object, Object> m = getRegistry();
    234            if (m != null) {
    235                m.remove(value);
    236                if (m.isEmpty()) {
    237                    REGISTRY.remove();
    238                }
    239            }
    240        }
    241    }
    242
    243    /**
    244     * Whether to use the field names, the default is {@code true}.
    245     */
    246    private boolean useFieldNames = true;
    247
    248    /**
    249     * Whether to use the class name, the default is {@code true}.
    250     */
    251    private boolean useClassName = true;
    252
    253    /**
    254     * Whether to use short class names, the default is {@code false}.
    255     */
    256    private boolean useShortClassName;
    257
    258    /**
    259     * Whether to use the identity hash code, the default is {@code true}.
    260     */
    261    private boolean useIdentityHashCode = true;
    262
    263    /**
    264     * The content start {@code '['}.
    265     */
    266    private String contentStart = "[";
    267
    268    /**
    269     * The content end {@code ']'}.
    270     */
    271    private String contentEnd = "]";
    272
    273    /**
    274     * The field name value separator {@code '='}.
    275     */
    276    private String fieldNameValueSeparator = "=";
    277
    278    /**
    279     * Whether the field separator should be added before any other fields.
    280     */
    281    private boolean fieldSeparatorAtStart;
    282
    283    /**
    284     * Whether the field separator should be added after any other fields.
    285     */
    286    private boolean fieldSeparatorAtEnd;
    287
    288    /**
    289     * The field separator {@code ','}.
    290     */
    291    private String fieldSeparator = ",";
    292
    293    /**
    294     * The array start <code>'{'</code>.
    295     */
    296    private String arrayStart = "{";
    297
    298    /**
    299     * The array separator {@code ','}.
    300     */
    301    private String arraySeparator = ",";
    302
    303    /**
    304     * The detail for array content.
    305     */
    306    private boolean arrayContentDetail = true;
    307
    308    /**
    309     * The array end {@code '}'}.
    310     */
    311    private String arrayEnd = "}";
    312
    313    /**
    314     * The value to use when fullDetail is {@code null},
    315     * the default value is {@code true}.
    316     */
    317    private boolean defaultFullDetail = true;
    318
    319    /**
    320     * The {@code null} text {@code '&lt;null&gt;'}.
    321     */
    322    private String nullText = "<null>";
    323
    324    /**
    325     * The summary size text start {@code '&lt;size'}.
    326     */
    327    private String sizeStartText = "<size=";
    328
    329    /**
    330     * The summary size text start {@code '&gt;'}.
    331     */
    332    private String sizeEndText = ">";
    333
    334    /**
    335     * The summary object text start {@code '&lt;'}.
    336     */
    337    private String summaryObjectStartText = "<";
    338
    339    /**
    340     * The summary object text start {@code '&gt;'}.
    341     */
    342    private String summaryObjectEndText = ">";
    343
    344    /**
    345     * Constructor.
    346     */
    347    protected ToStringStyle() {
    348    }
    349
    350    /**
    351     * Appends to the {@code toString} the superclass toString.
    352     * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle.</p>
    353     *
    354     * <p>A {@code null} {@code superToString} is ignored.</p>
    355     *
    356     * @param buffer  the {@link StringBuffer} to populate
    357     * @param superToString  the {@code super.toString()}
    358     * @since 2.0
    359     */
    360    public void appendSuper(final StringBuffer buffer, final String superToString) {
    361        appendToString(buffer, superToString);
    362    }
    363
    364    /**
    365     * Appends to the {@code toString} another toString.
    366     * <p>NOTE: It assumes that the toString has been created from the same ToStringStyle.</p>
    367     *
    368     * <p>A {@code null} {@code toString} is ignored.</p>
    369     *
    370     * @param buffer  the {@link StringBuffer} to populate
    371     * @param toString  the additional {@code toString}
    372     * @since 2.0
    373     */
    374    public void appendToString(final StringBuffer buffer, final String toString) {
    375        if (toString != null) {
    376            final int pos1 = toString.indexOf(contentStart) + contentStart.length();
    377            final int pos2 = toString.lastIndexOf(contentEnd);
    378            if (pos1 != pos2 && pos1 >= 0 && pos2 >= 0) {
    379                if (fieldSeparatorAtStart) {
    380                    removeLastFieldSeparator(buffer);
    381                }
    382                buffer.append(toString, pos1, pos2);
    383                appendFieldSeparator(buffer);
    384            }
    385        }
    386    }
    387
    388    /**
    389     * Appends to the {@code toString} the start of data indicator.
    390     *
    391     * @param buffer  the {@link StringBuffer} to populate
    392     * @param object  the {@link Object} to build a {@code toString} for
    393     */
    394    public void appendStart(final StringBuffer buffer, final Object object) {
    395        if (object != null) {
    396            appendClassName(buffer, object);
    397            appendIdentityHashCode(buffer, object);
    398            appendContentStart(buffer);
    399            if (fieldSeparatorAtStart) {
    400                appendFieldSeparator(buffer);
    401            }
    402        }
    403    }
    404
    405    /**
    406     * Appends to the {@code toString} the end of data indicator.
    407     *
    408     * @param buffer  the {@link StringBuffer} to populate
    409     * @param object  the {@link Object} to build a
    410     *  {@code toString} for.
    411     */
    412    public void appendEnd(final StringBuffer buffer, final Object object) {
    413        if (!this.fieldSeparatorAtEnd) {
    414            removeLastFieldSeparator(buffer);
    415        }
    416        appendContentEnd(buffer);
    417        unregister(object);
    418    }
    419
    420    /**
    421     * Remove the last field separator from the buffer.
    422     *
    423     * @param buffer  the {@link StringBuffer} to populate
    424     * @since 2.0
    425     */
    426    protected void removeLastFieldSeparator(final StringBuffer buffer) {
    427        if (StringUtils.endsWith(buffer, fieldSeparator)) {
    428            buffer.setLength(buffer.length() - fieldSeparator.length());
    429        }
    430    }
    431
    432    /**
    433     * Appends to the {@code toString} an {@link Object}
    434     * value, printing the full {@code toString} of the
    435     * {@link Object} passed in.
    436     *
    437     * @param buffer  the {@link StringBuffer} to populate
    438     * @param fieldName  the field name
    439     * @param value  the value to add to the {@code toString}
    440     * @param fullDetail  {@code true} for detail, {@code false}
    441     *  for summary info, {@code null} for style decides
    442     */
    443    public void append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail) {
    444        appendFieldStart(buffer, fieldName);
    445
    446        if (value == null) {
    447            appendNullText(buffer, fieldName);
    448
    449        } else {
    450            appendInternal(buffer, fieldName, value, isFullDetail(fullDetail));
    451        }
    452
    453        appendFieldEnd(buffer, fieldName);
    454    }
    455
    456    /**
    457     * Appends to the {@code toString} an {@link Object},
    458     * correctly interpreting its type.
    459     *
    460     * <p>This method performs the main lookup by Class type to correctly
    461     * route arrays, {@link Collection}s, {@link Map}s and
    462     * {@link Objects} to the appropriate method.</p>
    463     *
    464     * <p>Either detail or summary views can be specified.</p>
    465     *
    466     * <p>If a cycle is detected, an object will be appended with the
    467     * {@code Object.toString()} format.</p>
    468     *
    469     * @param buffer  the {@link StringBuffer} to populate
    470     * @param fieldName  the field name, typically not used as already appended
    471     * @param value  the value to add to the {@code toString},
    472     *  not {@code null}
    473     * @param detail  output detail or not
    474     */
    475    protected void appendInternal(final StringBuffer buffer, final String fieldName, final Object value, final boolean detail) {
    476        if (isRegistered(value)
    477            && !(value instanceof Number || value instanceof Boolean || value instanceof Character)) {
    478           appendCyclicObject(buffer, fieldName, value);
    479           return;
    480        }
    481
    482        register(value);
    483
    484        try {
    485            if (value instanceof Collection<?>) {
    486                if (detail) {
    487                    appendDetail(buffer, fieldName, (Collection<?>) value);
    488                } else {
    489                    appendSummarySize(buffer, fieldName, ((Collection<?>) value).size());
    490                }
    491
    492            } else if (value instanceof Map<?, ?>) {
    493                if (detail) {
    494                    appendDetail(buffer, fieldName, (Map<?, ?>) value);
    495                } else {
    496                    appendSummarySize(buffer, fieldName, ((Map<?, ?>) value).size());
    497                }
    498
    499            } else if (value instanceof long[]) {
    500                if (detail) {
    501                    appendDetail(buffer, fieldName, (long[]) value);
    502                } else {
    503                    appendSummary(buffer, fieldName, (long[]) value);
    504                }
    505
    506            } else if (value instanceof int[]) {
    507                if (detail) {
    508                    appendDetail(buffer, fieldName, (int[]) value);
    509                } else {
    510                    appendSummary(buffer, fieldName, (int[]) value);
    511                }
    512
    513            } else if (value instanceof short[]) {
    514                if (detail) {
    515                    appendDetail(buffer, fieldName, (short[]) value);
    516                } else {
    517                    appendSummary(buffer, fieldName, (short[]) value);
    518                }
    519
    520            } else if (value instanceof byte[]) {
    521                if (detail) {
    522                    appendDetail(buffer, fieldName, (byte[]) value);
    523                } else {
    524                    appendSummary(buffer, fieldName, (byte[]) value);
    525                }
    526
    527            } else if (value instanceof char[]) {
    528                if (detail) {
    529                    appendDetail(buffer, fieldName, (char[]) value);
    530                } else {
    531                    appendSummary(buffer, fieldName, (char[]) value);
    532                }
    533
    534            } else if (value instanceof double[]) {
    535                if (detail) {
    536                    appendDetail(buffer, fieldName, (double[]) value);
    537                } else {
    538                    appendSummary(buffer, fieldName, (double[]) value);
    539                }
    540
    541            } else if (value instanceof float[]) {
    542                if (detail) {
    543                    appendDetail(buffer, fieldName, (float[]) value);
    544                } else {
    545                    appendSummary(buffer, fieldName, (float[]) value);
    546                }
    547
    548            } else if (value instanceof boolean[]) {
    549                if (detail) {
    550                    appendDetail(buffer, fieldName, (boolean[]) value);
    551                } else {
    552                    appendSummary(buffer, fieldName, (boolean[]) value);
    553                }
    554
    555            } else if (ObjectUtils.isArray(value)) {
    556                if (detail) {
    557                    appendDetail(buffer, fieldName, (Object[]) value);
    558                } else {
    559                    appendSummary(buffer, fieldName, (Object[]) value);
    560                }
    561
    562            } else if (detail) {
    563                appendDetail(buffer, fieldName, value);
    564            } else {
    565                appendSummary(buffer, fieldName, value);
    566            }
    567        } finally {
    568            unregister(value);
    569        }
    570    }
    571
    572    /**
    573     * Appends to the {@code toString} an {@link Object}
    574     * value that has been detected to participate in a cycle. This
    575     * implementation will print the standard string value of the value.
    576     *
    577     * @param buffer  the {@link StringBuffer} to populate
    578     * @param fieldName  the field name, typically not used as already appended
    579     * @param value  the value to add to the {@code toString},
    580     *  not {@code null}
    581     *
    582     * @since 2.2
    583     */
    584    protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) {
    585       ObjectUtils.identityToString(buffer, value);
    586    }
    587
    588    /**
    589     * Appends to the {@code toString} an {@link Object}
    590     * value, printing the full detail of the {@link Object}.
    591     *
    592     * @param buffer  the {@link StringBuffer} to populate
    593     * @param fieldName  the field name, typically not used as already appended
    594     * @param value  the value to add to the {@code toString},
    595     *  not {@code null}
    596     */
    597    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
    598        buffer.append(value);
    599    }
    600
    601    /**
    602     * Appends to the {@code toString} a {@link Collection}.
    603     *
    604     * @param buffer  the {@link StringBuffer} to populate
    605     * @param fieldName  the field name, typically not used as already appended
    606     * @param coll  the {@link Collection} to add to the
    607     *  {@code toString}, not {@code null}
    608     */
    609    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll) {
    610        buffer.append(coll);
    611    }
    612
    613    /**
    614     * Appends to the {@code toString} a {@link Map}.
    615     *
    616     * @param buffer  the {@link StringBuffer} to populate
    617     * @param fieldName  the field name, typically not used as already appended
    618     * @param map  the {@link Map} to add to the {@code toString},
    619     *  not {@code null}
    620     */
    621    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map) {
    622        buffer.append(map);
    623    }
    624
    625    /**
    626     * Appends to the {@code toString} an {@link Object}
    627     * value, printing a summary of the {@link Object}.
    628     *
    629     * @param buffer  the {@link StringBuffer} to populate
    630     * @param fieldName  the field name, typically not used as already appended
    631     * @param value  the value to add to the {@code toString},
    632     *  not {@code null}
    633     */
    634    protected void appendSummary(final StringBuffer buffer, final String fieldName, final Object value) {
    635        buffer.append(summaryObjectStartText);
    636        buffer.append(getShortClassName(value.getClass()));
    637        buffer.append(summaryObjectEndText);
    638    }
    639
    640    /**
    641     * <p>Appends to the {@code toString} a {@code long}
    642     * value.
    643     *
    644     * @param buffer  the {@link StringBuffer} to populate
    645     * @param fieldName  the field name
    646     * @param value  the value to add to the {@code toString}
    647     */
    648    public void append(final StringBuffer buffer, final String fieldName, final long value) {
    649        appendFieldStart(buffer, fieldName);
    650        appendDetail(buffer, fieldName, value);
    651        appendFieldEnd(buffer, fieldName);
    652    }
    653
    654    /**
    655     * Appends to the {@code toString} a {@code long}
    656     * value.
    657     *
    658     * @param buffer  the {@link StringBuffer} to populate
    659     * @param fieldName  the field name, typically not used as already appended
    660     * @param value  the value to add to the {@code toString}
    661     */
    662    protected void appendDetail(final StringBuffer buffer, final String fieldName, final long value) {
    663        buffer.append(value);
    664    }
    665
    666    /**
    667     * Appends to the {@code toString} an {@code int}
    668     * value.
    669     *
    670     * @param buffer  the {@link StringBuffer} to populate
    671     * @param fieldName  the field name
    672     * @param value  the value to add to the {@code toString}
    673     */
    674    public void append(final StringBuffer buffer, final String fieldName, final int value) {
    675        appendFieldStart(buffer, fieldName);
    676        appendDetail(buffer, fieldName, value);
    677        appendFieldEnd(buffer, fieldName);
    678    }
    679
    680    /**
    681     * Appends to the {@code toString} an {@code int}
    682     * value.
    683     *
    684     * @param buffer  the {@link StringBuffer} to populate
    685     * @param fieldName  the field name, typically not used as already appended
    686     * @param value  the value to add to the {@code toString}
    687     */
    688    protected void appendDetail(final StringBuffer buffer, final String fieldName, final int value) {
    689        buffer.append(value);
    690    }
    691
    692    /**
    693     * Appends to the {@code toString} a {@code short}
    694     * value.
    695     *
    696     * @param buffer  the {@link StringBuffer} to populate
    697     * @param fieldName  the field name
    698     * @param value  the value to add to the {@code toString}
    699     */
    700    public void append(final StringBuffer buffer, final String fieldName, final short value) {
    701        appendFieldStart(buffer, fieldName);
    702        appendDetail(buffer, fieldName, value);
    703        appendFieldEnd(buffer, fieldName);
    704    }
    705
    706    /**
    707     * Appends to the {@code toString} a {@code short}
    708     * value.
    709     *
    710     * @param buffer  the {@link StringBuffer} to populate
    711     * @param fieldName  the field name, typically not used as already appended
    712     * @param value  the value to add to the {@code toString}
    713     */
    714    protected void appendDetail(final StringBuffer buffer, final String fieldName, final short value) {
    715        buffer.append(value);
    716    }
    717
    718    /**
    719     * Appends to the {@code toString} a {@code byte}
    720     * value.
    721     *
    722     * @param buffer  the {@link StringBuffer} to populate
    723     * @param fieldName  the field name
    724     * @param value  the value to add to the {@code toString}
    725     */
    726    public void append(final StringBuffer buffer, final String fieldName, final byte value) {
    727        appendFieldStart(buffer, fieldName);
    728        appendDetail(buffer, fieldName, value);
    729        appendFieldEnd(buffer, fieldName);
    730    }
    731
    732    /**
    733     * Appends to the {@code toString} a {@code byte}
    734     * value.
    735     *
    736     * @param buffer  the {@link StringBuffer} to populate
    737     * @param fieldName  the field name, typically not used as already appended
    738     * @param value  the value to add to the {@code toString}
    739     */
    740    protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte value) {
    741        buffer.append(value);
    742    }
    743
    744    /**
    745     * Appends to the {@code toString} a {@code char}
    746     * value.
    747     *
    748     * @param buffer  the {@link StringBuffer} to populate
    749     * @param fieldName  the field name
    750     * @param value  the value to add to the {@code toString}
    751     */
    752    public void append(final StringBuffer buffer, final String fieldName, final char value) {
    753        appendFieldStart(buffer, fieldName);
    754        appendDetail(buffer, fieldName, value);
    755        appendFieldEnd(buffer, fieldName);
    756    }
    757
    758    /**
    759     * Appends to the {@code toString} a {@code char}
    760     * value.
    761     *
    762     * @param buffer  the {@link StringBuffer} to populate
    763     * @param fieldName  the field name, typically not used as already appended
    764     * @param value  the value to add to the {@code toString}
    765     */
    766    protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) {
    767        buffer.append(value);
    768    }
    769
    770    /**
    771     * Appends to the {@code toString} a {@code double}
    772     * value.
    773     *
    774     * @param buffer  the {@link StringBuffer} to populate
    775     * @param fieldName  the field name
    776     * @param value  the value to add to the {@code toString}
    777     */
    778    public void append(final StringBuffer buffer, final String fieldName, final double value) {
    779        appendFieldStart(buffer, fieldName);
    780        appendDetail(buffer, fieldName, value);
    781        appendFieldEnd(buffer, fieldName);
    782    }
    783
    784    /**
    785     * Appends to the {@code toString} a {@code double}
    786     * value.
    787     *
    788     * @param buffer  the {@link StringBuffer} to populate
    789     * @param fieldName  the field name, typically not used as already appended
    790     * @param value  the value to add to the {@code toString}
    791     */
    792    protected void appendDetail(final StringBuffer buffer, final String fieldName, final double value) {
    793        buffer.append(value);
    794    }
    795
    796    /**
    797     * Appends to the {@code toString} a {@code float}
    798     * value.
    799     *
    800     * @param buffer  the {@link StringBuffer} to populate
    801     * @param fieldName  the field name
    802     * @param value  the value to add to the {@code toString}
    803     */
    804    public void append(final StringBuffer buffer, final String fieldName, final float value) {
    805        appendFieldStart(buffer, fieldName);
    806        appendDetail(buffer, fieldName, value);
    807        appendFieldEnd(buffer, fieldName);
    808    }
    809
    810    /**
    811     * Appends to the {@code toString} a {@code float}
    812     * value.
    813     *
    814     * @param buffer  the {@link StringBuffer} to populate
    815     * @param fieldName  the field name, typically not used as already appended
    816     * @param value  the value to add to the {@code toString}
    817     */
    818    protected void appendDetail(final StringBuffer buffer, final String fieldName, final float value) {
    819        buffer.append(value);
    820    }
    821
    822    /**
    823     * Appends to the {@code toString} a {@code boolean}
    824     * value.
    825     *
    826     * @param buffer  the {@link StringBuffer} to populate
    827     * @param fieldName  the field name
    828     * @param value  the value to add to the {@code toString}
    829     */
    830    public void append(final StringBuffer buffer, final String fieldName, final boolean value) {
    831        appendFieldStart(buffer, fieldName);
    832        appendDetail(buffer, fieldName, value);
    833        appendFieldEnd(buffer, fieldName);
    834    }
    835
    836    /**
    837     * Appends to the {@code toString} a {@code boolean}
    838     * value.
    839     *
    840     * @param buffer  the {@link StringBuffer} to populate
    841     * @param fieldName  the field name, typically not used as already appended
    842     * @param value  the value to add to the {@code toString}
    843     */
    844    protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean value) {
    845        buffer.append(value);
    846    }
    847
    848    /**
    849     * Appends to the {@code toString} an {@link Object}
    850     * array.
    851     *
    852     * @param buffer  the {@link StringBuffer} to populate
    853     * @param fieldName  the field name
    854     * @param array  the array to add to the toString
    855     * @param fullDetail  {@code true} for detail, {@code false}
    856     *  for summary info, {@code null} for style decides
    857     */
    858    public void append(final StringBuffer buffer, final String fieldName, final Object[] array, final Boolean fullDetail) {
    859        appendFieldStart(buffer, fieldName);
    860
    861        if (array == null) {
    862            appendNullText(buffer, fieldName);
    863
    864        } else if (isFullDetail(fullDetail)) {
    865            appendDetail(buffer, fieldName, array);
    866
    867        } else {
    868            appendSummary(buffer, fieldName, array);
    869        }
    870
    871        appendFieldEnd(buffer, fieldName);
    872    }
    873
    874    /**
    875     * Appends to the {@code toString} the detail of an
    876     * {@link Object} array.
    877     *
    878     * @param buffer  the {@link StringBuffer} to populate
    879     * @param fieldName  the field name, typically not used as already appended
    880     * @param array  the array to add to the {@code toString},
    881     *  not {@code null}
    882     */
    883    protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array) {
    884        buffer.append(arrayStart);
    885        for (int i = 0; i < array.length; i++) {
    886            appendDetail(buffer, fieldName, i, array[i]);
    887        }
    888        buffer.append(arrayEnd);
    889    }
    890
    891    /**
    892     * Appends to the {@code toString} the detail of an
    893     * {@link Object} array item.
    894     *
    895     * @param buffer  the {@link StringBuffer} to populate
    896     * @param fieldName  the field name, typically not used as already appended
    897     * @param i the array item index to add
    898     * @param item the array item to add
    899     * @since 3.11
    900     */
    901    protected void appendDetail(final StringBuffer buffer, final String fieldName, final int i, final Object item) {
    902        if (i > 0) {
    903            buffer.append(arraySeparator);
    904        }
    905        if (item == null) {
    906            appendNullText(buffer, fieldName);
    907        } else {
    908            appendInternal(buffer, fieldName, item, arrayContentDetail);
    909        }
    910    }
    911
    912    /**
    913     * Appends to the {@code toString} the detail of an array type.
    914     *
    915     * @param buffer  the {@link StringBuffer} to populate
    916     * @param fieldName  the field name, typically not used as already appended
    917     * @param array  the array to add to the {@code toString},
    918     *  not {@code null}
    919     * @since 2.0
    920     */
    921    protected void reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array) {
    922        buffer.append(arrayStart);
    923        final int length = Array.getLength(array);
    924        for (int i = 0; i < length; i++) {
    925            appendDetail(buffer, fieldName, i, Array.get(array, i));
    926        }
    927        buffer.append(arrayEnd);
    928    }
    929
    930    /**
    931     * Appends to the {@code toString} a summary of an
    932     * {@link Object} array.
    933     *
    934     * @param buffer  the {@link StringBuffer} to populate
    935     * @param fieldName  the field name, typically not used as already appended
    936     * @param array  the array to add to the {@code toString},
    937     *  not {@code null}
    938     */
    939    protected void appendSummary(final StringBuffer buffer, final String fieldName, final Object[] array) {
    940        appendSummarySize(buffer, fieldName, array.length);
    941    }
    942
    943    /**
    944     * Appends to the {@code toString} a {@code long}
    945     * array.
    946     *
    947     * @param buffer  the {@link StringBuffer} to populate
    948     * @param fieldName  the field name
    949     * @param array  the array to add to the {@code toString}
    950     * @param fullDetail  {@code true} for detail, {@code false}
    951     *  for summary info, {@code null} for style decides
    952     */
    953    public void append(final StringBuffer buffer, final String fieldName, final long[] array, final Boolean fullDetail) {
    954        appendFieldStart(buffer, fieldName);
    955
    956        if (array == null) {
    957            appendNullText(buffer, fieldName);
    958
    959        } else if (isFullDetail(fullDetail)) {
    960            appendDetail(buffer, fieldName, array);
    961
    962        } else {
    963            appendSummary(buffer, fieldName, array);
    964        }
    965
    966        appendFieldEnd(buffer, fieldName);
    967    }
    968
    969    /**
    970     * Appends to the {@code toString} the detail of a
    971     * {@code long} array.
    972     *
    973     * @param buffer  the {@link StringBuffer} to populate
    974     * @param fieldName  the field name, typically not used as already appended
    975     * @param array  the array to add to the {@code toString},
    976     *  not {@code null}
    977     */
    978    protected void appendDetail(final StringBuffer buffer, final String fieldName, final long[] array) {
    979        buffer.append(arrayStart);
    980        for (int i = 0; i < array.length; i++) {
    981            if (i > 0) {
    982                buffer.append(arraySeparator);
    983            }
    984            appendDetail(buffer, fieldName, array[i]);
    985        }
    986        buffer.append(arrayEnd);
    987    }
    988
    989    /**
    990     * Appends to the {@code toString} a summary of a
    991     * {@code long} array.
    992     *
    993     * @param buffer  the {@link StringBuffer} to populate
    994     * @param fieldName  the field name, typically not used as already appended
    995     * @param array  the array to add to the {@code toString},
    996     *  not {@code null}
    997     */
    998    protected void appendSummary(final StringBuffer buffer, final String fieldName, final long[] array) {
    999        appendSummarySize(buffer, fieldName, array.length);
    1000    }
    1001
    1002    /**
    1003     * Appends to the {@code toString} an {@code int}
    1004     * array.
    1005     *
    1006     * @param buffer  the {@link StringBuffer} to populate
    1007     * @param fieldName  the field name
    1008     * @param array  the array to add to the {@code toString}
    1009     * @param fullDetail  {@code true} for detail, {@code false}
    1010     *  for summary info, {@code null} for style decides
    1011     */
    1012    public void append(final StringBuffer buffer, final String fieldName, final int[] array, final Boolean fullDetail) {
    1013        appendFieldStart(buffer, fieldName);
    1014
    1015        if (array == null) {
    1016            appendNullText(buffer, fieldName);
    1017
    1018        } else if (isFullDetail(fullDetail)) {
    1019            appendDetail(buffer, fieldName, array);
    1020
    1021        } else {
    1022            appendSummary(buffer, fieldName, array);
    1023        }
    1024
    1025        appendFieldEnd(buffer, fieldName);
    1026    }
    1027
    1028    /**
    1029     * Appends to the {@code toString} the detail of an
    1030     * {@code int} array.
    1031     *
    1032     * @param buffer  the {@link StringBuffer} to populate
    1033     * @param fieldName  the field name, typically not used as already appended
    1034     * @param array  the array to add to the {@code toString},
    1035     *  not {@code null}
    1036     */
    1037    protected void appendDetail(final StringBuffer buffer, final String fieldName, final int[] array) {
    1038        buffer.append(arrayStart);
    1039        for (int i = 0; i < array.length; i++) {
    1040            if (i > 0) {
    1041                buffer.append(arraySeparator);
    1042            }
    1043            appendDetail(buffer, fieldName, array[i]);
    1044        }
    1045        buffer.append(arrayEnd);
    1046    }
    1047
    1048    /**
    1049     * Appends to the {@code toString} a summary of an
    1050     * {@code int} array.
    1051     *
    1052     * @param buffer  the {@link StringBuffer} to populate
    1053     * @param fieldName  the field name, typically not used as already appended
    1054     * @param array  the array to add to the {@code toString},
    1055     *  not {@code null}
    1056     */
    1057    protected void appendSummary(final StringBuffer buffer, final String fieldName, final int[] array) {
    1058        appendSummarySize(buffer, fieldName, array.length);
    1059    }
    1060
    1061    /**
    1062     * Appends to the {@code toString} a {@code short}
    1063     * array.
    1064     *
    1065     * @param buffer  the {@link StringBuffer} to populate
    1066     * @param fieldName  the field name
    1067     * @param array  the array to add to the {@code toString}
    1068     * @param fullDetail  {@code true} for detail, {@code false}
    1069     *  for summary info, {@code null} for style decides
    1070     */
    1071    public void append(final StringBuffer buffer, final String fieldName, final short[] array, final Boolean fullDetail) {
    1072        appendFieldStart(buffer, fieldName);
    1073
    1074        if (array == null) {
    1075            appendNullText(buffer, fieldName);
    1076
    1077        } else if (isFullDetail(fullDetail)) {
    1078            appendDetail(buffer, fieldName, array);
    1079
    1080        } else {
    1081            appendSummary(buffer, fieldName, array);
    1082        }
    1083
    1084        appendFieldEnd(buffer, fieldName);
    1085    }
    1086
    1087    /**
    1088     * Appends to the {@code toString} the detail of a
    1089     * {@code short} array.
    1090     *
    1091     * @param buffer  the {@link StringBuffer} to populate
    1092     * @param fieldName  the field name, typically not used as already appended
    1093     * @param array  the array to add to the {@code toString},
    1094     *  not {@code null}
    1095     */
    1096    protected void appendDetail(final StringBuffer buffer, final String fieldName, final short[] array) {
    1097        buffer.append(arrayStart);
    1098        for (int i = 0; i < array.length; i++) {
    1099            if (i > 0) {
    1100                buffer.append(arraySeparator);
    1101            }
    1102            appendDetail(buffer, fieldName, array[i]);
    1103        }
    1104        buffer.append(arrayEnd);
    1105    }
    1106
    1107    /**
    1108     * Appends to the {@code toString} a summary of a
    1109     * {@code short} array.
    1110     *
    1111     * @param buffer  the {@link StringBuffer} to populate
    1112     * @param fieldName  the field name, typically not used as already appended
    1113     * @param array  the array to add to the {@code toString},
    1114     *  not {@code null}
    1115     */
    1116    protected void appendSummary(final StringBuffer buffer, final String fieldName, final short[] array) {
    1117        appendSummarySize(buffer, fieldName, array.length);
    1118    }
    1119
    1120    /**
    1121     * Appends to the {@code toString} a {@code byte}
    1122     * array.
    1123     *
    1124     * @param buffer  the {@link StringBuffer} to populate
    1125     * @param fieldName  the field name
    1126     * @param array  the array to add to the {@code toString}
    1127     * @param fullDetail  {@code true} for detail, {@code false}
    1128     *  for summary info, {@code null} for style decides
    1129     */
    1130    public void append(final StringBuffer buffer, final String fieldName, final byte[] array, final Boolean fullDetail) {
    1131        appendFieldStart(buffer, fieldName);
    1132
    1133        if (array == null) {
    1134            appendNullText(buffer, fieldName);
    1135
    1136        } else if (isFullDetail(fullDetail)) {
    1137            appendDetail(buffer, fieldName, array);
    1138
    1139        } else {
    1140            appendSummary(buffer, fieldName, array);
    1141        }
    1142
    1143        appendFieldEnd(buffer, fieldName);
    1144    }
    1145
    1146    /**
    1147     * Appends to the {@code toString} the detail of a
    1148     * {@code byte} array.
    1149     *
    1150     * @param buffer  the {@link StringBuffer} to populate
    1151     * @param fieldName  the field name, typically not used as already appended
    1152     * @param array  the array to add to the {@code toString},
    1153     *  not {@code null}
    1154     */
    1155    protected void appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array) {
    1156        buffer.append(arrayStart);
    1157        for (int i = 0; i < array.length; i++) {
    1158            if (i > 0) {
    1159                buffer.append(arraySeparator);
    1160            }
    1161            appendDetail(buffer, fieldName, array[i]);
    1162        }
    1163        buffer.append(arrayEnd);
    1164    }
    1165
    1166    /**
    1167     * Appends to the {@code toString} a summary of a
    1168     * {@code byte} array.
    1169     *
    1170     * @param buffer  the {@link StringBuffer} to populate
    1171     * @param fieldName  the field name, typically not used as already appended
    1172     * @param array  the array to add to the {@code toString},
    1173     *  not {@code null}
    1174     */
    1175    protected void appendSummary(final StringBuffer buffer, final String fieldName, final byte[] array) {
    1176        appendSummarySize(buffer, fieldName, array.length);
    1177    }
    1178
    1179    /**
    1180     * Appends to the {@code toString} a {@code char}
    1181     * array.
    1182     *
    1183     * @param buffer  the {@link StringBuffer} to populate
    1184     * @param fieldName  the field name
    1185     * @param array  the array to add to the {@code toString}
    1186     * @param fullDetail  {@code true} for detail, {@code false}
    1187     *  for summary info, {@code null} for style decides
    1188     */
    1189    public void append(final StringBuffer buffer, final String fieldName, final char[] array, final Boolean fullDetail) {
    1190        appendFieldStart(buffer, fieldName);
    1191
    1192        if (array == null) {
    1193            appendNullText(buffer, fieldName);
    1194
    1195        } else if (isFullDetail(fullDetail)) {
    1196            appendDetail(buffer, fieldName, array);
    1197
    1198        } else {
    1199            appendSummary(buffer, fieldName, array);
    1200        }
    1201
    1202        appendFieldEnd(buffer, fieldName);
    1203    }
    1204
    1205    /**
    1206     * Appends to the {@code toString} the detail of a
    1207     * {@code char} array.
    1208     *
    1209     * @param buffer  the {@link StringBuffer} to populate
    1210     * @param fieldName  the field name, typically not used as already appended
    1211     * @param array  the array to add to the {@code toString},
    1212     *  not {@code null}
    1213     */
    1214    protected void appendDetail(final StringBuffer buffer, final String fieldName, final char[] array) {
    1215        buffer.append(arrayStart);
    1216        for (int i = 0; i < array.length; i++) {
    1217            if (i > 0) {
    1218                buffer.append(arraySeparator);
    1219            }
    1220            appendDetail(buffer, fieldName, array[i]);
    1221        }
    1222        buffer.append(arrayEnd);
    1223    }
    1224
    1225    /**
    1226     * Appends to the {@code toString} a summary of a
    1227     * {@code char} array.
    1228     *
    1229     * @param buffer  the {@link StringBuffer} to populate
    1230     * @param fieldName  the field name, typically not used as already appended
    1231     * @param array  the array to add to the {@code toString},
    1232     *  not {@code null}
    1233     */
    1234    protected void appendSummary(final StringBuffer buffer, final String fieldName, final char[] array) {
    1235        appendSummarySize(buffer, fieldName, array.length);
    1236    }
    1237
    1238    /**
    1239     * Appends to the {@code toString} a {@code double}
    1240     * array.
    1241     *
    1242     * @param buffer  the {@link StringBuffer} to populate
    1243     * @param fieldName  the field name
    1244     * @param array  the array to add to the toString
    1245     * @param fullDetail  {@code true} for detail, {@code false}
    1246     *  for summary info, {@code null} for style decides
    1247     */
    1248    public void append(final StringBuffer buffer, final String fieldName, final double[] array, final Boolean fullDetail) {
    1249        appendFieldStart(buffer, fieldName);
    1250
    1251        if (array == null) {
    1252            appendNullText(buffer, fieldName);
    1253
    1254        } else if (isFullDetail(fullDetail)) {
    1255            appendDetail(buffer, fieldName, array);
    1256
    1257        } else {
    1258            appendSummary(buffer, fieldName, array);
    1259        }
    1260
    1261        appendFieldEnd(buffer, fieldName);
    1262    }
    1263
    1264    /**
    1265     * Appends to the {@code toString} the detail of a
    1266     * {@code double} array.
    1267     *
    1268     * @param buffer  the {@link StringBuffer} to populate
    1269     * @param fieldName  the field name, typically not used as already appended
    1270     * @param array  the array to add to the {@code toString},
    1271     *  not {@code null}
    1272     */
    1273    protected void appendDetail(final StringBuffer buffer, final String fieldName, final double[] array) {
    1274        buffer.append(arrayStart);
    1275        for (int i = 0; i < array.length; i++) {
    1276            if (i > 0) {
    1277                buffer.append(arraySeparator);
    1278            }
    1279            appendDetail(buffer, fieldName, array[i]);
    1280        }
    1281        buffer.append(arrayEnd);
    1282    }
    1283
    1284    /**
    1285     * Appends to the {@code toString} a summary of a
    1286     * {@code double} array.
    1287     *
    1288     * @param buffer  the {@link StringBuffer} to populate
    1289     * @param fieldName  the field name, typically not used as already appended
    1290     * @param array  the array to add to the {@code toString},
    1291     *  not {@code null}
    1292     */
    1293    protected void appendSummary(final StringBuffer buffer, final String fieldName, final double[] array) {
    1294        appendSummarySize(buffer, fieldName, array.length);
    1295    }
    1296
    1297    /**
    1298     * Appends to the {@code toString} a {@code float}
    1299     * array.
    1300     *
    1301     * @param buffer  the {@link StringBuffer} to populate
    1302     * @param fieldName  the field name
    1303     * @param array  the array to add to the toString
    1304     * @param fullDetail  {@code true} for detail, {@code false}
    1305     *  for summary info, {@code null} for style decides
    1306     */
    1307    public void append(final StringBuffer buffer, final String fieldName, final float[] array, final Boolean fullDetail) {
    1308        appendFieldStart(buffer, fieldName);
    1309
    1310        if (array == null) {
    1311            appendNullText(buffer, fieldName);
    1312
    1313        } else if (isFullDetail(fullDetail)) {
    1314            appendDetail(buffer, fieldName, array);
    1315
    1316        } else {
    1317            appendSummary(buffer, fieldName, array);
    1318        }
    1319
    1320        appendFieldEnd(buffer, fieldName);
    1321    }
    1322
    1323    /**
    1324     * Appends to the {@code toString} the detail of a
    1325     * {@code float} array.
    1326     *
    1327     * @param buffer  the {@link StringBuffer} to populate
    1328     * @param fieldName  the field name, typically not used as already appended
    1329     * @param array  the array to add to the {@code toString},
    1330     *  not {@code null}
    1331     */
    1332    protected void appendDetail(final StringBuffer buffer, final String fieldName, final float[] array) {
    1333        buffer.append(arrayStart);
    1334        for (int i = 0; i < array.length; i++) {
    1335            if (i > 0) {
    1336                buffer.append(arraySeparator);
    1337            }
    1338            appendDetail(buffer, fieldName, array[i]);
    1339        }
    1340        buffer.append(arrayEnd);
    1341    }
    1342
    1343    /**
    1344     * Appends to the {@code toString} a summary of a
    1345     * {@code float} array.
    1346     *
    1347     * @param buffer  the {@link StringBuffer} to populate
    1348     * @param fieldName  the field name, typically not used as already appended
    1349     * @param array  the array to add to the {@code toString},
    1350     *  not {@code null}
    1351     */
    1352    protected void appendSummary(final StringBuffer buffer, final String fieldName, final float[] array) {
    1353        appendSummarySize(buffer, fieldName, array.length);
    1354    }
    1355
    1356    /**
    1357     * Appends to the {@code toString} a {@code boolean}
    1358     * array.
    1359     *
    1360     * @param buffer  the {@link StringBuffer} to populate
    1361     * @param fieldName  the field name
    1362     * @param array  the array to add to the toString
    1363     * @param fullDetail  {@code true} for detail, {@code false}
    1364     *  for summary info, {@code null} for style decides
    1365     */
    1366    public void append(final StringBuffer buffer, final String fieldName, final boolean[] array, final Boolean fullDetail) {
    1367        appendFieldStart(buffer, fieldName);
    1368
    1369        if (array == null) {
    1370            appendNullText(buffer, fieldName);
    1371
    1372        } else if (isFullDetail(fullDetail)) {
    1373            appendDetail(buffer, fieldName, array);
    1374
    1375        } else {
    1376            appendSummary(buffer, fieldName, array);
    1377        }
    1378
    1379        appendFieldEnd(buffer, fieldName);
    1380    }
    1381
    1382    /**
    1383     * Appends to the {@code toString} the detail of a
    1384     * {@code boolean} array.
    1385     *
    1386     * @param buffer  the {@link StringBuffer} to populate
    1387     * @param fieldName  the field name, typically not used as already appended
    1388     * @param array  the array to add to the {@code toString},
    1389     *  not {@code null}
    1390     */
    1391    protected void appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array) {
    1392        buffer.append(arrayStart);
    1393        for (int i = 0; i < array.length; i++) {
    1394            if (i > 0) {
    1395                buffer.append(arraySeparator);
    1396            }
    1397            appendDetail(buffer, fieldName, array[i]);
    1398        }
    1399        buffer.append(arrayEnd);
    1400    }
    1401
    1402    /**
    1403     * Appends to the {@code toString} a summary of a
    1404     * {@code boolean} array.
    1405     *
    1406     * @param buffer  the {@link StringBuffer} to populate
    1407     * @param fieldName  the field name, typically not used as already appended
    1408     * @param array  the array to add to the {@code toString},
    1409     *  not {@code null}
    1410     */
    1411    protected void appendSummary(final StringBuffer buffer, final String fieldName, final boolean[] array) {
    1412        appendSummarySize(buffer, fieldName, array.length);
    1413    }
    1414
    1415    /**
    1416     * Appends to the {@code toString} the class name.
    1417     *
    1418     * @param buffer  the {@link StringBuffer} to populate
    1419     * @param object  the {@link Object} whose name to output
    1420     */
    1421    protected void appendClassName(final StringBuffer buffer, final Object object) {
    1422        if (useClassName && object != null) {
    1423            register(object);
    1424            if (useShortClassName) {
    1425                buffer.append(getShortClassName(object.getClass()));
    1426            } else {
    1427                buffer.append(object.getClass().getName());
    1428            }
    1429        }
    1430    }
    1431
    1432    /**
    1433     * Appends the {@link System#identityHashCode(java.lang.Object)}.
    1434     *
    1435     * @param buffer  the {@link StringBuffer} to populate
    1436     * @param object  the {@link Object} whose id to output
    1437     */
    1438    protected void appendIdentityHashCode(final StringBuffer buffer, final Object object) {
    1439        if (this.isUseIdentityHashCode() && object != null) {
    1440            register(object);
    1441            buffer.append('@');
    1442            buffer.append(ObjectUtils.identityHashCodeHex(object));
    1443        }
    1444    }
    1445
    1446    /**
    1447     * Appends to the {@code toString} the content start.
    1448     *
    1449     * @param buffer  the {@link StringBuffer} to populate
    1450     */
    1451    protected void appendContentStart(final StringBuffer buffer) {
    1452        buffer.append(contentStart);
    1453    }
    1454
    1455    /**
    1456     * Appends to the {@code toString} the content end.
    1457     *
    1458     * @param buffer  the {@link StringBuffer} to populate
    1459     */
    1460    protected void appendContentEnd(final StringBuffer buffer) {
    1461        buffer.append(contentEnd);
    1462    }
    1463
    1464    /**
    1465     * Appends to the {@code toString} an indicator for {@code null}.
    1466     *
    1467     * <p>The default indicator is {@code '&lt;null&gt;'}.</p>
    1468     *
    1469     * @param buffer  the {@link StringBuffer} to populate
    1470     * @param fieldName  the field name, typically not used as already appended
    1471     */
    1472    protected void appendNullText(final StringBuffer buffer, final String fieldName) {
    1473        buffer.append(nullText);
    1474    }
    1475
    1476    /**
    1477     * Appends to the {@code toString} the field separator.
    1478     *
    1479     * @param buffer  the {@link StringBuffer} to populate
    1480     */
    1481    protected void appendFieldSeparator(final StringBuffer buffer) {
    1482        buffer.append(fieldSeparator);
    1483    }
    1484
    1485    /**
    1486     * Appends to the {@code toString} the field start.
    1487     *
    1488     * @param buffer  the {@link StringBuffer} to populate
    1489     * @param fieldName  the field name
    1490     */
    1491    protected void appendFieldStart(final StringBuffer buffer, final String fieldName) {
    1492        if (useFieldNames && fieldName != null) {
    1493            buffer.append(fieldName);
    1494            buffer.append(fieldNameValueSeparator);
    1495        }
    1496    }
    1497
    1498    /**
    1499     * Appends to the {@code toString} the field end.
    1500     *
    1501     * @param buffer  the {@link StringBuffer} to populate
    1502     * @param fieldName  the field name, typically not used as already appended
    1503     */
    1504    protected void appendFieldEnd(final StringBuffer buffer, final String fieldName) {
    1505        appendFieldSeparator(buffer);
    1506    }
    1507
    1508    /**
    1509     * Appends to the {@code toString} a size summary.
    1510     *
    1511     * <p>The size summary is used to summarize the contents of
    1512     * {@link Collection}s, {@link Map}s and arrays.</p>
    1513     *
    1514     * <p>The output consists of a prefix, the passed in size
    1515     * and a suffix.</p>
    1516     *
    1517     * <p>The default format is {@code '&lt;size=n&gt;'}.</p>
    1518     *
    1519     * @param buffer  the {@link StringBuffer} to populate
    1520     * @param fieldName  the field name, typically not used as already appended
    1521     * @param size  the size to append
    1522     */
    1523    protected void appendSummarySize(final StringBuffer buffer, final String fieldName, final int size) {
    1524        buffer.append(sizeStartText);
    1525        buffer.append(size);
    1526        buffer.append(sizeEndText);
    1527    }
    1528
    1529    /**
    1530     * Is this field to be output in full detail.
    1531     *
    1532     * <p>This method converts a detail request into a detail level.
    1533     * The calling code may request full detail ({@code true}),
    1534     * but a subclass might ignore that and always return
    1535     * {@code false}. The calling code may pass in
    1536     * {@code null} indicating that it doesn't care about
    1537     * the detail level. In this case the default detail level is
    1538     * used.</p>
    1539     *
    1540     * @param fullDetailRequest  the detail level requested
    1541     * @return whether full detail is to be shown
    1542     */
    1543    protected boolean isFullDetail(final Boolean fullDetailRequest) {
    1544        if (fullDetailRequest == null) {
    1545            return defaultFullDetail;
    1546        }
    1547        return fullDetailRequest.booleanValue();
    1548    }
    1549
    1550    /**
    1551     * Gets the short class name for a class.
    1552     *
    1553     * <p>The short class name is the classname excluding
    1554     * the package name.</p>
    1555     *
    1556     * @param cls  the {@link Class} to get the short name of
    1557     * @return the short name
    1558     */
    1559    protected String getShortClassName(final Class<?> cls) {
    1560        return ClassUtils.getShortClassName(cls);
    1561    }
    1562
    1563    // Setters and getters for the customizable parts of the style
    1564    // These methods are not expected to be overridden, except to make public
    1565    // (They are not public so that immutable subclasses can be written)
    1566    /**
    1567     * Gets whether to use the class name.
    1568     *
    1569     * @return the current useClassName flag
    1570     */
    1571    protected boolean isUseClassName() {
    1572        return useClassName;
    1573    }
    1574
    1575    /**
    1576     * Sets whether to use the class name.
    1577     *
    1578     * @param useClassName  the new useClassName flag
    1579     */
    1580    protected void setUseClassName(final boolean useClassName) {
    1581        this.useClassName = useClassName;
    1582    }
    1583
    1584    /**
    1585     * Gets whether to output short or long class names.
    1586     *
    1587     * @return the current useShortClassName flag
    1588     * @since 2.0
    1589     */
    1590    protected boolean isUseShortClassName() {
    1591        return useShortClassName;
    1592    }
    1593
    1594    /**
    1595     * Sets whether to output short or long class names.
    1596     *
    1597     * @param useShortClassName  the new useShortClassName flag
    1598     * @since 2.0
    1599     */
    1600    protected void setUseShortClassName(final boolean useShortClassName) {
    1601        this.useShortClassName = useShortClassName;
    1602    }
    1603
    1604    /**
    1605     * Gets whether to use the identity hash code.
    1606     *
    1607     * @return the current useIdentityHashCode flag
    1608     */
    1609    protected boolean isUseIdentityHashCode() {
    1610        return useIdentityHashCode;
    1611    }
    1612
    1613    /**
    1614     * Sets whether to use the identity hash code.
    1615     *
    1616     * @param useIdentityHashCode  the new useIdentityHashCode flag
    1617     */
    1618    protected void setUseIdentityHashCode(final boolean useIdentityHashCode) {
    1619        this.useIdentityHashCode = useIdentityHashCode;
    1620    }
    1621
    1622    /**
    1623     * Gets whether to use the field names passed in.
    1624     *
    1625     * @return the current useFieldNames flag
    1626     */
    1627    protected boolean isUseFieldNames() {
    1628        return useFieldNames;
    1629    }
    1630
    1631    /**
    1632     * Sets whether to use the field names passed in.
    1633     *
    1634     * @param useFieldNames  the new useFieldNames flag
    1635     */
    1636    protected void setUseFieldNames(final boolean useFieldNames) {
    1637        this.useFieldNames = useFieldNames;
    1638    }
    1639
    1640    /**
    1641     * Gets whether to use full detail when the caller doesn't
    1642     * specify.
    1643     *
    1644     * @return the current defaultFullDetail flag
    1645     */
    1646    protected boolean isDefaultFullDetail() {
    1647        return defaultFullDetail;
    1648    }
    1649
    1650    /**
    1651     * Sets whether to use full detail when the caller doesn't
    1652     * specify.
    1653     *
    1654     * @param defaultFullDetail  the new defaultFullDetail flag
    1655     */
    1656    protected void setDefaultFullDetail(final boolean defaultFullDetail) {
    1657        this.defaultFullDetail = defaultFullDetail;
    1658    }
    1659
    1660    /**
    1661     * Gets whether to output array content detail.
    1662     *
    1663     * @return the current array content detail setting
    1664     */
    1665    protected boolean isArrayContentDetail() {
    1666        return arrayContentDetail;
    1667    }
    1668
    1669    /**
    1670     * Sets whether to output array content detail.
    1671     *
    1672     * @param arrayContentDetail  the new arrayContentDetail flag
    1673     */
    1674    protected void setArrayContentDetail(final boolean arrayContentDetail) {
    1675        this.arrayContentDetail = arrayContentDetail;
    1676    }
    1677
    1678    /**
    1679     * Gets the array start text.
    1680     *
    1681     * @return the current array start text
    1682     */
    1683    protected String getArrayStart() {
    1684        return arrayStart;
    1685    }
    1686
    1687    /**
    1688     * Sets the array start text.
    1689     *
    1690     * <p>{@code null} is accepted, but will be converted to
    1691     * an empty String.</p>
    1692     *
    1693     * @param arrayStart  the new array start text
    1694     */
    1695    protected void setArrayStart(String arrayStart) {
    1696        if (arrayStart == null) {
    1697            arrayStart = StringUtils.EMPTY;
    1698        }
    1699        this.arrayStart = arrayStart;
    1700    }
    1701
    1702    /**
    1703     * Gets the array end text.
    1704     *
    1705     * @return the current array end text
    1706     */
    1707    protected String getArrayEnd() {
    1708        return arrayEnd;
    1709    }
    1710
    1711    /**
    1712     * Sets the array end text.
    1713     *
    1714     * <p>{@code null} is accepted, but will be converted to
    1715     * an empty String.</p>
    1716     *
    1717     * @param arrayEnd  the new array end text
    1718     */
    1719    protected void setArrayEnd(String arrayEnd) {
    1720        if (arrayEnd == null) {
    1721            arrayEnd = StringUtils.EMPTY;
    1722        }
    1723        this.arrayEnd = arrayEnd;
    1724    }
    1725
    1726    /**
    1727     * Gets the array separator text.
    1728     *
    1729     * @return the current array separator text
    1730     */
    1731    protected String getArraySeparator() {
    1732        return arraySeparator;
    1733    }
    1734
    1735    /**
    1736     * Sets the array separator text.
    1737     *
    1738     * <p>{@code null} is accepted, but will be converted to
    1739     * an empty String.</p>
    1740     *
    1741     * @param arraySeparator  the new array separator text
    1742     */
    1743    protected void setArraySeparator(String arraySeparator) {
    1744        if (arraySeparator == null) {
    1745            arraySeparator = StringUtils.EMPTY;
    1746        }
    1747        this.arraySeparator = arraySeparator;
    1748    }
    1749
    1750    /**
    1751     * Gets the content start text.
    1752     *
    1753     * @return the current content start text
    1754     */
    1755    protected String getContentStart() {
    1756        return contentStart;
    1757    }
    1758
    1759    /**
    1760     * Sets the content start text.
    1761     *
    1762     * <p>{@code null} is accepted, but will be converted to
    1763     * an empty String.</p>
    1764     *
    1765     * @param contentStart  the new content start text
    1766     */
    1767    protected void setContentStart(String contentStart) {
    1768        if (contentStart == null) {
    1769            contentStart = StringUtils.EMPTY;
    1770        }
    1771        this.contentStart = contentStart;
    1772    }
    1773
    1774    /**
    1775     * Gets the content end text.
    1776     *
    1777     * @return the current content end text
    1778     */
    1779    protected String getContentEnd() {
    1780        return contentEnd;
    1781    }
    1782
    1783    /**
    1784     * Sets the content end text.
    1785     *
    1786     * <p>{@code null} is accepted, but will be converted to
    1787     * an empty String.</p>
    1788     *
    1789     * @param contentEnd  the new content end text
    1790     */
    1791    protected void setContentEnd(String contentEnd) {
    1792        if (contentEnd == null) {
    1793            contentEnd = StringUtils.EMPTY;
    1794        }
    1795        this.contentEnd = contentEnd;
    1796    }
    1797
    1798    /**
    1799     * Gets the field name value separator text.
    1800     *
    1801     * @return the current field name value separator text
    1802     */
    1803    protected String getFieldNameValueSeparator() {
    1804        return fieldNameValueSeparator;
    1805    }
    1806
    1807    /**
    1808     * Sets the field name value separator text.
    1809     *
    1810     * <p>{@code null} is accepted, but will be converted to
    1811     * an empty String.</p>
    1812     *
    1813     * @param fieldNameValueSeparator  the new field name value separator text
    1814     */
    1815    protected void setFieldNameValueSeparator(String fieldNameValueSeparator) {
    1816        if (fieldNameValueSeparator == null) {
    1817            fieldNameValueSeparator = StringUtils.EMPTY;
    1818        }
    1819        this.fieldNameValueSeparator = fieldNameValueSeparator;
    1820    }
    1821
    1822    /**
    1823     * Gets the field separator text.
    1824     *
    1825     * @return the current field separator text
    1826     */
    1827    protected String getFieldSeparator() {
    1828        return fieldSeparator;
    1829    }
    1830
    1831    /**
    1832     * Sets the field separator text.
    1833     *
    1834     * <p>{@code null} is accepted, but will be converted to
    1835     * an empty String.</p>
    1836     *
    1837     * @param fieldSeparator  the new field separator text
    1838     */
    1839    protected void setFieldSeparator(String fieldSeparator) {
    1840        if (fieldSeparator == null) {
    1841            fieldSeparator = StringUtils.EMPTY;
    1842        }
    1843        this.fieldSeparator = fieldSeparator;
    1844    }
    1845
    1846    /**
    1847     * Gets whether the field separator should be added at the start
    1848     * of each buffer.
    1849     *
    1850     * @return the fieldSeparatorAtStart flag
    1851     * @since 2.0
    1852     */
    1853    protected boolean isFieldSeparatorAtStart() {
    1854        return fieldSeparatorAtStart;
    1855    }
    1856
    1857    /**
    1858     * Sets whether the field separator should be added at the start
    1859     * of each buffer.
    1860     *
    1861     * @param fieldSeparatorAtStart  the fieldSeparatorAtStart flag
    1862     * @since 2.0
    1863     */
    1864    protected void setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart) {
    1865        this.fieldSeparatorAtStart = fieldSeparatorAtStart;
    1866    }
    1867
    1868    /**
    1869     * Gets whether the field separator should be added at the end
    1870     * of each buffer.
    1871     *
    1872     * @return fieldSeparatorAtEnd flag
    1873     * @since 2.0
    1874     */
    1875    protected boolean isFieldSeparatorAtEnd() {
    1876        return fieldSeparatorAtEnd;
    1877    }
    1878
    1879    /**
    1880     * Sets whether the field separator should be added at the end
    1881     * of each buffer.
    1882     *
    1883     * @param fieldSeparatorAtEnd  the fieldSeparatorAtEnd flag
    1884     * @since 2.0
    1885     */
    1886    protected void setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd) {
    1887        this.fieldSeparatorAtEnd = fieldSeparatorAtEnd;
    1888    }
    1889
    1890    /**
    1891     * Gets the text to output when {@code null} found.
    1892     *
    1893     * @return the current text to output when null found
    1894     */
    1895    protected String getNullText() {
    1896        return nullText;
    1897    }
    1898
    1899    /**
    1900     * Sets the text to output when {@code null} found.
    1901     *
    1902     * <p>{@code null} is accepted, but will be converted to
    1903     * an empty String.</p>
    1904     *
    1905     * @param nullText  the new text to output when null found
    1906     */
    1907    protected void setNullText(String nullText) {
    1908        if (nullText == null) {
    1909            nullText = StringUtils.EMPTY;
    1910        }
    1911        this.nullText = nullText;
    1912    }
    1913
    1914    /**
    1915     * Gets the start text to output when a {@link Collection},
    1916     * {@link Map} or array size is output.
    1917     *
    1918     * <p>This is output before the size value.</p>
    1919     *
    1920     * @return the current start of size text
    1921     */
    1922    protected String getSizeStartText() {
    1923        return sizeStartText;
    1924    }
    1925
    1926    /**
    1927     * Sets the start text to output when a {@link Collection},
    1928     * {@link Map} or array size is output.
    1929     *
    1930     * <p>This is output before the size value.</p>
    1931     *
    1932     * <p>{@code null} is accepted, but will be converted to
    1933     * an empty String.</p>
    1934     *
    1935     * @param sizeStartText  the new start of size text
    1936     */
    1937    protected void setSizeStartText(String sizeStartText) {
    1938        if (sizeStartText == null) {
    1939            sizeStartText = StringUtils.EMPTY;
    1940        }
    1941        this.sizeStartText = sizeStartText;
    1942    }
    1943
    1944    /**
    1945     * Gets the end text to output when a {@link Collection},
    1946     * {@link Map} or array size is output.
    1947     *
    1948     * <p>This is output after the size value.</p>
    1949     *
    1950     * @return the current end of size text
    1951     */
    1952    protected String getSizeEndText() {
    1953        return sizeEndText;
    1954    }
    1955
    1956    /**
    1957     * Sets the end text to output when a {@link Collection},
    1958     * {@link Map} or array size is output.
    1959     *
    1960     * <p>This is output after the size value.</p>
    1961     *
    1962     * <p>{@code null} is accepted, but will be converted to
    1963     * an empty String.</p>
    1964     *
    1965     * @param sizeEndText  the new end of size text
    1966     */
    1967    protected void setSizeEndText(String sizeEndText) {
    1968        if (sizeEndText == null) {
    1969            sizeEndText = StringUtils.EMPTY;
    1970        }
    1971        this.sizeEndText = sizeEndText;
    1972    }
    1973
    1974    /**
    1975     * Gets the start text to output when an {@link Object} is
    1976     * output in summary mode.
    1977     *
    1978     * <p>This is output before the size value.</p>
    1979     *
    1980     * @return the current start of summary text
    1981     */
    1982    protected String getSummaryObjectStartText() {
    1983        return summaryObjectStartText;
    1984    }
    1985
    1986    /**
    1987     * Sets the start text to output when an {@link Object} is
    1988     * output in summary mode.
    1989     *
    1990     * <p>This is output before the size value.</p>
    1991     *
    1992     * <p>{@code null} is accepted, but will be converted to
    1993     * an empty String.</p>
    1994     *
    1995     * @param summaryObjectStartText  the new start of summary text
    1996     */
    1997    protected void setSummaryObjectStartText(String summaryObjectStartText) {
    1998        if (summaryObjectStartText == null) {
    1999            summaryObjectStartText = StringUtils.EMPTY;
    2000        }
    2001        this.summaryObjectStartText = summaryObjectStartText;
    2002    }
    2003
    2004    /**
    2005     * Gets the end text to output when an {@link Object} is
    2006     * output in summary mode.
    2007     *
    2008     * <p>This is output after the size value.</p>
    2009     *
    2010     * @return the current end of summary text
    2011     */
    2012    protected String getSummaryObjectEndText() {
    2013        return summaryObjectEndText;
    2014    }
    2015
    2016    /**
    2017     * Sets the end text to output when an {@link Object} is
    2018     * output in summary mode.
    2019     *
    2020     * <p>This is output after the size value.</p>
    2021     *
    2022     * <p>{@code null} is accepted, but will be converted to
    2023     * an empty String.</p>
    2024     *
    2025     * @param summaryObjectEndText  the new end of summary text
    2026     */
    2027    protected void setSummaryObjectEndText(String summaryObjectEndText) {
    2028        if (summaryObjectEndText == null) {
    2029            summaryObjectEndText = StringUtils.EMPTY;
    2030        }
    2031        this.summaryObjectEndText = summaryObjectEndText;
    2032    }
    2033
    2034    /**
    2035     * Default {@link ToStringStyle}.
    2036     *
    2037     * <p>This is an inner class rather than using
    2038     * {@link StandardToStringStyle} to ensure its immutability.</p>
    2039     */
    2040    private static final class DefaultToStringStyle extends ToStringStyle {
    2041
    2042        /**
    2043         * Required for serialization support.
    2044         *
    2045         * @see java.io.Serializable
    2046         */
    2047        private static final long serialVersionUID = 1L;
    2048
    2049        /**
    2050         * Constructor.
    2051         *
    2052         * <p>Use the static constant rather than instantiating.</p>
    2053         */
    2054        DefaultToStringStyle() {
    2055        }
    2056
    2057        /**
    2058         * Ensure Singleton after serialization.
    2059         *
    2060         * @return the singleton
    2061         */
    2062        private Object readResolve() {
    2063            return DEFAULT_STYLE;
    2064        }
    2065
    2066    }
    2067
    2068    /**
    2069     * {@link ToStringStyle} that does not print out
    2070     * the field names.
    2071     *
    2072     * <p>This is an inner class rather than using
    2073     * {@link StandardToStringStyle} to ensure its immutability.
    2074     */
    2075    private static final class NoFieldNameToStringStyle extends ToStringStyle {
    2076
    2077        private static final long serialVersionUID = 1L;
    2078
    2079        /**
    2080         * Constructor.
    2081         *
    2082         * <p>Use the static constant rather than instantiating.</p>
    2083         */
    2084        NoFieldNameToStringStyle() {
    2085            this.setUseFieldNames(false);
    2086        }
    2087
    2088        /**
    2089         * Ensure Singleton after serialization.
    2090         *
    2091         * @return the singleton
    2092         */
    2093        private Object readResolve() {
    2094            return NO_FIELD_NAMES_STYLE;
    2095        }
    2096
    2097    }
    2098
    2099    /**
    2100     * {@link ToStringStyle} that prints out the short
    2101     * class name and no identity hash code.
    2102     *
    2103     * <p>This is an inner class rather than using
    2104     * {@link StandardToStringStyle} to ensure its immutability.</p>
    2105     */
    2106    private static final class ShortPrefixToStringStyle extends ToStringStyle {
    2107
    2108        private static final long serialVersionUID = 1L;
    2109
    2110        /**
    2111         * Constructor.
    2112         *
    2113         * <p>Use the static constant rather than instantiating.</p>
    2114         */
    2115        ShortPrefixToStringStyle() {
    2116            this.setUseShortClassName(true);
    2117            this.setUseIdentityHashCode(false);
    2118        }
    2119
    2120        /**
    2121         * Ensure <code>Singleton</ode> after serialization.
    2122         * @return the singleton
    2123         */
    2124        private Object readResolve() {
    2125            return SHORT_PREFIX_STYLE;
    2126        }
    2127
    2128    }
    2129
    2130    /**
    2131     * {@link ToStringStyle} that does not print out the
    2132     * classname, identity hash code, content start or field name.
    2133     *
    2134     * <p>This is an inner class rather than using
    2135     * {@link StandardToStringStyle} to ensure its immutability.</p>
    2136     */
    2137    private static final class SimpleToStringStyle extends ToStringStyle {
    2138
    2139        private static final long serialVersionUID = 1L;
    2140
    2141        /**
    2142         * Constructor.
    2143         *
    2144         * <p>Use the static constant rather than instantiating.</p>
    2145         */
    2146        SimpleToStringStyle() {
    2147            this.setUseClassName(false);
    2148            this.setUseIdentityHashCode(false);
    2149            this.setUseFieldNames(false);
    2150            this.setContentStart(StringUtils.EMPTY);
    2151            this.setContentEnd(StringUtils.EMPTY);
    2152        }
    2153
    2154        /**
    2155         * Ensure <code>Singleton</ode> after serialization.
    2156         * @return the singleton
    2157         */
    2158        private Object readResolve() {
    2159            return SIMPLE_STYLE;
    2160        }
    2161
    2162    }
    2163
    2164    /**
    2165     * {@link ToStringStyle} that outputs on multiple lines.
    2166     *
    2167     * <p>This is an inner class rather than using
    2168     * {@link StandardToStringStyle} to ensure its immutability.</p>
    2169     */
    2170    private static final class MultiLineToStringStyle extends ToStringStyle {
    2171
    2172        private static final long serialVersionUID = 1L;
    2173
    2174        /**
    2175         * Constructor.
    2176         *
    2177         * <p>Use the static constant rather than instantiating.</p>
    2178         */
    2179        MultiLineToStringStyle() {
    2180            this.setContentStart("[");
    2181            this.setFieldSeparator(System.lineSeparator() + "  ");
    2182            this.setFieldSeparatorAtStart(true);
    2183            this.setContentEnd(System.lineSeparator() + "]");
    2184        }
    2185
    2186        /**
    2187         * Ensure Singleton after serialization.
    2188         *
    2189         * @return the singleton
    2190         */
    2191        private Object readResolve() {
    2192            return MULTI_LINE_STYLE;
    2193        }
    2194
    2195    }
    2196
    2197    /**
    2198     * {@link ToStringStyle} that does not print out the classname
    2199     * and identity hash code but prints content start and field names.
    2200     *
    2201     * <p>This is an inner class rather than using
    2202     * {@link StandardToStringStyle} to ensure its immutability.</p>
    2203     */
    2204    private static final class NoClassNameToStringStyle extends ToStringStyle {
    2205
    2206        private static final long serialVersionUID = 1L;
    2207
    2208        /**
    2209         * Constructor.
    2210         *
    2211         * <p>Use the static constant rather than instantiating.</p>
    2212         */
    2213        NoClassNameToStringStyle() {
    2214            this.setUseClassName(false);
    2215            this.setUseIdentityHashCode(false);
    2216        }
    2217
    2218        /**
    2219         * Ensure Singleton after serialization.
    2220         *
    2221         * @return the singleton
    2222         */
    2223        private Object readResolve() {
    2224            return NO_CLASS_NAME_STYLE;
    2225        }
    2226
    2227    }
    2228
    2229    /**
    2230     * {@link ToStringStyle} that outputs with JSON format.
    2231     *
    2232     * <p>
    2233     * This is an inner class rather than using
    2234     * {@link StandardToStringStyle} to ensure its immutability.
    2235     * </p>
    2236     *
    2237     * @since 3.4
    2238     * @see <a href="https://www.json.org/">json.org</a>
    2239     */
    2240    private static final class JsonToStringStyle extends ToStringStyle {
    2241
    2242        private static final long serialVersionUID = 1L;
    2243
    2244        private static final String FIELD_NAME_QUOTE = "\"";
    2245
    2246        /**
    2247         * Constructor.
    2248         *
    2249         * <p>
    2250         * Use the static constant rather than instantiating.
    2251         * </p>
    2252         */
    2253        JsonToStringStyle() {
    2254            this.setUseClassName(false);
    2255            this.setUseIdentityHashCode(false);
    2256
    2257            this.setContentStart("{");
    2258            this.setContentEnd("}");
    2259
    2260            this.setArrayStart("[");
    2261            this.setArrayEnd("]");
    2262
    2263            this.setFieldSeparator(",");
    2264            this.setFieldNameValueSeparator(":");
    2265
    2266            this.setNullText("null");
    2267
    2268            this.setSummaryObjectStartText("\"<");
    2269            this.setSummaryObjectEndText(">\"");
    2270
    2271            this.setSizeStartText("\"<size=");
    2272            this.setSizeEndText(">\"");
    2273        }
    2274
    2275        @Override
    2276        public void append(final StringBuffer buffer, final String fieldName,
    2277                           final Object[] array, final Boolean fullDetail) {
    2278
    2279            if (fieldName == null) {
    2280                throw new UnsupportedOperationException(
    2281                        "Field names are mandatory when using JsonToStringStyle");
    2282            }
    2283            if (!isFullDetail(fullDetail)) {
    2284                throw new UnsupportedOperationException(
    2285                        "FullDetail must be true when using JsonToStringStyle");
    2286            }
    2287
    2288            super.append(buffer, fieldName, array, fullDetail);
    2289        }
    2290
    2291        @Override
    2292        public void append(final StringBuffer buffer, final String fieldName, final long[] array,
    2293                           final Boolean fullDetail) {
    2294
    2295            if (fieldName == null) {
    2296                throw new UnsupportedOperationException(
    2297                        "Field names are mandatory when using JsonToStringStyle");
    2298            }
    2299            if (!isFullDetail(fullDetail)) {
    2300                throw new UnsupportedOperationException(
    2301                        "FullDetail must be true when using JsonToStringStyle");
    2302            }
    2303
    2304            super.append(buffer, fieldName, array, fullDetail);
    2305        }
    2306
    2307        @Override
    2308        public void append(final StringBuffer buffer, final String fieldName, final int[] array,
    2309                           final Boolean fullDetail) {
    2310
    2311            if (fieldName == null) {
    2312                throw new UnsupportedOperationException(
    2313                        "Field names are mandatory when using JsonToStringStyle");
    2314            }
    2315            if (!isFullDetail(fullDetail)) {
    2316                throw new UnsupportedOperationException(
    2317                        "FullDetail must be true when using JsonToStringStyle");
    2318            }
    2319
    2320            super.append(buffer, fieldName, array, fullDetail);
    2321        }
    2322
    2323        @Override
    2324        public void append(final StringBuffer buffer, final String fieldName,
    2325                           final short[] array, final Boolean fullDetail) {
    2326
    2327            if (fieldName == null) {
    2328                throw new UnsupportedOperationException(
    2329                        "Field names are mandatory when using JsonToStringStyle");
    2330            }
    2331            if (!isFullDetail(fullDetail)) {
    2332                throw new UnsupportedOperationException(
    2333                        "FullDetail must be true when using JsonToStringStyle");
    2334            }
    2335
    2336            super.append(buffer, fieldName, array, fullDetail);
    2337        }
    2338
    2339        @Override
    2340        public void append(final StringBuffer buffer, final String fieldName, final byte[] array,
    2341                           final Boolean fullDetail) {
    2342
    2343            if (fieldName == null) {
    2344                throw new UnsupportedOperationException(
    2345                        "Field names are mandatory when using JsonToStringStyle");
    2346            }
    2347            if (!isFullDetail(fullDetail)) {
    2348                throw new UnsupportedOperationException(
    2349                        "FullDetail must be true when using JsonToStringStyle");
    2350            }
    2351
    2352            super.append(buffer, fieldName, array, fullDetail);
    2353        }
    2354
    2355        @Override
    2356        public void append(final StringBuffer buffer, final String fieldName, final char[] array,
    2357                           final Boolean fullDetail) {
    2358
    2359            if (fieldName == null) {
    2360                throw new UnsupportedOperationException(
    2361                        "Field names are mandatory when using JsonToStringStyle");
    2362            }
    2363            if (!isFullDetail(fullDetail)) {
    2364                throw new UnsupportedOperationException(
    2365                        "FullDetail must be true when using JsonToStringStyle");
    2366            }
    2367
    2368            super.append(buffer, fieldName, array, fullDetail);
    2369        }
    2370
    2371        @Override
    2372        public void append(final StringBuffer buffer, final String fieldName,
    2373                           final double[] array, final Boolean fullDetail) {
    2374
    2375            if (fieldName == null) {
    2376                throw new UnsupportedOperationException(
    2377                        "Field names are mandatory when using JsonToStringStyle");
    2378            }
    2379            if (!isFullDetail(fullDetail)) {
    2380                throw new UnsupportedOperationException(
    2381                        "FullDetail must be true when using JsonToStringStyle");
    2382            }
    2383
    2384            super.append(buffer, fieldName, array, fullDetail);
    2385        }
    2386
    2387        @Override
    2388        public void append(final StringBuffer buffer, final String fieldName,
    2389                           final float[] array, final Boolean fullDetail) {
    2390
    2391            if (fieldName == null) {
    2392                throw new UnsupportedOperationException(
    2393                        "Field names are mandatory when using JsonToStringStyle");
    2394            }
    2395            if (!isFullDetail(fullDetail)) {
    2396                throw new UnsupportedOperationException(
    2397                        "FullDetail must be true when using JsonToStringStyle");
    2398            }
    2399
    2400            super.append(buffer, fieldName, array, fullDetail);
    2401        }
    2402
    2403        @Override
    2404        public void append(final StringBuffer buffer, final String fieldName,
    2405                           final boolean[] array, final Boolean fullDetail) {
    2406
    2407            if (fieldName == null) {
    2408                throw new UnsupportedOperationException(
    2409                        "Field names are mandatory when using JsonToStringStyle");
    2410            }
    2411            if (!isFullDetail(fullDetail)) {
    2412                throw new UnsupportedOperationException(
    2413                        "FullDetail must be true when using JsonToStringStyle");
    2414            }
    2415
    2416            super.append(buffer, fieldName, array, fullDetail);
    2417        }
    2418
    2419        @Override
    2420        public void append(final StringBuffer buffer, final String fieldName, final Object value,
    2421                           final Boolean fullDetail) {
    2422
    2423            if (fieldName == null) {
    2424                throw new UnsupportedOperationException(
    2425                        "Field names are mandatory when using JsonToStringStyle");
    2426            }
    2427            if (!isFullDetail(fullDetail)) {
    2428                throw new UnsupportedOperationException(
    2429                        "FullDetail must be true when using JsonToStringStyle");
    2430            }
    2431
    2432            super.append(buffer, fieldName, value, fullDetail);
    2433        }
    2434
    2435        @Override
    2436        protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) {
    2437            appendValueAsString(buffer, String.valueOf(value));
    2438        }
    2439
    2440        @Override
    2441        protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
    2442
    2443            if (value == null) {
    2444                appendNullText(buffer, fieldName);
    2445                return;
    2446            }
    2447
    2448            if (value instanceof String || value instanceof Character) {
    2449                appendValueAsString(buffer, value.toString());
    2450                return;
    2451            }
    2452
    2453            if (value instanceof Number || value instanceof Boolean) {
    2454                buffer.append(value);
    2455                return;
    2456            }
    2457
    2458            final String valueAsString = value.toString();
    2459            if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) {
    2460                buffer.append(value);
    2461                return;
    2462            }
    2463
    2464            appendDetail(buffer, fieldName, valueAsString);
    2465        }
    2466
    2467        @Override
    2468        protected void appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll) {
    2469            if (coll != null && !coll.isEmpty()) {
    2470                buffer.append(getArrayStart());
    2471                int i = 0;
    2472                for (final Object item : coll) {
    2473                    appendDetail(buffer, fieldName, i++, item);
    2474                }
    2475                buffer.append(getArrayEnd());
    2476                return;
    2477            }
    2478
    2479            buffer.append(coll);
    2480        }
    2481
    2482        @Override
    2483        protected void appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map) {
    2484            if (map != null && !map.isEmpty()) {
    2485                buffer.append(getContentStart());
    2486
    2487                boolean firstItem = true;
    2488                for (final Entry<?, ?> entry : map.entrySet()) {
    2489                    final String keyStr = Objects.toString(entry.getKey(), null);
    2490                    if (keyStr != null) {
    2491                        if (firstItem) {
    2492                            firstItem = false;
    2493                        } else {
    2494                            appendFieldEnd(buffer, keyStr);
    2495                        }
    2496                        appendFieldStart(buffer, keyStr);
    2497                        final Object value = entry.getValue();
    2498                        if (value == null) {
    2499                            appendNullText(buffer, keyStr);
    2500                        } else {
    2501                            appendInternal(buffer, keyStr, value, true);
    2502                        }
    2503                    }
    2504                }
    2505
    2506                buffer.append(getContentEnd());
    2507                return;
    2508            }
    2509
    2510            buffer.append(map);
    2511        }
    2512
    2513        private boolean isJsonArray(final String valueAsString) {
    2514            return valueAsString.startsWith(getArrayStart())
    2515                    && valueAsString.endsWith(getArrayEnd());
    2516        }
    2517
    2518        private boolean isJsonObject(final String valueAsString) {
    2519            return valueAsString.startsWith(getContentStart())
    2520                    && valueAsString.endsWith(getContentEnd());
    2521        }
    2522
    2523        /**
    2524         * Appends the given String enclosed in double-quotes to the given StringBuffer.
    2525         *
    2526         * @param buffer the StringBuffer to append the value to.
    2527         * @param value the value to append.
    2528         */
    2529        private void appendValueAsString(final StringBuffer buffer, final String value) {
    2530            buffer.append('"').append(StringEscapeUtils.escapeJson(value)).append('"');
    2531        }
    2532
    2533        @Override
    2534        protected void appendFieldStart(final StringBuffer buffer, final String fieldName) {
    2535
    2536            if (fieldName == null) {
    2537                throw new UnsupportedOperationException(
    2538                        "Field names are mandatory when using JsonToStringStyle");
    2539            }
    2540
    2541            super.appendFieldStart(buffer, FIELD_NAME_QUOTE + StringEscapeUtils.escapeJson(fieldName)
    2542                    + FIELD_NAME_QUOTE);
    2543        }
    2544
    2545        /**
    2546         * Ensure Singleton after serialization.
    2547         *
    2548         * @return the singleton
    2549         */
    2550        private Object readResolve() {
    2551            return JSON_STYLE;
    2552        }
    2553
    2554    }
    2555}