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