summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java
diff options
context:
space:
mode:
Diffstat (limited to 'projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java')
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java1312
1 files changed, 1312 insertions, 0 deletions
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 + "</" + tagName + ">" );
+ }
+
+ 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 <standalone/> 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.
+ *
+ *
+ */
+