Coverage Report - net.wotonomy.web.WOComponent
 
Classes in this File Line Coverage Branch Coverage Complexity
WOComponent
0% 
0% 
3.356
 
 1  
 /*
 2  
 Wotonomy: OpenStep design patterns for pure Java applications.
 3  
 Copyright (C) 2000 Blacksmith, Inc.
 4  
 
 5  
 This library is free software; you can redistribute it and/or
 6  
 modify it under the terms of the GNU Lesser General Public
 7  
 License as published by the Free Software Foundation; either
 8  
 version 2.1 of the License, or (at your option) any later version.
 9  
 
 10  
 This library is distributed in the hope that it will be useful,
 11  
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 Lesser General Public License for more details.
 14  
 
 15  
 You should have received a copy of the GNU Lesser General Public
 16  
 License along with this library; if not, see http://www.gnu.org
 17  
 */
 18  
 
 19  
 package net.wotonomy.web;
 20  
 
 21  
 import java.io.BufferedReader;
 22  
 import java.io.ByteArrayInputStream;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.io.InputStreamReader;
 26  
 import java.io.LineNumberReader;
 27  
 import java.io.PushbackInputStream;
 28  
 import java.io.StringReader;
 29  
 import java.lang.reflect.InvocationTargetException;
 30  
 import java.util.Enumeration;
 31  
 import java.util.HashMap;
 32  
 import java.util.Iterator;
 33  
 import java.util.LinkedList;
 34  
 import java.util.List;
 35  
 import java.util.Map;
 36  
 import java.util.StringTokenizer;
 37  
 
 38  
 import net.wotonomy.control.EOKeyValueCodingSupport;
 39  
 import net.wotonomy.foundation.NSArray;
 40  
 import net.wotonomy.foundation.NSDictionary;
 41  
 import net.wotonomy.foundation.NSMutableDictionary;
 42  
 import net.wotonomy.foundation.NSSelector;
 43  
 
 44  
 /**
 45  
 * Pure java implementation of WOComponent.
 46  
 *
 47  
 * @author michael@mpowers.net
 48  
 * @author $Author: cgruber $
 49  
 * @version $Revision: 905 $
 50  
 */
 51  
 public class WOComponent
 52  
     extends WOElement
 53  
     implements WOActionResults,
 54  
                net.wotonomy.control.EOKeyValueCodingAdditions,
 55  
                net.wotonomy.control.EOKeyValueCoding
 56  
 {
 57  
         WOElement rootElement;
 58  
     
 59  
     private static final String DIRECTORY_SUFFIX = ".wo";
 60  
     private static final String TEMPLATE_SUFFIX = ".html";
 61  
     private static final String DECLARATION_SUFFIX = ".wod";
 62  
 
 63  
     private static final String OPEN_TAG = "webobject";
 64  
     private static final String CLOSE_TAG = "/webobject";
 65  
     private static final String NAME_KEY = "name";
 66  
     
 67  
     protected transient WOContext context; // don't persist
 68  
     protected boolean cachingEnabled;
 69  
     protected WOElement template;
 70  
     protected WOComponent parent;
 71  
 
 72  
         /**
 73  
         * Default constructor.  Deprecated in latest spec.
 74  
         */
 75  0
     public WOComponent ()
 76  0
     {
 77  0
         parent = null;
 78  0
             cachingEnabled = true;
 79  0
         template = null;
 80  0
     }
 81  
     
 82  
     /**
 83  
     * Constructor specifying a context.
 84  
     */
 85  
     public WOComponent( WOContext aContext )
 86  
     {
 87  0
         this();
 88  0
         context = aContext;
 89  0
     }
 90  
     
 91  
         /**
 92  
         * Returns the name of the component, which is usually just the class name.
 93  
         */
 94  
     public String name ()
 95  
     {
 96  0
             return justTheClassName();
 97  
     }
 98  
     
 99  
     /** 
 100  
     * Returns the system-dependent file path to the current component
 101  
     * directory, including the ".wo" extension.
 102  
     */
 103  
     public String path ()
 104  
     {
 105  0
             throw new RuntimeException( "Not implemented yet." );
 106  
     }
 107  
 
 108  
         /**
 109  
         * Returns the URL for this component, relative to the server's
 110  
         * document root on the server's file system. 
 111  
     * This is not an http url.
 112  
         */
 113  
     public String baseURL ()
 114  
     {
 115  0
             throw new RuntimeException( "Not implemented yet." );
 116  
     }
 117  
 
 118  
         /**
 119  
         * Returns the name of the framework that contains this component,
 120  
         * or null if the component does not belong to a framework.
 121  
         * This currently returns the package path of the class, or
 122  
         * null if it does not belong to a package.
 123  
         */
 124  
     public String frameworkName ()
 125  
     {
 126  0
         return justTheResourcePath();
 127  
     }
 128  
     
 129  
     /**
 130  
     * Sets whether templates are cached.  If true, templates will
 131  
     * only be read once per application lifetime.  Otherwise, templates
 132  
     * will be read each time this class is instantiated.  Defaults to false.
 133  
     */
 134  
     public void setCachingEnabled (boolean enabled)
 135  
         {
 136  0
                 cachingEnabled = enabled;
 137  0
         }
 138  
 
 139  
     /**
 140  
     * Returns whether templates are cached.  If true, templates are
 141  
     * read once per application lifetime.  Otherwise, templates are
 142  
     * read each time this class is instantiated.
 143  
     */
 144  
     public boolean isCachingEnabled ()
 145  
     {
 146  0
             return cachingEnabled && WOApplication.application().isCachingEnabled();
 147  
     }
 148  
     
 149  
     /**
 150  
     * Returns the root of the tree of elements produced by parsing
 151  
     * the templates in the component directory for this component.
 152  
     */
 153  
     public WOElement template()
 154  
     {
 155  0
         return template;
 156  
     }
 157  
     
 158  
     /**
 159  
     * Returns the root of the tree of elements produced by parsing
 160  
     * the templates in the component directory for the named component.
 161  
     * @deprecated Use template() instead.
 162  
     */
 163  
     public WOElement templateWithName(String aComponentName)
 164  
     {
 165  0
         return templateWithName( aComponentName, null );
 166  
     }
 167  
     
 168  
     /**
 169  
     * Returns the root of the tree of elements produced by parsing
 170  
     * the templates in the component directory for the named component.
 171  
     */
 172  
     WOElement templateWithName(String aComponentName, String aFramework)
 173  
     {
 174  0
         NSArray languages = null;
 175  0
         WOContext context = context();
 176  0
         if ( context != null )
 177  
         {
 178  0
             languages = context.request().browserLanguages();
 179  
         }
 180  0
         WOElement result = templateWithHTMLString( 
 181  0
             readTemplateResource( aComponentName, aFramework, TEMPLATE_SUFFIX, languages ), 
 182  0
             readTemplateResource( aComponentName, aFramework, DECLARATION_SUFFIX, languages ), 
 183  0
             languages );
 184  0
         if ( result == null )
 185  
         {
 186  0
             System.out.println( "WOComponent.templateWithName: failed for " + aComponentName );
 187  
         }
 188  0
         return result;
 189  
     }
 190  
 
 191  
     /**
 192  
     * Returns the root of the tree of elements produced by parsing
 193  
     * the specfified HTML string and bindings declaration string.
 194  
     * Note: language list is currently ignored.
 195  
     */
 196  
     public static WOElement templateWithHTMLString (
 197  
             String anHTMLString, String aDeclaration, List aLanguageList)
 198  
     {
 199  0
         if ( anHTMLString == null ) return null;
 200  0
         WOElement result = null;
 201  
         try
 202  
         {
 203  0
                         NSDictionary bindings = processDeclaration( aDeclaration );
 204  0
             List elements = new LinkedList();
 205  0
             int index = processTemplate( elements, anHTMLString, 0, bindings, aLanguageList );
 206  0
             if ( index == -1 )
 207  
             {
 208  0
                 if ( elements.size() == 1 )
 209  
                 {
 210  0
                     result = (WOElement) elements.get(0);
 211  0
                 }
 212  
                 else
 213  
                 {
 214  0
                     result = new WOParentElement( elements );
 215  
                 }
 216  0
             }
 217  
             else // entire template did not process
 218  
             {
 219  0
                 throw new RuntimeException( "No closing tag: " + anHTMLString.substring( index ) );
 220  
             }
 221  
         }
 222  0
         catch ( Exception exc )
 223  
         {
 224  0
             exc.printStackTrace();
 225  0
         }
 226  0
                 return result;
 227  
     }
 228  
     
 229  
     /**
 230  
     * Called at the beginning of a request-response cycle.
 231  
     * Override to perform any necessary initialization.
 232  
     * This implementation does nothing.
 233  
     */
 234  
     public void awake ()
 235  
     {
 236  0
     }
 237  
 
 238  
     /**
 239  
     * Package access only.  Called to initialize the component with 
 240  
     * the proper context before the start of the request-response cycle.
 241  
     * If the context has a current component, that component becomes
 242  
     * this component's parent.
 243  
     */
 244  
     void ensureAwakeInContext (WOContext aContext)
 245  
     {
 246  0
                 context = aContext;
 247  0
                 parent = aContext.parent();
 248  0
         if ( template == null )
 249  
         {
 250  0
                 template = templateWithName( name(), frameworkName() );
 251  
         }
 252  0
         if ( template != null )
 253  
         {
 254  0
             template.ensureAwakeInContext( aContext );
 255  
         }
 256  0
         awake();
 257  0
     }
 258  
 
 259  
     public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
 260  
     {
 261  0
         if ( synchronizesVariablesWithBindings() )
 262  
         {
 263  0
             pullValuesFromParent();
 264  0
             if ( template != null )
 265  
             {
 266  0
                 template.takeValuesFromRequest( aRequest, aContext ); 
 267  
             }
 268  0
             pushValuesToParent();
 269  0
         }
 270  
         else
 271  0
         if ( template != null )
 272  
         {
 273  0
             template.takeValuesFromRequest( aRequest, aContext ); 
 274  
         }
 275  0
     }
 276  
 
 277  
     public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
 278  
     {
 279  0
             WOActionResults result = null;
 280  0
         if ( synchronizesVariablesWithBindings() )
 281  
         {
 282  0
             pullValuesFromParent();
 283  0
             if ( template != null )
 284  
             {
 285  0
                 result = template.invokeAction( aRequest, aContext ); 
 286  
             }
 287  0
             pushValuesToParent();
 288  0
         }
 289  
         else
 290  0
         if ( template != null )
 291  
         {
 292  0
             result = template.invokeAction( aRequest, aContext ); 
 293  
         }
 294  0
                 return result;
 295  
     }
 296  
 
 297  
     public void appendToResponse (WOResponse aResponse, WOContext aContext)
 298  
     {
 299  0
         if ( synchronizesVariablesWithBindings() )
 300  
         {
 301  0
             pullValuesFromParent();
 302  0
             if ( template != null )
 303  
             {
 304  0
                 template.appendToResponse( aResponse, aContext ); 
 305  
             }
 306  0
             pushValuesToParent();
 307  0
         }
 308  
         else
 309  0
         if ( template != null )
 310  
         {
 311  0
             template.appendToResponse( aResponse, aContext ); 
 312  
         }
 313  0
         context = null;
 314  0
     }
 315  
 
 316  
     /**
 317  
     * Called at the end of a request-response cycle.
 318  
     * Override to perform any necessary clean-up.
 319  
     * This implementation does nothing.
 320  
     */
 321  
     public void sleep ()
 322  
     {
 323  0
     }
 324  
     
 325  
     /**
 326  
     * Generates a WOResponse and calls appendToResponse() on it.
 327  
     */
 328  
     public WOResponse generateResponse ()
 329  
     {
 330  0
             WOResponse response = new WOResponse();
 331  0
         WOContext context = context();
 332  0
         appendToResponse( response, context ); // nulls out context
 333  0
         context.session().savePage( this ); //?is this the right place for this?
 334  0
         return response;
 335  
     }
 336  
 
 337  
         /**
 338  
         * Returns this component's parent component, or null if none.
 339  
         */    
 340  
     public WOComponent parent()
 341  
     {
 342  0
             return parent;
 343  
     }
 344  
 
 345  
     /**
 346  
     * Invokes the specified action on this component's parent.
 347  
     * Variables will be synchronized when this method returns.
 348  
     */
 349  
     public WOActionResults performParentAction(String anAction)
 350  
     {
 351  0
         WOActionResults result = parent().performAction( anAction );
 352  0
         if ( synchronizesVariablesWithBindings() )
 353  
         {
 354  0
             pullValuesFromParent();
 355  
         }
 356  0
         return result;
 357  
     }
 358  
     
 359  
     /**
 360  
     * Invokes the specified action on this component.
 361  
     */
 362  
     WOActionResults performAction( String anAction )
 363  
     {
 364  
             try 
 365  
             {
 366  0
                     return (WOActionResults) NSSelector.invoke( anAction, this );
 367  
                 }
 368  0
                 catch ( NoSuchMethodException exc )
 369  
                 {
 370  
                         // returns below
 371  
                 }
 372  0
                 catch ( InvocationTargetException exc )
 373  
                 {
 374  0
                         Throwable t = exc.getTargetException();
 375  0
             exc.printStackTrace();
 376  0
                         throw new RuntimeException( t.toString() );
 377  
                 }
 378  0
                 catch ( Exception exc )
 379  
                 {
 380  0
             exc.printStackTrace();
 381  0
                         throw new RuntimeException( exc.toString() );
 382  0
                 }
 383  0
         return null;
 384  
     }
 385  
 
 386  
     /**
 387  
     * Called before each phase of the request-response cycle,
 388  
     * if synchronizesVariablesWithBindings is true and the 
 389  
     * component is not stateless.
 390  
     */
 391  
     public void pullValuesFromParent()
 392  
     {
 393  0
         if ( associations == null ) return;
 394  
         String key;
 395  0
         Enumeration e = associations.keyEnumerator();
 396  0
         while ( e.hasMoreElements() )
 397  
         {
 398  0
             key = e.nextElement().toString();
 399  0
             takeValueForKey( valueForBinding( key ), key );
 400  0
         }
 401  0
     }
 402  
     
 403  
     /**
 404  
     * Called after each phase of the request-response cycle,
 405  
     * if synchronizesVariablesWithBindings is true and the 
 406  
     * component is not stateless.
 407  
     */
 408  
     public void pushValuesToParent()
 409  
     {
 410  0
         if ( associations == null ) return;
 411  
         String key;
 412  0
         Enumeration e = associations.keyEnumerator();
 413  0
         while ( e.hasMoreElements() )
 414  
         {
 415  0
             key = e.nextElement().toString();
 416  0
             setValueForBinding( valueForKey( key ), key );
 417  0
         }
 418  0
     }
 419  
     
 420  
     /**
 421  
     * Returns whether this component should be considered stateless.
 422  
     * Stateless components are shared between sessions to conserve memory.
 423  
     * This implementation returns false; override to return true.
 424  
     */
 425  
     public boolean isStateless()
 426  
     {
 427  0
         return false;
 428  
     }
 429  
     
 430  
     /**
 431  
     * Called only on stateless components to tell themselves to reset
 432  
     * themselves for another invocation using a different context.
 433  
     * This implementation does nothing.
 434  
     */
 435  
     public void reset()    
 436  
     {
 437  
         // does nothing
 438  0
     }
 439  
     
 440  
     /**
 441  
     * Returns the application containing this instance of the class. 
 442  
     */ 
 443  
     public WOApplication application ()
 444  
     {
 445  0
             return context.application();
 446  
     }
 447  
     
 448  
     /**
 449  
     * Returns whether a session has been created for this user.
 450  
     */
 451  
     public boolean hasSession ()
 452  
     {
 453  0
             return context.hasSession();
 454  
     }
 455  
 
 456  
     /**
 457  
     * Returns the current session object, creating it if it doesn't exist.
 458  
     */
 459  
     public WOSession session ()
 460  
     {
 461  0
             return context.session();
 462  
     }
 463  
 
 464  
         /**
 465  
         * Returns the current context for this component.
 466  
         */
 467  
     public WOContext context ()
 468  
     {
 469  0
             return context;
 470  
     }
 471  
 
 472  
         /**
 473  
         * Returns a new WOComponent with the specified name.
 474  
         * If null, returns the component named "Main".
 475  
         * If the named component doesn't exist, returns null.
 476  
         */ 
 477  
     public WOComponent pageWithName (String aName)
 478  
     {
 479  0
             return application().pageWithName( aName, context() );
 480  
     }
 481  
 
 482  
         /**
 483  
         * Called when exceptions are raised by assigning values 
 484  
         * to this object.  This implementation does nothing, but 
 485  
         * subclasses may override to do something useful.
 486  
         */
 487  
     public void validationFailedWithException (
 488  
             Throwable anException, Object aValue, String aPath)
 489  
     {
 490  
         // does nothing
 491  0
     }
 492  
 
 493  
         /**
 494  
         * Called on the component that represents the requested page.
 495  
         * Override to return logging information specific to your 
 496  
         * component.  This implementation returns the component's name.
 497  
         */ 
 498  
     public String descriptionForResponse (
 499  
             WOResponse aResponse, WOContext aContext)
 500  
     {
 501  0
             return name();
 502  
     }
 503  
 
 504  
         /**
 505  
         * Returns true if this component should get and set values
 506  
         * in its parent.  This implementation returns true.
 507  
         * Override to create a component that does not automatically
 508  
     * synchronize bindings with its parent, useful if you wish
 509  
     * to handle synchronization manually.
 510  
         */
 511  
     public boolean synchronizesVariablesWithBindings ()
 512  
     {
 513  0
             return true;
 514  
     }
 515  
 
 516  
     /**
 517  
     * Returns whether this component has a readable value that maps
 518  
     * to the specified binding.  This implementation calls 
 519  
     * hasBinding(aBinding).
 520  
     */
 521  
     public boolean canGetValueForBinding(String aBinding)
 522  
     {
 523  0
         return hasBinding( aBinding );
 524  
     }
 525  
     
 526  
     /**
 527  
     * Returns whether this component has a writable value that maps
 528  
     * to the specified binding.  
 529  
     */
 530  
     public boolean canSetValueForBinding(String aBinding)
 531  
     {
 532  0
         WOAssociation assoc = 
 533  0
             (WOAssociation)associations.objectForKey(aBinding);
 534  0
         if (assoc != null) 
 535  
         {
 536  0
             if ( assoc.isValueSettable() ) return true;
 537  
         }
 538  0
         return false;
 539  
     }
 540  
 
 541  
         /** 
 542  
         * Returns whether this component has the specified binding.
 543  
         */
 544  
     public boolean hasBinding (String aBinding)
 545  
     {
 546  0
         if ( associations == null ) return false;
 547  0
             return associations.containsKey( aBinding );
 548  
     }
 549  
 
 550  
         /**
 551  
         * Returns the value for the specified binding for this component.
 552  
         * The parent component is expected to have set the binding for 
 553  
         * this component. If no such binding exists, the binding is 
 554  
         * treated as a property is and obtained using valueForKey.
 555  
         * If the property is not found, this method returns null.
 556  
         */
 557  
     public Object valueForBinding (String aBinding)
 558  
     {
 559  0
         WOComponent parent = parent();
 560  0
         if ( associations != null )
 561  
         {
 562  0
             WOAssociation assoc = 
 563  0
                 (WOAssociation)associations.objectForKey(aBinding);
 564  0
             if (assoc != null && parent != null) 
 565  
             {
 566  0
                 return assoc.valueInComponent( parent );
 567  
             }
 568  
         }
 569  0
         if ( parent != null )
 570  
         {
 571  0
             return parent.valueForKey( aBinding );
 572  
         }
 573  0
         return null;
 574  
    }
 575  
 
 576  
         /**
 577  
         * Sets the value for the specified binding for this component.
 578  
         * The parent component is expected to have set the binding
 579  
         * for this component.  If no such binding exists, the binding
 580  
         * is treated as a property and is set using takeValueForKey.
 581  
         * If the property is not found, this method fails silently.
 582  
         */
 583  
     public void setValueForBinding (Object aValue, String aBinding)
 584  
     {
 585  0
         if ( associations == null ) return;
 586  
         
 587  0
         WOComponent parent = parent();
 588  
 
 589  0
         if ( associations != null )
 590  
         {
 591  0
             WOAssociation assoc = 
 592  0
                 (WOAssociation)associations.objectForKey(aBinding);
 593  0
             if (assoc != null && parent != null) 
 594  
             {
 595  0
                 if ( assoc.isValueSettable() )
 596  
                 {
 597  0
                     assoc.setValue( aValue, parent );
 598  0
                     return;
 599  
                 }
 600  
             }
 601  
         }
 602  0
         if ( parent != null )
 603  
         {
 604  0
             parent.takeValueForKey( aValue, aBinding );
 605  
         }
 606  0
     }
 607  
  
 608  
     public static void logString (String aString)
 609  
     {
 610  0
             System.out.println( aString );
 611  0
     }
 612  
 
 613  
     public static void debugString (String aString)
 614  
     {
 615  0
             System.err.println( aString );
 616  0
     }
 617  
 
 618  
     public Object valueForKeyPath (String aPath)
 619  
     {
 620  
         // currently key value coding support also handles keypaths
 621  0
             return valueForKey( aPath );
 622  
     }
 623  
 
 624  
     public void takeValueForKeyPath (Object aValue, String aPath)
 625  
     {
 626  
         // currently key value coding support also handles keypaths
 627  0
             takeValueForKey( aValue, aPath );
 628  0
     }
 629  
 
 630  
     public NSDictionary valuesForKeys (List aKeyList)
 631  
     {
 632  0
             throw new RuntimeException( "Not implemented yet." );
 633  
     }
 634  
 
 635  
     public void takeValuesFromDictionary (Map aValueMap)
 636  
     {
 637  0
             throw new RuntimeException( "Not implemented yet." );
 638  
     }
 639  
 
 640  
     public Object valueForKey (String aKey)
 641  
     { // System.out.println( "valueForKey: " + aKey + "->" + this );      
 642  
         // handle "^" property keys
 643  0
         if ( aKey.startsWith( "^" ) )
 644  
         {
 645  0
             return valueForBinding( aKey.substring(1) );
 646  
         }
 647  0
             return EOKeyValueCodingSupport.valueForKey( this, aKey );
 648  
     }
 649  
 
 650  
     public void takeValueForKey (Object aValue, String aKey)
 651  
     { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this );      
 652  
         // handle "^" property keys
 653  0
         if ( aKey.startsWith( "^" ) )
 654  
         {
 655  0
             setValueForBinding( aValue, aKey.substring(1) );
 656  0
             return;
 657  
         }
 658  0
             EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey );
 659  0
     }
 660  
 
 661  
     public Object storedValueForKey (String aKey)
 662  
     {
 663  0
             return EOKeyValueCodingSupport.storedValueForKey( this, aKey );
 664  
     }
 665  
 
 666  
     public void takeStoredValueForKey (Object aValue, String aKey)
 667  
     {
 668  0
             EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey );
 669  0
     }
 670  
 
 671  
     public Object handleQueryWithUnboundKey (String aKey)
 672  
     {
 673  0
             return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey );
 674  
     }
 675  
 
 676  
     public void handleTakeValueForUnboundKey (Object aValue, String aKey)
 677  
     {
 678  0
             EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey );
 679  0
     }
 680  
 
 681  
     public void unableToSetNullForKey (String aKey)
 682  
     {
 683  0
             EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey );
 684  0
     }
 685  
 
 686  
     public Object validateTakeValueForKeyPath (Object aValue, String aKey)
 687  
     {
 688  0
             throw new RuntimeException( "Not implemented yet." );
 689  
     }
 690  
 
 691  
 
 692  
         // Template Processing
 693  
 
 694  
     /**
 695  
     * Takes a template string and a location to begin parsing,
 696  
     * looking only for interesting tags, and calling itself recursively
 697  
     * as necessary.  Returns the index to resume parsing, or -1 if done.
 698  
     */
 699  
     static private int processTemplate(
 700  
         List elements, String template, int index, 
 701  
                 Map bindings, List aLanguageList )
 702  
             throws java.io.IOException
 703  
     { //System.out.println( "processTemplate: " + index );
 704  0
             if ( template == null ) return -1;
 705  
         
 706  0
         int start = index;
 707  
         
 708  0
         while ( true )
 709  
         {
 710  
             // search for start of next tag
 711  0
             start = template.indexOf( '<', start );
 712  
     
 713  0
             if ( start == -1 )
 714  
             {
 715  
                 // if no tags, send output and return
 716  0
                 elements.add( new WOStaticElement( template.substring( index ) ) );
 717  0
                 return -1;
 718  
             }
 719  
             
 720  
             // search for end of opening tag
 721  0
             int end = template.indexOf( ">", start + 1 );
 722  0
             if ( end == -1 )
 723  
             {
 724  
                 // if no end to tag
 725  0
                 throw new RuntimeException( "No end to tag: "
 726  0
                     + template.substring( start ) );
 727  
             }
 728  
     
 729  0
             boolean hasBody = true;        
 730  0
             if ( template.charAt( end - 1 ) == '/' )
 731  
             {
 732  
                 // tag is standalone - no body
 733  0
                 end = end - 1;
 734  0
                 hasBody = false;
 735  
             }
 736  
             
 737  
             // search for name of tag
 738  0
             int endName = start + 1;
 739  0
             while ( endName < end ) 
 740  
             {
 741  0
                 if ( Character.isWhitespace( 
 742  0
                     template.charAt(endName) ) ) break;
 743  0
                 endName++;
 744  0
             }
 745  
             
 746  0
             String name = template.substring( start + 1, endName );
 747  
     
 748  0
             if ( name.toLowerCase().startsWith( OPEN_TAG ) )
 749  
             {
 750  
                 // add the contents before the tag
 751  
                 //System.out.println( index + " : " + start + " : " + hasBody );                
 752  0
                 elements.add( new WOStaticElement( template.substring( index, start ) ) );
 753  
     
 754  
                 // interesting tag; parse parameters
 755  0
                 Map params = new HashMap( 5 ); // arbitrary init length                        
 756  0
                 if ( endName < end )
 757  
                 {
 758  
                     // delimit by whitespace
 759  0
                     StringTokenizer tokens = new StringTokenizer(
 760  0
                         template.substring( endName+1, end ) );
 761  
                     int equals;
 762  
                     String token;
 763  
                     String value;
 764  0
                     while ( tokens.hasMoreTokens() )
 765  
                     {
 766  0
                         token = tokens.nextToken();
 767  0
                         equals = token.indexOf( '=' );
 768  0
                         if ( equals != -1 )
 769  
                         {
 770  0
                             value = token.substring( equals+1 );
 771  
     
 772  0
                             if ( value.startsWith( "\"" ) )
 773  
                             {
 774  
                                 // handle spaces within parameter names
 775  0
                                 while ( ! value.endsWith( "\"" ) ) 
 776  
                                 { 
 777  0
                                     value = value + " " + tokens.nextToken();   
 778  0
                                 }
 779  
     
 780  
                                 // strip quotation marks
 781  0
                                 if ( value.endsWith( "\"" ) )
 782  
                                 {
 783  0
                                     value = value.substring( 1, value.length()-1 );
 784  
                                 }
 785  
                             }
 786  
     
 787  
                             // register key with specified value
 788  0
                             params.put( 
 789  0
                                 token.substring( 0, equals ).toLowerCase(), value );
 790  
                                 
 791  0
                         }
 792  
                         else
 793  
                         {
 794  
                             // no value found, register the key name
 795  0
                             params.put( token.toLowerCase(), "" );
 796  
                         }
 797  0
                     }
 798  
                 }
 799  
             
 800  0
                 index = end + (hasBody?1:2);
 801  
                 
 802  0
                 WOElement body = null;
 803  0
                 if ( hasBody )
 804  
                 {
 805  0
                     List childElements = new LinkedList();
 806  
                     
 807  0
                     index = processTemplate( 
 808  0
                         childElements, template, index, 
 809  0
                         bindings, aLanguageList );
 810  0
                     start = index;
 811  
                         
 812  0
                     if ( index == -1 )
 813  
                     {
 814  0
                         throw new RuntimeException( 
 815  0
                             "No closing tag found: " + template.substring( end ) );
 816  
                     }
 817  
                     
 818  0
                     if ( childElements.size() == 1 )
 819  
                     {
 820  0
                         body = (WOElement) childElements.get(0);
 821  0
                     }
 822  
                     else
 823  
                     {
 824  0
                         body = new WOParentElement( childElements );
 825  
                     }
 826  
                 }
 827  
                 
 828  0
                 WOElement element = null;
 829  0
                 String nameProperty = (String) params.get( NAME_KEY );
 830  0
                 NSDictionary original = (NSDictionary) bindings.get( nameProperty );
 831  
                 //System.out.println( nameProperty + " : " + associations );                
 832  0
                 if ( original == null ) 
 833  
                 {
 834  0
                     original = NSDictionary.EmptyDictionary;
 835  0
                     System.err.println( "No associations for: " + nameProperty );
 836  0
                     System.err.println( bindings );
 837  
                 }
 838  
                 
 839  0
                 NSDictionary associations = new NSMutableDictionary( original );
 840  0
                 String elementClass = (String) associations.remove( WOApplication.ELEMENT_CLASS );
 841  
             
 842  0
                 WOApplication application = WOApplication.application();
 843  0
                 element = application.dynamicElementWithName(
 844  0
                     elementClass, associations, body, aLanguageList );
 845  0
                 if ( element == null )
 846  
                 {
 847  
                     // unable to create element: show assocs in static element
 848  0
                     element = new WOStaticElement( associations.toString() );
 849  
                 }
 850  
 
 851  
                 //System.out.println( element );                
 852  0
                 elements.add( element );
 853  
                 
 854  0
                 if ( !hasBody )
 855  
                 {
 856  0
                     start = end + 2;
 857  
                 }
 858  0
             }
 859  
             else
 860  0
             if ( name.toLowerCase().startsWith( CLOSE_TAG ) )
 861  
             {
 862  
                 // add any contents before the tag
 863  0
                 elements.add( new WOStaticElement( template.substring( index, start ) ) );
 864  
     
 865  
 //                return end + name.length() + 1; // "<" + ">" - 1 = 1
 866  0
                 return end + (hasBody?1:2); // "<" + ">" - 1 = 1
 867  
             }
 868  
             else
 869  
             {
 870  
                 // tag not interesting: continue
 871  0
                 start = end + (hasBody?1:2);
 872  
             }
 873  0
         }
 874  
     }
 875  
 
 876  
 
 877  
     // Utility Methods
 878  
     
 879  
     static private void rewriteTag( String tagName, 
 880  
             Map properties, String body, StringBuffer context )
 881  
             throws java.io.IOException
 882  
         {
 883  0
                 context.append( "<"+tagName );
 884  0
                 Iterator it = properties.keySet().iterator();
 885  
                 String key;
 886  0
                 while ( it.hasNext() )
 887  
                 {
 888  0
                         key = (String) it.next();
 889  0
                     context.append( " " + key + "=\"" + properties.get( key ) + "\"" );
 890  0
                 }
 891  
                 
 892  0
                 if ( body == null )
 893  
                 {
 894  0
                         context.append( "/>" );
 895  0
                         return;
 896  
                 }
 897  
                 
 898  0
                 context.append( ">" + body + "</" + tagName + ">" );
 899  0
         }
 900  
         
 901  
     private String justTheClassName()
 902  
     {
 903  0
             String className = getClass().getName();
 904  0
             int index = className.lastIndexOf( "." );
 905  0
             if ( index == -1 ) return className;
 906  0
             return className.substring( index+1 );
 907  
     }
 908  
 
 909  
     private String justTheResourcePath()
 910  
     {
 911  0
         int last = -1;
 912  0
         char[] src = getClass().getName().toCharArray();
 913  0
         char[] dst = new char[ src.length ];
 914  0
         for ( int i = 0; i < src.length; i++ )
 915  
         {
 916  0
             if ( src[i] == '.' )
 917  
             {
 918  0
                 dst[i] = '/';
 919  0
                 last = i;
 920  0
             }
 921  
             else
 922  
             {
 923  0
                 dst[i] = src[i];
 924  
             }
 925  
         }
 926  0
         if ( last == -1 ) return null;
 927  0
         return new String( dst, 0, last );
 928  
     }
 929  
 
 930  
     private String readTemplateResource( String name, String framework, String suffix, NSArray languages )
 931  
     {
 932  0
         if ( name == null ) return null;
 933  0
         name = name + DIRECTORY_SUFFIX + '/' + name + suffix;
 934  0
         InputStream is = null;
 935  0
         if ( isCachingEnabled() )
 936  
         {
 937  0
             byte[] data = WOApplication.application().resourceManager().bytesForResourceNamed( 
 938  0
                 name, framework, languages );
 939  0
             if ( data != null )
 940  
             {
 941  0
                 is = new ByteArrayInputStream( data );
 942  
             }
 943  0
         }
 944  
         else
 945  
         {
 946  0
             is = WOApplication.application().resourceManager().inputStreamForResourceNamed( 
 947  0
                 name, framework, languages );
 948  
         }
 949  0
         if ( is == null ) 
 950  
         {
 951  0
             System.err.println( "No resources found for: " + name );            
 952  0
             return null;
 953  
         }
 954  
         
 955  
         // try to autodetect encoding
 956  0
         String encoding = "ISO8859_1";
 957  
         try
 958  
         {
 959  0
             byte[] header = new byte[4];
 960  0
             is = new PushbackInputStream( is, 4 );
 961  0
             is.read( header );
 962  0
             if ( header[0] < 33 || header[0] > 126 ) 
 963  
             {
 964  
                 // if any funny characters, presume UTF-16
 965  0
                 encoding = "UTF-16";
 966  0
                 if (!( header[1] < 33 || header[1] > 126 ))
 967  
                 {
 968  
                     // if second character is valid, presume UTF-8
 969  0
                     encoding = "UTF-8";
 970  
                 }
 971  
             }
 972  
             // check byte-order-mark
 973  0
             if (header[0] == 0xef && header[1] == 0xbb && header[2] == 0xbf) // utf-8
 974  
             {
 975  0
                 encoding = "UTF-8";
 976  0
             }
 977  
             else
 978  0
             if (header[0] == 0xfe && header[1] == 0xff) // utf-16
 979  
             {
 980  0
                 encoding = "UTF-16";
 981  0
             }
 982  
             else
 983  0
             if (header[0] == 0 && header[1] == 0 && header[2] == 0xfe && header[3] == 0xff) // ucs-4
 984  
             {
 985  0
                 encoding = "UCS-4"; //??
 986  0
             }                
 987  
             else
 988  0
             if (header[0] == 0xff && header[1] == 0xfe) // ucs-2le, ucs-4le, and 
 989  
             {
 990  0
                 encoding = "UCS-16le"; //??
 991  
             }
 992  
             // put back the header
 993  0
             ((PushbackInputStream)is).unread( header );
 994  
         }
 995  0
         catch ( Throwable t )
 996  
         {
 997  0
             t.printStackTrace();
 998  0
             System.err.println( 
 999  0
                 "Error while autodetecting encoding: should never happen" );
 1000  0
         }
 1001  
 
 1002  
         try
 1003  
         {
 1004  
             String line;
 1005  0
             StringBuffer buf = new StringBuffer();
 1006  0
             BufferedReader r = new BufferedReader( new InputStreamReader( is, encoding ) );
 1007  0
             while ( ( line = r.readLine() ) != null )
 1008  
             {
 1009  0
                 buf.append( line );
 1010  0
                 buf.append( '\n' );
 1011  0
             }
 1012  0
             is.close(); // release the resource
 1013  0
             return buf.toString();
 1014  
         }
 1015  0
         catch ( IOException exc )
 1016  
         {
 1017  0
             System.err.println( "Error while reading: " + name );            
 1018  0
             exc.printStackTrace();
 1019  0
             return null;
 1020  
         }
 1021  
     }
 1022  
     
 1023  
     // Declaration Parsing
 1024  
 
 1025  
     /**
 1026  
     * Parses the declarations in the specified content and returns a map of element names 
 1027  
     * to maps of attribute names to WOAssociations.
 1028  
     */
 1029  
     private static NSDictionary processDeclaration( String content )
 1030  
     {
 1031  
             int index;
 1032  0
             NSMutableDictionary result = new NSMutableDictionary();
 1033  
             
 1034  
             // strip out comments
 1035  0
             StringBuffer stripped = new StringBuffer();
 1036  
             try
 1037  
             {
 1038  0
             LineNumberReader reader = 
 1039  0
                             new LineNumberReader( new StringReader( content ) );
 1040  
             String line;
 1041  0
             while ( ( line = reader.readLine() ) != null )
 1042  
             {
 1043  0
                 index = line.indexOf("//");
 1044  0
                 while (index > -1) {
 1045  
                     //(chochos) This used to truncate lines with quoted URLs
 1046  
                     //in them. We have to check that the "//" is not inside quotes.
 1047  0
                     boolean quoted = false;
 1048  0
                     if (index > 0) {
 1049  0
                         for (int _position = 0; _position < index; _position++)
 1050  0
                             if (line.charAt(_position) == '"')
 1051  0
                                 quoted = !quoted;
 1052  
                     }
 1053  0
                     if (!quoted) {
 1054  0
                         line = line.substring( 0, index );
 1055  0
                         index = -1;
 1056  0
                     } else {
 1057  
                         //if we didn't truncate the line it's because the //
 1058  
                         //were quoted. let's look for more, and check if they're not quoted...
 1059  0
                         index = line.indexOf("\"", index);
 1060  0
                         if (index > 0) {
 1061  0
                             index = line.indexOf("//", index);
 1062  
                         }
 1063  
                     }
 1064  0
                 }
 1065  0
                 stripped.append( line );
 1066  0
             }
 1067  
                 }
 1068  0
                 catch ( IOException exc )
 1069  
                 {
 1070  0
                         throw new RuntimeException( 
 1071  0
                                 "Error while stripping comments from declaration: " + stripped );
 1072  0
                 }
 1073  0
         while ( (index = stripped.toString().indexOf( "/*" )) != -1 )
 1074  
         {
 1075  0
             int j = stripped.toString().indexOf( "*/", index+1 );
 1076  0
             if ( j == -1 ) break;
 1077  0
             stripped.delete( index, j+2 );
 1078  0
         }
 1079  
             
 1080  
             String token;
 1081  0
             StringTokenizer tokens = new StringTokenizer( stripped.toString(), "{}", true );
 1082  0
             while ( tokens.hasMoreTokens() )
 1083  
             {
 1084  0
                     token = tokens.nextToken();
 1085  
             
 1086  
                     // next token is the name and class
 1087  
                     String name, cl;
 1088  0
                         index = token.indexOf( ":" );
 1089  0
                         if ( index > -1 )
 1090  
                         {
 1091  0
                             name = token.substring( 0, index ).trim();
 1092  0
                                 cl = token.substring( index+1 ).trim();
 1093  0
                         }
 1094  
                         else
 1095  
                         {
 1096  0
                 System.err.println( "Could not parse declaration:" );                    
 1097  0
                 System.err.println( content );                    
 1098  0
                                 throw new RuntimeException( 
 1099  0
                                         "Could not parse declaration: " + token );
 1100  
                         }    
 1101  
 
 1102  
                         // next token is the declaration for the name and class
 1103  0
                         if ( ! tokens.hasMoreTokens() )
 1104  
                         {
 1105  0
                 System.err.println( "Could not find associations for declaration:" );                    
 1106  0
                 System.err.println( content );                    
 1107  0
                                 throw new RuntimeException( 
 1108  0
                                         "Could not find associations for declaration: " + name );
 1109  
                         }
 1110  
                         
 1111  0
                     token = tokens.nextToken();
 1112  0
             if ( token.equals( "{" ) ) 
 1113  
             {
 1114  0
                 if ( !tokens.hasMoreTokens() ) throw new RuntimeException(
 1115  0
                     "Error parsing declaration: expected { but found: '" + token + "'" );
 1116  0
                 token = tokens.nextToken();
 1117  
             }
 1118  
             
 1119  0
             NSMutableDictionary associations = new NSMutableDictionary();
 1120  
             
 1121  0
             if ( !token.equals( "}" ) ) 
 1122  
             {
 1123  
                 String line, key, value; 
 1124  0
                 StringTokenizer lines = 
 1125  0
                     new StringTokenizer( token, ";" );
 1126  0
                 while ( lines.hasMoreElements() )
 1127  
                 {
 1128  0
                     line = lines.nextToken(); 
 1129  0
                     index = line.indexOf( "=" );
 1130  0
                     if ( index > -1 )
 1131  
                     {
 1132  0
                         if ( line.length() == index+ 1 ) line += " ";
 1133  0
                         key = line.substring( 0, index ).trim();
 1134  0
                         value = line.substring( index+1 ).trim();
 1135  0
                     }
 1136  
                     else
 1137  
                     {
 1138  
                         // not a valid key: skip
 1139  0
                         key = null;
 1140  0
                         value = null;
 1141  
                     }
 1142  
     
 1143  0
                     if ( key != null )
 1144  
                     {
 1145  
                         // if in quotation marks                                
 1146  0
                         if ( ( value.startsWith( "\"" ) ) && ( value.endsWith( "\"" ) ) )
 1147  
                         {
 1148  
                             // it's a constant value association
 1149  0
                             value = value.substring( 1, value.length()-1 );
 1150  0
                             associations.put( key, 
 1151  0
                                 WOAssociation.associationWithValue( value ) );
 1152  0
                         }
 1153  
                         else
 1154  0
                         if ( value.equalsIgnoreCase( "true" ) || value.equalsIgnoreCase( "false" ) )
 1155  
                         {
 1156  
                             //HACK: needed to be compatible with woextensions
 1157  
                             // apparently true and false are allowed without quotes
 1158  0
                             associations.put( key, 
 1159  0
                                 WOAssociation.associationWithValue( value ) );
 1160  0
                         }
 1161  
                         else
 1162  
                         {
 1163  
                             //HACK: needed to be compatible with woextensions:
 1164  
                             // apparently a standalone integer is allowed without quotes.
 1165  
                             try 
 1166  
                             {
 1167  0
                                 Integer.parseInt( value ); // does it parse?
 1168  0
                                 associations.put( key, 
 1169  0
                                     WOAssociation.associationWithValue( value ) );
 1170  
                             }
 1171  0
                             catch ( NumberFormatException nfe )
 1172  
                             {
 1173  
                                 // did not parse:
 1174  
                                 // it's a key path association
 1175  0
                                 associations.put( key, 
 1176  0
                                     WOAssociation.associationWithKeyPath( value ) );
 1177  0
                             }
 1178  
                         }
 1179  0
                     }
 1180  
                 }
 1181  0
                 if ( tokens.hasMoreTokens() ) 
 1182  
                 {
 1183  0
                     token = tokens.nextToken();
 1184  0
                     if ( !token.equals( "}" ) ) throw new RuntimeException( 
 1185  0
                         "Error parsing declaration: expected } but found: '" + token + "'" );
 1186  
                 }
 1187  
             }
 1188  0
             associations.put( WOApplication.ELEMENT_CLASS, cl ); // store classname
 1189  0
             result.put( name, associations );
 1190  
             
 1191  0
             }
 1192  
         //System.out.println( "processDeclaration: " + result );
 1193  0
             return result;
 1194  
     }
 1195  
 
 1196  
 }
 1197  
 
 1198  
 /*
 1199  
  * $Log$
 1200  
  * Revision 1.2  2006/02/19 01:44:02  cgruber
 1201  
  * Add xmlrpc files
 1202  
  * Remove jclark and replace with dom4j and javax.xml.sax stuff
 1203  
  * Re-work dependencies and imports so it all compiles.
 1204  
  *
 1205  
  * Revision 1.1  2006/02/16 13:22:22  cgruber
 1206  
  * Check in all sources in eclipse-friendly maven-enabled packages.
 1207  
  *
 1208  
  * Revision 1.32  2003/08/07 00:15:14  chochos
 1209  
  * general cleanup (mostly removing unused imports)
 1210  
  *
 1211  
  * Revision 1.31  2003/07/24 00:23:21  chochos
 1212  
  * fixed problem with parsing wod files that have //-type comments. Quotes URL's would be truncated.
 1213  
  *
 1214  
  * Revision 1.30  2003/03/28 15:33:11  mpowers
 1215  
  * Now using a PushBackInputStream for auto detection of content encoding.
 1216  
  * No longer relying on markSupported() since jar input streams don't have it.
 1217  
  *
 1218  
  * Revision 1.29  2003/03/03 16:41:52  mpowers
 1219  
  * Bad characters in cvs log.
 1220  
  *
 1221  
  * Revision 1.28  2003/03/03 16:37:35  mpowers
 1222  
  * Better handling for string encodings.
 1223  
  * Now trying to autodetect unicode-formatted templates and declarations.
 1224  
  * Now handlings block-style comments in declarations.
 1225  
  *
 1226  
  * Revision 1.27  2003/01/28 19:33:51  mpowers
 1227  
  * Implemented the rest of WOResourceManager.
 1228  
  * Implemented support for java-style i18n.
 1229  
  * Components now use the resource manager to load templates.
 1230  
  *
 1231  
  * Revision 1.26  2003/01/24 20:13:22  mpowers
 1232  
  * Now accepting immutable NSDictionary in constructor, not Map.
 1233  
  *
 1234  
  * Revision 1.25  2003/01/21 17:53:45  mpowers
 1235  
  * Now correctly reporting error for missing bindings.
 1236  
  *
 1237  
  * Revision 1.24  2003/01/20 17:50:11  mpowers
 1238  
  * Caught a loop condition when same declaration was used twice.
 1239  
  *
 1240  
  * Revision 1.23  2003/01/19 22:33:25  mpowers
 1241  
  * Fixed problems with classpath and dynamic class loading.
 1242  
  * Dynamic elements now pass on ensureAwakeInContext.
 1243  
  * Parser how handles <standalone/> tags.
 1244  
  *
 1245  
  * Revision 1.22  2003/01/17 22:55:09  mpowers
 1246  
  * Straighted out the parent binding issue (I think).
 1247  
  * Fixes for woextensions compatibility.
 1248  
  *
 1249  
  * Revision 1.21  2003/01/17 20:34:57  mpowers
 1250  
  * Better handling for components and parents in the context's element stack.
 1251  
  *
 1252  
  * Revision 1.19  2003/01/17 15:32:22  mpowers
 1253  
  * Changes to better support generic elements and containers.
 1254  
  * Now preserving newlines in templates.
 1255  
  *
 1256  
  * Revision 1.17  2003/01/16 22:47:30  mpowers
 1257  
  * Compatibility changes to support compiling woextensions source.
 1258  
  * (34 out of 56 classes compile!)
 1259  
  *
 1260  
  * Revision 1.15  2003/01/16 15:50:43  mpowers
 1261  
  * More robust declaration parsing.
 1262  
  * Subcomponents are now supported.
 1263  
  * dynamicElementWithName can now return subcomponents.
 1264  
  *
 1265  
  * Revision 1.14  2003/01/15 19:50:49  mpowers
 1266  
  * Fixed issues with WOSession and Serializable.
 1267  
  * Can now persist sessions between classloaders (hot swap of class impls).
 1268  
  *
 1269  
  * Revision 1.13  2003/01/15 14:33:48  mpowers
 1270  
  * Refactoring: element id handling is now confined to WOParentElement.
 1271  
  * Other elements/components should not have to do element id incrementing.
 1272  
  *
 1273  
  * Revision 1.12  2003/01/14 16:05:12  mpowers
 1274  
  * Removed extraneous printlns.
 1275  
  *
 1276  
  * Revision 1.11  2003/01/13 22:24:25  mpowers
 1277  
  * Request-response cycle is working with session and page persistence.
 1278  
  *
 1279  
  * Revision 1.10  2003/01/10 19:33:28  mpowers
 1280  
  * Added contextID for the component url generation.
 1281  
  *
 1282  
  * Revision 1.9  2003/01/10 19:16:40  mpowers
 1283  
  * Implemented support for page caching.
 1284  
  *
 1285  
  * Revision 1.8  2003/01/09 21:16:48  mpowers
 1286  
  * Bringing request-response cycle more into conformance.
 1287  
  *
 1288  
  * Revision 1.7  2003/01/09 16:13:55  mpowers
 1289  
  * Implemented WOComponentRequestHandler:
 1290  
  * Bringing the request-response cycle more into conformance.
 1291  
  *
 1292  
  * Revision 1.6  2002/12/20 22:56:33  mpowers
 1293  
  * Reimplemented the template parsing again.
 1294  
  * Nested components are now correctly parsed.
 1295  
  * ElementID numbering is now working.
 1296  
  *
 1297  
  * Revision 1.3  2002/12/18 14:12:38  mpowers
 1298  
  * Support for differentiated request handlers.
 1299  
  * Support url generation for WOContext and WORequest.
 1300  
  *
 1301  
  * Revision 1.2  2002/11/07 18:52:33  mpowers
 1302  
  * New components courtesy of ezamudio@nasoft.com.  Many thanks!
 1303  
  *
 1304  
  * Revision 1.1.1.1  2000/12/21 15:53:01  mpowers
 1305  
  * Contributing wotonomy.
 1306  
  *
 1307  
  * Revision 1.2  2000/12/20 16:25:49  michael
 1308  
  * Added log to all files.
 1309  
  *
 1310  
  *
 1311  
  */
 1312