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