• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.lang3.builder;
18 
19 import java.io.Serializable;
20 import java.lang.reflect.Array;
21 import java.util.Collection;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Objects;
25 import java.util.WeakHashMap;
26 
27 import org.apache.commons.lang3.ClassUtils;
28 import org.apache.commons.lang3.ObjectUtils;
29 import org.apache.commons.lang3.StringEscapeUtils;
30 import org.apache.commons.lang3.StringUtils;
31 
32 /**
33  * Controls {@link String} formatting for {@link ToStringBuilder}.
34  * The main public interface is always via {@link ToStringBuilder}.
35  *
36  * <p>These classes are intended to be used as <em>singletons</em>.
37  * There is no need to instantiate a new style each time. A program
38  * will generally use one of the predefined constants on this class.
39  * Alternatively, the {@link StandardToStringStyle} class can be used
40  * to set the individual settings. Thus most styles can be achieved
41  * without subclassing.</p>
42  *
43  * <p>If required, a subclass can override as many or as few of the
44  * methods as it requires. Each object type (from {@code boolean}
45  * to {@code long} to {@link Object} to {@code int[]}) has
46  * its own methods to output it. Most have two versions, detail and summary.
47  *
48  * <p>For example, the detail version of the array based methods will
49  * output the whole array, whereas the summary method will just output
50  * the array length.</p>
51  *
52  * <p>If you want to format the output of certain objects, such as dates, you
53  * must create a subclass and override a method.
54  * </p>
55  * <pre>
56  * public class MyStyle extends ToStringStyle {
57  *   protected void appendDetail(StringBuffer buffer, String fieldName, Object value) {
58  *     if (value instanceof Date) {
59  *       value = new SimpleDateFormat("yyyy-MM-dd").format(value);
60  *     }
61  *     buffer.append(value);
62  *   }
63  * }
64  * </pre>
65  *
66  * @since 1.0
67  */
68 @SuppressWarnings("deprecation") // StringEscapeUtils
69 public abstract class ToStringStyle implements Serializable {
70 
71     /**
72      * Serialization version ID.
73      */
74     private static final long serialVersionUID = -2587890625525655916L;
75 
76     /**
77      * The default toString style. Using the {@code Person}
78      * example from {@link ToStringBuilder}, the output would look like this:
79      *
80      * <pre>
81      * Person@182f0db[name=John Doe,age=33,smoker=false]
82      * </pre>
83      */
84     public static final ToStringStyle DEFAULT_STYLE = new DefaultToStringStyle();
85 
86     /**
87      * The multi line toString style. Using the {@code Person}
88      * example from {@link ToStringBuilder}, the output would look like this:
89      *
90      * <pre>
91      * Person@182f0db[
92      *   name=John Doe
93      *   age=33
94      *   smoker=false
95      * ]
96      * </pre>
97      */
98     public static final ToStringStyle MULTI_LINE_STYLE = new MultiLineToStringStyle();
99 
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      */
getRegistry()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      */
isRegistered(final Object value)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      */
register(final Object value)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      */
unregister(final Object value)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      */
ToStringStyle()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      */
appendSuper(final StringBuffer buffer, final String superToString)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      */
appendToString(final StringBuffer buffer, final String toString)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      */
appendStart(final StringBuffer buffer, final Object object)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      */
appendEnd(final StringBuffer buffer, final Object object)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      */
removeLastFieldSeparator(final StringBuffer buffer)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      */
append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail)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      */
appendInternal(final StringBuffer buffer, final String fieldName, final Object value, final boolean detail)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      */
appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final Object value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final Object value)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      */
append(final StringBuffer buffer, final String fieldName, final long value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final long value)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      */
append(final StringBuffer buffer, final String fieldName, final int value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final int value)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      */
append(final StringBuffer buffer, final String fieldName, final short value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final short value)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      */
append(final StringBuffer buffer, final String fieldName, final byte value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final byte value)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      */
append(final StringBuffer buffer, final String fieldName, final char value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final char value)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      */
append(final StringBuffer buffer, final String fieldName, final double value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final double value)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      */
append(final StringBuffer buffer, final String fieldName, final float value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final float value)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      */
append(final StringBuffer buffer, final String fieldName, final boolean value)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final boolean value)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      */
append(final StringBuffer buffer, final String fieldName, final Object[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final Object[] array)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final int i, final Object item)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      */
reflectionAppendArrayDetail(final StringBuffer buffer, final String fieldName, final Object array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final Object[] array)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      */
append(final StringBuffer buffer, final String fieldName, final long[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final long[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final long[] array)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      */
append(final StringBuffer buffer, final String fieldName, final int[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final int[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final int[] array)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      */
append(final StringBuffer buffer, final String fieldName, final short[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final short[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final short[] array)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      */
append(final StringBuffer buffer, final String fieldName, final byte[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final byte[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final byte[] array)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      */
append(final StringBuffer buffer, final String fieldName, final char[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final char[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final char[] array)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      */
append(final StringBuffer buffer, final String fieldName, final double[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final double[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final double[] array)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      */
append(final StringBuffer buffer, final String fieldName, final float[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final float[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final float[] array)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      */
append(final StringBuffer buffer, final String fieldName, final boolean[] array, final Boolean fullDetail)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      */
appendDetail(final StringBuffer buffer, final String fieldName, final boolean[] array)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      */
appendSummary(final StringBuffer buffer, final String fieldName, final boolean[] array)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      */
appendClassName(final StringBuffer buffer, final Object object)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      */
appendIdentityHashCode(final StringBuffer buffer, final Object object)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      */
appendContentStart(final StringBuffer buffer)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      */
appendContentEnd(final StringBuffer buffer)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      */
appendNullText(final StringBuffer buffer, final String fieldName)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      */
appendFieldSeparator(final StringBuffer buffer)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      */
appendFieldStart(final StringBuffer buffer, final String fieldName)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      */
appendFieldEnd(final StringBuffer buffer, final String fieldName)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      */
appendSummarySize(final StringBuffer buffer, final String fieldName, final int size)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      */
isFullDetail(final Boolean fullDetailRequest)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      */
getShortClassName(final Class<?> cls)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      */
isUseClassName()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      */
setUseClassName(final boolean useClassName)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      */
isUseShortClassName()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      */
setUseShortClassName(final boolean useShortClassName)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      */
isUseIdentityHashCode()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      */
setUseIdentityHashCode(final boolean useIdentityHashCode)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      */
isUseFieldNames()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      */
setUseFieldNames(final boolean useFieldNames)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      */
isDefaultFullDetail()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      */
setDefaultFullDetail(final boolean defaultFullDetail)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      */
isArrayContentDetail()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      */
setArrayContentDetail(final boolean arrayContentDetail)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      */
getArrayStart()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      */
setArrayStart(String arrayStart)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      */
getArrayEnd()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      */
setArrayEnd(String arrayEnd)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      */
getArraySeparator()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      */
setArraySeparator(String arraySeparator)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      */
getContentStart()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      */
setContentStart(String contentStart)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      */
getContentEnd()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      */
setContentEnd(String contentEnd)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      */
getFieldNameValueSeparator()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      */
setFieldNameValueSeparator(String fieldNameValueSeparator)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      */
getFieldSeparator()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      */
setFieldSeparator(String fieldSeparator)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      */
isFieldSeparatorAtStart()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      */
setFieldSeparatorAtStart(final boolean fieldSeparatorAtStart)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      */
isFieldSeparatorAtEnd()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      */
setFieldSeparatorAtEnd(final boolean fieldSeparatorAtEnd)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      */
getNullText()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      */
setNullText(String nullText)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      */
getSizeStartText()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      */
setSizeStartText(String sizeStartText)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      */
getSizeEndText()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      */
setSizeEndText(String sizeEndText)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      */
getSummaryObjectStartText()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      */
setSummaryObjectStartText(String summaryObjectStartText)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      */
getSummaryObjectEndText()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      */
setSummaryObjectEndText(String summaryObjectEndText)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          */
DefaultToStringStyle()2054         DefaultToStringStyle() {
2055         }
2056 
2057         /**
2058          * Ensure Singleton after serialization.
2059          *
2060          * @return the singleton
2061          */
readResolve()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          */
NoFieldNameToStringStyle()2084         NoFieldNameToStringStyle() {
2085             this.setUseFieldNames(false);
2086         }
2087 
2088         /**
2089          * Ensure Singleton after serialization.
2090          *
2091          * @return the singleton
2092          */
readResolve()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 hashcode.
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          */
ShortPrefixToStringStyle()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          */
readResolve()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 hashcode, 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          */
SimpleToStringStyle()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          */
readResolve()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          */
MultiLineToStringStyle()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          */
readResolve()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          */
NoClassNameToStringStyle()2213         NoClassNameToStringStyle() {
2214             this.setUseClassName(false);
2215             this.setUseIdentityHashCode(false);
2216         }
2217 
2218         /**
2219          * Ensure Singleton after serialization.
2220          *
2221          * @return the singleton
2222          */
readResolve()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          */
JsonToStringStyle()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
append(final StringBuffer buffer, final String fieldName, final Object[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final long[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final int[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final short[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final byte[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final char[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final double[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final float[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final boolean[] array, final Boolean fullDetail)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
append(final StringBuffer buffer, final String fieldName, final Object value, final Boolean fullDetail)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
appendDetail(final StringBuffer buffer, final String fieldName, final char value)2436         protected void appendDetail(final StringBuffer buffer, final String fieldName, final char value) {
2437             appendValueAsString(buffer, String.valueOf(value));
2438         }
2439 
2440         @Override
appendDetail(final StringBuffer buffer, final String fieldName, final Object value)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
appendDetail(final StringBuffer buffer, final String fieldName, final Collection<?> coll)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
appendDetail(final StringBuffer buffer, final String fieldName, final Map<?, ?> map)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 
isJsonArray(final String valueAsString)2513         private boolean isJsonArray(final String valueAsString) {
2514             return valueAsString.startsWith(getArrayStart())
2515                     && valueAsString.endsWith(getArrayEnd());
2516         }
2517 
isJsonObject(final String valueAsString)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          */
appendValueAsString(final StringBuffer buffer, final String value)2529         private void appendValueAsString(final StringBuffer buffer, final String value) {
2530             buffer.append('"').append(StringEscapeUtils.escapeJson(value)).append('"');
2531         }
2532 
2533         @Override
appendFieldStart(final StringBuffer buffer, final String fieldName)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          */
readResolve()2550         private Object readResolve() {
2551             return JSON_STYLE;
2552         }
2553 
2554     }
2555 }
2556