/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.web; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.io.PushbackInputStream; import java.io.StringReader; import java.lang.reflect.InvocationTargetException; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import net.wotonomy.control.EOKeyValueCodingSupport; import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; import net.wotonomy.foundation.NSMutableDictionary; import net.wotonomy.foundation.NSSelector; /** * Pure java implementation of WOComponent. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 905 $ */ public class WOComponent extends WOElement implements WOActionResults, net.wotonomy.control.EOKeyValueCodingAdditions, net.wotonomy.control.EOKeyValueCoding { WOElement rootElement; private static final String DIRECTORY_SUFFIX = ".wo"; private static final String TEMPLATE_SUFFIX = ".html"; private static final String DECLARATION_SUFFIX = ".wod"; private static final String OPEN_TAG = "webobject"; private static final String CLOSE_TAG = "/webobject"; private static final String NAME_KEY = "name"; protected transient WOContext context; // don't persist protected boolean cachingEnabled; protected WOElement template; protected WOComponent parent; /** * Default constructor. Deprecated in latest spec. */ public WOComponent () { parent = null; cachingEnabled = true; template = null; } /** * Constructor specifying a context. */ public WOComponent( WOContext aContext ) { this(); context = aContext; } /** * Returns the name of the component, which is usually just the class name. */ public String name () { return justTheClassName(); } /** * Returns the system-dependent file path to the current component * directory, including the ".wo" extension. */ public String path () { throw new RuntimeException( "Not implemented yet." ); } /** * Returns the URL for this component, relative to the server's * document root on the server's file system. * This is not an http url. */ public String baseURL () { throw new RuntimeException( "Not implemented yet." ); } /** * Returns the name of the framework that contains this component, * or null if the component does not belong to a framework. * This currently returns the package path of the class, or * null if it does not belong to a package. */ public String frameworkName () { return justTheResourcePath(); } /** * Sets whether templates are cached. If true, templates will * only be read once per application lifetime. Otherwise, templates * will be read each time this class is instantiated. Defaults to false. */ public void setCachingEnabled (boolean enabled) { cachingEnabled = enabled; } /** * Returns whether templates are cached. If true, templates are * read once per application lifetime. Otherwise, templates are * read each time this class is instantiated. */ public boolean isCachingEnabled () { return cachingEnabled && WOApplication.application().isCachingEnabled(); } /** * Returns the root of the tree of elements produced by parsing * the templates in the component directory for this component. */ public WOElement template() { return template; } /** * Returns the root of the tree of elements produced by parsing * the templates in the component directory for the named component. * @deprecated Use template() instead. */ public WOElement templateWithName(String aComponentName) { return templateWithName( aComponentName, null ); } /** * Returns the root of the tree of elements produced by parsing * the templates in the component directory for the named component. */ WOElement templateWithName(String aComponentName, String aFramework) { NSArray languages = null; WOContext context = context(); if ( context != null ) { languages = context.request().browserLanguages(); } WOElement result = templateWithHTMLString( readTemplateResource( aComponentName, aFramework, TEMPLATE_SUFFIX, languages ), readTemplateResource( aComponentName, aFramework, DECLARATION_SUFFIX, languages ), languages ); if ( result == null ) { System.out.println( "WOComponent.templateWithName: failed for " + aComponentName ); } return result; } /** * Returns the root of the tree of elements produced by parsing * the specfified HTML string and bindings declaration string. * Note: language list is currently ignored. */ public static WOElement templateWithHTMLString ( String anHTMLString, String aDeclaration, List aLanguageList) { if ( anHTMLString == null ) return null; WOElement result = null; try { NSDictionary bindings = processDeclaration( aDeclaration ); List elements = new LinkedList(); int index = processTemplate( elements, anHTMLString, 0, bindings, aLanguageList ); if ( index == -1 ) { if ( elements.size() == 1 ) { result = (WOElement) elements.get(0); } else { result = new WOParentElement( elements ); } } else // entire template did not process { throw new RuntimeException( "No closing tag: " + anHTMLString.substring( index ) ); } } catch ( Exception exc ) { exc.printStackTrace(); } return result; } /** * Called at the beginning of a request-response cycle. * Override to perform any necessary initialization. * This implementation does nothing. */ public void awake () { } /** * Package access only. Called to initialize the component with * the proper context before the start of the request-response cycle. * If the context has a current component, that component becomes * this component's parent. */ void ensureAwakeInContext (WOContext aContext) { context = aContext; parent = aContext.parent(); if ( template == null ) { template = templateWithName( name(), frameworkName() ); } if ( template != null ) { template.ensureAwakeInContext( aContext ); } awake(); } public void takeValuesFromRequest (WORequest aRequest, WOContext aContext) { if ( synchronizesVariablesWithBindings() ) { pullValuesFromParent(); if ( template != null ) { template.takeValuesFromRequest( aRequest, aContext ); } pushValuesToParent(); } else if ( template != null ) { template.takeValuesFromRequest( aRequest, aContext ); } } public WOActionResults invokeAction (WORequest aRequest, WOContext aContext) { WOActionResults result = null; if ( synchronizesVariablesWithBindings() ) { pullValuesFromParent(); if ( template != null ) { result = template.invokeAction( aRequest, aContext ); } pushValuesToParent(); } else if ( template != null ) { result = template.invokeAction( aRequest, aContext ); } return result; } public void appendToResponse (WOResponse aResponse, WOContext aContext) { if ( synchronizesVariablesWithBindings() ) { pullValuesFromParent(); if ( template != null ) { template.appendToResponse( aResponse, aContext ); } pushValuesToParent(); } else if ( template != null ) { template.appendToResponse( aResponse, aContext ); } context = null; } /** * Called at the end of a request-response cycle. * Override to perform any necessary clean-up. * This implementation does nothing. */ public void sleep () { } /** * Generates a WOResponse and calls appendToResponse() on it. */ public WOResponse generateResponse () { WOResponse response = new WOResponse(); WOContext context = context(); appendToResponse( response, context ); // nulls out context context.session().savePage( this ); //?is this the right place for this? return response; } /** * Returns this component's parent component, or null if none. */ public WOComponent parent() { return parent; } /** * Invokes the specified action on this component's parent. * Variables will be synchronized when this method returns. */ public WOActionResults performParentAction(String anAction) { WOActionResults result = parent().performAction( anAction ); if ( synchronizesVariablesWithBindings() ) { pullValuesFromParent(); } return result; } /** * Invokes the specified action on this component. */ WOActionResults performAction( String anAction ) { try { return (WOActionResults) NSSelector.invoke( anAction, this ); } catch ( NoSuchMethodException exc ) { // returns below } catch ( InvocationTargetException exc ) { Throwable t = exc.getTargetException(); exc.printStackTrace(); throw new RuntimeException( t.toString() ); } catch ( Exception exc ) { exc.printStackTrace(); throw new RuntimeException( exc.toString() ); } return null; } /** * Called before each phase of the request-response cycle, * if synchronizesVariablesWithBindings is true and the * component is not stateless. */ public void pullValuesFromParent() { if ( associations == null ) return; String key; Enumeration e = associations.keyEnumerator(); while ( e.hasMoreElements() ) { key = e.nextElement().toString(); takeValueForKey( valueForBinding( key ), key ); } } /** * Called after each phase of the request-response cycle, * if synchronizesVariablesWithBindings is true and the * component is not stateless. */ public void pushValuesToParent() { if ( associations == null ) return; String key; Enumeration e = associations.keyEnumerator(); while ( e.hasMoreElements() ) { key = e.nextElement().toString(); setValueForBinding( valueForKey( key ), key ); } } /** * Returns whether this component should be considered stateless. * Stateless components are shared between sessions to conserve memory. * This implementation returns false; override to return true. */ public boolean isStateless() { return false; } /** * Called only on stateless components to tell themselves to reset * themselves for another invocation using a different context. * This implementation does nothing. */ public void reset() { // does nothing } /** * Returns the application containing this instance of the class. */ public WOApplication application () { return context.application(); } /** * Returns whether a session has been created for this user. */ public boolean hasSession () { return context.hasSession(); } /** * Returns the current session object, creating it if it doesn't exist. */ public WOSession session () { return context.session(); } /** * Returns the current context for this component. */ public WOContext context () { return context; } /** * Returns a new WOComponent with the specified name. * If null, returns the component named "Main". * If the named component doesn't exist, returns null. */ public WOComponent pageWithName (String aName) { return application().pageWithName( aName, context() ); } /** * Called when exceptions are raised by assigning values * to this object. This implementation does nothing, but * subclasses may override to do something useful. */ public void validationFailedWithException ( Throwable anException, Object aValue, String aPath) { // does nothing } /** * Called on the component that represents the requested page. * Override to return logging information specific to your * component. This implementation returns the component's name. */ public String descriptionForResponse ( WOResponse aResponse, WOContext aContext) { return name(); } /** * Returns true if this component should get and set values * in its parent. This implementation returns true. * Override to create a component that does not automatically * synchronize bindings with its parent, useful if you wish * to handle synchronization manually. */ public boolean synchronizesVariablesWithBindings () { return true; } /** * Returns whether this component has a readable value that maps * to the specified binding. This implementation calls * hasBinding(aBinding). */ public boolean canGetValueForBinding(String aBinding) { return hasBinding( aBinding ); } /** * Returns whether this component has a writable value that maps * to the specified binding. */ public boolean canSetValueForBinding(String aBinding) { WOAssociation assoc = (WOAssociation)associations.objectForKey(aBinding); if (assoc != null) { if ( assoc.isValueSettable() ) return true; } return false; } /** * Returns whether this component has the specified binding. */ public boolean hasBinding (String aBinding) { if ( associations == null ) return false; return associations.containsKey( aBinding ); } /** * Returns the value for the specified binding for this component. * The parent component is expected to have set the binding for * this component. If no such binding exists, the binding is * treated as a property is and obtained using valueForKey. * If the property is not found, this method returns null. */ public Object valueForBinding (String aBinding) { WOComponent parent = parent(); if ( associations != null ) { WOAssociation assoc = (WOAssociation)associations.objectForKey(aBinding); if (assoc != null && parent != null) { return assoc.valueInComponent( parent ); } } if ( parent != null ) { return parent.valueForKey( aBinding ); } return null; } /** * Sets the value for the specified binding for this component. * The parent component is expected to have set the binding * for this component. If no such binding exists, the binding * is treated as a property and is set using takeValueForKey. * If the property is not found, this method fails silently. */ public void setValueForBinding (Object aValue, String aBinding) { if ( associations == null ) return; WOComponent parent = parent(); if ( associations != null ) { WOAssociation assoc = (WOAssociation)associations.objectForKey(aBinding); if (assoc != null && parent != null) { if ( assoc.isValueSettable() ) { assoc.setValue( aValue, parent ); return; } } } if ( parent != null ) { parent.takeValueForKey( aValue, aBinding ); } } public static void logString (String aString) { System.out.println( aString ); } public static void debugString (String aString) { System.err.println( aString ); } public Object valueForKeyPath (String aPath) { // currently key value coding support also handles keypaths return valueForKey( aPath ); } public void takeValueForKeyPath (Object aValue, String aPath) { // currently key value coding support also handles keypaths takeValueForKey( aValue, aPath ); } public NSDictionary valuesForKeys (List aKeyList) { throw new RuntimeException( "Not implemented yet." ); } public void takeValuesFromDictionary (Map aValueMap) { throw new RuntimeException( "Not implemented yet." ); } public Object valueForKey (String aKey) { // System.out.println( "valueForKey: " + aKey + "->" + this ); // handle "^" property keys if ( aKey.startsWith( "^" ) ) { return valueForBinding( aKey.substring(1) ); } return EOKeyValueCodingSupport.valueForKey( this, aKey ); } public void takeValueForKey (Object aValue, String aKey) { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); // handle "^" property keys if ( aKey.startsWith( "^" ) ) { setValueForBinding( aValue, aKey.substring(1) ); return; } EOKeyValueCodingSupport.takeValueForKey( this, aValue, aKey ); } public Object storedValueForKey (String aKey) { return EOKeyValueCodingSupport.storedValueForKey( this, aKey ); } public void takeStoredValueForKey (Object aValue, String aKey) { EOKeyValueCodingSupport.takeStoredValueForKey( this, aValue, aKey ); } public Object handleQueryWithUnboundKey (String aKey) { return EOKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); } public void handleTakeValueForUnboundKey (Object aValue, String aKey) { EOKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); } public void unableToSetNullForKey (String aKey) { EOKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); } public Object validateTakeValueForKeyPath (Object aValue, String aKey) { throw new RuntimeException( "Not implemented yet." ); } // Template Processing /** * Takes a template string and a location to begin parsing, * looking only for interesting tags, and calling itself recursively * as necessary. Returns the index to resume parsing, or -1 if done. */ static private int processTemplate( List elements, String template, int index, Map bindings, List aLanguageList ) throws java.io.IOException { //System.out.println( "processTemplate: " + index ); if ( template == null ) return -1; int start = index; while ( true ) { // search for start of next tag start = template.indexOf( '<', start ); if ( start == -1 ) { // if no tags, send output and return elements.add( new WOStaticElement( template.substring( index ) ) ); return -1; } // search for end of opening tag int end = template.indexOf( ">", start + 1 ); if ( end == -1 ) { // if no end to tag throw new RuntimeException( "No end to tag: " + template.substring( start ) ); } boolean hasBody = true; if ( template.charAt( end - 1 ) == '/' ) { // tag is standalone - no body end = end - 1; hasBody = false; } // search for name of tag int endName = start + 1; while ( endName < end ) { if ( Character.isWhitespace( template.charAt(endName) ) ) break; endName++; } String name = template.substring( start + 1, endName ); if ( name.toLowerCase().startsWith( OPEN_TAG ) ) { // add the contents before the tag //System.out.println( index + " : " + start + " : " + hasBody ); elements.add( new WOStaticElement( template.substring( index, start ) ) ); // interesting tag; parse parameters Map params = new HashMap( 5 ); // arbitrary init length if ( endName < end ) { // delimit by whitespace StringTokenizer tokens = new StringTokenizer( template.substring( endName+1, end ) ); int equals; String token; String value; while ( tokens.hasMoreTokens() ) { token = tokens.nextToken(); equals = token.indexOf( '=' ); if ( equals != -1 ) { value = token.substring( equals+1 ); if ( value.startsWith( "\"" ) ) { // handle spaces within parameter names while ( ! value.endsWith( "\"" ) ) { value = value + " " + tokens.nextToken(); } // strip quotation marks if ( value.endsWith( "\"" ) ) { value = value.substring( 1, value.length()-1 ); } } // register key with specified value params.put( token.substring( 0, equals ).toLowerCase(), value ); } else { // no value found, register the key name params.put( token.toLowerCase(), "" ); } } } index = end + (hasBody?1:2); WOElement body = null; if ( hasBody ) { List childElements = new LinkedList(); index = processTemplate( childElements, template, index, bindings, aLanguageList ); start = index; if ( index == -1 ) { throw new RuntimeException( "No closing tag found: " + template.substring( end ) ); } if ( childElements.size() == 1 ) { body = (WOElement) childElements.get(0); } else { body = new WOParentElement( childElements ); } } WOElement element = null; String nameProperty = (String) params.get( NAME_KEY ); NSDictionary original = (NSDictionary) bindings.get( nameProperty ); //System.out.println( nameProperty + " : " + associations ); if ( original == null ) { original = NSDictionary.EmptyDictionary; System.err.println( "No associations for: " + nameProperty ); System.err.println( bindings ); } NSDictionary associations = new NSMutableDictionary( original ); String elementClass = (String) associations.remove( WOApplication.ELEMENT_CLASS ); WOApplication application = WOApplication.application(); element = application.dynamicElementWithName( elementClass, associations, body, aLanguageList ); if ( element == null ) { // unable to create element: show assocs in static element element = new WOStaticElement( associations.toString() ); } //System.out.println( element ); elements.add( element ); if ( !hasBody ) { start = end + 2; } } else if ( name.toLowerCase().startsWith( CLOSE_TAG ) ) { // add any contents before the tag elements.add( new WOStaticElement( template.substring( index, start ) ) ); // return end + name.length() + 1; // "<" + ">" - 1 = 1 return end + (hasBody?1:2); // "<" + ">" - 1 = 1 } else { // tag not interesting: continue start = end + (hasBody?1:2); } } } // Utility Methods static private void rewriteTag( String tagName, Map properties, String body, StringBuffer context ) throws java.io.IOException { context.append( "<"+tagName ); Iterator it = properties.keySet().iterator(); String key; while ( it.hasNext() ) { key = (String) it.next(); context.append( " " + key + "=\"" + properties.get( key ) + "\"" ); } if ( body == null ) { context.append( "/>" ); return; } context.append( ">" + body + "" ); } private String justTheClassName() { String className = getClass().getName(); int index = className.lastIndexOf( "." ); if ( index == -1 ) return className; return className.substring( index+1 ); } private String justTheResourcePath() { int last = -1; char[] src = getClass().getName().toCharArray(); char[] dst = new char[ src.length ]; for ( int i = 0; i < src.length; i++ ) { if ( src[i] == '.' ) { dst[i] = '/'; last = i; } else { dst[i] = src[i]; } } if ( last == -1 ) return null; return new String( dst, 0, last ); } private String readTemplateResource( String name, String framework, String suffix, NSArray languages ) { if ( name == null ) return null; name = name + DIRECTORY_SUFFIX + '/' + name + suffix; InputStream is = null; if ( isCachingEnabled() ) { byte[] data = WOApplication.application().resourceManager().bytesForResourceNamed( name, framework, languages ); if ( data != null ) { is = new ByteArrayInputStream( data ); } } else { is = WOApplication.application().resourceManager().inputStreamForResourceNamed( name, framework, languages ); } if ( is == null ) { System.err.println( "No resources found for: " + name ); return null; } // try to autodetect encoding String encoding = "ISO8859_1"; try { byte[] header = new byte[4]; is = new PushbackInputStream( is, 4 ); is.read( header ); if ( header[0] < 33 || header[0] > 126 ) { // if any funny characters, presume UTF-16 encoding = "UTF-16"; if (!( header[1] < 33 || header[1] > 126 )) { // if second character is valid, presume UTF-8 encoding = "UTF-8"; } } // check byte-order-mark if (header[0] == 0xef && header[1] == 0xbb && header[2] == 0xbf) // utf-8 { encoding = "UTF-8"; } else if (header[0] == 0xfe && header[1] == 0xff) // utf-16 { encoding = "UTF-16"; } else if (header[0] == 0 && header[1] == 0 && header[2] == 0xfe && header[3] == 0xff) // ucs-4 { encoding = "UCS-4"; //?? } else if (header[0] == 0xff && header[1] == 0xfe) // ucs-2le, ucs-4le, and { encoding = "UCS-16le"; //?? } // put back the header ((PushbackInputStream)is).unread( header ); } catch ( Throwable t ) { t.printStackTrace(); System.err.println( "Error while autodetecting encoding: should never happen" ); } try { String line; StringBuffer buf = new StringBuffer(); BufferedReader r = new BufferedReader( new InputStreamReader( is, encoding ) ); while ( ( line = r.readLine() ) != null ) { buf.append( line ); buf.append( '\n' ); } is.close(); // release the resource return buf.toString(); } catch ( IOException exc ) { System.err.println( "Error while reading: " + name ); exc.printStackTrace(); return null; } } // Declaration Parsing /** * Parses the declarations in the specified content and returns a map of element names * to maps of attribute names to WOAssociations. */ private static NSDictionary processDeclaration( String content ) { int index; NSMutableDictionary result = new NSMutableDictionary(); // strip out comments StringBuffer stripped = new StringBuffer(); try { LineNumberReader reader = new LineNumberReader( new StringReader( content ) ); String line; while ( ( line = reader.readLine() ) != null ) { index = line.indexOf("//"); while (index > -1) { //(chochos) This used to truncate lines with quoted URLs //in them. We have to check that the "//" is not inside quotes. boolean quoted = false; if (index > 0) { for (int _position = 0; _position < index; _position++) if (line.charAt(_position) == '"') quoted = !quoted; } if (!quoted) { line = line.substring( 0, index ); index = -1; } else { //if we didn't truncate the line it's because the // //were quoted. let's look for more, and check if they're not quoted... index = line.indexOf("\"", index); if (index > 0) { index = line.indexOf("//", index); } } } stripped.append( line ); } } catch ( IOException exc ) { throw new RuntimeException( "Error while stripping comments from declaration: " + stripped ); } while ( (index = stripped.toString().indexOf( "/*" )) != -1 ) { int j = stripped.toString().indexOf( "*/", index+1 ); if ( j == -1 ) break; stripped.delete( index, j+2 ); } String token; StringTokenizer tokens = new StringTokenizer( stripped.toString(), "{}", true ); while ( tokens.hasMoreTokens() ) { token = tokens.nextToken(); // next token is the name and class String name, cl; index = token.indexOf( ":" ); if ( index > -1 ) { name = token.substring( 0, index ).trim(); cl = token.substring( index+1 ).trim(); } else { System.err.println( "Could not parse declaration:" ); System.err.println( content ); throw new RuntimeException( "Could not parse declaration: " + token ); } // next token is the declaration for the name and class if ( ! tokens.hasMoreTokens() ) { System.err.println( "Could not find associations for declaration:" ); System.err.println( content ); throw new RuntimeException( "Could not find associations for declaration: " + name ); } token = tokens.nextToken(); if ( token.equals( "{" ) ) { if ( !tokens.hasMoreTokens() ) throw new RuntimeException( "Error parsing declaration: expected { but found: '" + token + "'" ); token = tokens.nextToken(); } NSMutableDictionary associations = new NSMutableDictionary(); if ( !token.equals( "}" ) ) { String line, key, value; StringTokenizer lines = new StringTokenizer( token, ";" ); while ( lines.hasMoreElements() ) { line = lines.nextToken(); index = line.indexOf( "=" ); if ( index > -1 ) { if ( line.length() == index+ 1 ) line += " "; key = line.substring( 0, index ).trim(); value = line.substring( index+1 ).trim(); } else { // not a valid key: skip key = null; value = null; } if ( key != null ) { // if in quotation marks if ( ( value.startsWith( "\"" ) ) && ( value.endsWith( "\"" ) ) ) { // it's a constant value association value = value.substring( 1, value.length()-1 ); associations.put( key, WOAssociation.associationWithValue( value ) ); } else if ( value.equalsIgnoreCase( "true" ) || value.equalsIgnoreCase( "false" ) ) { //HACK: needed to be compatible with woextensions // apparently true and false are allowed without quotes associations.put( key, WOAssociation.associationWithValue( value ) ); } else { //HACK: needed to be compatible with woextensions: // apparently a standalone integer is allowed without quotes. try { Integer.parseInt( value ); // does it parse? associations.put( key, WOAssociation.associationWithValue( value ) ); } catch ( NumberFormatException nfe ) { // did not parse: // it's a key path association associations.put( key, WOAssociation.associationWithKeyPath( value ) ); } } } } if ( tokens.hasMoreTokens() ) { token = tokens.nextToken(); if ( !token.equals( "}" ) ) throw new RuntimeException( "Error parsing declaration: expected } but found: '" + token + "'" ); } } associations.put( WOApplication.ELEMENT_CLASS, cl ); // store classname result.put( name, associations ); } //System.out.println( "processDeclaration: " + result ); return result; } } /* * $Log$ * Revision 1.2 2006/02/19 01:44:02 cgruber * Add xmlrpc files * Remove jclark and replace with dom4j and javax.xml.sax stuff * Re-work dependencies and imports so it all compiles. * * Revision 1.1 2006/02/16 13:22:22 cgruber * Check in all sources in eclipse-friendly maven-enabled packages. * * Revision 1.32 2003/08/07 00:15:14 chochos * general cleanup (mostly removing unused imports) * * Revision 1.31 2003/07/24 00:23:21 chochos * fixed problem with parsing wod files that have //-type comments. Quotes URL's would be truncated. * * Revision 1.30 2003/03/28 15:33:11 mpowers * Now using a PushBackInputStream for auto detection of content encoding. * No longer relying on markSupported() since jar input streams don't have it. * * Revision 1.29 2003/03/03 16:41:52 mpowers * Bad characters in cvs log. * * Revision 1.28 2003/03/03 16:37:35 mpowers * Better handling for string encodings. * Now trying to autodetect unicode-formatted templates and declarations. * Now handlings block-style comments in declarations. * * Revision 1.27 2003/01/28 19:33:51 mpowers * Implemented the rest of WOResourceManager. * Implemented support for java-style i18n. * Components now use the resource manager to load templates. * * Revision 1.26 2003/01/24 20:13:22 mpowers * Now accepting immutable NSDictionary in constructor, not Map. * * Revision 1.25 2003/01/21 17:53:45 mpowers * Now correctly reporting error for missing bindings. * * Revision 1.24 2003/01/20 17:50:11 mpowers * Caught a loop condition when same declaration was used twice. * * Revision 1.23 2003/01/19 22:33:25 mpowers * Fixed problems with classpath and dynamic class loading. * Dynamic elements now pass on ensureAwakeInContext. * Parser how handles tags. * * Revision 1.22 2003/01/17 22:55:09 mpowers * Straighted out the parent binding issue (I think). * Fixes for woextensions compatibility. * * Revision 1.21 2003/01/17 20:34:57 mpowers * Better handling for components and parents in the context's element stack. * * Revision 1.19 2003/01/17 15:32:22 mpowers * Changes to better support generic elements and containers. * Now preserving newlines in templates. * * Revision 1.17 2003/01/16 22:47:30 mpowers * Compatibility changes to support compiling woextensions source. * (34 out of 56 classes compile!) * * Revision 1.15 2003/01/16 15:50:43 mpowers * More robust declaration parsing. * Subcomponents are now supported. * dynamicElementWithName can now return subcomponents. * * Revision 1.14 2003/01/15 19:50:49 mpowers * Fixed issues with WOSession and Serializable. * Can now persist sessions between classloaders (hot swap of class impls). * * Revision 1.13 2003/01/15 14:33:48 mpowers * Refactoring: element id handling is now confined to WOParentElement. * Other elements/components should not have to do element id incrementing. * * Revision 1.12 2003/01/14 16:05:12 mpowers * Removed extraneous printlns. * * Revision 1.11 2003/01/13 22:24:25 mpowers * Request-response cycle is working with session and page persistence. * * Revision 1.10 2003/01/10 19:33:28 mpowers * Added contextID for the component url generation. * * Revision 1.9 2003/01/10 19:16:40 mpowers * Implemented support for page caching. * * Revision 1.8 2003/01/09 21:16:48 mpowers * Bringing request-response cycle more into conformance. * * Revision 1.7 2003/01/09 16:13:55 mpowers * Implemented WOComponentRequestHandler: * Bringing the request-response cycle more into conformance. * * Revision 1.6 2002/12/20 22:56:33 mpowers * Reimplemented the template parsing again. * Nested components are now correctly parsed. * ElementID numbering is now working. * * Revision 1.3 2002/12/18 14:12:38 mpowers * Support for differentiated request handlers. * Support url generation for WOContext and WORequest. * * Revision 1.2 2002/11/07 18:52:33 mpowers * New components courtesy of ezamudio@nasoft.com. Many thanks! * * Revision 1.1.1.1 2000/12/21 15:53:01 mpowers * Contributing wotonomy. * * Revision 1.2 2000/12/20 16:25:49 michael * Added log to all files. * * */