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 006package fc.util; 007 008import fc.io.*; 009import fc.util.*; 010 011import java.util.*; 012import java.lang.reflect.*; 013 014/** 015Makes creating <tt>toString</tt> methods easier. (For example, 016provides ability to introspect and write field values). Idea 017inspired by a similar apache/jakarta utility. 018<p> 019Methods of the form append(String, type) imply the name 020specified by the string (typically a field name) is shown 021with value specified by type. 022<p> 023Example usage:<br> 024<tt>foo</tt> and <tt>bar</tt> are fields of this object. 025<blockquote> 026<pre> 027public 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> 035Another example:<br> 036Automatically prints this entire object using reflection. 037<blockquote> 038<pre> 039public 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> 047The class only needs to be instantiated once so here's a more 048efficient approach: 049<blockquote> 050<pre> 051{ //instance initializer 052ToString tostr = new ToString(this); 053} 054public 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**/ 064public class ToString 065{ 066private static final boolean dbg = false; 067private static Style defaultStyle = new Style(); 068 069Object client; 070Style style; 071StringBuffer result; 072boolean firstFieldDone; 073 074/** 075Creates a ToString for the specified object, using the 076default {@link ToString.Style}. 077**/ 078public ToString(Object obj) { 079 this(obj, (Style)null); 080 } 081 082/** 083Creates a ToString for the specified object using the 084specified style 085@param obj the target object 086@param style the formatting style 087**/ 088public 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/** 102Creates a ToString for the specified object with the 103specified visibility level. 104 105@param obj the target object 106@param style the formatting style 107**/ 108public 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/** 119Returns the default style object. Changes to this will affect 120the default formatting 121**/ 122public static ToString.Style getDefaultStyle() 123 { 124 return defaultStyle; 125 } 126 127/** 128Sets the style object to use as the default. This style will 129be used by default by all new instances of ToString. 130 131@param style the default style 132**/ 133public static void setDefaultStyle(ToString.Style style) 134 { 135 Argcheck.notnull(style, "style cannot be null"); 136 defaultStyle = style; 137 } 138 139/** 140Returns the style being currently used by this instance. 141Modifications to this style this will affect rendering 142output appropriately. 143**/ 144public ToString.Style getStyle() { 145 return style; 146 } 147 148 149/** Returns the internal buffer used to create the string **/ 150public StringBuffer getBuffer() { 151 return result; 152 } 153 154/** 155Uses reflection to get the contents of the object. Reflection 156does not expand and print all the array values even if the 157current style's {@link ToString.Style#expandArray expandArray} 158is set to true. To print all array values, use the 159<tt>append</tt> methods. 160**/ 161public 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 173void reflectImpl(Object obj, Class clazz) 174throws 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 252void 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 279void doEnd(StringBuffer buf) { 280 buf.append(style.endContent); 281 buf.append(style.endString); 282 } 283 284/** Renders the string **/ 285public 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/** 295Returns information about the current state of the ToString 296object itself. To get the target object's string, use the 297{@link render} method. 298**/ 299public String toString() 300 { 301 return getClass().getName() + "; using: " + style; 302 } 303 304/** Unit test **/ 305public 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 390private static class TestClass 391{ 392private Style style; 393private boolean useReflection; 394 395TestClass(Style style, boolean useReflection) { 396 this.style = style; 397 this.useReflection = useReflection; 398 } 399 400static int staticInt = 10; 401public static String staticString = "staticString"; 402 403public String pubString = "publicString"; 404protected 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(); 410private String privateString = "privateStrng"; 411 412public 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/** 427Drives the formatting behavior. Behavior different than 428the defaults can be achieved by instantiating a new object 429and setting it's properties appropriately. 430**/ 431public 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/** 547Appends an arbitrary string to the result. This can be 548used as a prologue, epilogue etc. 549**/ 550public ToString append(Object str) 551 { 552 result.append(str); 553 return this; 554 } 555 556public 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 571public 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 587public 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 601public 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 615public 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 629public 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 643public 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 657public 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 671public 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 685public 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 700public 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 728public 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 756public 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 784public 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 812public 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 840public 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 868public 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 896public 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 924public 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