Coverage Report - org.apache.ojb.broker.metadata.ObjectReferenceDescriptor
 
Classes in this File Line Coverage Branch Coverage Complexity
ObjectReferenceDescriptor
N/A
N/A
2.308
 
 1  
 package org.apache.ojb.broker.metadata;
 2  
 
 3  
 /* Copyright 2002-2005 The Apache Software Foundation
 4  
  *
 5  
  * Licensed under the Apache License, Version 2.0 (the "License");
 6  
  * you may not use this file except in compliance with the License.
 7  
  * 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  
 
 18  
 import java.util.Hashtable;
 19  
 import java.util.Iterator;
 20  
 import java.util.Vector;
 21  
 
 22  
 import org.apache.commons.lang.builder.ToStringBuilder;
 23  
 import org.apache.ojb.broker.OJBRuntimeException;
 24  
 import org.apache.ojb.broker.PersistenceBrokerException;
 25  
 import org.apache.ojb.broker.core.proxy.ProxyHelper;
 26  
 import org.apache.ojb.broker.metadata.fieldaccess.PersistentField;
 27  
 
 28  
 /**
 29  
  * Describes a Field containing a reference to another class. Provides handling for foreign keys etc.
 30  
  * <br>
 31  
  * Note: Be careful when use references of this class or caching instances of this class,
 32  
  * because instances could become invalid (see {@link MetadataManager}).
 33  
  *
 34  
  * @author <a href="mailto:thma@apache.org">Thomas Mahler<a>
 35  
  *
 36  
  */
 37  
 public class ObjectReferenceDescriptor extends AttributeDescriptorBase implements XmlCapable
 38  
 {
 39  
     private static final long serialVersionUID = 5561562217150972131L;
 40  
 
 41  
     public static final int CASCADE_NONE = 17;
 42  
     public static final int CASCADE_LINK = 19;
 43  
     public static final int CASCADE_OBJECT = 23;
 44  
 
 45  
     private Class m_ClassOfItems = null;
 46  
     private Vector m_ForeignKeyFields = new Vector();
 47  
     private boolean m_CascadeRetrieve = true;
 48  
     private int m_CascadeStore = CASCADE_NONE;
 49  
     private int m_CascadeDelete = CASCADE_NONE;
 50  
     private int m_ProxyPrefetchingLimit = 50;
 51  
 
 52  
     private Class m_ProxyOfItems = null;
 53  
     private boolean m_LookedUpProxy = false;
 54  
     private boolean m_OtmDependent = false;
 55  
 
 56  
     /**
 57  
      * holds the foreign-key field descriptor array for a specified class
 58  
      */
 59  
     private Hashtable fkFieldMap = new Hashtable();
 60  
     /**
 61  
      * define loading strategy of the resulting object
 62  
      */
 63  
     private boolean lazy = false;
 64  
     /**
 65  
      * if true relationship is refreshed when owner is found in cache
 66  
      */
 67  
     private boolean refresh = false;
 68  
 
 69  
     /**
 70  
      *
 71  
      */
 72  
     public ObjectReferenceDescriptor(ClassDescriptor descriptor)
 73  
     {
 74  
         super(descriptor);
 75  
     }
 76  
 
 77  
     /**
 78  
      *
 79  
      */
 80  
     public Class getItemProxyClass() throws PersistenceBrokerException
 81  
     {
 82  
         if (!m_LookedUpProxy)
 83  
         {
 84  
             m_ProxyOfItems = getClassDescriptor().getRepository().
 85  
                                 getDescriptorFor(m_ClassOfItems).getProxyClass();
 86  
             m_LookedUpProxy = true;
 87  
         }
 88  
         return m_ProxyOfItems;
 89  
     }
 90  
 
 91  
     /**
 92  
      *
 93  
      */
 94  
         public FieldDescriptor[] getForeignKeyFieldDescriptors(ClassDescriptor cld)
 95  
         {
 96  
                 FieldDescriptor[] foreignKeyFieldDescriptors;
 97  
                 if ((foreignKeyFieldDescriptors = (FieldDescriptor[]) fkFieldMap.get(cld)) == null)
 98  
                 {
 99  
                         // 1. collect vector of indices of Fk-Fields
 100  
                         Vector v = getForeignKeyFields();
 101  
                         // 2. get FieldDescriptor for each index from Class-descriptor
 102  
                         // 2A. In a many-to-many relationship foreignkeyfields vector will be null.
 103  
                         if (v != null)
 104  
                         {
 105  
                                 Vector ret;
 106  
                                 if (cld.isInterface())
 107  
                                 {
 108  
                                         //exchange interface class descriptor with first concrete
 109  
                                         //class
 110  
                                         Vector extents = cld.getExtentClasses();
 111  
                                         Class firstConcreteClass = (Class) extents.get(0);
 112  
                                         cld = getClassDescriptor().getRepository().getDescriptorFor(firstConcreteClass);
 113  
                                 }
 114  
                                 ret = new Vector();
 115  
 
 116  
                                 Iterator iter = v.iterator();
 117  
                                 while (iter.hasNext())
 118  
                                 {
 119  
                                         Object fk = iter.next();
 120  
                                         FieldDescriptor fkfd = null;
 121  
                                         /*
 122  
                     OJB-55
 123  
                     it's possible that the FK field is declared in the super classes of this object,
 124  
                     so we can search for a valid field in super class-descriptor
 125  
                     */
 126  
                     ClassDescriptor tmp = cld;
 127  
                     while(tmp != null)
 128  
                     {
 129  
                         if (fk instanceof Integer)
 130  
                         {
 131  
                             Integer index = (Integer) fk;
 132  
                             fkfd = cld.getFieldDescriptorByIndex(index.intValue());
 133  
                         }
 134  
                         else
 135  
                         {
 136  
                             fkfd = tmp.getFieldDescriptorByName((String) fk);
 137  
                         }
 138  
                         if(fkfd != null)
 139  
                         {
 140  
                             break;
 141  
                         }
 142  
                         else
 143  
                         {
 144  
                             tmp = tmp.getSuperClassDescriptor();
 145  
                         }
 146  
                     }
 147  
 
 148  
                     if (fkfd == null)
 149  
                                         {
 150  
                         throw new OJBRuntimeException("Incorrect or not found field reference name '"
 151  
                                 + fk + "' in descriptor " + this + " for class-descriptor '"
 152  
                                 + (cld != null ? cld.getClassNameOfObject() + "'" : "'null'"));
 153  
                                         }
 154  
                                         ret.add(fkfd);
 155  
                                 }
 156  
                                 foreignKeyFieldDescriptors = (FieldDescriptor[]) ret.toArray(new FieldDescriptor[ret.size()]);
 157  
                                 fkFieldMap.put(cld, foreignKeyFieldDescriptors);
 158  
                         }
 159  
                 }
 160  
                 return foreignKeyFieldDescriptors;
 161  
         }
 162  
 
 163  
     /**
 164  
      * Returns an Object array of all FK field values of the specified object.
 165  
      * If the specified object is an unmaterialized Proxy, it will be materialized
 166  
      * to read the FK values.
 167  
      *
 168  
      * @throws MetadataException if an error occours while accessing ForeingKey values on obj
 169  
      */
 170  
     public Object[] getForeignKeyValues(Object obj, ClassDescriptor mif)
 171  
             throws PersistenceBrokerException
 172  
     {
 173  
         FieldDescriptor[] fks = getForeignKeyFieldDescriptors(mif);
 174  
         // materialize object only if FK fields are declared
 175  
         if(fks.length > 0) obj = ProxyHelper.getRealObject(obj);
 176  
         Object[] result = new Object[fks.length];
 177  
         for (int i = 0; i < result.length; i++)
 178  
         {
 179  
             FieldDescriptor fmd = fks[i];
 180  
             PersistentField f = fmd.getPersistentField();
 181  
 
 182  
             // BRJ: do NOT convert.
 183  
             // conversion is done when binding the sql-statement
 184  
             //
 185  
             // FieldConversion fc = fmd.getFieldConversion();
 186  
             // Object val = fc.javaToSql(f.get(obj));
 187  
 
 188  
             result[i] = f.get(obj);
 189  
         }
 190  
         return result;
 191  
     }
 192  
 
 193  
     /**
 194  
      *
 195  
      */
 196  
     public Class getItemClass()
 197  
     {
 198  
         return m_ClassOfItems;
 199  
     }
 200  
 
 201  
     /**
 202  
      * @return the fully qualified name of the item class for this descriptor.
 203  
      */
 204  
     public String getItemClassName()
 205  
     {
 206  
         return this.m_ClassOfItems != null ? this.m_ClassOfItems.getName() : null;
 207  
     }
 208  
 
 209  
     /**
 210  
      * sets the item class
 211  
      * @param c the items class object
 212  
      */
 213  
     public void setItemClass(Class c)
 214  
     {
 215  
         m_ClassOfItems = c;
 216  
     }
 217  
 
 218  
     /**
 219  
      *
 220  
      */
 221  
     public Vector getForeignKeyFields()
 222  
     {
 223  
         return m_ForeignKeyFields;
 224  
     }
 225  
 
 226  
     /**
 227  
      *
 228  
      */
 229  
     public void setForeignKeyFields(Vector vec)
 230  
     {
 231  
         m_ForeignKeyFields = vec;
 232  
     }
 233  
 
 234  
     /**
 235  
      * add a foreign key field ID
 236  
      */
 237  
     public void addForeignKeyField(int newId)
 238  
     {
 239  
         if (m_ForeignKeyFields == null)
 240  
         {
 241  
             m_ForeignKeyFields = new Vector();
 242  
         }
 243  
         m_ForeignKeyFields.add(new Integer(newId));
 244  
     }
 245  
 
 246  
     /**
 247  
      * add a foreign key field
 248  
      */
 249  
     public void addForeignKeyField(String newField)
 250  
     {
 251  
         if (m_ForeignKeyFields == null)
 252  
         {
 253  
             m_ForeignKeyFields = new Vector();
 254  
         }
 255  
         m_ForeignKeyFields.add(newField);
 256  
     }
 257  
 
 258  
     /**
 259  
      * Gets the refresh.
 260  
      * @return Returns a boolean
 261  
      */
 262  
     public boolean isRefresh()
 263  
     {
 264  
         return refresh;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Sets the refresh.
 269  
      * @param refresh The refresh to set
 270  
      */
 271  
     public void setRefresh(boolean refresh)
 272  
     {
 273  
         this.refresh = refresh;
 274  
     }
 275  
 
 276  
     /**
 277  
      * Gets the lazy.
 278  
      * @return Returns a boolean
 279  
      */
 280  
     public boolean isLazy()
 281  
     {
 282  
         return lazy;
 283  
     }
 284  
 
 285  
     /**
 286  
      * Sets the lazy.
 287  
      * @param lazy The lazy to set
 288  
      */
 289  
     public void setLazy(boolean lazy)
 290  
     {
 291  
         this.lazy = lazy;
 292  
     }
 293  
 
 294  
     /**
 295  
      *
 296  
      */
 297  
     public boolean getCascadeRetrieve()
 298  
     {
 299  
         return m_CascadeRetrieve;
 300  
     }
 301  
 
 302  
     /**
 303  
      *
 304  
      */
 305  
     public void setCascadeRetrieve(boolean b)
 306  
     {
 307  
         m_CascadeRetrieve = b;
 308  
     }
 309  
 
 310  
     /**
 311  
      *
 312  
      */
 313  
     public int getCascadingStore()
 314  
     {
 315  
         return m_CascadeStore;
 316  
     }
 317  
 
 318  
     /**
 319  
      *
 320  
      */
 321  
     public void setCascadingStore(int cascade)
 322  
     {
 323  
         m_CascadeStore = cascade;
 324  
     }
 325  
 
 326  
     public void setCascadingStore(String value)
 327  
     {
 328  
         setCascadingStore(getCascadeStoreValue(value));
 329  
     }
 330  
 
 331  
     /**
 332  
      * @deprecated use {@link #getCascadingStore} instead.
 333  
      */
 334  
     public boolean getCascadeStore()
 335  
     {
 336  
         return getCascadingStore() == CASCADE_OBJECT;
 337  
     }
 338  
 
 339  
     /**
 340  
      * @deprecated use {@link #setCascadingStore(int)} instead.
 341  
      */
 342  
     public void setCascadeStore(boolean cascade)
 343  
     {
 344  
         if(cascade)
 345  
         {
 346  
             setCascadingStore(getCascadeStoreValue("true"));
 347  
         }
 348  
         else
 349  
         {
 350  
             setCascadingStore(getCascadeStoreValue("false"));
 351  
         }
 352  
     }
 353  
 
 354  
     /**
 355  
      *
 356  
      */
 357  
     public int getCascadingDelete()
 358  
     {
 359  
         return m_CascadeDelete;
 360  
     }
 361  
 
 362  
     /**
 363  
      *
 364  
      */
 365  
     public void setCascadingDelete(int cascade)
 366  
     {
 367  
         m_CascadeDelete = cascade;
 368  
     }
 369  
 
 370  
     public void setCascadingDelete(String value)
 371  
     {
 372  
         setCascadingDelete(getCascadeDeleteValue(value));
 373  
     }
 374  
 
 375  
     /**
 376  
      * @deprecated use {@link #getCascadingDelete} instead.
 377  
      */
 378  
     public boolean getCascadeDelete()
 379  
     {
 380  
         return getCascadingDelete() == CASCADE_OBJECT;
 381  
     }
 382  
 
 383  
     /**
 384  
      * @deprecated use {@link #setCascadingDelete(int)}
 385  
      */
 386  
     public void setCascadeDelete(boolean cascade)
 387  
     {
 388  
         if(cascade)
 389  
         {
 390  
             setCascadingDelete(getCascadeDeleteValue("true"));
 391  
         }
 392  
         else
 393  
         {
 394  
             setCascadingDelete(getCascadeDeleteValue("false"));
 395  
         }
 396  
     }
 397  
 
 398  
     protected int getCascadeStoreValue(String cascade)
 399  
     {
 400  
         if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR))
 401  
         {
 402  
             return CASCADE_NONE;
 403  
         }
 404  
         else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR))
 405  
         {
 406  
             return CASCADE_LINK;
 407  
         }
 408  
         else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR))
 409  
         {
 410  
             return CASCADE_OBJECT;
 411  
         }
 412  
         else if(cascade.equalsIgnoreCase("true"))
 413  
         {
 414  
             return CASCADE_OBJECT;
 415  
         }
 416  
         else if(cascade.equalsIgnoreCase("false"))
 417  
         {
 418  
             /*
 419  
             in old implementation the FK values of an 1:1 relation are always
 420  
             set. Thus we choose 'link' instead of 'none'
 421  
             The CollectionDescriptor should override this behaviour.
 422  
             */
 423  
             return CASCADE_LINK;
 424  
         }
 425  
         else
 426  
         {
 427  
             throw new OJBRuntimeException("Invalid value! Given value was '" + cascade
 428  
                     + "', expected values are: " + RepositoryTags.CASCADE_NONE_STR + ", "
 429  
                     + RepositoryTags.CASCADE_LINK_STR + ", " + RepositoryTags.CASCADE_OBJECT_STR
 430  
                     + " ('false' and 'true' are deprecated but still valid)");
 431  
         }
 432  
     }
 433  
 
 434  
     protected int getCascadeDeleteValue(String cascade)
 435  
     {
 436  
         if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_NONE_STR))
 437  
         {
 438  
             return CASCADE_NONE;
 439  
         }
 440  
         else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_LINK_STR))
 441  
         {
 442  
             return CASCADE_LINK;
 443  
         }
 444  
         else if(cascade.equalsIgnoreCase(RepositoryTags.CASCADE_OBJECT_STR))
 445  
         {
 446  
             return CASCADE_OBJECT;
 447  
         }
 448  
         else if(cascade.equalsIgnoreCase("true"))
 449  
         {
 450  
             return CASCADE_OBJECT;
 451  
         }
 452  
         else if(cascade.equalsIgnoreCase("false"))
 453  
         {
 454  
             return CASCADE_NONE;
 455  
         }
 456  
         else
 457  
         {
 458  
             throw new OJBRuntimeException("Invalid value! Given value was '" + cascade
 459  
                     + "', expected values are: " + RepositoryTags.CASCADE_NONE_STR + ", "
 460  
                     + RepositoryTags.CASCADE_LINK_STR + ", " + RepositoryTags.CASCADE_OBJECT_STR
 461  
                     + " ('false' and 'true' are deprecated but still valid)");
 462  
         }
 463  
     }
 464  
 
 465  
     public String getCascadeAsString(int cascade)
 466  
     {
 467  
         String result = null;
 468  
         switch(cascade)
 469  
         {
 470  
             case CASCADE_NONE:
 471  
                 result = RepositoryTags.CASCADE_NONE_STR;
 472  
                 break;
 473  
             case CASCADE_LINK:
 474  
                 result = RepositoryTags.CASCADE_LINK_STR;
 475  
                 break;
 476  
             case CASCADE_OBJECT:
 477  
                 result = RepositoryTags.CASCADE_OBJECT_STR;
 478  
                 break;
 479  
         }
 480  
         return result;
 481  
     }
 482  
 
 483  
     public int getProxyPrefetchingLimit()
 484  
     {
 485  
         return m_ProxyPrefetchingLimit;
 486  
     }
 487  
 
 488  
     public void setProxyPrefetchingLimit(int proxyPrefetchingLimit)
 489  
     {
 490  
         m_ProxyPrefetchingLimit = proxyPrefetchingLimit;
 491  
     }
 492  
 
 493  
     /**
 494  
      *
 495  
      */
 496  
     public boolean getOtmDependent()
 497  
     {
 498  
         return m_OtmDependent;
 499  
     }
 500  
 
 501  
     /**
 502  
      *
 503  
      */
 504  
     public void setOtmDependent(boolean b)
 505  
     {
 506  
         m_OtmDependent = b;
 507  
     }
 508  
 
 509  
     /**
 510  
      * Returns <code>true</code> if this descriptor was used to
 511  
      * describe a reference to a super class of an object.
 512  
      *
 513  
      * @return always <code>false</code> for this instance.
 514  
      */
 515  
     public boolean isSuperReferenceDescriptor()
 516  
     {
 517  
         return false;
 518  
     }
 519  
 
 520  
     /**
 521  
      * Returns <em>true</em> if a foreign key constraint to the referenced object is
 522  
      * declared, else <em>false</em> is returned.
 523  
      */
 524  
     public boolean hasConstraint()
 525  
     {
 526  
         /*
 527  
         arminw: Currently we don't have a ForeignKey descriptor object and
 528  
         a official xml-element to support FK settings. As a workaround I introduce
 529  
         a custom-attribute to handle FK settings in collection-/reference-decriptor
 530  
         */
 531  
         String result = getAttribute("constraint");
 532  
         return result != null && result.equalsIgnoreCase("true");
 533  
     }
 534  
 
 535  
     /**
 536  
      * Set a foreign key constraint flag for this reference - see {@link #hasConstraint()}
 537  
      * @param constraint If set <em>true</em>, signals a foreign key constraint in database. 
 538  
      */
 539  
     public void setConstraint(boolean constraint)
 540  
     {
 541  
         addAttribute("constraint", constraint ? "true" : "false");
 542  
     }
 543  
 
 544  
     public String toString()
 545  
     {
 546  
         return new ToStringBuilder(this)
 547  
                 .append("cascade_retrieve", getCascadeRetrieve())
 548  
                 .append("cascade_store", getCascadeAsString(m_CascadeStore))
 549  
                 .append("cascade_delete", getCascadeAsString(m_CascadeDelete))
 550  
                 .append("is_lazy", lazy)
 551  
                 .append("class_of_Items", m_ClassOfItems)
 552  
                 .toString();
 553  
     }
 554  
 
 555  
     /*
 556  
      * @see XmlCapable#toXML()
 557  
      */
 558  
     public String toXML()
 559  
     {
 560  
         RepositoryTags tags = RepositoryTags.getInstance();
 561  
         String eol = System.getProperty( "line.separator" );
 562  
 
 563  
         // opening tag
 564  
         StringBuffer result = new StringBuffer( 1024 );
 565  
         result.append( "      " );
 566  
         result.append( tags.getOpeningTagNonClosingById( REFERENCE_DESCRIPTOR ) );
 567  
         result.append( eol );
 568  
 
 569  
         // attributes
 570  
         // name
 571  
         String name = this.getAttributeName();
 572  
         if( name == null )
 573  
         {
 574  
             name = RepositoryElements.TAG_SUPER;
 575  
         }
 576  
         result.append( "        " );
 577  
         result.append( tags.getAttribute( FIELD_NAME, name ) );
 578  
         result.append( eol );
 579  
 
 580  
         // class-ref
 581  
         result.append( "        " );
 582  
         result.append( tags.getAttribute( REFERENCED_CLASS, this.getItemClassName() ) );
 583  
         result.append( eol );
 584  
 
 585  
         // proxyReference is optional
 586  
         if( isLazy() )
 587  
         {
 588  
             result.append( "        " );
 589  
             result.append( tags.getAttribute( PROXY_REFERENCE, "true" ) );
 590  
             result.append( eol );
 591  
             result.append( "        " );
 592  
             result.append( tags.getAttribute( PROXY_PREFETCHING_LIMIT, "" + this.getProxyPrefetchingLimit() ) );
 593  
             result.append( eol );
 594  
         }
 595  
 
 596  
         //reference refresh is optional, disabled by default
 597  
         if( isRefresh() )
 598  
         {
 599  
             result.append( "        " );
 600  
             result.append( tags.getAttribute( REFRESH, "true" ) );
 601  
             result.append( eol );
 602  
         }
 603  
 
 604  
         //auto retrieve
 605  
         result.append( "        " );
 606  
         result.append( tags.getAttribute( AUTO_RETRIEVE, "" + getCascadeRetrieve() ) );
 607  
         result.append( eol );
 608  
 
 609  
         //auto update
 610  
         result.append( "        " );
 611  
         result.append( tags.getAttribute( AUTO_UPDATE, getCascadeAsString( getCascadingStore() ) ) );
 612  
         result.append( eol );
 613  
 
 614  
         //auto delete
 615  
         result.append( "        " );
 616  
         result.append( tags.getAttribute( AUTO_DELETE, getCascadeAsString( getCascadingDelete() ) ) );
 617  
         result.append( eol );
 618  
 
 619  
         //otm-dependent is optional, disabled by default
 620  
         if( getOtmDependent() )
 621  
         {
 622  
             result.append( "        " );
 623  
             result.append( tags.getAttribute( OTM_DEPENDENT, "true" ) );
 624  
             result.append( eol );
 625  
         }
 626  
 
 627  
         // close opening tag
 628  
         result.append( "      >" );
 629  
         result.append( eol );
 630  
 
 631  
         // elements
 632  
         // write foreignkey elements
 633  
         for( int i = 0; i < getForeignKeyFields().size(); i++ )
 634  
         {
 635  
             Object obj = getForeignKeyFields().get( i );
 636  
             if( obj instanceof Integer )
 637  
             {
 638  
                 String fkId = obj.toString();
 639  
                 result.append( "        " );
 640  
                 result.append( tags.getOpeningTagNonClosingById( FOREIGN_KEY ) );
 641  
                 result.append( " " );
 642  
                 result.append( tags.getAttribute( FIELD_ID_REF, fkId ) );
 643  
                 result.append( "/>" );
 644  
                 result.append( eol );
 645  
             }
 646  
             else
 647  
             {
 648  
                 String fk = ( String ) obj;
 649  
                 result.append( "        " );
 650  
                 result.append( tags.getOpeningTagNonClosingById( FOREIGN_KEY ) );
 651  
                 result.append( " " );
 652  
                 result.append( tags.getAttribute( FIELD_REF, fk ) );
 653  
                 result.append( "/>" );
 654  
                 result.append( eol );
 655  
             }
 656  
         }
 657  
 
 658  
         // closing tag
 659  
         result.append( "      " );
 660  
         result.append( tags.getClosingTagById( REFERENCE_DESCRIPTOR ) );
 661  
         result.append( eol );
 662  
         return result.toString();
 663  
     }
 664  
 }