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