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