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