001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org)
002 // The Molly framework is freely distributable under the terms of an
003 // MIT-style license. For details, see the molly pages web site at:
004 // http://www.mollypages.org/. Use, modify, have fun !
005
006 package fc.util;
007
008 import fc.io.*;
009 import fc.util.*;
010
011 import java.util.*;
012 import java.lang.reflect.*;
013
014 /**
015 Makes creating <tt>toString</tt> methods easier. (For example,
016 provides ability to introspect and write field values). Idea
017 inspired by a similar apache/jakarta utility.
018 <p>
019 Methods of the form append(String, type) imply the name
020 specified by the string (typically a field name) is shown
021 with value specified by type.
022 <p>
023 Example usage:<br>
024 <tt>foo</tt> and <tt>bar</tt> are fields of this object.
025 <blockquote>
026 <pre>
027 public String toString() {
028 return new ToString(this).
029 <font color="blue">append</font>("foo","some-value").
030 <font color="blue">append</font>("bar",123).
031 <font color="blue">render</font>();
032 }
033 </pre>
034 </blockquote>
035 Another example:<br>
036 Automatically prints this entire object using reflection.
037 <blockquote>
038 <pre>
039 public String toString() {
040 return new ToString(this).<font color="blue">reflect</font>().
041 <font color="blue">render</font>();
042 }
043 </pre>
044 </blockquote>
045 <i><b>Note</b>: Don't forget the <tt>render()</tt> call at the end.</i>
046 <p>
047 The class only needs to be instantiated once so here's a more
048 efficient approach:
049 <blockquote>
050 <pre>
051 { //instance initializer
052 ToString tostr = new ToString(this);
053 }
054 public String toString() {
055 return tostr.<font color="blue">reflect</font>().
056 <font color="blue">render</font>();
057 }
058 </pre>
059 </blockquote>
060 <p>
061
062 @author hursh jain
063 **/
064 public class ToString
065 {
066 private static final boolean dbg = false;
067 private static Style defaultStyle = new Style();
068
069 Object client;
070 Style style;
071 StringBuffer result;
072 boolean firstFieldDone;
073
074 /**
075 Creates a ToString for the specified object, using the
076 default {@link ToString.Style}.
077 **/
078 public ToString(Object obj) {
079 this(obj, (Style)null);
080 }
081
082 /**
083 Creates a ToString for the specified object using the
084 specified style
085 @param obj the target object
086 @param style the formatting style
087 **/
088 public ToString(Object obj, Style style)
089 {
090 Argcheck.notnull(obj, "target object cannot be null");
091 client = obj;
092
093 if (style == null)
094 this.style = defaultStyle;
095 else
096 this.style = style;
097
098 result = new StringBuffer();
099 }
100
101 /**
102 Creates a ToString for the specified object with the
103 specified visibility level.
104
105 @param obj the target object
106 @param style the formatting style
107 **/
108 public ToString(Object obj, Style.VisibleLevel level)
109 {
110 Argcheck.notnull(obj, "target object cannot be null");
111 client = obj;
112 this.style = defaultStyle;
113 this.style.reflectVisibleLevel = level;
114 result = new StringBuffer();
115 }
116
117
118 /**
119 Returns the default style object. Changes to this will affect
120 the default formatting
121 **/
122 public static ToString.Style getDefaultStyle()
123 {
124 return defaultStyle;
125 }
126
127 /**
128 Sets the style object to use as the default. This style will
129 be used by default by all new instances of ToString.
130
131 @param style the default style
132 **/
133 public static void setDefaultStyle(ToString.Style style)
134 {
135 Argcheck.notnull(style, "style cannot be null");
136 defaultStyle = style;
137 }
138
139 /**
140 Returns the style being currently used by this instance.
141 Modifications to this style this will affect rendering
142 output appropriately.
143 **/
144 public ToString.Style getStyle() {
145 return style;
146 }
147
148
149 /** Returns the internal buffer used to create the string **/
150 public StringBuffer getBuffer() {
151 return result;
152 }
153
154 /**
155 Uses reflection to get the contents of the object. Reflection
156 does not expand and print all the array values even if the
157 current style's {@link ToString.Style#expandArray expandArray}
158 is set to true. To print all array values, use the
159 <tt>append</tt> methods.
160 **/
161 public ToString reflect()
162 {
163 try {
164 reflectImpl(client, client.getClass());
165 }
166 catch (IllegalAccessException e) {
167 result.append("Cannot convert to string using reflection");
168 result.append(e.toString());
169 }
170 return this;
171 }
172
173 void reflectImpl(Object obj, Class clazz)
174 throws IllegalAccessException
175 {
176 if (dbg) System.out.println("reflectImpl("+clazz+")");
177
178 Field[] fields = clazz.getDeclaredFields();
179
180 if (dbg) System.out.println("got declared fields: " + Arrays.asList(fields));
181
182 //need this call, otherwise getting the fields does not work
183 Field.setAccessible(fields, true);
184
185 for(int n = 0; n < fields.length; n++)
186 {
187 //if (dbg) System.out.println("declaredField["+n+"]; '"+fields[n].getName()+"'="+fields[n].get(obj));
188
189 Field f = fields[n];
190 int mod = f.getModifiers();
191
192 if (dbg) System.out.println(f.getName() +" modifier="+mod);
193
194 if (! style.reflectStatics && Modifier.isStatic(mod)) {
195 if (dbg) System.out.println("reflectStatics = false, ignoring static field: " + f.getName());
196 continue;
197 }
198
199 boolean defaultVis = ! (Modifier.isPublic(mod)
200 || Modifier.isProtected(mod)
201 || Modifier.isPrivate(mod));
202
203
204 if (style.ignoredFieldNames.contains(f.getName().toLowerCase())) {
205 if (dbg) System.out.println("ignoring: " + f.getName() + " [in ingorelist]");
206 continue;
207 }
208
209 if (style.reflectVisibleLevel==Style.VisibleLevel.PUBLIC)
210 {
211 if (Modifier.isPublic(mod)) {
212 append(f.getName(), f.get(obj));
213 }
214 }
215 else if (style.reflectVisibleLevel==Style.VisibleLevel.PROTECTED)
216 {
217 if (Modifier.isPublic(mod) || Modifier.isProtected(mod)) {
218 append(f.getName(), f.get(obj));
219 }
220 }
221 else if (style.reflectVisibleLevel==Style.VisibleLevel.DEFAULT)
222 {
223 if (Modifier.isPublic(mod) || Modifier.isProtected(mod)
224 || defaultVis) {
225 append(f.getName(), f.get(obj));
226 }
227 }
228 else if (style.reflectVisibleLevel==Style.VisibleLevel.PRIVATE)
229 {
230 if (Modifier.isPublic(mod) || Modifier.isProtected(mod)
231 || Modifier.isPrivate(mod) || defaultVis) {
232 append(f.getName(), f.get(obj));
233 }
234 }
235 else
236 {
237 System.out.println("ERROR in ToString().reflecImpl(), this should not happen, toString won't be accurate");
238 }
239
240 if (dbg) System.out.println("buf=>>"+result+"<<");
241 } //~for
242
243 if (style.reflectSuperClass) {
244 Class superclazz = clazz.getSuperclass();
245 if (superclazz != null && superclazz != Object.class)
246 reflectImpl(obj, superclazz);
247 }
248
249 } //reflectImpl
250
251
252 void doStart(StringBuffer buf)
253 {
254 buf.append(style.startString);
255
256 Class clazz = client.getClass();
257 String name = clazz.getName();
258
259 if (style.className)
260 {
261 if (style.fullClassName) {
262 buf.append(name);
263 }
264 else {
265 int last = name.lastIndexOf(".");
266 last = (last < 0) ? 0 : last + 1; //+1 to skip '.' itself
267 buf.append( name.substring(last, name.length()) );
268 }
269 }
270
271 if (style.idHashCode) {
272 buf.append("@");
273 buf.append(System.identityHashCode(client));
274 }
275
276 buf.append(style.startContent);
277 }
278
279 void doEnd(StringBuffer buf) {
280 buf.append(style.endContent);
281 buf.append(style.endString);
282 }
283
284 /** Renders the string **/
285 public String render()
286 {
287 StringBuffer finalResult = new StringBuffer(result.length() + 128);
288 doStart(finalResult);
289 finalResult.append(result);
290 doEnd(finalResult);
291 return finalResult.toString();
292 }
293
294 /**
295 Returns information about the current state of the ToString
296 object itself. To get the target object's string, use the
297 {@link render} method.
298 **/
299 public String toString()
300 {
301 return getClass().getName() + "; using: " + style;
302 }
303
304 /** Unit test **/
305 public static void main(String[] args)
306 {
307 System.out.println("=== Test using reflection ===");
308
309 Style style = null;
310
311 style = new Style();
312 style.ignoreFieldName("style");
313 style.reflectVisibleLevel = Style.VisibleLevel.PUBLIC;
314 style.reflectStatics = true;
315 System.out.println("public fields [including statics]");
316 System.out.println(new TestClass(style, true));
317 System.out.println("");
318
319 style = new Style();
320 style.ignoreFieldName("style");
321 style.reflectVisibleLevel = Style.VisibleLevel.PUBLIC;
322 System.out.println("public fields only");
323 System.out.println(new TestClass(style, true));
324 System.out.println("");
325
326 style = new Style();
327 style.ignoreFieldName("style");
328 style.reflectVisibleLevel = Style.VisibleLevel.PUBLIC;
329 style.idHashCode = false;
330 style.className = false;
331 System.out.println("public fields only, no id ref or class name");
332 System.out.println(new TestClass(style, true));
333 System.out.println("");
334
335 style = new Style();
336 style.ignoreFieldName("style");
337 style.reflectVisibleLevel = Style.VisibleLevel.PROTECTED;
338 System.out.println("protected and higher");
339 System.out.println(new TestClass(style, true));
340 System.out.println("");
341
342 style = new Style();
343 style.ignoreFieldName("style");
344 style.reflectStatics = true;
345 style.reflectVisibleLevel = Style.VisibleLevel.DEFAULT;
346 System.out.println("package and higher [including statics]");
347 System.out.println(new TestClass(style, true));
348 System.out.println("");
349
350 style = new Style();
351 style.ignoreFieldName("style");
352 style.reflectVisibleLevel = Style.VisibleLevel.DEFAULT;
353 style.expandArrays = true;
354 System.out.println("package and higher fields, arrays EXPANDED");
355 System.out.println(new TestClass(style, true));
356 System.out.println("");
357
358 style = new Style();
359 style.ignoreFieldName("style");
360 style.reflectVisibleLevel = Style.VisibleLevel.PRIVATE;
361 System.out.println("private (all) fields");
362 System.out.println(new TestClass(style, true));
363 System.out.println("");
364
365 style = new Style();
366 style.ignoreFieldName("style");
367 System.out.println("default style output");
368 System.out.println(new TestClass(style, true));
369 System.out.println("");
370
371 System.out.println("==== Test without reflection ====");
372 style = new Style();
373 System.out.println(new TestClass(style, false));
374 System.out.println("");
375
376 System.out.println("With no field names");
377 style = new Style();
378 style.fieldName = false;
379 System.out.println(new TestClass(style, false));
380 System.out.println("");
381
382 style = new Style();
383 style.reflectVisibleLevel = Style.VisibleLevel.DEFAULT;
384 style.expandArrays = true;
385 System.out.println("Expanded arrays");
386 System.out.println(new TestClass(style, false));
387 System.out.println("");
388 }
389
390 private static class TestClass
391 {
392 private Style style;
393 private boolean useReflection;
394
395 TestClass(Style style, boolean useReflection) {
396 this.style = style;
397 this.useReflection = useReflection;
398 }
399
400 static int staticInt = 10;
401 public static String staticString = "staticString";
402
403 public String pubString = "publicString";
404 protected String protectedString = "protectedString";
405 String defString = "defaultString";
406 int[] intArray = new int[] { 1, 2, 3};
407 double[] doubleArray = new double[] {1.3, 2.6, 3.9};
408 Object[] objectArray = new Object[] { null, new Object() };
409 List someList = new ArrayList();
410 private String privateString = "privateStrng";
411
412 public String toString()
413 {
414 if (useReflection)
415 return new ToString(this, style).reflect().render();
416 else {
417 return new ToString(this, style).
418 append("intArray", intArray).
419 append("doubleArray", doubleArray).
420 append("pubString", pubString).render();
421 }
422 }
423 } //~TestClass
424
425
426 /**
427 Drives the formatting behavior. Behavior different than
428 the defaults can be achieved by instantiating a new object
429 and setting it's properties appropriately.
430 **/
431 public static class Style
432 {
433 private List ignoredFieldNames = new ArrayList();
434
435 /**
436 Case insensitive field names that will be ignored (for example a
437 public field "foo" may be printed otherwise, but if added to this
438 list, it would be ignored). Use this method to add as many
439 field names as needed.
440 **/
441 public void ignoreFieldName(String name) {
442 ignoredFieldNames.add(name.toLowerCase());
443 }
444
445 /**
446 The start of the entire string. default to empty: <tt>""</tt>
447 **/
448 public String startString = "";
449
450 /**
451 The end of the entire string. default to empty: <tt>""</tt>
452 **/
453 public String endString = "" ;
454
455 /**
456 The start of the string <b>after</b> the object classname and
457 identity reference. default: <tt>[</tt>
458 **/
459 public String startContent = "[" ;
460
461 /**
462 The end of the string <b>after</b> the object classname and
463 identity reference. default: <tt>]</tt>
464 **/
465 public String endContent = "]";
466
467 /** default: <tt>=</tt> **/
468 public String fieldAndValSep = "=";
469
470 /** default: <tt>,</tt> **/
471 public String fieldSep = ", ";
472
473 /** default: <tt>{</tt> **/
474 public String startArray = "{";
475
476 /** default: <tt>}</tt> **/
477 public String endArray = "}";
478
479 /** default: <tt>,</tt> **/
480 public String arrayValSep = ",";
481
482 /**
483 Expand array values, default: <tt>false</tt>. <bNote:</b>
484 expansion only works when arrays are manually added using
485 one of the <tt>append(..)</tt> methods, not when using
486 reflection.
487 **/
488 public boolean expandArrays = false;
489
490 /** Print the field name ? default: <tt>true</tt>. If
491 field names are <b>not</b> printed, then neither is
492 the {@link #FieldAndValSep} - only the value of a field
493 is printed.
494 **/
495 public boolean fieldName = true;
496
497 /** Print the class name at all ? default: <tt>true</tt> **/
498 public boolean className = true;
499
500 /** print full class name ? default: <tt>false</tt> **/
501 public boolean fullClassName = false;
502
503 /** print indentity hash code for the object ? default: <tt>true</tt> **/
504 public boolean idHashCode = true;
505
506 /** print field names when using reflection ? default: <tt>true</tt>**/
507 public boolean reflectFieldName = true;
508
509 /**
510 Prints the superclass's variables when using reflection ? default: <tt>false</tt>
511 **/
512 public boolean reflectSuperClass = false;
513
514 /**
515 Reflects static variables. By default this is <tt>false</tt>
516 since statics are not part of the object's instance-state.
517 **/
518 public boolean reflectStatics = false;
519
520 /**
521 Default access level when using reflection (fields with
522 this or looser access will be printed). default: PRIVATE (EVERYTHING
523 IS PRINTED)
524 **/
525 public VisibleLevel reflectVisibleLevel = VisibleLevel.PRIVATE;
526
527 public static final class VisibleLevel
528 {
529 private VisibleLevel() { }
530 public static VisibleLevel PUBLIC = new VisibleLevel ();
531 public static VisibleLevel PROTECTED = new VisibleLevel ();
532 public static VisibleLevel DEFAULT = new VisibleLevel ();
533 public static VisibleLevel PRIVATE = new VisibleLevel ();
534 }
535
536 public Style() { }
537 public String toString() {
538 return new ToString(this).reflect().render();
539 }
540
541 } //~class Style
542
543
544 //==appends===========================================
545
546 /**
547 Appends an arbitrary string to the result. This can be
548 used as a prologue, epilogue etc.
549 **/
550 public ToString append(Object str)
551 {
552 result.append(str);
553 return this;
554 }
555
556 public ToString append(String fieldName, Object val)
557 {
558 if (firstFieldDone)
559 result.append(style.fieldSep);
560
561 if (style.fieldName) {
562 result.append(fieldName);
563 result.append(style.fieldAndValSep);
564 }
565 result.append(val);
566
567 firstFieldDone = true;
568 return this;
569 }
570
571 public ToString append(String fieldName, String val)
572 {
573 if (firstFieldDone)
574 result.append(style.fieldSep);
575
576 if (style.fieldName) {
577 result.append(fieldName);
578 result.append(style.fieldAndValSep);
579 }
580 result.append(val);
581
582 firstFieldDone = true;
583 return this;
584 }
585
586 //Primitive Types
587 public ToString append(String fieldName, long val)
588 {
589 if (firstFieldDone)
590 result.append(style.fieldSep);
591 if (style.fieldName) {
592 result.append(fieldName);
593 result.append(style.fieldAndValSep);
594 }
595 result.append(val);
596
597 firstFieldDone = true;
598 return this;
599 }
600
601 public ToString append(String fieldName, int val)
602 {
603 if (firstFieldDone)
604 result.append(style.fieldSep);
605 if (style.fieldName) {
606 result.append(fieldName);
607 result.append(style.fieldAndValSep);
608 }
609 result.append(val);
610
611 firstFieldDone = true;
612 return this;
613 }
614
615 public ToString append(String fieldName, short val)
616 {
617 if (firstFieldDone)
618 result.append(style.fieldSep);
619 if (style.fieldName) {
620 result.append(fieldName);
621 result.append(style.fieldAndValSep);
622 }
623 result.append(val);
624
625 firstFieldDone = true;
626 return this;
627 }
628
629 public ToString append(String fieldName, byte val)
630 {
631 if (firstFieldDone)
632 result.append(style.fieldSep);
633 if (style.fieldName) {
634 result.append(fieldName);
635 result.append(style.fieldAndValSep);
636 }
637 result.append(val);
638
639 firstFieldDone = true;
640 return this;
641 }
642
643 public ToString append(String fieldName, double val)
644 {
645 if (firstFieldDone)
646 result.append(style.fieldSep);
647 if (style.fieldName) {
648 result.append(fieldName);
649 result.append(style.fieldAndValSep);
650 }
651 result.append(val);
652
653 firstFieldDone = true;
654 return this;
655 }
656
657 public ToString append(String fieldName, float val)
658 {
659 if (firstFieldDone)
660 result.append(style.fieldSep);
661 if (style.fieldName) {
662 result.append(fieldName);
663 result.append(style.fieldAndValSep);
664 }
665 result.append(val);
666
667 firstFieldDone = true;
668 return this;
669 }
670
671 public ToString append(String fieldName, char val)
672 {
673 if (firstFieldDone)
674 result.append(style.fieldSep);
675 if (style.fieldName) {
676 result.append(fieldName);
677 result.append(style.fieldAndValSep);
678 }
679 result.append(val);
680
681 firstFieldDone = true;
682 return this;
683 }
684
685 public ToString append(String fieldName, boolean val)
686 {
687 if (firstFieldDone)
688 result.append(style.fieldSep);
689 if (style.fieldName) {
690 result.append(fieldName);
691 result.append(style.fieldAndValSep);
692 }
693 result.append(val);
694
695 firstFieldDone = true;
696 return this;
697 }
698
699 //Array types
700 public ToString append(String fieldName, Object[] val)
701 {
702 if (firstFieldDone)
703 result.append(style.fieldSep);
704
705 if (style.fieldName) {
706 result.append(fieldName);
707 result.append(style.fieldAndValSep);
708 }
709
710 if (style.expandArrays)
711 {
712 result.append(style.startArray);
713 for (int n = 0; n < val.length; n++)
714 {
715 if (n != 0)
716 result.append(style.arrayValSep);
717 result.append(val[n]);
718 }
719 result.append(style.endArray);
720 }
721 else
722 result.append(val);
723
724 firstFieldDone = true;
725 return this;
726 }
727
728 public ToString append(String fieldName, long[] val)
729 {
730 if (firstFieldDone)
731 result.append(style.fieldSep);
732
733 if (style.fieldName) {
734 result.append(fieldName);
735 result.append(style.fieldAndValSep);
736 }
737
738 if (style.expandArrays)
739 {
740 result.append(style.startArray);
741 for (int n = 0; n < val.length; n++)
742 {
743 if (n != 0)
744 result.append(style.arrayValSep);
745 result.append(val[n]);
746 }
747 result.append(style.endArray);
748 }
749 else
750 result.append(val);
751
752 firstFieldDone = true;
753 return this;
754 }
755
756 public ToString append(String fieldName, int[] val)
757 {
758 if (firstFieldDone)
759 result.append(style.fieldSep);
760
761 if (style.fieldName) {
762 result.append(fieldName);
763 result.append(style.fieldAndValSep);
764 }
765
766 if (style.expandArrays)
767 {
768 result.append(style.startArray);
769 for (int n = 0; n < val.length; n++)
770 {
771 if (n != 0)
772 result.append(style.arrayValSep);
773 result.append(val[n]);
774 }
775 result.append(style.endArray);
776 }
777 else
778 result.append(val);
779
780 firstFieldDone = true;
781 return this;
782 }
783
784 public ToString append(String fieldName, short[] val)
785 {
786 if (firstFieldDone)
787 result.append(style.fieldSep);
788
789 if (style.fieldName) {
790 result.append(fieldName);
791 result.append(style.fieldAndValSep);
792 }
793
794 if (style.expandArrays)
795 {
796 result.append(style.startArray);
797 for (int n = 0; n < val.length; n++)
798 {
799 if (n != 0)
800 result.append(style.arrayValSep);
801 result.append(val[n]);
802 }
803 result.append(style.endArray);
804 }
805 else
806 result.append(val);
807
808 firstFieldDone = true;
809 return this;
810 }
811
812 public ToString append(String fieldName, byte[] val)
813 {
814 if (firstFieldDone)
815 result.append(style.fieldSep);
816
817 if (style.fieldName) {
818 result.append(fieldName);
819 result.append(style.fieldAndValSep);
820 }
821
822 if (style.expandArrays)
823 {
824 result.append(style.startArray);
825 for (int n = 0; n < val.length; n++)
826 {
827 if (n != 0)
828 result.append(style.arrayValSep);
829 result.append(val[n]);
830 }
831 result.append(style.endArray);
832 }
833 else
834 result.append(val);
835
836 firstFieldDone = true;
837 return this;
838 }
839
840 public ToString append(String fieldName, char[] val)
841 {
842 if (firstFieldDone)
843 result.append(style.fieldSep);
844
845 if (style.fieldName) {
846 result.append(fieldName);
847 result.append(style.fieldAndValSep);
848 }
849
850 if (style.expandArrays)
851 {
852 result.append(style.startArray);
853 for (int n = 0; n < val.length; n++)
854 {
855 if (n != 0)
856 result.append(style.arrayValSep);
857 result.append(val[n]);
858 }
859 result.append(style.endArray);
860 }
861 else
862 result.append(val);
863
864 firstFieldDone = true;
865 return this;
866 }
867
868 public ToString append(String fieldName, double[] val)
869 {
870 if (firstFieldDone)
871 result.append(style.fieldSep);
872
873 if (style.fieldName) {
874 result.append(fieldName);
875 result.append(style.fieldAndValSep);
876 }
877
878 if (style.expandArrays)
879 {
880 result.append(style.startArray);
881 for (int n = 0; n < val.length; n++)
882 {
883 if (n != 0)
884 result.append(style.arrayValSep);
885 result.append(val[n]);
886 }
887 result.append(style.endArray);
888 }
889 else
890 result.append(val);
891
892 firstFieldDone = true;
893 return this;
894 }
895
896 public ToString append(String fieldName, float[] val)
897 {
898 if (firstFieldDone)
899 result.append(style.fieldSep);
900
901 if (style.fieldName) {
902 result.append(fieldName);
903 result.append(style.fieldAndValSep);
904 }
905
906 if (style.expandArrays)
907 {
908 result.append(style.startArray);
909 for (int n = 0; n < val.length; n++)
910 {
911 if (n != 0)
912 result.append(style.arrayValSep);
913 result.append(val[n]);
914 }
915 result.append(style.endArray);
916 }
917 else
918 result.append(val);
919
920 firstFieldDone = true;
921 return this;
922 }
923
924 public ToString append(String fieldName, boolean[] val)
925 {
926 if (firstFieldDone)
927 result.append(style.fieldSep);
928
929 if (style.fieldName) {
930 result.append(fieldName);
931 result.append(style.fieldAndValSep);
932 }
933
934 if (style.expandArrays)
935 {
936 result.append(style.startArray);
937 for (int n = 0; n < val.length; n++)
938 {
939 if (n != 0)
940 result.append(style.arrayValSep);
941 result.append(val[n]);
942 }
943 result.append(style.endArray);
944 }
945 else
946 result.append(val);
947
948 firstFieldDone = true;
949 return this;
950 }
951
952 //==end appends============================================
953
954 } //~class ToString