From aedc34d55462a75e329bbf342251ff6504cd117e Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Sun, 19 May 2024 17:56:33 -0400 Subject: Initial import from SVN --- .../main/java/net/wotonomy/web/WOComponent.java | 1312 ++++++++++++++++++++ 1 file changed, 1312 insertions(+) create mode 100644 projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java (limited to 'projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java') diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java new file mode 100644 index 0000000..20d8b0a --- /dev/null +++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java @@ -0,0 +1,1312 @@ +/* +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. + * + * + */ + -- cgit v1.2.3