summaryrefslogtreecommitdiff
path: root/projects/net.wotonomy.web/src/main/java/net/wotonomy/web
diff options
context:
space:
mode:
authorBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
committerBenjamin Culkin <scorpress@gmail.com>2024-05-19 17:56:33 -0400
commitaedc34d55462a75e329bbf342251ff6504cd117e (patch)
treebcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.web/src/main/java/net/wotonomy/web
Initial import from SVN
Diffstat (limited to 'projects/net.wotonomy.web/src/main/java/net/wotonomy/web')
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java351
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java3464
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java51
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java57
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java81
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java1193
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java168
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java121
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java81
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponent.java1312
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java45
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java229
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java99
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java572
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java203
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java317
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java221
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java2455
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java208
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java97
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java124
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java60
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java61
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java95
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java42
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java249
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java150
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java58
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java102
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java54
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java333
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java178
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java20
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java135
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java20
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java179
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java586
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java68
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java50
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java489
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java122
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java31
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java184
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java188
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java528
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java93
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java70
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java136
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java70
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java112
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java72
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java59
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/package.html11
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java665
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java114
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java534
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java526
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java72
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java238
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java283
-rw-r--r--projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/package.html30
61 files changed, 18516 insertions, 0 deletions
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java
new file mode 100644
index 0000000..cef1372
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/ObservableArray.java
@@ -0,0 +1,351 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Michael Powers
+
+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.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.control.EOObserving;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSRange;
+
+/**
+* A package class that extends NSMutableArray but makes use
+* of the fact that wotonomy's implementation extends ArrayList
+* to intercept insertions and deletion and register and
+* unregister objects for change notifications as appropriate.
+* Since we can't be sure of ArrayList's implementation, we're
+* forced to override each and every add and remove method,
+* some of which probably call each other. However,
+* EOObserverCenter will only register us once per object.
+*/
+class ObservableArray extends NSMutableArray
+{
+ EOObserving observer;
+
+ ObservableArray( EOObserving anObserver )
+ {
+ observer = anObserver;
+ }
+
+ /**
+ * Removes the last object from the array.
+ */
+ public void removeLastObject ()
+ {
+ remove( count() - 1 );
+ }
+
+ /**
+ * Removes the object at the specified index.
+ */
+ public void removeObjectAtIndex (int index)
+ {
+ remove( index );
+ }
+
+ /**
+ * Adds all objects in the specified collection.
+ */
+ public void addObjectsFromArray (Collection aCollection)
+ {
+ addAll( aCollection );
+ }
+
+ /**
+ * Removes all objects from the array.
+ */
+ public void removeAllObjects ()
+ {
+ clear();
+ }
+
+ /**
+ * Removes all objects equivalent to the specified object
+ * within the range of specified indices.
+ */
+ public void removeObject (Object anObject, NSRange aRange)
+ {
+ if ( ( anObject == null ) || ( aRange == null ) ) return;
+
+ int loc = aRange.location();
+ int max = aRange.maxRange();
+ for ( int i = loc; i < max; i++ )
+ {
+ if ( anObject.equals( get( i ) ) )
+ {
+ remove( i );
+ i = i - 1;
+ max = max - 1;
+ }
+ }
+ }
+
+ /**
+ * Removes all instances of the specified object within the
+ * range of specified indices, comparing by reference.
+ */
+ public void removeIdenticalObject (Object anObject, NSRange aRange)
+ {
+ if ( ( anObject == null ) || ( aRange == null ) ) return;
+
+ int loc = aRange.location();
+ int max = aRange.maxRange();
+ for ( int i = loc; i < max; i++ )
+ {
+ if ( anObject == get( i ) )
+ {
+ remove( i );
+ i = i - 1;
+ max = max - 1;
+ }
+ }
+ }
+
+ /**
+ * Removes all objects in the specified collection from the array.
+ */
+ public void removeObjectsInArray (Collection aCollection)
+ {
+ removeAll( aCollection );
+ }
+
+ /**
+ * Removes all objects in the indices within the specified range
+ * from the array.
+ */
+ public void removeObjectsInRange (NSRange aRange)
+ {
+ if ( aRange == null ) return;
+
+ for ( int i = 0; i < aRange.length(); i++ )
+ {
+ remove( aRange.location() );
+ }
+ }
+
+ /**
+ * Replaces objects in the current range with objects from
+ * the specified range of the specified array. If currentRange
+ * is larger than otherRange, the extra objects are removed.
+ * If otherRange is larger than currentRange, the extra objects
+ * are added.
+ */
+ public void replaceObjectsInRange (NSRange currentRange,
+ List otherArray, NSRange otherRange)
+ {
+ if ( ( currentRange == null ) || ( otherArray == null ) ||
+ ( otherRange == null ) ) return;
+
+ // transform otherRange if out of bounds for array
+ if ( otherRange.maxRange() > otherArray.size() )
+ {
+ // TODO: Test this logic.
+ int loc = Math.min( otherRange.location(), otherArray.size() - 1 );
+ otherRange = new NSRange( loc, otherArray.size() - loc );
+ }
+
+ Object o;
+ List subList = subList(
+ currentRange.location(), currentRange.maxRange() );
+ int otherIndex = otherRange.location();
+ // TODO: Test this logic.
+ for ( int i = 0; i < subList.size(); i++ )
+ {
+ if ( otherIndex < otherRange.maxRange() )
+ { // set object
+ subList.set( i, otherArray.get( otherIndex ) );
+ }
+ else
+ { // remove extra elements from currentRange
+ subList.remove( i );
+ i--;
+ }
+ otherIndex++;
+ }
+ // TODO: Test this logic.
+ for ( int i = otherIndex; i < otherRange.maxRange(); i++ )
+ {
+ add( otherArray.get( i ) );
+ }
+ }
+
+ /**
+ * Clears the current array and then populates it with the
+ * contents of the specified collection.
+ */
+ public void setArray (Collection aCollection)
+ {
+ clear();
+ addAll( aCollection );
+ }
+
+ /**
+ * Removes all objects equivalent to the specified object.
+ */
+ public void removeObject (Object anObject)
+ {
+ remove( anObject );
+ }
+
+ /**
+ * Removes all occurences of the specified object,
+ * comparing by reference.
+ */
+ public void removeIdenticalObject (Object anObject)
+ {
+ EOObserverCenter.removeObserver( observer, anObject );
+ super.removeIdenticalObject( anObject );
+ }
+
+ /**
+ * Inserts the specified object into this array at the
+ * specified index.
+ */
+ public void insertObjectAtIndex (Object anObject, int anIndex)
+ {
+ add( anIndex, anObject );
+ }
+
+ /**
+ * Replaces the object at the specified index with the
+ * specified object.
+ */
+ public void replaceObjectAtIndex (int anIndex, Object anObject)
+ {
+ set( anIndex, anObject );
+ }
+
+ /**
+ * Adds the specified object to the end of this array.
+ */
+ public void addObject (Object anObject)
+ {
+ add( anObject );
+ }
+
+ // interface List: mutators
+
+ public void add(int index, Object element)
+ {
+ EOObserverCenter.addObserver( observer, element );
+ super.add( index, element );
+ }
+
+ public boolean add(Object o)
+ {
+ EOObserverCenter.addObserver( observer, o );
+ return super.add(o);
+ }
+
+ public boolean addAll(Collection coll)
+ {
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.addObserver( observer, it.next() );
+ }
+ return super.addAll(coll);
+ }
+
+ public boolean addAll(int index, Collection c)
+ {
+ Iterator it = c.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.addObserver( observer, it.next() );
+ }
+ return super.addAll( index, c );
+ }
+
+ public void clear()
+ {
+ Iterator it = iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.removeObserver( observer, it.next() );
+ }
+ super.clear();
+ }
+
+ public Object remove(int index)
+ {
+ EOObserverCenter.removeObserver( observer, get(index) );
+ return super.remove( index );
+ }
+
+ public boolean remove(Object o)
+ {
+ EOObserverCenter.removeObserver( observer, o );
+ return super.remove(o);
+ }
+
+ public boolean removeAll(Collection coll)
+ {
+ Iterator it = coll.iterator();
+ while ( it.hasNext() )
+ {
+ EOObserverCenter.removeObserver( observer, it.next() );
+ }
+ return super.removeAll(coll);
+ }
+
+ public boolean retainAll(Collection coll)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object set(int index, Object element)
+ {
+ EOObserverCenter.removeObserver( observer, get(index) );
+ EOObserverCenter.addObserver( observer, element );
+ return super.set( index, element );
+ }
+}
+
+/*
+ * $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.1 2003/01/18 23:31:32 mpowers
+ * Needed for WODisplayGroup.
+ *
+ * Revision 1.2 2002/10/24 21:15:36 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.1 2001/02/20 16:38:55 mpowers
+ * MasterDetailAssociations now observe their controlled display group's
+ * objects for changes to that the parent object will be marked as updated.
+ * Before, only inserts and deletes to an object's items are registered.
+ * Also, moved ObservableArray to package access.
+ *
+ * Revision 1.1 2001/01/24 14:37:24 mpowers
+ * Contributing a delegate useful for debugging.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java
new file mode 100644
index 0000000..41f77f5
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/URI.java
@@ -0,0 +1,3464 @@
+/*
+ * $Header$
+ * $Revision: 905 $
+ * $Date: 2006-02-18 20:44:03 -0500 (Sat, 18 Feb 2006) $
+ *
+ * ====================================================================
+ *
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 2002 the Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ * [Additional notices, if required by prior licensing conditions]
+ *
+ */
+
+// excellent class borrowed from Apache Commons project:
+//package org.apache.commons.httpclient;
+
+package net.wotonomy.web;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.security.AccessController;
+import java.util.BitSet;
+import java.util.Hashtable;
+import java.util.Locale;
+
+import sun.security.action.GetPropertyAction;
+
+/**
+ * The interface for the URI(Uniform Resource Identifiers) version of RFC 2396.
+ * This class has the purpose of supportting of parsing a URI reference to
+ * extend any specific protocols, the character encoding of the protocol to
+ * be transported and the charset of the document.
+ * <p>
+ * A URI is always in an "escaped" form, since escaping or unescaping a
+ * completed URI might change its semantics.
+ * <p>
+ * Implementers should be careful not to escape or unescape the same string
+ * more than once, since unescaping an already unescaped string might lead to
+ * misinterpreting a percent data character as another escaped character,
+ * or vice versa in the case of escaping an already escaped string.
+ * <p>
+ * In order to avoid these problems, data types used as follows:
+ * <p><blockquote><pre>
+ * URI character sequence: char
+ * octet sequence: byte
+ * original character sequence: String
+ * </pre></blockquote><p>
+ *
+ * So, a URI is a sequence of characters as an array of a char type, which
+ * is not always represented as a sequence of octets as an array of byte.
+ * <p>
+ *
+ * URI Syntactic Components
+ * <p><blockquote><pre>
+ * - In general, written as follows:
+ * Absolute URI = &lt;scheme&gt:&lt;scheme-specific-part&gt;
+ * Generic URI = &lt;scheme&gt;://&lt;authority&gt;&lt;path&gt;?&lt;query&gt;
+ *
+ * - Syntax
+ * absoluteURI = scheme ":" ( hier_part | opaque_part )
+ * hier_part = ( net_path | abs_path ) [ "?" query ]
+ * net_path = "//" authority [ abs_path ]
+ * abs_path = "/" path_segments
+ * </pre></blockquote><p>
+ *
+ * The following examples illustrate URI that are in common use.
+ * <pre>
+ * ftp://ftp.is.co.za/rfc/rfc1808.txt
+ * -- ftp scheme for File Transfer Protocol services
+ * gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles
+ * -- gopher scheme for Gopher and Gopher+ Protocol services
+ * http://www.math.uio.no/faq/compression-faq/part1.html
+ * -- http scheme for Hypertext Transfer Protocol services
+ * mailto:mduerst@ifi.unizh.ch
+ * -- mailto scheme for electronic mail addresses
+ * news:comp.infosystems.www.servers.unix
+ * -- news scheme for USENET news groups and articles
+ * telnet://melvyl.ucop.edu/
+ * -- telnet scheme for interactive services via the TELNET Protocol
+ * </pre>
+ * Please, notice that there are many modifications from URL(RFC 1738) and
+ * relative URL(RFC 1808).
+ * <p>
+ * <b>The expressions for a URI</b>
+ * <p><pre>
+ * For escaped URI forms
+ * - URI(char[]) // constructor
+ * - char[] getRawXxx() // method
+ * - String getEscapedXxx() // method
+ * - String toString() // method
+ * <p>
+ * For unescaped URI forms
+ * - URI(String) // constructor
+ * - String getXXX() // method
+ * </pre><p>
+ *
+ * @author <a href="mailto:jericho@apache.org">Sung-Gu</a>
+ * @version $Revision: 905 $ $Date: 2002/03/14 15:14:01
+ */
+class URI implements Cloneable, Comparable, Serializable {
+
+
+ // ----------------------------------------------------------- Constructors
+
+ protected URI() {
+ }
+
+ /**
+ * Construct a URI as an escaped form of a character array.
+ * An URI can be placed within double-quotes or angle brackets like
+ * "http://test.com/" and &lt;http://test.com/&gt;
+ *
+ * @param escaped the URI character sequence
+ * @exception IOException
+ * @throws NullPointerException if <code>escaped</code> is <code>null</code>
+ */
+ public URI(char[] escaped) throws IOException {
+ parseUriReference(new String(escaped), true);
+ }
+
+
+ /**
+ * Construct a URI from the given string.
+ * <p><blockquote><pre>
+ * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ * </pre></blockquote><p>
+ * An URI can be placed within double-quotes or angle brackets like
+ * "http://test.com/" and &lt;http://test.com/&gt;
+ *
+ * @param original the string to be represented to URI character sequence
+ * It is one of absoluteURI and relativeURI.
+ * @exception IOException
+ */
+ public URI(String original) throws IOException {
+ parseUriReference(original, false);
+ }
+
+ /**
+ * Construct a URI from a URL.
+ *
+ * @param url a valid URL.
+ * @throws IOException
+ * @since 2.0
+ */
+ public URI(URL url) throws IOException {
+ this(url.toString());
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ * <p><blockquote><pre>
+ * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ * absoluteURI = scheme ":" ( hier_part | opaque_part )
+ * opaque_part = uric_no_slash *uric
+ * </pre></blockquote><p>
+ * It's for absolute URI = &lt;scheme&gt;:&lt;scheme-specific-part&gt;#
+ * &lt;fragment&gt;.
+ *
+ * @param scheme the scheme string
+ * @param scheme_specific_part scheme_specific_part
+ * @param fragment the fragment string
+ * @exception IOException
+ */
+ public URI(String scheme, String scheme_specific_part, String fragment)
+ throws IOException {
+
+ // validate and contruct the URI character sequence
+ if (scheme == null) {
+ throw new IOException(/*IOException.PARSING,*/ "URI: scheme required");
+ }
+ char[] s = scheme.toLowerCase().toCharArray();
+ if (validate(s, URI.scheme)) {
+ _scheme = s; // is_absoluteURI
+ } else {
+ throw new IOException(/*IOException.PARSING,*/ "URI: incorrect scheme");
+ }
+ _opaque = encode(scheme_specific_part, allowed_opaque_part);
+ // Set flag
+ _is_opaque_part = true;
+ setUriReference();
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ * <p><blockquote><pre>
+ * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ * absoluteURI = scheme ":" ( hier_part | opaque_part )
+ * relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+ * hier_part = ( net_path | abs_path ) [ "?" query ]
+ * </pre></blockquote><p>
+ * It's for absolute URI = &lt;scheme&gt;:&lt;path&gt;?&lt;query&gt;#&lt;
+ * fragment&gt; and relative URI = &lt;path&gt;?&lt;query&gt;#&lt;fragment
+ * &gt;.
+ *
+ * @param scheme the scheme string
+ * @param authority the authority string
+ * @param path the path string
+ * @param query the query string
+ * @param fragment the fragment string
+ * @exception IOException
+ */
+ public URI(String scheme, String authority, String path, String query,
+ String fragment) throws IOException {
+
+ // validate and contruct the URI character sequence
+ StringBuffer buff = new StringBuffer();
+ if (scheme != null) {
+ buff.append(scheme);
+ buff.append(':');
+ }
+ if (authority != null) {
+ buff.append("//");
+ buff.append(authority);
+ }
+ if (path != null) { // accept empty path
+ if ((scheme != null || authority != null)
+ && !path.startsWith("/")) {
+ throw new IOException(/*IOException.PARSING*,*/
+ "URI: abs_path requested");
+ }
+ buff.append(path);
+ }
+ if (query != null) {
+ buff.append('?');
+ buff.append(query);
+ }
+ if (fragment != null) {
+ buff.append('#');
+ buff.append(fragment);
+ }
+ parseUriReference(buff.toString(), false);
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ *
+ * @param scheme the scheme string
+ * @param userinfo the userinfo string
+ * @param host the host string
+ * @param port the port number
+ * @exception IOException
+ */
+ public URI(String scheme, String userinfo, String host, int port)
+ throws IOException {
+
+ this(scheme, userinfo, host, port, null, null, null);
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ *
+ * @param scheme the scheme string
+ * @param userinfo the userinfo string
+ * @param host the host string
+ * @param port the port number
+ * @param path the path string
+ * @exception IOException
+ */
+ public URI(String scheme, String userinfo, String host, int port,
+ String path) throws IOException {
+
+ this(scheme, userinfo, host, port, path, null, null);
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ *
+ * @param scheme the scheme string
+ * @param userinfo the userinfo string
+ * @param host the host string
+ * @param port the port number
+ * @param path the path string
+ * @param query the query string
+ * @exception IOException
+ */
+ public URI(String scheme, String userinfo, String host, int port,
+ String path, String query) throws IOException {
+
+ this(scheme, userinfo, host, port, path, query, null);
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ *
+ * @param scheme the scheme string
+ * @param userinfo the userinfo string
+ * @param host the host string
+ * @param port the port number
+ * @param path the path string
+ * @param query the query string
+ * @param fragment the fragment string
+ * @exception IOException
+ */
+ public URI(String scheme, String userinfo, String host, int port,
+ String path, String query, String fragment) throws IOException {
+
+ this(scheme, (host == null) ? null :
+ ((userinfo != null) ? userinfo + '@' : "") + host +
+ ((port != -1) ? ":" + port : ""), path, query, fragment);
+ }
+
+
+ /**
+ * Construct a general URI from the given components.
+ *
+ * @param scheme the scheme string
+ * @param host the host string
+ * @param path the path string
+ * @param fragment the fragment string
+ * @exception IOException
+ */
+ public URI(String scheme, String host, String path, String fragment)
+ throws IOException {
+
+ this(scheme, host, path, null, fragment);
+ }
+
+
+ /**
+ * Construct a general URI with the given relative URI string.
+ *
+ * @param base the base URI
+ * @param relative the relative URI string
+ * @exception IOException
+ */
+ public URI(URI base, String relative) throws IOException {
+ this(base, new URI(relative));
+ }
+
+
+ /**
+ * Construct a general URI with the given relative URI.
+ * <p><blockquote><pre>
+ * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ * relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+ * </pre></blockquote><p>
+ * Resolving Relative References to Absolute Form.
+ *
+ * <strong>Examples of Resolving Relative URI References</strong>
+ *
+ * Within an object with a well-defined base URI of
+ * <p><blockquote><pre>
+ * http://a/b/c/d;p?q
+ * </pre></blockquote><p>
+ * the relative URI would be resolved as follows:
+ *
+ * Normal Examples
+ *
+ * <p><blockquote><pre>
+ * g:h = g:h
+ * g = http://a/b/c/g
+ * ./g = http://a/b/c/g
+ * g/ = http://a/b/c/g/
+ * /g = http://a/g
+ * //g = http://g
+ * ?y = http://a/b/c/?y
+ * g?y = http://a/b/c/g?y
+ * #s = (current document)#s
+ * g#s = http://a/b/c/g#s
+ * g?y#s = http://a/b/c/g?y#s
+ * ;x = http://a/b/c/;x
+ * g;x = http://a/b/c/g;x
+ * g;x?y#s = http://a/b/c/g;x?y#s
+ * . = http://a/b/c/
+ * ./ = http://a/b/c/
+ * .. = http://a/b/
+ * ../ = http://a/b/
+ * ../g = http://a/b/g
+ * ../.. = http://a/
+ * ../../ = http://a/
+ * ../../g = http://a/g
+ * </pre></blockquote><p>
+ *
+ * Some URI schemes do not allow a hierarchical syntax matching the
+ * <hier_part> syntax, and thus cannot use relative references.
+ *
+ * @param base the base URI
+ * @param relative the relative URI
+ * @exception IOException
+ */
+ public URI(URI base, URI relative) throws IOException {
+
+ if (base._scheme == null) {
+ throw new IOException(/* IOException.PARSING,*/ "URI: base URI required");
+ }
+ if (base._scheme != null) {
+ this._scheme = base._scheme;
+ this._authority = base._authority;
+ }
+ if (base._is_opaque_part || relative._is_opaque_part) {
+ this._scheme = base._scheme;
+ this._is_opaque_part = relative._is_opaque_part;
+ this._opaque = relative._opaque;
+ this._fragment = relative._fragment;
+ this.setUriReference();
+ return;
+ }
+ if (relative._scheme != null) {
+ this._scheme = relative._scheme;
+ this._is_net_path = relative._is_net_path;
+ this._authority = relative._authority;
+ if (relative._is_server) {
+ this._userinfo = relative._userinfo;
+ this._host = relative._host;
+ this._port = relative._port;
+ } else if (relative._is_reg_name) {
+ this._is_reg_name = relative._is_reg_name;
+ }
+ this._is_abs_path = relative._is_abs_path;
+ this._is_rel_path = relative._is_rel_path;
+ this._path = relative._path;
+ } else if (base._authority != null && relative._scheme == null) {
+ this._is_net_path = base._is_net_path;
+ this._authority = base._authority;
+ if (base._is_server) {
+ this._userinfo = base._userinfo;
+ this._host = base._host;
+ this._port = base._port;
+ } else if (base._is_reg_name) {
+ this._is_reg_name = base._is_reg_name;
+ }
+ }
+ if (relative._authority != null) {
+ this._is_net_path = relative._is_net_path;
+ this._authority = relative._authority;
+ if (relative._is_server) {
+ this._is_server = relative._is_server;
+ this._userinfo = relative._userinfo;
+ this._host = relative._host;
+ this._port = relative._port;
+ } else if (relative._is_reg_name) {
+ this._is_reg_name = relative._is_reg_name;
+ }
+ this._is_abs_path = relative._is_abs_path;
+ this._is_rel_path = relative._is_rel_path;
+ this._path = relative._path;
+ }
+ // resolve the path
+ if (relative._scheme == null && relative._authority == null ||
+ equals(base._scheme, relative._scheme)) {
+ this._path = resolvePath(base._path, relative._path);
+ }
+ // base._query removed
+ if (relative._query != null) {
+ this._query = relative._query;
+ }
+ // base._fragment removed
+ if (relative._fragment != null) {
+ this._fragment = relative._fragment;
+ }
+ this.setUriReference();
+ }
+
+ // --------------------------------------------------- Instance Variables
+
+ static final long serialVersionUID = 604752400577948726L;
+
+
+ /**
+ * This Uniform Resource Identifier (URI).
+ * The URI is always in an "escaped" form, since escaping or unescaping
+ * a completed URI might change its semantics.
+ */
+ protected char[] _uri = null;
+
+
+ /**
+ * The default charset of the protocol. RFC 2277, 2396
+ */
+ protected static String _protocolCharset = "UTF-8";
+
+
+ /**
+ * The default charset of the document. RFC 2277, 2396
+ * The platform's charset is used for the document by default.
+ */
+ protected static String _documentCharset = null;
+ // Static initializer for _documentCharset
+ static {
+ Locale locale = Locale.getDefault();
+ if (locale != null) {
+ // in order to support backward compatiblity
+ _documentCharset = LocaleToCharsetMap.getCharset(locale);
+ } else {
+ _documentCharset = (String)AccessController.doPrivileged(
+ new GetPropertyAction("file.encoding"));
+ }
+ }
+
+ /**
+ * The scheme.
+ */
+ protected char[] _scheme = null;
+
+
+ /**
+ * The opaque.
+ */
+ protected char[] _opaque = null;
+
+
+ /**
+ * The authority.
+ */
+ protected char[] _authority = null;
+
+
+ /**
+ * The userinfo.
+ */
+ protected char[] _userinfo = null;
+
+
+ /**
+ * The host.
+ */
+ protected char[] _host = null;
+
+
+ /**
+ * The port.
+ */
+ protected int _port = -1;
+
+
+ /**
+ * The path.
+ */
+ protected char[] _path = null;
+
+
+ /**
+ * The query.
+ */
+ protected char[] _query = null;
+
+
+ /**
+ * The fragment.
+ */
+ protected char[] _fragment = null;
+
+
+ /**
+ * The root path.
+ */
+ protected static char[] rootPath = { '/' };
+
+ // ---------------------- Generous characters for each component validation
+
+ /**
+ * The percent "%" character always has the reserved purpose of being the
+ * escape indicator, it must be escaped as "%25" in order to be used as
+ * data within a URI.
+ */
+ protected static final BitSet percent = new BitSet(256);
+ // Static initializer for percent
+ static {
+ percent.set('%');
+ }
+
+
+ /**
+ * BitSet for digit.
+ * <p><blockquote><pre>
+ * digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" |
+ * "8" | "9"
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet digit = new BitSet(256);
+ // Static initializer for digit
+ static {
+ for(int i = '0'; i <= '9'; i++) {
+ digit.set(i);
+ }
+ }
+
+
+ /**
+ * BitSet for alpha.
+ * <p><blockquote><pre>
+ * alpha = lowalpha | upalpha
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet alpha = new BitSet(256);
+ // Static initializer for alpha
+ static {
+ for (int i = 'a'; i <= 'z'; i++) {
+ alpha.set(i);
+ }
+ for (int i = 'A'; i <= 'Z'; i++) {
+ alpha.set(i);
+ }
+ }
+
+
+ /**
+ * BitSet for alphanum (join of alpha &amp; digit).
+ * <p><blockquote><pre>
+ * alphanum = alpha | digit
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet alphanum = new BitSet(256);
+ // Static initializer for alphanum
+ static {
+ alphanum.or(alpha);
+ alphanum.or(digit);
+ }
+
+
+ /**
+ * BitSet for hex.
+ * <p><blockquote><pre>
+ * hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
+ * "a" | "b" | "c" | "d" | "e" | "f"
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet hex = new BitSet(256);
+ // Static initializer for hex
+ static {
+ hex.or(digit);
+ for(int i = 'a'; i <= 'f'; i++) {
+ hex.set(i);
+ }
+ for(int i = 'A'; i <= 'F'; i++) {
+ hex.set(i);
+ }
+ }
+
+
+ /**
+ * BitSet for escaped.
+ * <p><blockquote><pre>
+ * escaped = "%" hex hex
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet escaped = new BitSet(256);
+ // Static initializer for escaped
+ static {
+ escaped.or(percent);
+ escaped.or(hex);
+ }
+
+
+ /**
+ * BitSet for mark.
+ * <p><blockquote><pre>
+ * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |
+ * "(" | ")"
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet mark = new BitSet(256);
+ // Static initializer for mark
+ static {
+ mark.set('-');
+ mark.set('_');
+ mark.set('.');
+ mark.set('!');
+ mark.set('~');
+ mark.set('*');
+ mark.set('\'');
+ mark.set('(');
+ mark.set(')');
+ }
+
+
+ /**
+ * Data characters that are allowed in a URI but do not have a reserved
+ * purpose are called unreserved.
+ * <p><blockquote><pre>
+ * unreserved = alphanum | mark
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet unreserved = new BitSet(256);
+ // Static initializer for unreserved
+ static {
+ unreserved.or(alphanum);
+ unreserved.or(mark);
+ }
+
+
+ /**
+ * BitSet for reserved.
+ * <p><blockquote><pre>
+ * reserved = ";" | "/" | "?" | ":" | "@" | "&amp;" | "=" | "+" |
+ * "$" | ","
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet reserved = new BitSet(256);
+ // Static initializer for reserved
+ static {
+ reserved.set(';');
+ reserved.set('/');
+ reserved.set('?');
+ reserved.set(':');
+ reserved.set('@');
+ reserved.set('&');
+ reserved.set('=');
+ reserved.set('+');
+ reserved.set('$');
+ reserved.set(',');
+ }
+
+
+ /**
+ * BitSet for uric.
+ * <p><blockquote><pre>
+ * uric = reserved | unreserved | escaped
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet uric = new BitSet(256);
+ // Static initializer for uric
+ static {
+ uric.or(reserved);
+ uric.or(unreserved);
+ uric.or(escaped);
+ }
+
+
+ /**
+ * BitSet for fragment (alias for uric).
+ * <p><blockquote><pre>
+ * fragment = *uric
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet fragment = uric;
+
+
+ /**
+ * BitSet for query (alias for uric).
+ * <p><blockquote><pre>
+ * query = *uric
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet query = uric;
+
+
+ /**
+ * BitSet for pchar.
+ * <p><blockquote><pre>
+ * pchar = unreserved | escaped |
+ * ":" | "@" | "&amp;" | "=" | "+" | "$" | ","
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet pchar = new BitSet(256);
+ // Static initializer for pchar
+ static {
+ pchar.or(unreserved);
+ pchar.or(escaped);
+ pchar.set(':');
+ pchar.set('@');
+ pchar.set('&');
+ pchar.set('=');
+ pchar.set('+');
+ pchar.set('$');
+ pchar.set(',');
+ }
+
+
+ /**
+ * BitSet for param (alias for pchar).
+ * <p><blockquote><pre>
+ * param = *pchar
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet param = pchar;
+
+
+ /**
+ * BitSet for segment.
+ * <p><blockquote><pre>
+ * segment = *pchar *( ";" param )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet segment = new BitSet(256);
+ // Static initializer for segment
+ static {
+ segment.or(pchar);
+ segment.set(';');
+ segment.or(param);
+ }
+
+
+ /**
+ * BitSet for path segments.
+ * <p><blockquote><pre>
+ * path_segments = segment *( "/" segment )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet path_segments = new BitSet(256);
+ // Static initializer for path_segments
+ static {
+ path_segments.set('/');
+ path_segments.or(segment);
+ }
+
+
+ /**
+ * URI absolute path.
+ * <p><blockquote><pre>
+ * abs_path = "/" path_segments
+ * </pre><blockquote><p>
+ */
+ protected static final BitSet abs_path = new BitSet(256);
+ // Static initializer for abs_path
+ static {
+ abs_path.set('/');
+ abs_path.or(path_segments);
+ }
+
+
+ /**
+ * URI bitset for encoding typical non-slash characters.
+ * <p><blockquote><pre>
+ * uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
+ * "&amp;" | "=" | "+" | "$" | ","
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet uric_no_slash = new BitSet(256);
+ // Static initializer for uric_no_slash
+ static {
+ uric_no_slash.or(unreserved);
+ uric_no_slash.or(escaped);
+ uric_no_slash.set(';');
+ uric_no_slash.set('?');
+ uric_no_slash.set(';');
+ uric_no_slash.set('@');
+ uric_no_slash.set('&');
+ uric_no_slash.set('=');
+ uric_no_slash.set('+');
+ uric_no_slash.set('$');
+ uric_no_slash.set(',');
+ }
+
+
+ /**
+ * URI bitset that combines uric_no_slash and uric.
+ * <p><blockquote><pre>
+ * opaque_part = uric_no_slash *uric
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet opaque_part = new BitSet(256);
+ // Static initializer for opaque_part
+ static {
+ opaque_part.or(uric_no_slash);
+ opaque_part.or(uric);
+ }
+
+
+ /**
+ * URI bitset that combines absolute path and opaque part.
+ * <p><blockquote><pre>
+ * path = [ abs_path | opaque_part ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet path = new BitSet(256);
+ // Static initializer for path
+ static {
+ path.or(abs_path);
+ path.or(opaque_part);
+ }
+
+
+ /**
+ * Port, a logical alias for digit.
+ */
+ protected static final BitSet port = digit;
+
+
+ /**
+ * Bitset that combines digit and dot fo IPv$address.
+ * <p><blockquote><pre>
+ * IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet IPv4address = new BitSet(256);
+ // Static initializer for IPv4address
+ static {
+ IPv4address.or(digit);
+ IPv4address.set('.');
+ }
+
+
+ /**
+ * RFC 2373.
+ * <p><blockquote><pre>
+ * IPv6address = hexpart [ ":" IPv4address ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet IPv6address = new BitSet(256);
+ // Static initializer for IPv6address reference
+ static {
+ IPv6address.or(hex); // hexpart
+ IPv6address.set(':');
+ IPv6address.or(IPv4address);
+ }
+
+
+ /**
+ * RFC 2732, 2373.
+ * <p><blockquote><pre>
+ * IPv6reference = "[" IPv6address "]"
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet IPv6reference = new BitSet(256);
+ // Static initializer for IPv6reference
+ static {
+ IPv6reference.set('[');
+ IPv6reference.or(IPv6address);
+ IPv6reference.set(']');
+ }
+
+
+ /**
+ * BitSet for toplabel.
+ * <p><blockquote><pre>
+ * toplabel = alpha | alpha *( alphanum | "-" ) alphanum
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet toplabel = new BitSet(256);
+ // Static initializer for toplabel
+ static {
+ toplabel.or(alphanum);
+ toplabel.set('-');
+ }
+
+
+ /**
+ * BitSet for domainlabel.
+ * <p><blockquote><pre>
+ * domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet domainlabel = toplabel;
+
+
+ /**
+ * BitSet for hostname.
+ * <p><blockquote><pre>
+ * hostname = *( domainlabel "." ) toplabel [ "." ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet hostname = new BitSet(256);
+ // Static initializer for hostname
+ static {
+ hostname.or(toplabel);
+ // hostname.or(domainlabel);
+ hostname.set('.');
+ }
+
+
+ /**
+ * BitSet for host.
+ * <p><blockquote><pre>
+ * host = hostname | IPv4address | IPv6reference
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet host = new BitSet(256);
+ // Static initializer for host
+ static {
+ host.or(hostname);
+ // host.or(IPv4address);
+ host.or(IPv6reference); // IPv4address
+ }
+
+
+ /**
+ * BitSet for hostport.
+ * <p><blockquote><pre>
+ * hostport = host [ ":" port ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet hostport = new BitSet(256);
+ // Static initializer for hostport
+ static {
+ hostport.or(host);
+ hostport.set(':');
+ hostport.or(port);
+ }
+
+
+ /**
+ * Bitset for userinfo.
+ * <p><blockquote><pre>
+ * userinfo = *( unreserved | escaped |
+ * ";" | ":" | "&amp;" | "=" | "+" | "$" | "," )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet userinfo = new BitSet(256);
+ // Static initializer for userinfo
+ static {
+ userinfo.or(unreserved);
+ userinfo.or(escaped);
+ userinfo.set(';');
+ userinfo.set(':');
+ userinfo.set('&');
+ userinfo.set('=');
+ userinfo.set('+');
+ userinfo.set('$');
+ userinfo.set(',');
+ }
+
+
+ /**
+ * BitSet for within the userinfo component like user and password.
+ */
+ public static final BitSet within_userinfo = new BitSet(256);
+ // Static initializer for within_userinfo
+ static {
+ within_userinfo.or(userinfo);
+ within_userinfo.clear(';'); // reserved within authority
+ within_userinfo.clear(':');
+ within_userinfo.clear('@');
+ within_userinfo.clear('?');
+ within_userinfo.clear('/');
+ }
+
+
+ /**
+ * Bitset for server.
+ * <p><blockquote><pre>
+ * server = [ [ userinfo "@" ] hostport ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet server = new BitSet(256);
+ // Static initializer for server
+ static {
+ server.or(userinfo);
+ server.set('@');
+ server.or(hostport);
+ }
+
+
+ /**
+ * BitSet for reg_name.
+ * <p><blockquote><pre>
+ * reg_name = 1*( unreserved | escaped | "$" | "," |
+ * ";" | ":" | "@" | "&amp;" | "=" | "+" )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet reg_name = new BitSet(256);
+ // Static initializer for reg_name
+ static {
+ reg_name.or(unreserved);
+ reg_name.or(escaped);
+ reg_name.set('$');
+ reg_name.set(',');
+ reg_name.set(';');
+ reg_name.set(':');
+ reg_name.set('@');
+ reg_name.set('&');
+ reg_name.set('=');
+ reg_name.set('+');
+ }
+
+
+ /**
+ * BitSet for authority.
+ * <p><blockquote><pre>
+ * authority = server | reg_name
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet authority = new BitSet(256);
+ // Static initializer for authority
+ static {
+ authority.or(server);
+ authority.or(reg_name);
+ }
+
+
+ /**
+ * BitSet for scheme.
+ * <p><blockquote><pre>
+ * scheme = alpha *( alpha | digit | "+" | "-" | "." )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet scheme = new BitSet(256);
+ // Static initializer for scheme
+ static {
+ scheme.or(alpha);
+ scheme.or(digit);
+ scheme.set('+');
+ scheme.set('-');
+ scheme.set('.');
+ }
+
+
+ /**
+ * BitSet for rel_segment.
+ * <p><blockquote><pre>
+ * rel_segment = 1*( unreserved | escaped |
+ * ";" | "@" | "&amp;" | "=" | "+" | "$" | "," )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet rel_segment = new BitSet(256);
+ // Static initializer for rel_segment
+ static {
+ rel_segment.or(unreserved);
+ rel_segment.or(escaped);
+ rel_segment.set(';');
+ rel_segment.set('@');
+ rel_segment.set('&');
+ rel_segment.set('=');
+ rel_segment.set('+');
+ rel_segment.set('$');
+ rel_segment.set(',');
+ }
+
+
+ /**
+ * BitSet for rel_path.
+ * <p><blockquote><pre>
+ * rel_path = rel_segment [ abs_path ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet rel_path = new BitSet(256);
+ // Static initializer for rel_path
+ static {
+ rel_path.or(rel_segment);
+ rel_path.or(abs_path);
+ }
+
+
+ /**
+ * BitSet for net_path.
+ * <p><blockquote><pre>
+ * net_path = "//" authority [ abs_path ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet net_path = new BitSet(256);
+ // Static initializer for net_path
+ static {
+ net_path.set('/');
+ net_path.or(authority);
+ net_path.or(abs_path);
+ }
+
+
+ /**
+ * BitSet for hier_part.
+ * <p><blockquote><pre>
+ * hier_part = ( net_path | abs_path ) [ "?" query ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet hier_part = new BitSet(256);
+ // Static initializer for hier_part
+ static {
+ hier_part.or(net_path);
+ hier_part.or(abs_path);
+ // hier_part.set('?'); aleady included
+ hier_part.or(query);
+ }
+
+
+ /**
+ * BitSet for relativeURI.
+ * <p><blockquote><pre>
+ * relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet relativeURI = new BitSet(256);
+ // Static initializer for relativeURI
+ static {
+ relativeURI.or(net_path);
+ relativeURI.or(abs_path);
+ relativeURI.or(rel_path);
+ // relativeURI.set('?'); aleady included
+ relativeURI.or(query);
+ }
+
+
+ /**
+ * BitSet for absoluteURI.
+ * <p><blockquote><pre>
+ * absoluteURI = scheme ":" ( hier_part | opaque_part )
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet absoluteURI = new BitSet(256);
+ // Static initializer for absoluteURI
+ static {
+ absoluteURI.or(scheme);
+ absoluteURI.set(':');
+ absoluteURI.or(hier_part);
+ absoluteURI.or(opaque_part);
+ }
+
+
+ /**
+ * BitSet for URI-reference.
+ * <p><blockquote><pre>
+ * URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ * </pre></blockquote><p>
+ */
+ protected static final BitSet URI_reference = new BitSet(256);
+ // Static initializer for URI_reference
+ static {
+ URI_reference.or(absoluteURI);
+ URI_reference.or(relativeURI);
+ URI_reference.set('#');
+ URI_reference.or(fragment);
+ }
+
+ // ---------------------------- Characters disallowed within the URI syntax
+ // Excluded US-ASCII Characters are like control, space, delims and unwise
+
+ /**
+ * BitSet for control.
+ */
+ public static final BitSet control = new BitSet(256);
+ // Static initializer for control
+ static {
+ for (int i = 0; i <= 0x1F; i++) {
+ control.set(i);
+ }
+ control.set(0x7F);
+ }
+
+ /**
+ * BitSet for space.
+ */
+ public static final BitSet space = new BitSet(256);
+ // Static initializer for space
+ static {
+ space.set(0x20);
+ }
+
+
+ /**
+ * BitSet for delims.
+ */
+ public static final BitSet delims = new BitSet(256);
+ // Static initializer for delims
+ static {
+ delims.set('<');
+ delims.set('>');
+ delims.set('#');
+ delims.set('%');
+ delims.set('"');
+ }
+
+
+ /**
+ * BitSet for unwise.
+ */
+ public static final BitSet unwise = new BitSet(256);
+ // Static initializer for unwise
+ static {
+ unwise.set('{');
+ unwise.set('}');
+ unwise.set('|');
+ unwise.set('\\');
+ unwise.set('^');
+ unwise.set('[');
+ unwise.set(']');
+ unwise.set('`');
+ }
+
+
+ /**
+ * Disallowed rel_path before escaping.
+ */
+ public static final BitSet disallowed_rel_path = new BitSet(256);
+ // Static initializer for disallowed_rel_path
+ static {
+ disallowed_rel_path.or(uric);
+ disallowed_rel_path.andNot(rel_path);
+ }
+
+
+ /**
+ * Disallowed opaque_part before escaping.
+ */
+ public static final BitSet disallowed_opaque_part = new BitSet(256);
+ // Static initializer for disallowed_opaque_part
+ static {
+ disallowed_opaque_part.or(uric);
+ disallowed_opaque_part.andNot(opaque_part);
+ }
+
+ // ----------------------- Characters allowed within and for each component
+
+ /**
+ * Those characters that are allowed for the authority component.
+ */
+ public static final BitSet allowed_authority = new BitSet(256);
+ // Static initializer for allowed_authority
+ static {
+ allowed_authority.or(authority);
+ allowed_authority.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed for the opaque_part.
+ */
+ public static final BitSet allowed_opaque_part = new BitSet(256);
+ // Static initializer for allowed_opaque_part
+ static {
+ allowed_opaque_part.or(opaque_part);
+ allowed_opaque_part.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed for the reg_name.
+ */
+ public static final BitSet allowed_reg_name = new BitSet(256);
+ // Static initializer for allowed_reg_name
+ static {
+ allowed_reg_name.or(reg_name);
+ // allowed_reg_name.andNot(percent);
+ allowed_reg_name.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed for the userinfo component.
+ */
+ public static final BitSet allowed_userinfo = new BitSet(256);
+ // Static initializer for allowed_userinfo
+ static {
+ allowed_userinfo.or(userinfo);
+ // allowed_userinfo.andNot(percent);
+ allowed_userinfo.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed for within the userinfo component.
+ */
+ public static final BitSet allowed_within_userinfo = new BitSet(256);
+ // Static initializer for allowed_within_userinfo
+ static {
+ allowed_within_userinfo.or(within_userinfo);
+ allowed_within_userinfo.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed for the IPv6reference component.
+ * The characters '[', ']' in IPv6reference should be excluded.
+ */
+ public static final BitSet allowed_IPv6reference = new BitSet(256);
+ // Static initializer for allowed_IPv6reference
+ static {
+ allowed_IPv6reference.or(IPv6reference);
+ // allowed_IPv6reference.andNot(unwise);
+ allowed_IPv6reference.clear('[');
+ allowed_IPv6reference.clear(']');
+ }
+
+
+ /**
+ * Those characters that are allowed for the host component.
+ * The characters '[', ']' in IPv6reference should be excluded.
+ */
+ public static final BitSet allowed_host = new BitSet(256);
+ // Static initializer for allowed_host
+ static {
+ allowed_host.or(hostname);
+ allowed_host.or(allowed_IPv6reference);
+ }
+
+
+ /**
+ * Those characters that are allowed for the authority component.
+ */
+ public static final BitSet allowed_within_authority = new BitSet(256);
+ // Static initializer for allowed_within_authority
+ static {
+ allowed_within_authority.or(server);
+ allowed_within_authority.or(reg_name);
+ allowed_within_authority.clear(';');
+ allowed_within_authority.clear(':');
+ allowed_within_authority.clear('@');
+ allowed_within_authority.clear('?');
+ allowed_within_authority.clear('/');
+ }
+
+
+ /**
+ * Those characters that are allowed for the abs_path.
+ */
+ public static final BitSet allowed_abs_path = new BitSet(256);
+ // Static initializer for allowed_abs_path
+ static {
+ allowed_abs_path.or(abs_path);
+ // allowed_abs_path.set('/'); // aleady included
+ allowed_abs_path.andNot(percent);
+ }
+
+
+ /**
+ * Those characters that are allowed for the rel_path.
+ */
+ public static final BitSet allowed_rel_path = new BitSet(256);
+ // Static initializer for allowed_rel_path
+ static {
+ allowed_rel_path.or(rel_path);
+ allowed_rel_path.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed within the path.
+ */
+ public static final BitSet allowed_within_path = new BitSet(256);
+ // Static initializer for allowed_within_path
+ static {
+ allowed_within_path.or(abs_path);
+ allowed_within_path.clear('/');
+ allowed_within_path.clear(';');
+ allowed_within_path.clear('=');
+ allowed_within_path.clear('?');
+ }
+
+
+ /**
+ * Those characters that are allowed for the query component.
+ */
+ public static final BitSet allowed_query = new BitSet(256);
+ // Static initializer for allowed_query
+ static {
+ allowed_query.or(uric);
+ allowed_query.clear('%');
+ }
+
+
+ /**
+ * Those characters that are allowed within the query component.
+ */
+ public static final BitSet allowed_within_query = new BitSet(256);
+ // Static initializer for allowed_within_query
+ static {
+ allowed_within_query.or(allowed_query);
+ allowed_within_query.andNot(reserved); // excluded 'reserved'
+ allowed_within_query.clear('#'); // avoid confict with the fragment
+ }
+
+
+ /**
+ * Those characters that are allowed for the fragment component.
+ */
+ public static final BitSet allowed_fragment = new BitSet(256);
+ // Static initializer for allowed_fragment
+ static {
+ allowed_fragment.or(uric);
+ allowed_fragment.clear('%');
+ }
+
+ // ------------------------------------------- Flags for this URI-reference
+
+ // URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
+ // absoluteURI = scheme ":" ( hier_part | opaque_part )
+ protected boolean _is_hier_part;
+ protected boolean _is_opaque_part;
+ // relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
+ // hier_part = ( net_path | abs_path ) [ "?" query ]
+ protected boolean _is_net_path;
+ protected boolean _is_abs_path;
+ protected boolean _is_rel_path;
+ // net_path = "//" authority [ abs_path ]
+ // authority = server | reg_name
+ protected boolean _is_reg_name;
+ protected boolean _is_server; // = _has_server
+ // server = [ [ userinfo "@" ] hostport ]
+ // host = hostname | IPv4address | IPv6reference
+ protected boolean _is_hostname;
+ protected boolean _is_IPv4address;
+ protected boolean _is_IPv6reference;
+
+ // ------------------------------------------ Character and escape encoding
+
+ /**
+ * Encode with the default protocol charset.
+ *
+ * @param original the original character sequence
+ * @param allowed those characters that are allowed within a component
+ * @return URI character sequence
+ * @exception IOException null component or unsupported character encoding
+ */
+ protected static char[] encode(String original, BitSet allowed)
+ throws IOException {
+
+ return encode(original, allowed, _protocolCharset);
+ }
+
+
+ /**
+ * Encodes URI string.
+ *
+ * This is a two mapping, one from original characters to octets, and
+ * subsequently a second from octets to URI characters:
+ * <p><blockquote><pre>
+ * original character sequence->octet sequence->URI character sequence
+ * </pre></blockquote><p>
+ *
+ * An escaped octet is encoded as a character triplet, consisting of the
+ * percent character "%" followed by the two hexadecimal digits
+ * representing the octet code. For example, "%20" is the escaped
+ * encoding for the US-ASCII space character.
+ * <p>
+ * Conversion from the local filesystem character set to UTF-8 will
+ * normally involve a two step process. First convert the local character
+ * set to the UCS; then convert the UCS to UTF-8.
+ * The first step in the process can be performed by maintaining a mapping
+ * table that includes the local character set code and the corresponding
+ * UCS code.
+ * The next step is to convert the UCS character code to the UTF-8 encoding.
+ * <p>
+ * Mapping between vendor codepages can be done in a very similar manner
+ * as described above.
+ * <p>
+ * The only time escape encodings can allowedly be made is when a URI is
+ * being created from its component parts. The escape and validate methods
+ * are internally performed within this method.
+ *
+ * @param original the original character sequence
+ * @param allowed those characters that are allowed within a component
+ * @param charset the protocol charset
+ * @return URI character sequence
+ * @exception IOException null component or unsupported character encoding
+ */
+ protected static char[] encode(String original, BitSet allowed,
+ String charset) throws IOException {
+
+ // encode original to uri characters.
+ if (original == null) {
+ throw new IOException(/*IOException.PARSING,*/ "URI: null");
+ }
+ // escape octet to uri characters.
+ if (allowed == null) {
+ throw new IOException(/*IOException.PARSING,*/
+ "URI: null allowed characters");
+ }
+ byte[] octets;
+ try {
+ octets = original.getBytes(charset);
+ } catch (UnsupportedEncodingException error) {
+ throw new IOException(/*IOException.UNSUPPORTED_ENCODING,*/ "Unsupported Encoding: " + charset);
+ }
+ StringBuffer buf = new StringBuffer(octets.length);
+ for (int i = 0; i < octets.length; i++) {
+ char c = (char) octets[i];
+ if (allowed.get(c)) {
+ buf.append(c);
+ } else {
+ buf.append('%');
+ byte b = octets[i]; // use the original byte value
+ char hexadecimal = Character.forDigit((b >> 4) & 0xF, 16);
+ buf.append(Character.toUpperCase(hexadecimal)); // high
+ hexadecimal = Character.forDigit(b & 0xF, 16);
+ buf.append(Character.toUpperCase(hexadecimal)); // low
+ }
+ }
+
+ return buf.toString().toCharArray();
+ }
+
+
+ /**
+ * Decode with the default protocol charset.
+ *
+ * @param component the URI character sequence
+ * @return original character sequence
+ * @exception IOException incomplete trailing escape pattern
+ * or unsupported character encoding
+ */
+ protected static String decode(char[] component) throws IOException {
+ return decode(component, _protocolCharset);
+ }
+
+
+ /**
+ * Decodes URI encoded string.
+ *
+ * This is a two mapping, one from URI characters to octets, and
+ * subsequently a second from octets to original characters:
+ * <p><blockquote><pre>
+ * URI character sequence->octet sequence->original character sequence
+ * </pre></blockquote><p>
+ *
+ * A URI must be separated into its components before the escaped
+ * characters within those components can be allowedly decoded.
+ * <p>
+ * Notice that there is a chance that URI characters that are non UTF-8
+ * may be parsed as valid UTF-8. A recent non-scientific analysis found
+ * that EUC encoded Japanese words had a 2.7% false reading; SJIS had a
+ * 0.0005% false reading; other encoding such as ASCII or KOI-8 have a 0%
+ * false reading.
+ * <p>
+ * The percent "%" character always has the reserved purpose of being
+ * the escape indicator, it must be escaped as "%25" in order to be used
+ * as data within a URI.
+ * <p>
+ * The unescape method is internally performed within this method.
+ *
+ * @param component the URI character sequence
+ * @param charset the protocol charset
+ * @return original character sequence
+ * @exception IOException incomplete trailing escape pattern
+ * or unsupported character encoding
+ */
+ protected static String decode(char[] component, String charset)
+ throws IOException {
+
+ // unescape uri characters to octets
+ if (component == null) return null;
+
+ byte[] octets;
+ try {
+ octets = new String(component).getBytes(charset);
+ } catch (UnsupportedEncodingException error) {
+ throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */
+ "URI: not supported " + charset + " encoding");
+ }
+ int length = octets.length;
+ int oi = 0; // output index
+ for (int ii = 0; ii < length; oi++) {
+ byte aByte = (byte) octets[ii++];
+ if (aByte == '%' && ii+2 <= length) {
+ byte high = (byte) Character.digit((char) octets[ii++], 16);
+ byte low = (byte) Character.digit((char) octets[ii++], 16);
+ if (high == -1 || low == -1) {
+ throw new IOException(/* IOException.ESCAPING, */
+ "URI: incomplete trailing escape pattern");
+
+ }
+ aByte = (byte) ((high << 4) + low);
+ }
+ octets[oi] = (byte) aByte;
+ }
+
+ String result;
+ try {
+ result = new String(octets, 0, oi, charset);
+ } catch (UnsupportedEncodingException error) {
+ throw new IOException(/* IOException.UNSUPPORTED_ENCODING, */
+ "URI: not supported " + charset + " encoding");
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Pre-validate the unescaped URI string within a specific component.
+ *
+ * @param component the component string within the component
+ * @param disallowed those characters disallowed within the component
+ * @return if true, it doesn't have the disallowed characters
+ * if false, the component is undefined or an incorrect one
+ */
+ protected boolean prevalidate(String component, BitSet disallowed) {
+ // prevalidate the given component by disallowed characters
+ if (component == null) {
+ return false; // undefined
+ }
+ char[] target = component.toCharArray();
+ for (int i = 0; i < target.length; i++) {
+ if (disallowed.get(target[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Validate the URI characters within a specific component.
+ * The component must be performed after escape encoding. Or it doesn't
+ * include escaped characters.
+ *
+ * @param component the characters sequence within the component
+ * @param generous those characters that are allowed within a component
+ * @return if true, it's the correct URI character sequence
+ */
+ protected boolean validate(char[] component, BitSet generous) {
+ // validate each component by generous characters
+ return validate(component, 0, -1, generous);
+ }
+
+
+ /**
+ * Validate the URI characters within a specific component.
+ * The component must be performed after escape encoding. Or it doesn't
+ * include escaped characters.
+ * <p>
+ * It's not that much strict, generous. The strict validation might be
+ * performed before being called this method.
+ *
+ * @param component the characters sequence within the component
+ * @param soffset the starting offset of the given component
+ * @param eoffset the ending offset of the given component
+ * if -1, it means the length of the component
+ * @param generous those characters that are allowed within a component
+ * @return if true, it's the correct URI character sequence
+ * @throws NullPointerException null component
+ */
+ protected boolean validate(char[] component, int soffset, int eoffset,
+ BitSet generous) {
+ // validate each component by generous characters
+ if (eoffset == -1) {
+ eoffset = component.length -1;
+ }
+ for (int i = soffset; i <= eoffset; i++) {
+ if (!generous.get(component[i])) return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * In order to avoid any possilbity of conflict with non-ASCII characters,
+ * Parse a URI reference as a <code>String</code> with the character
+ * encoding of the local system or the document.
+ * <p>
+ * The following line is the regular expression for breaking-down a URI
+ * reference into its components.
+ * <p><blockquote><pre>
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * 12 3 4 5 6 7 8 9
+ * </pre></blockquote><p>
+ * For example, matching the above expression to
+ * http://jakarta.apache.org/ietf/uri/#Related
+ * results in the following subexpression matches:
+ * <p><blockquote><pre>
+ * $1 = http:
+ * scheme = $2 = http
+ * $3 = //jakarta.apache.org
+ * authority = $4 = jakarta.apache.org
+ * path = $5 = /ietf/uri/
+ * $6 = <undefined>
+ * query = $7 = <undefined>
+ * $8 = #Related
+ * fragment = $9 = Related
+ * </pre></blockquote><p>
+ *
+ * @param original the original character sequence
+ * @param escaped <code>true</code> if <code>original</code> is escaped
+ * @return the original character sequence
+ * @exception IOException
+ */
+ protected void parseUriReference(String original, boolean escaped)
+ throws IOException {
+
+ // validate and contruct the URI character sequence
+ if (original == null || original.length() == 0) {
+ throw new IOException("URI-Reference required");
+ }
+
+ /** @
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ */
+ String tmp = original.trim();
+
+ /**
+ * The length of the string sequence of characters.
+ * It may not be equal to the length of the byte array.
+ */
+ int length = tmp.length();
+
+ /**
+ * Remove the delimiters like angle brackets around an URI.
+ */
+ char[] firstDelimiter = { tmp.charAt(0) };
+ if (validate(firstDelimiter, delims)) {
+ if (length >= 2) {
+ char[] lastDelimiter = { tmp.charAt(length - 1) };
+ if (validate(lastDelimiter, delims)) {
+ tmp = tmp.substring(1, length - 1);
+ length = length - 2;
+ }
+ }
+ }
+
+ /**
+ * The starting index
+ */
+ int from = 0;
+
+ /**
+ * The test flag whether the URI is started from the path component.
+ */
+ boolean isStartedFromPath = false;
+ int atColon = tmp.indexOf(':');
+ int atSlash = tmp.indexOf('/');
+ if (atColon < 0 || (atSlash >= 0 && atSlash < atColon)) {
+ isStartedFromPath = true;
+ }
+
+ /**
+ * <p><blockquote><pre>
+ * @@@@@@@@
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * </pre></blockquote><p>
+ */
+ int at = indexFirstOf(tmp, isStartedFromPath ? "/?#" : ":/?#", from);
+ if (at == -1) at = 0;
+
+ /**
+ * Parse the scheme.
+ * <p><blockquote><pre>
+ * scheme = $2 = http
+ * @
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * </pre></blockquote><p>
+ */
+ if (at < length && tmp.charAt(at) == ':') {
+ char[] target = tmp.substring(0, at).toLowerCase().toCharArray();
+ if (validate(target, scheme)) {
+ _scheme = target;
+ } else {
+ throw new IOException("incorrect scheme");
+ }
+ from = ++at;
+ }
+
+ /**
+ * Parse the authority component.
+ * <p><blockquote><pre>
+ * authority = $4 = jakarta.apache.org
+ * @@
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * </pre></blockquote><p>
+ */
+ // Reset flags
+ _is_net_path = _is_abs_path = _is_rel_path = _is_hier_part = false;
+ if (0 <= at && at < length && tmp.charAt(at) == '/') {
+ // Set flag
+ _is_hier_part = true;
+ if (at + 2 < length && tmp.charAt(at + 1) == '/') {
+ // the temporary index to start the search from
+ int next = indexFirstOf(tmp, "/?#", at + 2);
+ if (next == -1) {
+ next = (tmp.substring(at + 2).length() == 0) ? at + 2 :
+ tmp.length();
+ }
+ parseAuthority(tmp.substring(at + 2, next), escaped);
+ from = at = next;
+ // Set flag
+ _is_net_path = true;
+ }
+ if (from == at) {
+ // Set flag
+ _is_abs_path = true;
+ }
+ }
+
+ /**
+ * Parse the path component.
+ * <p><blockquote><pre>
+ * path = $5 = /ietf/uri/
+ * @@@@@@
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * </pre></blockquote><p>
+ */
+ if (from < length) {
+ // rel_path = rel_segment [ abs_path ]
+ int next = indexFirstOf(tmp, "?#", from);
+ if (next == -1) {
+ next = tmp.length();
+ }
+ if (!_is_abs_path) {
+ if (!escaped && prevalidate(tmp.substring(from, next),
+ disallowed_rel_path) || escaped &&
+ validate(tmp.substring(from, next).toCharArray(),
+ rel_path)) {
+ // Set flag
+ _is_rel_path = true;
+ } else if (!escaped && prevalidate(tmp.substring(from, next),
+ disallowed_opaque_part) || escaped &&
+ validate(tmp.substring(from, next).toCharArray(),
+ opaque_part)) {
+ // Set flag
+ _is_opaque_part = true;
+ } else {
+ // the path component may be empty
+ _path = null;
+ }
+ }
+ setPath(tmp.substring(from, next));
+ at = next;
+ }
+
+ /**
+ * Parse the query component.
+ * <p><blockquote><pre>
+ * query = $7 = <undefined>
+ * @@@@@@@@@
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * </pre></blockquote><p>
+ */
+ if (0 <= at && at+1 < length && tmp.charAt(at) == '?') {
+ int next = tmp.indexOf('#', at + 1);
+ if (next == -1) {
+ next = tmp.length();
+ }
+ _query = (escaped) ? tmp.substring(at + 1, next).toCharArray() :
+ encode(tmp.substring(at + 1, next), allowed_query);
+ at = next;
+ }
+
+ /**
+ * Parse the fragment component.
+ * <p><blockquote><pre>
+ * fragment = $9 = Related
+ * @@@@@@@@
+ * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ * </pre></blockquote><p>
+ */
+ if (0 <= at && at+1 < length && tmp.charAt(at) == '#') {
+ _fragment = (escaped) ? tmp.substring(at + 1).toCharArray() :
+ encode(tmp.substring(at + 1), allowed_fragment);
+ }
+
+ // set this URI.
+ setUriReference();
+ }
+
+
+ /**
+ * Get the earlier index that to be searched for the first occurrance in
+ * one of any of the given string.
+ *
+ * @param s the string to be indexed
+ * @param delims the delimiters used to index
+ * @return the earlier index if there are delimiters
+ */
+ protected int indexFirstOf(String s, String delims) {
+ return indexFirstOf(s, delims, -1);
+ }
+
+
+ /**
+ * Get the earlier index that to be searched for the first occurrance in
+ * one of any of the given string.
+ *
+ * @param s the string to be indexed
+ * @param delims the delimiters used to index
+ * @param offset the from index
+ * @return the earlier index if there are delimiters
+ */
+ protected int indexFirstOf(String s, String delims, int offset) {
+ if (s == null || s.length() == 0) {
+ return -1;
+ }
+ if (delims == null || delims.length() == 0) {
+ return -1;
+ }
+ // check boundaries
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > s.length()) {
+ return -1;
+ }
+ // s is never null
+ int min = s.length();
+ char[] delim = delims.toCharArray();
+ for (int i = 0; i < delim.length; i++) {
+ int at = s.indexOf(delim[i], offset);
+ if (at >= 0 && at < min) {
+ min = at;
+ }
+ }
+ return (min == s.length()) ? -1 : min;
+ }
+
+
+ /**
+ * Get the earlier index that to be searched for the first occurrance in
+ * one of any of the given array.
+ *
+ * @param s the character array to be indexed
+ * @param delim the delimiter used to index
+ * @return the ealier index if there are a delimiter
+ */
+ protected int indexFirstOf(char[] s, char delim) {
+ return indexFirstOf(s, delim, 0);
+ }
+
+
+ /**
+ * Get the earlier index that to be searched for the first occurrance in
+ * one of any of the given array.
+ *
+ * @param s the character array to be indexed
+ * @param delim the delimiter used to index
+ * @return the ealier index if there is a delimiter
+ */
+ protected int indexFirstOf(char[] s, char delim, int offset) {
+ if (s == null || s.length == 0) {
+ return -1;
+ }
+ // check boundaries
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > s.length) {
+ return -1;
+ }
+ for (int i = offset; i < s.length; i++) {
+ if (s[i] == delim) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+ /**
+ * Parse the authority component.
+ *
+ * @param original the original character sequence of authority component
+ * @param escaped <code>true</code> if <code>original</code> is escaped
+ * @exception IOException
+ */
+ protected void parseAuthority(String original, boolean escaped)
+ throws IOException {
+
+ // Reset flags
+ _is_reg_name = _is_server =
+ _is_hostname = _is_IPv4address = _is_IPv6reference = false;
+
+ boolean has_port = true;
+ int from = 0;
+ int next = original.indexOf('@');
+ if (next != -1) { // neither -1 and 0
+ // each protocol extented from URI supports the specific userinfo
+ _userinfo = (escaped) ? original.substring(0, next).toCharArray() :
+ encode(original.substring(0, next), allowed_userinfo);
+ from = next + 1;
+ }
+ next = original.indexOf('[', from);
+ if (next >= from) {
+ next = original.indexOf(']', from);
+ if (next == -1) {
+ throw new IOException(/* IOException.PARSING,*/ "URI: IPv6reference");
+ } else {
+ next++;
+ }
+ // In IPv6reference, '[', ']' should be excluded
+ _host = (escaped) ? original.substring(from, next).toCharArray() :
+ encode(original.substring(from, next), allowed_IPv6reference);
+ // Set flag
+ _is_IPv6reference = true;
+ } else { // only for !_is_IPv6reference
+ next = original.indexOf(':', from);
+ if (next == -1) {
+ next = original.length();
+ has_port = false;
+ }
+ // REMINDME: it doesn't need the pre-validation
+ _host = original.substring(from, next).toCharArray();
+ if (validate(_host, IPv4address)) {
+ // Set flag
+ _is_IPv4address = true;
+ } else if (validate(_host, hostname)) {
+ // Set flag
+ _is_hostname = true;
+ } else {
+ // Set flag
+ _is_reg_name = true;
+ }
+ }
+ if (_is_reg_name) {
+ // Reset flags for a server-based naming authority
+ _is_server = _is_hostname = _is_IPv4address =
+ _is_IPv6reference = false;
+ // set a registry-based naming authority
+ _authority = (escaped) ? original.toString().toCharArray() :
+ encode(original.toString(), allowed_reg_name);
+ } else {
+ if (original.length()-1 > next && has_port &&
+ original.charAt(next) == ':') { // not empty
+ from = next + 1;
+ try {
+ _port = Integer.parseInt(original.substring(from));
+ } catch (NumberFormatException error) {
+ throw new IOException(/*IOException.PARSING, */
+ "URI: invalid port number");
+ }
+ }
+ // set a server-based naming authority
+ StringBuffer buf = new StringBuffer();
+ if (_userinfo != null) { // has_userinfo
+ buf.append(_userinfo);
+ buf.append('@');
+ }
+ if (_host != null) {
+ buf.append(_host);
+ if (_port != -1) {
+ buf.append(':');
+ buf.append(_port);
+ }
+ }
+ _authority = buf.toString().toCharArray();
+ // Set flag
+ _is_server = true;
+ }
+ }
+
+
+ /**
+ * Once it's parsed successfully, set this URI.
+ *
+ * @see #getRawURI
+ */
+ protected void setUriReference() {
+ // set _uri
+ StringBuffer buf = new StringBuffer();
+ // ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
+ if (_scheme != null) {
+ buf.append(_scheme);
+ buf.append(':');
+ }
+ if (_is_net_path) {
+ buf.append("//");
+ if (_authority != null) { // has_authority
+ if (_userinfo != null) { // by default, remove userinfo part
+ if (_host != null) {
+ buf.append(_host);
+ if (_port != -1) {
+ buf.append(':');
+ buf.append(_port);
+ }
+ }
+ } else {
+ buf.append(_authority);
+ }
+ }
+ }
+ if (_opaque != null && _is_opaque_part) {
+ buf.append(_opaque);
+ } else if (_path != null) {
+ // _is_hier_part or _is_relativeURI
+ if (_path.length != 0) {
+ buf.append(_path);
+ }
+ }
+ if (_query != null) { // has_query
+ buf.append('?');
+ buf.append(_query);
+ }
+ if (_fragment != null) { // has_fragment
+ buf.append('#');
+ buf.append(_fragment);
+ }
+
+ _uri = buf.toString().toCharArray();
+ }
+
+ // ----------------------------------------------------------- Test methods
+
+
+ /**
+ * Tell whether or not this URI is absolute.
+ *
+ * @return true iif this URI is absoluteURI
+ */
+ public boolean isAbsoluteURI() {
+ return (_scheme != null);
+ }
+
+
+ /**
+ * Tell whether or not this URI is relative.
+ *
+ * @return true iif this URI is relativeURI
+ */
+ public boolean isRelativeURI() {
+ return (_scheme == null);
+ }
+
+
+ /**
+ * Tell whether or not the absoluteURI of this URI is hier_part.
+ *
+ * @return true iif the absoluteURI is hier_part
+ */
+ public boolean isHierPart() {
+ return _is_hier_part;
+ }
+
+
+ /**
+ * Tell whether or not the absoluteURI of this URI is opaque_part.
+ *
+ * @return true iif the absoluteURI is opaque_part
+ */
+ public boolean isOpaquePart() {
+ return _is_opaque_part;
+ }
+
+
+ /**
+ * Tell whether or not the relativeURI or heir_part of this URI is net_path.
+ * It's the same function as the has_authority() method.
+ *
+ * @return true iif the relativeURI or heir_part is net_path
+ * @see #hasAuthority
+ */
+ public boolean isNetPath() {
+ return _is_net_path || (_authority != null);
+ }
+
+
+ /**
+ * Tell whether or not the relativeURI or hier_part of this URI is abs_path.
+ *
+ * @return true iif the relativeURI or hier_part is abs_path
+ */
+ public boolean isAbsPath() {
+ return _is_abs_path;
+ }
+
+
+ /**
+ * Tell whether or not the relativeURI of this URI is rel_path.
+ *
+ * @return true iif the relativeURI is rel_path
+ */
+ public boolean isRelPath() {
+ return _is_rel_path;
+ }
+
+
+ /**
+ * Tell whether or not this URI has authority.
+ * It's the same function as the is_net_path() method.
+ *
+ * @return true iif this URI has authority
+ * @see #isNetPath
+ */
+ public boolean hasAuthority() {
+ return (_authority != null) || _is_net_path;
+ }
+
+ /**
+ * Tell whether or not the authority component of this URI is reg_name.
+ *
+ * @return true iif the authority component is reg_name
+ */
+ public boolean isRegName() {
+ return _is_reg_name;
+ }
+
+
+ /**
+ * Tell whether or not the authority component of this URI is server.
+ *
+ * @return true iif the authority component is server
+ */
+ public boolean isServer() {
+ return _is_server;
+ }
+
+
+ /**
+ * Tell whether or not this URI has userinfo.
+ *
+ * @return true iif this URI has userinfo
+ */
+ public boolean hasUserinfo() {
+ return (_userinfo != null);
+ }
+
+
+ /**
+ * Tell whether or not the host part of this URI is hostname.
+ *
+ * @return true iif the host part is hostname
+ */
+ public boolean isHostname() {
+ return _is_hostname;
+ }
+
+
+ /**
+ * Tell whether or not the host part of this URI is IPv4address.
+ *
+ * @return true iif the host part is IPv4address
+ */
+ public boolean isIPv4address() {
+ return _is_IPv4address;
+ }
+
+
+ /**
+ * Tell whether or not the host part of this URI is IPv6reference.
+ *
+ * @return true iif the host part is IPv6reference
+ */
+ public boolean isIPv6reference() {
+ return _is_IPv6reference;
+ }
+
+
+ /**
+ * Tell whether or not this URI has query.
+ *
+ * @return true iif this URI has query
+ */
+ public boolean hasQuery() {
+ return (_query != null);
+ }
+
+
+ /**
+ * Tell whether or not this URI has fragment.
+ *
+ * @return true iif this URI has fragment
+ */
+ public boolean hasFragment() {
+ return (_fragment != null);
+ }
+
+
+ // ---------------------------------------------------------------- Charset
+
+
+ /**
+ * Set the default charset of the protocol.
+ * <p>
+ * The character set used to store files SHALL remain a local decision and
+ * MAY depend on the capability of local operating systems. Prior to the
+ * exchange of URIs they SHOULD be converted into a ISO/IEC 10646 format
+ * and UTF-8 encoded. This approach, while allowing international exchange
+ * of URIs, will still allow backward compatibility with older systems
+ * because the code set positions for ASCII characters are identical to the
+ * one byte sequence in UTF-8.
+ * <p>
+ * An individual URI scheme may require a single charset, define a default
+ * charset, or provide a way to indicate the charset used.
+ *
+ * @param charset the default charset for each protocol
+ */
+ public static void setProtocolCharset(String charset) {
+ _protocolCharset = charset;
+ }
+
+
+ /**
+ * Get the default charset of the protocol.
+ * <p>
+ * An individual URI scheme may require a single charset, define a default
+ * charset, or provide a way to indicate the charset used.
+ * <p>
+ * To work globally either requires support of a number of character sets
+ * and to be able to convert between them, or the use of a single preferred
+ * character set.
+ * For support of global compatibility it is STRONGLY RECOMMENDED that
+ * clients and servers use UTF-8 encoding when exchanging URIs.
+ *
+ * @return the charset string
+ */
+ public static String getProtocolCharset() {
+ return _protocolCharset;
+ }
+
+
+ /**
+ * Set the default charset of the document.
+ * <p>
+ * Notice that it will be possible to contain mixed characters (e.g.
+ * ftp://host/KoreanNamespace/ChineseResource). To handle the Bi-directional
+ * display of these character sets, the protocol charset could be simply
+ * used again. Because it's not yet implemented that the insertion of BIDI
+ * control characters at different points during composition is extracted.
+ *
+ * @param charset the default charset for the document
+ */
+ public static void setDocumentCharset(String charset) {
+ _documentCharset = charset;
+ }
+
+
+ /**
+ * Get the default charset of the document.
+ *
+ * @return the charset string
+ */
+ public static String getDocumentCharset() {
+ return _documentCharset;
+ }
+
+ // ------------------------------------------------------------- The scheme
+
+ /**
+ * Get the scheme.
+ *
+ * @return the scheme
+ */
+ public char[] getRawScheme() {
+ return _scheme;
+ }
+
+
+ /**
+ * Get the scheme.
+ *
+ * @return the scheme
+ * null if undefined scheme
+ */
+ public String getScheme() {
+ return (_scheme == null) ? null : new String(_scheme);
+ }
+
+ // ---------------------------------------------------------- The authority
+
+ /**
+ * Set the authority. It can be one type of server, hostport, hostname,
+ * IPv4address, IPv6reference and reg_name.
+ * <p><blockquote><pre>
+ * authority = server | reg_name
+ * </pre></blockquote><p>
+ *
+ * @param escapedAuthority the raw escaped authority
+ * @exception IOException
+ * @throws NullPointerException null authority
+ */
+ public void setRawAuthority(char[] escapedAuthority) throws IOException {
+ parseAuthority(new String(escapedAuthority), true);
+ setUriReference();
+ }
+
+
+ /**
+ * Set the authority. It can be one type of server, hostport, hostname,
+ * IPv4address, IPv6reference and reg_name.
+ * Note that there is no setAuthority method by the escape encoding reason.
+ *
+ * @param escapedAuthority the escaped authority string
+ * @exception IOException
+ */
+ public void setEscapedAuthority(String escapedAuthority)
+ throws IOException {
+
+ parseAuthority(escapedAuthority, true);
+ setUriReference();
+ }
+
+
+ /**
+ * Get the raw-escaped authority.
+ *
+ * @return the raw-escaped authority
+ */
+ public char[] getRawAuthority() {
+ return _authority;
+ }
+
+
+ /**
+ * Get the escaped authority.
+ *
+ * @return the escaped authority
+ */
+ public String getEscapedAuthority() {
+ return (_authority == null) ? null : new String(_authority);
+ }
+
+
+ /**
+ * Get the authority.
+ *
+ * @return the authority
+ * @exception IOException
+ * @see #decode
+ */
+ public String getAuthority() throws IOException {
+ return (_authority == null) ? null : decode(_authority);
+ }
+
+ // ----------------------------------------------------------- The userinfo
+
+ /**
+ * Get the raw-escaped userinfo.
+ *
+ * @return the raw-escaped userinfo
+ * @see #getAuthority
+ */
+ public char[] getRawUserinfo() {
+ return _userinfo;
+ }
+
+
+ /**
+ * Get the escaped userinfo.
+ *
+ * @return the escaped userinfo
+ * @see #getAuthority
+ */
+ public String getEscapedUserinfo() {
+ return (_userinfo == null) ? null : new String(_userinfo);
+ }
+
+
+ /**
+ * Get the userinfo.
+ *
+ * @return the userinfo
+ * @exception IOException
+ * @see #decode
+ * @see #getAuthority
+ */
+ public String getUserinfo() throws IOException {
+ return (_userinfo == null) ? null : decode(_userinfo);
+ }
+
+ // --------------------------------------------------------------- The host
+
+ /**
+ * Get the host.
+ * <p><blockquote><pre>
+ * host = hostname | IPv4address | IPv6reference
+ * </pre></blockquote><p>
+ *
+ * @return the host
+ * @see #getAuthority
+ */
+ public char[] getRawHost() {
+ return _host;
+ }
+
+
+ /**
+ * Get the host.
+ * <p><blockquote><pre>
+ * host = hostname | IPv4address | IPv6reference
+ * </pre></blockquote><p>
+ *
+ * @return the host
+ * @exception IOException
+ * @see #decode
+ * @see #getAuthority
+ */
+ public String getHost() throws IOException {
+ return decode(_host);
+ }
+
+ // --------------------------------------------------------------- The port
+
+ /**
+ * Get the port. In order to get the specfic default port, the specific
+ * protocol-supported class extended from the URI class should be used.
+ * It has the server-based naming authority.
+ *
+ * @return the port
+ * if -1, it has the default port for the scheme or the server-based
+ * naming authority is not supported in the specific URI.
+ */
+ public int getPort() {
+ return _port;
+ }
+
+ // --------------------------------------------------------------- The path
+
+ /**
+ * Set the path. The method couldn't be used by API programmers.
+ *
+ * @param path the path string
+ * @exception IOException set incorrectly or fragment only
+ * @see #encode
+ */
+ protected void setPath(String path) throws IOException {
+
+ // set path
+ if (_is_net_path || _is_abs_path) {
+ _path = encode(path, allowed_abs_path);
+ } else if (_is_rel_path) {
+ StringBuffer buff = new StringBuffer(path.length());
+ int at = path.indexOf('/');
+ if (at > 0) { // never 0
+ buff.append(encode(path.substring(0, at), allowed_rel_path));
+ buff.append(encode(path.substring(at), allowed_abs_path));
+ } else {
+ buff.append(encode(path, allowed_rel_path));
+ }
+ _path = buff.toString().toCharArray();
+ } else if (_is_opaque_part) {
+ _opaque = encode(path, allowed_opaque_part);
+ } else {
+ throw new IOException(/*IOException.PARSING, */"URI: incorrect path");
+ }
+ }
+
+
+ /**
+ * Resolve the base and relative path.
+ *
+ * @param base_path a character array of the base_path
+ * @param rel_path a character array of the rel_path
+ * @return the resolved path
+ */
+ protected char[] resolvePath(char[] base_path, char[] rel_path) {
+
+ // REMINDME: paths are never null
+ String base = (base_path == null) ? "" : new String(base_path);
+ int at = base.lastIndexOf('/');
+ if (at != -1) {
+ base_path = base.substring(0, at + 1).toCharArray();
+ }
+ // _path could be empty
+ if (rel_path == null || rel_path.length == 0) {
+ return normalize(base_path);
+ } else if (rel_path[0] == '/') {
+ return rel_path;
+ } else {
+ StringBuffer buff = new StringBuffer(base.length() +
+ rel_path.length);
+ if (at != -1) {
+ buff.append(base.substring(0, at + 1));
+ buff.append(rel_path);
+ }
+ return normalize(buff.toString().toCharArray());
+ }
+ }
+
+
+ /**
+ * Get the raw-escaped current hierarchy level in the given path.
+ * If the last namespace is a collection, the slash mark ('/') should be
+ * ended with at the last character of the path string.
+ *
+ * @param path the path
+ * @return the current hierarchy level
+ * @exception IOException no hierarchy level
+ */
+ protected char[] getRawCurrentHierPath(char[] path) throws IOException {
+
+ if (_is_opaque_part) {
+ throw new IOException(/*IOException.PARSING,*/ "URI: no hierarchy level");
+ }
+ if (path == null) {
+ throw new IOException(/*IOException.PARSING,*/ "URI: emtpy path");
+ }
+ String buff = new String(path);
+ int first = buff.indexOf('/');
+ int last = buff.lastIndexOf('/');
+ if (last == 0) {
+ return rootPath;
+ } else if (first != last && last != -1) {
+ return buff.substring(0, last).toCharArray();
+ }
+ // FIXME: it could be a document on the server side
+ return path;
+ }
+
+
+ /**
+ * Get the raw-escaped current hierarchy level.
+ *
+ * @return the raw-escaped current hierarchy level
+ * @exception IOException no hierarchy level
+ */
+ public char[] getRawCurrentHierPath() throws IOException {
+ return (_path == null) ? null : getRawCurrentHierPath(_path);
+ }
+
+
+ /**
+ * Get the escaped current hierarchy level.
+ *
+ * @return the escaped current hierarchy level
+ * @exception IOException no hierarchy level
+ */
+ public String getEscapedCurrentHierPath() throws IOException {
+ char[] path = getRawCurrentHierPath();
+ return (path == null) ? null : new String(path);
+ }
+
+
+ /**
+ * Get the current hierarchy level.
+ *
+ * @return the current hierarchy level
+ * @exception IOException
+ * @see #decode
+ */
+ public String getCurrentHierPath() throws IOException {
+ char[] path = getRawCurrentHierPath();
+ return (path == null) ? null : decode(path);
+ }
+
+
+ /**
+ * Get the level above the this hierarchy level.
+ *
+ * @return the raw above hierarchy level
+ * @exception IOException
+ */
+ public char[] getRawAboveHierPath() throws IOException {
+ char[] path = getRawCurrentHierPath();
+ return (path == null) ? null : getRawCurrentHierPath(path);
+ }
+
+
+ /**
+ * Get the level above the this hierarchy level.
+ *
+ * @return the raw above hierarchy level
+ * @exception IOException
+ */
+ public String getEscapedAboveHierPath() throws IOException {
+ char[] path = getRawAboveHierPath();
+ return (path == null) ? null : new String(path);
+ }
+
+
+ /**
+ * Get the level above the this hierarchy level.
+ *
+ * @return the above hierarchy level
+ * @exception IOException
+ * @see #decode
+ */
+ public String getAboveHierPath() throws IOException {
+ char[] path = getRawAboveHierPath();
+ return (path == null) ? null : decode(path);
+ }
+
+
+ /**
+ * Get the raw-escaped path.
+ * <p><blockquote><pre>
+ * path = [ abs_path | opaque_part ]
+ * </pre></blockquote><p>
+ *
+ * @return the raw-escaped path
+ */
+ public char[] getRawPath() {
+ return _is_opaque_part ? _opaque : _path;
+ }
+
+
+ /**
+ * Get the escaped path.
+ * <p><blockquote><pre>
+ * path = [ abs_path | opaque_part ]
+ * abs_path = "/" path_segments
+ * opaque_part = uric_no_slash *uric
+ * </pre></blockquote><p>
+ *
+ * @return the escaped path string
+ */
+ public String getEscapedPath() {
+ char[] path = getRawPath();
+ return (path == null) ? null : new String(path);
+ }
+
+
+ /**
+ * Get the path.
+ * <p><blockquote><pre>
+ * path = [ abs_path | opaque_part ]
+ * </pre></blockquote><p>
+ * @return the path string
+ * @exception IOException
+ * @see #decode
+ */
+ public String getPath() throws IOException {
+ char[] path = getRawPath();
+ return (path == null) ? null : decode(path);
+ }
+
+
+ /**
+ * Get the raw-escaped basename of the path.
+ *
+ * @return the raw-escaped basename
+ */
+ public char[] getRawName() {
+ if (_path == null) return null;
+
+ int at = 0;
+ for (int i = _path.length - 1; i >= 0; i--) {
+ if (_path[i] == '/') {
+ at = i + 1;
+ break;
+ }
+ }
+ int len = _path.length - at;
+ char[] basename = new char[len];
+ System.arraycopy(_path, at, basename, 0, len);
+ return basename;
+ }
+
+
+ /**
+ * Get the escaped basename of the path.
+ *
+ * @return the escaped basename string
+ */
+ public String getEscapedName() {
+ char[] basename = getRawName();
+ return (basename == null) ? null : new String(basename);
+ }
+
+
+ /**
+ * Get the basename of the path.
+ *
+ * @return the basename string
+ * @exception IOException incomplete trailing escape pattern
+ * Or unsupported character encoding
+ * @see #decode
+ */
+ public String getName() throws IOException {
+ char[] basename = getRawName();
+ return (basename == null) ? null : decode(getRawName());
+ }
+
+ // ----------------------------------------------------- The path and query
+
+ /**
+ * Get the raw-escaped path and query.
+ *
+ * @return the raw-escaped path and query
+ */
+ public char[] getRawPathQuery() {
+
+ if (_path == null && _query == null) {
+ return null;
+ }
+ StringBuffer buff = new StringBuffer();
+ if (_path != null) {
+ buff.append(_path);
+ }
+ if (_query != null) {
+ buff.append('?');
+ buff.append(_query);
+ }
+ return buff.toString().toCharArray();
+ }
+
+
+ /**
+ * Get the escaped query.
+ *
+ * @return the escaped path and query string
+ */
+ public String getEscapedPathQuery() {
+ char[] rawPathQuery = getRawPathQuery();
+ return (rawPathQuery == null) ? null : new String(rawPathQuery);
+ }
+
+
+ /**
+ * Get the path and query.
+ *
+ * @return the path and query string.
+ * @exception IOException incomplete trailing escape pattern
+ * Or unsupported character encoding
+ * @see #decode
+ */
+ public String getPathQuery() throws IOException {
+ char[] rawPathQuery = getRawPathQuery();
+ return (rawPathQuery == null) ? null : decode(rawPathQuery);
+ }
+
+ // -------------------------------------------------------------- The query
+
+ /**
+ * Set the raw-escaped query.
+ *
+ * @param escapedQuery the raw-escaped query
+ * @exception IOException escaped query not valid
+ * @throws NullPointerException null query
+ */
+ public void setRawQuery(char[] escapedQuery) throws IOException {
+ if (!validate(escapedQuery, query))
+ throw new IOException(/*IOException.ESCAPING,*/
+ "URI: escaped query not valid");
+ _query = escapedQuery;
+ setUriReference();
+ }
+
+
+ /**
+ * Set the escaped query string.
+ *
+ * @param escapedQuery the escaped query string
+ * @exception IOException escaped query not valid
+ * @throws NullPointerException null query
+ */
+ public void setEscapedQuery(String escapedQuery) throws IOException {
+ setRawQuery(escapedQuery.toCharArray());
+ }
+
+
+ /**
+ * Set the query.
+ * When a query string is not misunderstood the reserved special characters
+ * ("&amp;", "=", "+", ",", and "$") within a query component, it is
+ * recommended to use in encoding the whole query with this method.
+ *
+ * @param query the query string.
+ * @exception IOException incomplete trailing escape pattern
+ * Or unsupported character encoding
+ * @throws NullPointerException null query
+ * @see #encode
+ */
+ public void setQuery(String query) throws IOException {
+ setRawQuery(encode(query, allowed_query));
+ }
+
+
+ /**
+ * Get the raw-escaped query.
+ *
+ * @return the raw-escaped query
+ */
+ public char[] getRawQuery() {
+ return _query;
+ }
+
+
+ /**
+ * Get the escaped query.
+ *
+ * @return the escaped query string
+ */
+ public String getEscapedQuery() {
+ return (_query == null) ? null : new String(_query);
+ }
+
+
+ /**
+ * Get the query.
+ *
+ * @return the query string.
+ * @exception IOException incomplete trailing escape pattern
+ * Or unsupported character encoding
+ * @see #decode
+ */
+ public String getQuery() throws IOException {
+ return (_query == null) ? null : decode(_query);
+ }
+
+ // ----------------------------------------------------------- The fragment
+
+ /**
+ * Set the raw-escaped fragment.
+ *
+ * @param escapedFragment the raw-escaped fragment
+ * @exception IOException escaped fragment not valid
+ * @throws NullPointerException null fragment
+ */
+ public void setRawFragment(char[] escapedFragment) throws IOException {
+ if (!validate(escapedFragment, fragment))
+ throw new IOException(/*IOException.ESCAPING,*/
+ "URI: escaped fragment not valid");
+ _fragment = escapedFragment;
+ setUriReference();
+ }
+
+
+ /**
+ * Set the escaped fragment string.
+ *
+ * @param escapedFragment the escaped fragment string
+ * @exception IOException escaped fragment not valid
+ * @throws NullPointerException null fragment
+ */
+ public void setEscapedFragment(String escapedFragment) throws IOException {
+ char[] fragmentSequence = escapedFragment.toCharArray();
+ if (!validate(fragmentSequence, fragment))
+ throw new IOException(/*IOException.ESCAPING,*/
+ "URI: escaped fragment not valid");
+ _fragment = fragmentSequence;
+ setUriReference();
+ }
+
+
+ /**
+ * Set the fragment.
+ *
+ * @param the fragment string.
+ * @exception IOException
+ * Or unsupported character encoding
+ * @throws NullPointerException null fragment
+ */
+ public void setFragment(String fragment) throws IOException {
+ _fragment = encode(fragment, allowed_fragment);
+ setUriReference();
+ }
+
+
+ /**
+ * Get the raw-escaped fragment.
+ * <p>
+ * The optional fragment identifier is not part of a URI, but is often used
+ * in conjunction with a URI.
+ * <p>
+ * The format and interpretation of fragment identifiers is dependent on
+ * the media type [RFC2046] of the retrieval result.
+ * <p>
+ * A fragment identifier is only meaningful when a URI reference is
+ * intended for retrieval and the result of that retrieval is a document
+ * for which the identified fragment is consistently defined.
+ *
+ * @return the raw-escaped fragment
+ */
+ public char[] getRawFragment() {
+ return _fragment;
+ }
+
+
+ /**
+ * Get the escaped fragment.
+ *
+ * @return the escaped fragment string
+ */
+ public String getEscapedFragment() {
+ return (_fragment == null) ? null : new String(_fragment);
+ }
+
+
+ /**
+ * Get the fragment.
+ *
+ * @return the fragment string
+ * @exception IOException incomplete trailing escape pattern
+ * Or unsupported character encoding
+ * @see #decode
+ */
+ public String getFragment() throws IOException {
+ return (_fragment == null) ? null : decode(_fragment);
+ }
+
+ // ------------------------------------------------------------- Utilities
+
+ /**
+ * Normalize the given hier path part.
+ *
+ * @param path the path to normalize
+ * @return the normalized path
+ */
+ protected char[] normalize(char[] path) {
+
+ if (path == null) return null;
+
+ String normalized = new String(path);
+ boolean endsWithSlash = true;
+ // precondition
+ if (!normalized.endsWith("/")) {
+ normalized += '/';
+ endsWithSlash = false;
+ }
+ if (normalized.endsWith("/./") || normalized.endsWith("/../")) {
+ endsWithSlash = true;
+ }
+ // Resolve occurrences of "/./" in the normalized path
+ while (true) {
+ int at = normalized.indexOf("/./");
+ if (at == -1) {
+ break;
+ }
+ normalized = normalized.substring(0, at) +
+ normalized.substring(at + 2);
+ }
+ // Resolve occurrences of "/../" in the normalized path
+ while (true) {
+ int at = normalized.indexOf("/../");
+ if (at == -1) {
+ break;
+ }
+ if (at == 0) {
+ normalized = "/";
+ break;
+ }
+ int backward = normalized.lastIndexOf('/', at - 1);
+ if (backward == -1) {
+ // consider the rel_path
+ normalized = normalized.substring(at + 4);
+ } else {
+ normalized = normalized.substring(0, backward) +
+ normalized.substring(at + 3);
+ }
+ }
+ // Resolve occurrences of "//" in the normalized path
+ while (true) {
+ int at = normalized.indexOf("//");
+ if (at == -1) {
+ break;
+ }
+ normalized = normalized.substring(0, at) +
+ normalized.substring(at + 1);
+ }
+ if (!endsWithSlash && normalized.endsWith("/")) {
+ normalized = normalized.substring(0, normalized.length()-1);
+ } else if (endsWithSlash && !normalized.endsWith("/")) {
+ normalized = normalized + "/";
+ }
+ // Set the normalized path that we have completed
+ return normalized.toCharArray();
+ }
+
+
+ /**
+ * Normalize the path part of this URI.
+ */
+ public void normalize() {
+ _path = normalize(_path);
+ }
+
+
+ /**
+ * Test if the first array is equal to the second array.
+ *
+ * @param first the first character array
+ * @param second the second character array
+ * @return true if they're equal
+ */
+ protected boolean equals(char[] first, char[] second) {
+
+ if (first == null && second == null) {
+ return true;
+ }
+ if (first == null || second == null) {
+ return false;
+ }
+ if (first.length != second.length) {
+ return false;
+ }
+ for (int i = 0; i < first.length; i++) {
+ if (first[i] != second[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Test an object if this URI is equal to another.
+ *
+ * @param obj an object to compare
+ * @return true if two URI objects are equal
+ */
+ public boolean equals(Object obj) {
+
+ // normalize and test each components
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof URI)) {
+ return false;
+ }
+ URI another = (URI) obj;
+ // scheme
+ if (!equals(_scheme, another._scheme)) {
+ return false;
+ }
+ // is_opaque_part or is_hier_part? and opaque
+ if (!equals(_opaque, another._opaque)) {
+ return false;
+ }
+ // is_hier_part
+ // has_authority
+ if (!equals(_authority, another._authority)) {
+ return false;
+ }
+ // path
+ if (!equals(_path, another._path)) {
+ return false;
+ }
+ // has_query
+ if (!equals(_query, another._query)) {
+ return false;
+ }
+ // has_fragment? should be careful of the only fragment case.
+ if (!equals(_fragment, another._fragment)) {
+ return false;
+ }
+ return true;
+ }
+
+ // ---------------------------------------------------------- Serialization
+
+ /**
+ * Write the content of this URI.
+ *
+ * @param oos the object-output stream
+ */
+ protected void writeObject(java.io.ObjectOutputStream oos)
+ throws IOException {
+
+ oos.defaultWriteObject();
+ }
+
+
+ /**
+ * Read a URI.
+ *
+ * @param ois the object-input stream
+ */
+ protected void readObject(java.io.ObjectInputStream ois)
+ throws ClassNotFoundException, IOException {
+
+ ois.defaultReadObject();
+ }
+
+ // ------------------------------------------------------------- Comparison
+
+ /**
+ * Compare this URI to another object.
+ *
+ * @param obj the object to be compared.
+ * @return 0, if it's same,
+ * -1, if failed, first being compared with in the authority component
+ * @exception ClassCastException not URI argument
+ * @throws NullPointerException null object
+ */
+ public int compareTo(Object obj) {
+
+ URI another = (URI) obj;
+ if (!equals(_authority, another.getRawAuthority())) return -1;
+ return toString().compareTo(another.toString());
+ }
+
+ // ------------------------------------------------------------------ Clone
+
+ /**
+ * Create and return a copy of this object, the URI-reference containing
+ * the userinfo component. Notice that the whole URI-reference including
+ * the userinfo component counld not be gotten as a <code>String</code>.
+ * <p>
+ * To copy the identical <code>URI</code> object including the userinfo
+ * component, it should be used.
+ *
+ * @return a clone of this instance
+ */
+ public synchronized Object clone() {
+
+ URI instance = new URI();
+
+ instance._uri = _uri;
+ instance._scheme = _scheme;
+ instance._opaque = _opaque;
+ instance._authority = _authority;
+ instance._userinfo = _userinfo;
+ instance._host = _host;
+ instance._port = _port;
+ instance._path = _path;
+ instance._query = _query;
+ instance._fragment = _fragment;
+ // flags
+ instance._is_hier_part = _is_hier_part;
+ instance._is_opaque_part = _is_opaque_part;
+ instance._is_net_path = _is_net_path;
+ instance._is_abs_path = _is_abs_path;
+ instance._is_rel_path = _is_rel_path;
+ instance._is_reg_name = _is_reg_name;
+ instance._is_server = _is_server;
+ instance._is_hostname = _is_hostname;
+ instance._is_IPv4address = _is_IPv4address;
+ instance._is_IPv6reference = _is_IPv6reference;
+
+ return instance;
+ }
+
+ // ------------------------------------------------------------ Get the URI
+
+ /**
+ * It can be gotten the URI character sequence. It's raw-escaped.
+ * For the purpose of the protocol to be transported, it will be useful.
+ * <p>
+ * It is clearly unwise to use a URL that contains a password which is
+ * intended to be secret. In particular, the use of a password within
+ * the 'userinfo' component of a URL is strongly disrecommended except
+ * in those rare cases where the 'password' parameter is intended to be
+ * public.
+ * <p>
+ * When you want to get each part of the userinfo, you need to use the
+ * specific methods in the specific URL. It depends on the specific URL.
+ *
+ * @return URI character sequence
+ */
+ public char[] getRawURI() {
+ return _uri;
+ }
+
+
+ /**
+ * It can be gotten the URI character sequence. It's escaped.
+ * For the purpose of the protocol to be transported, it will be useful.
+ *
+ * @return the URI string
+ */
+ public String getEscapedURI() {
+ return (_uri == null) ? null : new String(_uri);
+ }
+
+
+ /**
+ * It can be gotten the URI character sequence.
+ *
+ * @return the URI string
+ * @exception IOException incomplete trailing escape pattern
+ * Or unsupported character encoding
+ * @see #decode
+ */
+ public String getURI() throws IOException {
+ return (_uri == null) ? null : decode(_uri);
+ }
+
+
+ /**
+ * Get the escaped URI string.
+ * <p>
+ * On the document, the URI-reference form is only used without the userinfo
+ * component like http://jakarta.apache.org/ by the security reason.
+ * But the URI-reference form with the userinfo component could be parsed.
+ * <p>
+ * In other words, this URI and any its subclasses must not expose the
+ * URI-reference expression with the userinfo component like
+ * http://user:password@hostport/restricted_zone.<br>
+ * It means that the API client programmer should extract each user and
+ * password to access manually. Probably it will be supported in the each
+ * subclass, however, not a whole URI-reference expression.
+ *
+ * @return the URI string
+ * @see #clone()
+ */
+ public String toString() {
+ return getEscapedURI();
+ }
+
+
+ // ------------------------------------------------------------ Inner class
+
+ /**
+ * A mapping to determine the (somewhat arbitrarily) preferred charset for
+ * a given locale. Supports all locales recognized in JDK 1.1.
+ * <p>
+ * The distribution of this class is Servlets.com. It was originally
+ * written by Jason Hunter [jhunter at acm.org] and used by with permission.
+ */
+ public static class LocaleToCharsetMap {
+
+ private static Hashtable map;
+ static {
+ map = new Hashtable();
+ map.put("ar", "ISO-8859-6");
+ map.put("be", "ISO-8859-5");
+ map.put("bg", "ISO-8859-5");
+ map.put("ca", "ISO-8859-1");
+ map.put("cs", "ISO-8859-2");
+ map.put("da", "ISO-8859-1");
+ map.put("de", "ISO-8859-1");
+ map.put("el", "ISO-8859-7");
+ map.put("en", "ISO-8859-1");
+ map.put("es", "ISO-8859-1");
+ map.put("et", "ISO-8859-1");
+ map.put("fi", "ISO-8859-1");
+ map.put("fr", "ISO-8859-1");
+ map.put("hr", "ISO-8859-2");
+ map.put("hu", "ISO-8859-2");
+ map.put("is", "ISO-8859-1");
+ map.put("it", "ISO-8859-1");
+ map.put("iw", "ISO-8859-8");
+ map.put("ja", "Shift_JIS");
+ map.put("ko", "EUC-KR");
+ map.put("lt", "ISO-8859-2");
+ map.put("lv", "ISO-8859-2");
+ map.put("mk", "ISO-8859-5");
+ map.put("nl", "ISO-8859-1");
+ map.put("no", "ISO-8859-1");
+ map.put("pl", "ISO-8859-2");
+ map.put("pt", "ISO-8859-1");
+ map.put("ro", "ISO-8859-2");
+ map.put("ru", "ISO-8859-5");
+ map.put("sh", "ISO-8859-5");
+ map.put("sk", "ISO-8859-2");
+ map.put("sl", "ISO-8859-2");
+ map.put("sq", "ISO-8859-2");
+ map.put("sr", "ISO-8859-5");
+ map.put("sv", "ISO-8859-1");
+ map.put("tr", "ISO-8859-9");
+ map.put("uk", "ISO-8859-5");
+ map.put("zh", "GB2312");
+ map.put("zh_TW", "Big5");
+ }
+
+ /**
+ * Get the preferred charset for the given locale.
+ *
+ * @param locale the locale
+ * @return the preferred charset
+ * or null if the locale is not recognized
+ */
+ public static String getCharset(Locale locale) {
+ // try for an full name match (may include country)
+ String charset = (String) map.get(locale.toString());
+ if (charset != null) return charset;
+
+ // if a full name didn't match, try just the language
+ charset = (String) map.get(locale.getLanguage());
+ return charset; // may be null
+ }
+
+ }
+
+}
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java
new file mode 100644
index 0000000..377345c
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionResults.java
@@ -0,0 +1,51 @@
+/*
+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;
+
+/**
+* An interface defining the results of a DirectAction.
+* Implemented by both WOResponse and WOComponent so both
+* can be returned from a DirectAction.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public interface WOActionResults
+{
+/**
+* Returns a response object as appropriate for the target.
+*/
+ WOResponse generateResponse ();
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:52:44 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:49 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java
new file mode 100644
index 0000000..78191b6
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActionURL.java
@@ -0,0 +1,57 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+
+/**
+ * This dynamic element renders only the URL of a hyperlink.
+ * Bindings are:
+ * <ul>
+ * <li>href: The URL that the hyperlink should point to.</li>
+ * <li>pageName: The name of the WOComponent that the hyperlink should point to.</li>
+ * <li>directActionName: The name of the direct action to call when the link is activated.</li>
+ * <li>actionClass: The name of the WODirectAction subclass where the direct action resides.</li>
+ * <li>action: A pointer to a method on the component that contains this element. If the link is activated,
+ * the method will be called.
+ * <li>ref: The name of the anchor to go to inside the resulting page.</li>
+ * </ul>
+ *
+ * The href, pageName and directActionName/actionClass and name properties are mutually exclusive and you should
+ * only use at most one of them simultaneously.
+ *
+ * @author ezamudio@nasoft.com
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOActionURL extends WOHyperlink {
+
+ public WOActionURL() {
+ super();
+ }
+
+ public WOActionURL(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ r.appendContentString(actionURL(c));
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java
new file mode 100644
index 0000000..cad6f64
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOActiveImage.java
@@ -0,0 +1,81 @@
+
+package net.wotonomy.web;
+
+import java.util.Enumeration;
+
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+ * WOActiveImage renders a dynamically generated IMG tag, enclosed in a hyperlink.
+ * Internally, it uses a WOImage and a WOHyperlink to do the actual work.
+ *
+ * The bindings are those of WOImage and WOHyperlink combined.
+ *
+ * @author ezamudio@nasoft.com
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOActiveImage extends WODynamicElement {
+
+ protected WOActiveImage() {
+ super();
+ }
+
+ public WOActiveImage(String aName, NSDictionary aMap, WOElement template) {
+ super(aName, aMap, template);
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ NSMutableDictionary atribs = new NSMutableDictionary(5);
+ if (associations.objectForKey("mimeType") != null)
+ atribs.setObjectForKey(associations.objectForKey("mimeType"), "mimeType");
+ if (associations.objectForKey("data") != null)
+ atribs.setObjectForKey(associations.objectForKey("data"), "data");
+ if (associations.objectForKey("src") != null)
+ atribs.setObjectForKey(associations.objectForKey("src"), "src");
+ if (associations.objectForKey("framework") != null)
+ atribs.setObjectForKey(associations.objectForKey("framework"), "framework");
+ if (associations.objectForKey("filename") != null)
+ atribs.setObjectForKey(associations.objectForKey("filename"), "filename");
+ if (associations.objectForKey("alt") != null)
+ atribs.setObjectForKey(associations.objectForKey("alt"), "alt");
+ if (associations.objectForKey("border") != null)
+ atribs.setObjectForKey(associations.objectForKey("border"), "border");
+ if (associations.objectForKey("width") != null)
+ atribs.setObjectForKey(associations.objectForKey("width"), "width");
+ if (associations.objectForKey("height") != null)
+ atribs.setObjectForKey(associations.objectForKey("height"), "height");
+ WODynamicElement img = new WOImage("WOImage_" + name, atribs, null);
+ NSMutableDictionary uf = new NSMutableDictionary();
+ Enumeration enumeration = associations.keyEnumerator();
+ while (enumeration.hasMoreElements()) {
+ String key = (String)enumeration.nextElement();
+ if (key.startsWith("?"))
+ uf.setObjectForKey(associations.objectForKey(key), key);
+ }
+ createLink(img).appendToResponse(r, c);
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ return createLink(null).invokeAction(r, c);
+ }
+
+ protected WOHyperlink createLink(WOElement e) {
+ NSMutableDictionary atribs = new NSMutableDictionary(5);
+ if (associations.objectForKey("href") != null)
+ atribs.setObjectForKey(associations.objectForKey("href"), "href");
+ if (associations.objectForKey("pageName") != null)
+ atribs.setObjectForKey(associations.objectForKey("pageName"), "pageName");
+ if (associations.objectForKey("action") != null)
+ atribs.setObjectForKey(associations.objectForKey("action"), "action");
+ if (associations.objectForKey("directActionName") != null)
+ atribs.setObjectForKey(associations.objectForKey("directActionName"), "directActionName");
+ if (associations.objectForKey("actionClass") != null)
+ atribs.setObjectForKey(associations.objectForKey("actionClass"), "actionClass");
+ if (associations.objectForKey("target") != null)
+ atribs.setObjectForKey(associations.objectForKey("target"), "target");
+ return new WOHyperlink(name, atribs, e);
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java
new file mode 100644
index 0000000..90e40c6
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOApplication.java
@@ -0,0 +1,1193 @@
+/*
+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.lang.reflect.Constructor;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.web.util.BrowserLauncher;
+
+import org.mortbay.http.HttpListener;
+import org.mortbay.http.HttpServer;
+import org.mortbay.jetty.servlet.ServletHandler;
+import org.mortbay.util.InetAddrPort;
+
+/**
+* A pure java implementation of WOApplication. <br><br>
+*
+* The application is responsible for creating and managing sessions
+* and dispatching requests to the appropriate handlers. <br><br>
+*
+* This implementation extends HttpServlet, so the application itself
+* is a servlet and can be configured and managed as such by the servlet
+* container.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOApplication
+ extends HttpServlet
+{
+ /**
+ * A tricky way to allow multiple WOApplications
+ * in the same servlet container.
+ */
+ static private ThreadLocal threadLocal;
+ //static private WOApplication application;
+
+ /**
+ * Determines application-wide page caching.
+ * Pages may individually prevent caching.
+ */
+ static private boolean cachingEnabled = false;
+ private static boolean autoOpenInBrowser = true;
+
+ private String name;
+ private WORequestHandler defaultRequestHandler;
+ private NSMutableDictionary requestHandlers;
+ private WOSessionStore sessionStore;
+ private WOResourceManager resourceManager;
+ private boolean pageRefreshOnBacktrack;
+ private int pageCacheSize;
+ private int permanentPageCacheSize;
+
+ public static final String WOApplicationWillFinishLaunchingNotification
+ = "WOApplicationWillFinishLaunchingNotification";
+ public static final String WOApplicationDidFinishLaunchingNotification
+ = "WOApplicationDidFinishLaunchingNotification";
+ public static final String WOGarbageCollectionPeriodKey
+ = "WOGarbageCollectionPeriodKey";
+
+ static String _DirectActionRequestHandlerKey = "_DirectActionRequestHandlerKey";
+ static String _ComponentRequestHandlerKey = "_ComponentRequestHandlerKey";
+ static String _ResourceRequestHandlerKey = "_ResourceRequestHandlerKey";
+ static String WOPort = "WOPort";
+ static String WOSMTPHost = "WOSMTPHost";
+ static final String ELEMENT_CLASS = "elementClass";
+
+ public WOApplication ()
+ {
+ if ( threadLocal == null )
+ {
+ threadLocal = new ThreadLocal();
+ }
+ threadLocal.set( this );
+
+ //application = this;
+ resourceManager = createResourceManager();
+ requestHandlers = new NSMutableDictionary();
+ defaultRequestHandler = new WODirectActionRequestHandler();
+ registerRequestHandler( defaultRequestHandler, directActionRequestHandlerKey() );
+ registerRequestHandler( new WOComponentRequestHandler(), componentRequestHandlerKey() );
+ registerRequestHandler( new WOResourceRequestHandler(), resourceRequestHandlerKey() );
+ sessionStore = WOSessionStore.serverSessionStore();
+
+ pageRefreshOnBacktrack = true;
+ pageCacheSize = 30;
+ permanentPageCacheSize = 30;
+
+ threadLocal.set( null );
+ }
+
+ /**
+ * Dispatches the request and updates the specified response
+ * as appropriate. This implementation creates a new WORequest
+ * and WOContext from the request, sends the response to the
+ * appropriate WORequestHandler, and then updates the servlet
+ * response from the resulting WOResponse.
+ */
+ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, java.io.IOException
+ {
+ threadLocal.set( this );
+
+ WORequest request = new WORequest( req, this );
+ WOResponse response = dispatchRequest( request );
+ response.generateServletResponse( resp );
+ }
+
+ /**
+ * Handles post requests by calling doGet(), since the framework
+ * handles both gets and posts similarly. Override to handle
+ * post requests in a different manner.
+ */
+ protected void doPost(HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, java.io.IOException
+ {
+ doGet( req, resp );
+ }
+
+ // obtaining attributes
+
+ /**
+ * Returns the singleton instance of this application.
+ */
+ public static WOApplication application()
+ {
+ return (WOApplication) threadLocal.get();
+ //return application;
+ }
+
+ /**
+ * Returns the name of the application. This implementation returns
+ * the name the jar file or directory from which the class was loaded,
+ * with no extensions.
+ */
+ public String name()
+ {
+ if ( name == null )
+ {
+ name = path();
+ int i;
+ if ( name.endsWith( "/" ) )
+ { // path
+ name = name.substring( 0, name.length() - 1 );
+ }
+ else
+ { // jar file
+ i = name.lastIndexOf( '.' );
+ if ( i != -1 ) name = name.substring( 0, i );
+ }
+ i = name.lastIndexOf( '/' );
+ if ( i != -1 ) name = name.substring( i+1 );
+ }
+ return name;
+ }
+
+ /**
+ * Returns the absolute path to the application on the local file system.
+ * Note that the application might be embedded inside of a jar file.
+ */
+ public String path ()
+ {
+ return getClass().getProtectionDomain().getCodeSource().getLocation().toString();
+ }
+
+ /**
+ * Returns the path to the application on the local file system
+ * relative to the server's document root.
+ */
+ public String baseURL ()
+ {
+ String root = getServletContext().getRealPath( "/" );
+ String result = path();
+ if ( result.endsWith("/") )
+ { // path
+ if ( result.startsWith( root ) )
+ { // relative to root
+ result = result.substring( root.length() );
+ }
+ // else leave as absolute reference
+ }
+ // jar or war file: leave as absolute reference
+ return result;
+ }
+
+ // concurrent request handling
+
+ /**
+ * Returns whether this application allows request
+ * to be handled concurrently.
+ * This implementation returns true.
+ * Subclasses may override to return false to force
+ * single-threaded request handling, although this
+ * is not implemented.
+ */
+ public boolean allowsConcurrentRequestHandling ()
+ {
+ return true;
+ }
+
+ /**
+ * Returns whether this application allows request
+ * to be handled concurrently.
+ * This implementation returns true.
+ */
+ public boolean adaptorsDispatchRequestsConcurrently ()
+ {
+ return true;
+ }
+
+ /**
+ * Returns whether this application allows request
+ * to be handled concurrently.
+ * This implementation returns true.
+ */
+ public boolean isConcurrentRequestHandlingEnabled ()
+ {
+ return true;
+ }
+
+ // handling requests
+
+ /**
+ * Invoked first in the request-response cycle.
+ * Override to perform any kind of initialization at the
+ * start of a request. This implementation does nothing.
+ */
+ public void awake ()
+ {
+
+ }
+
+ /**
+ * Invoked to start the first phase of the request-response cycle,
+ * after all calls to awake() have been completed.
+ */
+ public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
+ {
+ aContext.session().takeValuesFromRequest( aRequest, aContext );
+ }
+
+ /**
+ * Invoked to start the second phase of the request-response cycle,
+ * after all calls to takeValuesFromRequest have finished.
+ */
+ public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
+ {
+ return aContext.session().invokeAction( aRequest, aContext );
+ }
+
+ /**
+ * Invoked to start the third phase of the request-response cycle,
+ * after invokeAction() has completed and returned a WOResponse.
+ */
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ aContext.session().appendToResponse( aResponse, aContext );
+ }
+
+ /**
+ * Invoked last in the request-response cycle.
+ * Override to perform any kind of clean-up at the
+ * end of a request. This implementation does nothing.
+ */
+ public void sleep ()
+ {
+
+ }
+
+ /**
+ * Dispatches the request to the appropriate handler.
+ */
+ public WOResponse dispatchRequest (WORequest aRequest)
+ {
+ return handlerForRequest( aRequest ).handleRequest( aRequest );
+ }
+
+ // request handling
+
+ /**
+ * Returns the default request handler used if the requested
+ * handler isn't specified or cannot be found. (This defaults
+ * to the WODirectActionRequestHandler.)
+ */
+ public WORequestHandler defaultRequestHandler ()
+ {
+ return defaultRequestHandler;
+ }
+
+ /**
+ * Sets the default request handler used if the requested
+ * handler isn't specified or cannot be found.
+ */
+ public void setDefaultRequestHandler (WORequestHandler aRequestHandler)
+ {
+ defaultRequestHandler = aRequestHandler;
+ }
+
+ /**
+ * Registers the specified request handler for the specified key.
+ */
+ public void registerRequestHandler (WORequestHandler aRequestHandler, String aKey)
+ {
+ requestHandlers.setObjectForKey( aRequestHandler, aKey );
+ }
+
+ /**
+ * Unregisters any existing request handler for the specified key
+ * returning the existing request handler, if any.
+ */
+ public WORequestHandler removeRequestHandlerForKey (String aKey)
+ {
+ WORequestHandler result = requestHandlerForKey( aKey );
+ requestHandlers.removeObjectForKey( aKey );
+ return result;
+ }
+
+ /**
+ * Returns the keys under which request handlers are registered.
+ */
+ public NSArray registeredRequestHandlerKeys ()
+ {
+ return requestHandlers.allKeys();
+ }
+
+ /**
+ * Returns the request handler registered for the specified key,
+ * or null if no request handler is registered for that key.
+ */
+ public WORequestHandler requestHandlerForKey (String aKey)
+ {
+ return (WORequestHandler) requestHandlers.objectForKey( aKey );
+ }
+
+ /**
+ * Returns the request handler that would best service the specified request.
+ */
+ public WORequestHandler handlerForRequest (WORequest aRequest)
+ {
+ WORequestHandler result = requestHandlerForKey( aRequest.requestHandlerKey() );
+ if ( aRequest == null ) result = defaultRequestHandler();
+ return result;
+ }
+
+ // handling errors
+
+ public WOResponse handleSessionCreationErrorInContext( WOContext aContext )
+ {
+ WOResponse response = new WOResponse();
+ response.setStatus( 500 ); // internal server error
+ //TODO: add more useful information to the response
+ System.err.println( "Failed to create session: " + aContext );
+new RuntimeException().printStackTrace(); // remove me
+ return response;
+ }
+
+ public WOResponse handleSessionRestorationErrorInContext( WOContext aContext )
+ {
+ WOResponse response = new WOResponse();
+ response.setStatus( 500 ); // internal server error
+ //TODO: add more useful information to the response
+ System.err.println( "Failed to restore session: " + aContext );
+new RuntimeException().printStackTrace(); // remove me
+ return response;
+ }
+
+ public WOResponse handlePageRestorationErrorInContext( WOContext aContext )
+ {
+ WOResponse response = new WOResponse();
+ response.setStatus( 500 ); // internal server error
+ //TODO: add more useful information to the response
+ System.err.println( "Failed to restore page: " + aContext );
+new RuntimeException().printStackTrace(); // remove me
+ return response;
+ }
+
+ public WOResponse handleException( Throwable aThrowable, WOContext aContext )
+ {
+ WOResponse response = new WOResponse();
+ response.setStatus( 500 ); // internal server error
+ System.err.println( "Exception occurred: " + aContext );
+ if ( aThrowable.getMessage() != null )
+ {
+ response.appendContentString( aThrowable.getMessage() );
+ aThrowable.printStackTrace();
+ }
+ else
+ {
+ response.appendContentString( aThrowable.toString() );
+ aThrowable.printStackTrace();
+ }
+ aThrowable.printStackTrace();
+ return response;
+ }
+
+ // managing pages
+
+ /**
+ * Sets the number of pages that will be retained
+ * in the user's session. Set to zero to disable page caching.
+ */
+ public void setPageCacheSize (int aPositiveInt)
+ {
+ pageCacheSize = aPositiveInt;
+ }
+
+ /**
+ * Returns the number of pages that will be retained
+ * in the user's session. The default page cache size is 30.
+ */
+ public int pageCacheSize ()
+ {
+ return pageCacheSize;
+ }
+
+ /**
+ * Returns the number of pages that will be retained in the
+ * longer-term "permanent" page cache in the user's session,
+ * which is typically used for navigation bars in frames, etc.
+ * The default permanent page cache size is 30.
+ */
+ public int permanentPageCacheSize ()
+ {
+ return permanentPageCacheSize;
+ }
+
+ /**
+ * Returns the number of pages that will be retained in the
+ * longer-term "permanent" page cache in the user's session,
+ * which is typically used for navigation bars in frames, etc.
+ * Set to zero to disable permanent page caching.
+ */
+ public void setPermanentPageCacheSize (int aPositiveInt)
+ {
+ permanentPageCacheSize = aPositiveInt;
+ }
+
+ /**
+ * Returns whether a "backtrack" for an existing page should
+ * simply call generateResponse() on the existing page instance.
+ * If false, a new page is created instead. The default is true.
+ */
+ public void setPageRefreshOnBacktrackEnabled (boolean enabled)
+ {
+ pageRefreshOnBacktrack = enabled;
+ }
+
+ /**
+ * Returns whether a "backtrack" for an existing page should
+ * simply call generateResponse() on the existing page instance.
+ * If false, a new page is created instead. The default is true.
+ */
+ public boolean isPageRefreshOnBacktrackEnabled ()
+ {
+ return pageRefreshOnBacktrack;
+ }
+
+ // managing sessions
+
+ /**
+ * Sets the session store used by this application to persist
+ * sessions between request-response transactions.
+ */
+ public void setSessionStore(WOSessionStore aSessionStore)
+ {
+ sessionStore = aSessionStore;
+ }
+
+ /**
+ * Returns the session store used by this application to persist
+ * sessions between request-response transactions.
+ */
+ public WOSessionStore sessionStore()
+ {
+ return sessionStore;
+ }
+
+ /**
+ * Called at the end of the request-response cycle
+ * to persist the current session until the user's next request.
+ */
+ public void saveSessionForContext(WOContext aContext)
+ {
+ sessionStore.saveSessionForContext( aContext );
+ }
+
+ /**
+ * Called at the beginning of the request-response cycle
+ * to obtain the current session from the user's last request.
+ * Returns null if no such session has been created.
+ * This method sets the context of the session to the specified context.
+ */
+ public WOSession restoreSessionWithID(String aSessionID, WOContext aContext)
+ {
+ WORequest request = aContext.request();
+ WOSession session = sessionStore.restoreSessionWithID( aSessionID, request );
+ if ( session != null )
+ {
+ session.setContext( aContext );
+ session.setServletSession( request.servletRequest().getSession() );
+ }
+ return session;
+ }
+
+ /**
+ * Called to create a session for a new request. This implementation
+ * looks for a class in the same package as the application class
+ * called "Session" and failing that returns a WOSession.
+ */
+ public WOSession createSessionForRequest(WORequest aRequest)
+ {
+ WOSession result = null;
+ try
+ {
+ // using our class loader, which is hopefully dynamic.
+ result = (WOSession) getLocalClass( "Session" ).newInstance();
+ }
+ catch ( Throwable t )
+ {
+ // ignore: fall back to WOSession
+ //t.printStackTrace();
+ }
+
+ if ( result == null )
+ {
+ result = new WOSession();
+ }
+
+ result.setServletSession( aRequest.servletRequest().getSession( true ) );
+ return result;
+ }
+
+ /**
+ * Returns the page component with the specified name.
+ * A context is created with the specified request,
+ * along with a session if necessary.
+ */
+ public WOComponent pageWithName (String aName, WORequest aRequest)
+ {
+ return pageWithName( aName, WOContext.contextWithRequest( aRequest ) );
+ }
+
+ /**
+ * Called to retrieve a component for the specified context.
+ */
+ public WOComponent pageWithName (String aName, WOContext aContext)
+ {
+ if ( aName == null )
+ {
+ throw new IllegalArgumentException(
+ "WOApplication.pageWithName: name is null" );
+ }
+
+ WOComponent result = null;
+ try
+ {
+ // using our class loader, which is hopefully dynamic.
+ Class c = getLocalClass( aName );
+
+ if ( c != null )
+ {
+ // get constructor
+ Constructor ctor;
+ try
+ {
+ ctor = c.getConstructor( new Class[] { WOContext.class } );
+ }
+ catch ( NoSuchMethodException nsme )
+ {
+ ctor = null;
+ }
+
+ // create instance of class
+ if ( ctor != null )
+ {
+ result = (WOComponent) ctor.newInstance( new Object[] { aContext } );
+ }
+ else // call back on default constructor (deprecated)
+ {
+ result = (WOComponent) c.newInstance();
+ }
+ }
+ }
+ catch ( Throwable t )
+ {
+ // ignore for now
+ //TODO: Throw appropriate exception here
+ //System.err.println( "Not found: pageWithName: " + aName );
+ t.printStackTrace();
+ }
+
+ if ( result != null && aContext != null )
+ {
+ // this is where components get their context
+ result.ensureAwakeInContext( aContext );
+ }
+ else
+ if ( result == null )
+ {
+ System.err.println( "Not found: pageWithName: " + aName );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a class in the same package as the Application class,
+ * or, failing that, from the WOApplication package, or finally
+ * from the root of the class path. Returns null if not found.
+ */
+ Class getLocalClass( String aName )
+ {
+ Class result = null;
+ if ( getClass() != WOApplication.class )
+ {
+ result = loadLocalClass( getClass(), aName );
+ }
+ if ( result == null )
+ {
+ result = loadLocalClass( WOApplication.class, aName );
+ }
+ if ( result == null )
+ {
+ result = loadLocalClass( null, aName );
+ }
+ return result;
+ }
+
+ private static final Class loadLocalClass( Class aClass, String aName )
+ {
+ ClassLoader loader;
+ String packageName = "";
+ if ( aClass != null )
+ {
+ loader = aClass.getClassLoader();
+ packageName = aClass.getName();
+ int index = packageName.lastIndexOf( "." );
+ if ( index > -1 )
+ {
+ packageName = packageName.substring( 0, index+1 );
+ }
+ else
+ {
+ packageName = "";
+ }
+ }
+ else
+ {
+ loader = WOApplication.class.getClassLoader();
+ }
+
+ try
+ {
+ return loader.loadClass( packageName + aName );
+ }
+ catch ( ClassNotFoundException e )
+ {
+ return null;
+ }
+ }
+
+ // creating elements
+
+ /**
+ * Returns either a dynamic element or a component
+ * for the specified name.
+ */
+ public WOElement dynamicElementWithName(
+ String anElementName, NSDictionary anAssociationMap,
+ WOElement aBodyElement, List aLanguageList)
+ {
+ WOElement element = null;
+ Class c = null;
+ try
+ {
+ c = getLocalClass( anElementName );
+ if ( c == null )
+ {
+ System.out.println( "Not found: dynamicElementWithName: " +
+ "could not find WODynamicElement subclass: " + anElementName );
+ c = WODynamicElement.class;
+ }
+
+ // get constructor
+ Class[] params = new Class[]
+ { String.class, NSDictionary.class, WOElement.class };
+ Constructor ctor = c.getConstructor( params );
+
+ // create instance of class
+ if ( ctor != null )
+ {
+ element = (WODynamicElement) ctor.newInstance(
+ new Object[] { anElementName, anAssociationMap, aBodyElement } );
+ }
+ }
+ catch ( Throwable t )
+ {
+ // ignore: not a dynamic element
+ //System.out.println( "Not a dynamic element: dynamicElementWithName: " + t );
+ //exc.printStackTrace();
+ }
+
+ // no dynamic element found: look for a component
+ if ( element == null )
+ {
+ WOComponent component = (WOComponent) pageWithName( anElementName, (WOContext) null );
+
+ // this seems hackish:
+ // I don't see another way of setting the bindings.
+ component.associations = anAssociationMap;
+ component.rootElement = aBodyElement;
+
+ element = component;
+ }
+
+ return element;
+ }
+
+ // resource handling
+
+ /**
+ * Called to create the application's resource manager.
+ * Override to create a custom resource manager.
+ */
+ public WOResourceManager createResourceManager()
+ {
+ return new WOResourceManager();
+ }
+
+ /**
+ * Returns the application's current resource manager.
+ */
+ public WOResourceManager resourceManager()
+ {
+ return resourceManager;
+ }
+
+ /**
+ * Installs a custom resource manager into the current application.
+ * @deprecated Override createResourceManager() instead.
+ */
+ public void setResourceManager(WOResourceManager aResourceManager)
+ {
+ resourceManager = aResourceManager;
+ }
+
+/*
+ // request handling undocumented
+
+ public WOComponent pageWithName (String);
+ public void savePage (WOComponent);
+ public WOComponent restorePageForContextID (String);
+ public WOContext context ();
+ public WOSession session ();
+ public WOSession createSession ();
+ public WOSession restoreSession ();
+ public void saveSession (WOSession);
+
+ public WOResponse handleRequest (WORequest aRequest)
+ {
+ }
+
+ // error handling undocumented
+
+ WOResponse handleSessionCreationError ();
+ WOResponse handleSessionRestorationError ();
+ WOResponse handlePageRestorationError ();
+ WOResponse handleException (Throwable);
+
+ // running
+
+ public NSRunLoop runLoop ();
+ public void run ();
+ public void setTimeOut (double);
+ public double timeOut ();
+ public void terminate ();
+ public boolean isTerminating ();
+
+ // script debugging
+
+ public void traceScriptedMessages (boolean);
+ public void traceAssignments (boolean);
+ public void traceStatements (boolean);
+ public void traceObjectiveCMessages (boolean);
+ public void trace (boolean);
+ public void logTakeValueForDeclarationNamed (String, String, String, String, Object);
+ public void logSetValueForDeclarationNamed (String, String, String, String, Object);
+
+ // script handling
+
+ public String scriptedClassNameWithPath (String);
+ public String scriptedClassNameWithPathEncoding (String, int);
+
+ // statistics report
+
+ public void setStatisticsStore (WOStatisticsStore);
+ public WOStatisticsStore statisticsStore ();
+ public NSDictionary statistics ();
+
+ // managing adaptors
+
+ public WOAdaptor adaptorWithName (String, NSDictionary);
+ public NSArray adaptors ();
+
+ // monitor support
+
+ public boolean monitoringEnabled ();
+ public int activeSessionsCount ();
+ public void refuseNewSessions (boolean);
+ public boolean isRefusingNewSessions ();
+ public void setMinimumActiveSessionsCount (int);
+ public int minimumActiveSessionsCount ();
+ public void terminateAfterTimeInterval (double);
+
+ // garbage collection undocumented
+
+ int garbageCollectionPeriod ();
+ void setGarbageCollectionPeriod (int);
+
+ // backwards compatibility
+
+ public boolean requiresWOF35RequestHandling ();
+ public boolean requiresWOF35TemplateParser ();
+
+ public void setPrintsHTMLParserDiagnostics (boolean);
+ public boolean printsHTMLParserDiagnostics ();
+
+ // configuration and defaults
+
+ public static NSArray loadFrameworks ();
+ public static void setLoadFrameworks (NSArray);
+*/
+ static boolean debuggingEnabled = false;
+ /**
+ * Returns whether the application is in "debug mode".
+ */
+ public static boolean isDebuggingEnabled()
+ {
+ return debuggingEnabled;
+ }
+
+ /**
+ * Sets whether the application is in "debug mode".
+ */
+ public static void setDebuggingEnabled( boolean enabled )
+ {
+ debuggingEnabled = enabled;
+ }
+
+ /**
+ * 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 static 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 static boolean isCachingEnabled ()
+ {
+ return cachingEnabled;
+ }
+
+ // configuration
+
+ /**
+ * Returns the component request handler key,
+ * which is defined by the system property _ComponentRequestHandlerKey.
+ * The default is "wo".
+ */
+ public static String componentRequestHandlerKey()
+ {
+ return System.getProperty( _ComponentRequestHandlerKey, "wo" );
+ }
+
+ /**
+ * Sets the component request handler key.
+ * @deprecated Set the system property _ComponentRequestHandlerKey.
+ */
+ public static void setComponentRequestHandlerKey(String aKey)
+ {
+ System.setProperty( _ComponentRequestHandlerKey, aKey );
+ }
+
+ /**
+ * Returns the direct action request handler key,
+ * which is defined by the system property _DirectActionRequestHandlerKey.
+ * The default is "wa".
+ */
+ public static String directActionRequestHandlerKey()
+ {
+ return System.getProperty( _DirectActionRequestHandlerKey, "wa" );
+ }
+
+ /**
+ * Sets the direct action request handler key.
+ * @deprecated Set the system property _DirectActionRequestHandlerKey.
+ */
+ public static void setDirectActionRequestHandlerKey(String aKey)
+ {
+ System.setProperty( _DirectActionRequestHandlerKey, aKey );
+ }
+
+ /**
+ * Returns the resource request handler key,
+ * which is defined by the system property _ResourceRequestHandlerKey.
+ * The default is "wr".
+ */
+ public static String resourceRequestHandlerKey()
+ {
+ return System.getProperty( _ResourceRequestHandlerKey, "wr" );
+ }
+
+ /**
+ * Sets the resource request handler key.
+ * @deprecated Set the system property _ResourceRequestHandlerKey.
+ */
+ public static void setResourceRequestHandlerKey(String aKey)
+ {
+ System.setProperty( _ResourceRequestHandlerKey, aKey );
+ }
+
+ /**
+ * Returns whether this application should attempt to open
+ * a web browser on the host machine when launched standalone.
+ * The default is true.
+ */
+ public static boolean autoOpenInBrowser()
+ {
+ return autoOpenInBrowser;
+ }
+
+ /**
+ * Sets whether this application should attempt to open
+ * a web browser on the host machine when launched standalone.
+ */
+ public static void setAutoOpenInBrowser(boolean autoOpen)
+ {
+ autoOpenInBrowser = autoOpen;
+ }
+
+ /**
+ * Gets the port used when run as a standalone server.
+ * Returns the value of the system property WOPort.
+ * By default, this is zero, which causes the application
+ * to automatically select an available port.
+ */
+ public static Number port ()
+ {
+ return Integer.getInteger( WOPort, 0 );
+ }
+
+ /**
+ * Gets the smtp server that will be used to send email.
+ * Returns the system property WOSMTPHost.
+ */
+ public static String SMTPHost()
+ {
+ return System.getProperty( WOSMTPHost );
+ }
+
+ /**
+ * Sets the smtp server that will be used to send email.
+ * @deprecated Set the system property WOSMTPHost.
+ */
+ public static void setSMTPHost( String aHost )
+ {
+ System.setProperty( WOSMTPHost, aHost );
+ }
+/*
+ public static boolean isDirectConnectEnabled ();
+ public static void setDirectConnectEnabled (boolean);
+ public static String cgiAdaptorURL ();
+ public static void setCGIAdaptorURL (String);
+ public static String applicationBaseURL ();
+ public static void setApplicationBaseURL (String);
+ public static String frameworksBaseURL ();
+ public static void setFrameworksBaseURL (String);
+ public static String recordingPath ();
+ public static void setRecordingPath (String);
+ public static NSArray projectSearchPath ();
+ public static void setProjectSearchPath (NSArray);
+ public static boolean isMonitorEnabled ();
+ public static void setMonitorEnabled (boolean);
+ public static String monitorHost ();
+ public static String adaptor ();
+ public String number (); // deprecated
+ public static Number listenQueueSize ();
+ public static void setListenQueueSize (Number);
+ public static NSArray additionalAdaptors ();
+ public static void setAdditionalAdaptors (NSArray);
+ public static boolean includeCommentsInResponses ();
+ public static void setIncludeCommentsInResponses (boolean);
+ public static void setSessionTimeOut (Number);
+ public static Number sessionTimeOut ();
+ public static void logString (String);
+ public static void debugString (String);
+ public static void logToMonitorString (String);
+*/
+
+ /**
+ * Main entry point for applications that do not subclass WOApplication.
+ */
+ public static void main( String[] argv )
+ {
+ main( argv, WOApplication.class );
+ }
+
+ /**
+ * Subclasses may call this method to start a self-hosted
+ * web server to serve themselves directly (for testing).
+ */
+ public static void main( String[] argv, Class subclass )
+ {
+ try
+ {
+ int port = 0;
+ boolean open = false;
+ try
+ {
+ port = ((Number)subclass.getMethod( "port",
+ new Class[0]).invoke(subclass,new Object[0])).intValue();
+ open = ((Boolean)subclass.getMethod( "autoOpenInBrowser",
+ new Class[0]).invoke(subclass,new Object[0])).booleanValue();
+ }
+ catch ( Throwable t )
+ {
+ System.err.print("Error reading configuration:" );
+ t.printStackTrace();
+ }
+
+ HttpServer server = new HttpServer();
+ HttpListener listener = server.addListener(new InetAddrPort(port));
+ org.mortbay.http.HttpContext context = server.getContext("/");
+ ServletHandler handler = new ServletHandler();
+ handler.addServlet("/",subclass.getName());
+ context.addHandler(handler);
+ server.start();
+ port = listener.getPort();
+ System.out.println("Waiting for requests: http://127.0.0.1:" + port);
+ if ( open )
+ {
+ BrowserLauncher.openURL( "http://127.0.0.1:" + port );
+ }
+ }
+ catch ( Throwable t )
+ {
+ t.printStackTrace();
+ }
+ }
+}
+
+/*
+ * $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.30 2003/03/28 18:01:19 mpowers
+ * Now defaulting port to zero.
+ *
+ * Revision 1.29 2003/03/28 17:31:58 mpowers
+ * Implemented support for autoselection of free port. (thanks gmuth!)
+ *
+ * Revision 1.28 2003/03/28 17:26:17 mpowers
+ * Implemented package support: Applications can now live in packages.
+ * Better support for locating package local classes.
+ *
+ * Revision 1.27 2003/02/21 16:40:22 mpowers
+ * Now reading port and smtp host from system properties.
+ * Implemented WOApplication.main.
+ *
+ * Revision 1.26 2003/02/14 22:33:18 mpowers
+ * Better handling for standalone mode.
+ *
+ * Revision 1.25 2003/02/14 15:18:27 mpowers
+ * Now launching standalone app as a servlet, not a webapp.
+ * Disabled jetty's event logging.
+ *
+ * Revision 1.24 2003/02/13 22:41:04 mpowers
+ * WOApplications can now be self-serving. Added configuration params too.
+ *
+ * Revision 1.23 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.22 2003/01/27 15:08:00 mpowers
+ * Implemented WOResourceManager, using java resources for now.
+ *
+ * Revision 1.21 2003/01/24 20:13:22 mpowers
+ * Now accepting immutable NSDictionary in constructor, not Map.
+ *
+ * Revision 1.20 2003/01/20 17:50:11 mpowers
+ * Caught a loop condition when same declaration was used twice.
+ *
+ * Revision 1.19 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.18 2003/01/18 23:54:50 mpowers
+ * Implemented debugging enabled.
+ *
+ * Revision 1.17 2003/01/17 20:58:18 mpowers
+ * Fixed up WOHyperlink.
+ *
+ * Revision 1.16 2003/01/17 20:34:17 mpowers
+ * Rudimentary support for resource requests.
+ *
+ * Revision 1.15 2003/01/17 15:31:56 mpowers
+ * Removed spurious error message.
+ *
+ * Revision 1.14 2003/01/17 14:39:00 mpowers
+ * Now calling preferred constructor: WOComponent(WOContext)
+ *
+ * Revision 1.13 2003/01/16 20:10:46 mpowers
+ * - components now synchronize bindings
+ * - support for WOComponentContent
+ * - implemented performParentAction
+ *
+ * Revision 1.12 2003/01/16 15:50:43 mpowers
+ * More robust declaration parsing.
+ * Subcomponents are now supported.
+ * dynamicElementWithName can now return subcomponents.
+ *
+ * Revision 1.11 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.10 2003/01/13 22:24:18 mpowers
+ * Request-response cycle is working with session and page persistence.
+ *
+ * Revision 1.9 2003/01/10 20:17:41 mpowers
+ * Component action urls are now working.
+ *
+ * Revision 1.8 2003/01/10 19:16:40 mpowers
+ * Implemented support for page caching.
+ *
+ * Revision 1.4 2002/12/19 17:58:52 mpowers
+ * Rewrote the template parsing - no longer confused about "root element".
+ *
+ * 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/12/17 14:57:41 mpowers
+ * Minor corrections to WORequests's parsing, and updated javadocs.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:49 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java
new file mode 100644
index 0000000..608f9fa
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOAssociation.java
@@ -0,0 +1,168 @@
+/*
+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;
+
+/**
+* A pure java implementation of WOAssociation. <br><br>
+*
+* A WOAssociation represents the mapping of a property on a
+* WOComponent to a property on a WOAssociation. For example: <br><br>
+*
+* MyAssociation: WOString { value = currentCustomer.location.city }; <br><br>
+*
+* This example represents a WOAssociation between the value field
+* on a WOString element and the city property of the location property
+* of the currentCustomer property of a WOComponent. <br><br>
+*
+* To resolve values, a property accessor method will be used in
+* preference to a public field, if both exist. Any null value
+* in the path will produce null. <br><br>
+*
+* A mapping represented in quotation marks: { value = "This is a test." }
+* is considered a constant value.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public class WOAssociation implements java.io.Serializable
+{
+ protected Object value;
+ protected String path;
+
+ /**
+ * The default constructor. The static factory methods should
+ * be used to create instances of WOAssociation.
+ */
+ protected WOAssociation ()
+ {
+ value = null;
+ path = null;
+ }
+
+ /**
+ * Creates a WOAssociation that maps to a constant value.
+ */
+ public static WOAssociation associationWithValue (Object anObject)
+ {
+ WOAssociation result = new WOAssociation();
+ result.value = anObject;
+ return result;
+ }
+
+ /**
+ * Creates a WOAssociation that maps to the specified key path.
+ * If the path is null, the association will map to null.
+ * Throws an exception if the property cannot be resolved.
+ */
+ public static WOAssociation associationWithKeyPath (String aString)
+ {
+ WOAssociation result = new WOAssociation();
+ result.path = aString;
+ return result;
+ }
+
+ /**
+ * Returns the value for this association's key path in the
+ * specified component, or null if any value in the path is
+ * null or if the key path is null.
+ */
+ public Object valueInComponent (WOComponent aComponent)
+ {
+ if ( aComponent == null ) return null;
+ if ( value != null ) return value;
+ if ( path != null ) return aComponent.valueForKey( path );
+ throw new RuntimeException(
+ "WOAssociation: neither value nor path specified!" );
+ }
+
+ /**
+ * Sets the property in the specified component to the specified value.
+ * Throws an exception if the property cannot be resolved.
+ */
+ public void setValue (Object aValue, WOComponent aComponent)
+ {
+ if ( path != null )
+ {
+ aComponent.takeValueForKey( aValue, path );
+ return;
+ }
+ throw new RuntimeException(
+ "WOAssociation: tried to set value but no path was specified!" );
+ }
+
+ /**
+ * Returns true if this association is writable; that is,
+ * returns true if this association is not constant.
+ */
+ public boolean isValueSettable ()
+ {
+ return ( path != null );
+ }
+
+ /**
+ * Returns true if this association is constant
+ * and therefore read-only.
+ */
+ public boolean isValueConstant ()
+ {
+ return ( path == null );
+ }
+
+ /**
+ * For debugging purposes.
+ */
+ public String toString()
+ {
+ if ( path != null )
+ {
+ return "[WOAssociation:" + path + "]";
+ }
+ return "[WOAssociation:\"" + value + "\"]";
+ }
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.6 2003/01/24 20:13:22 mpowers
+ * Now accepting immutable NSDictionary in constructor, not Map.
+ *
+ * Revision 1.5 2003/01/17 22:55:08 mpowers
+ * Straighted out the parent binding issue (I think).
+ * Fixes for woextensions compatibility.
+ *
+ * Revision 1.3 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.2 2003/01/14 15:51:48 mpowers
+ * Removed value() method from WOAssociaton.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:52:50 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.3 2000/12/20 16:25:49 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java
new file mode 100644
index 0000000..9f0707d
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOBody.java
@@ -0,0 +1,121 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2003 Intersect Software Corporation
+
+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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* WOBody represents a page's "body" tag and is used to dynamically
+* set the background image for a page, and therefore works much like WOImage.
+*
+* Bindings are:
+* <UL>
+* <li>src: A static URL for the image source.</li>
+* <li>data: A NSData object with the image content. Must be used with mimeType.</li>
+* <li>mimeType: The MIME type for the image data. Can be used with filename or data bindings.</li>
+* <li>filename: The path to a file containing an image.</li>
+* <li>framework: The optional framework from whence the image should be retrieved (used in conjunction with filename).</li>
+* <li>key: A key under which this resource will be cached for repeated access.</li>
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOBody extends WOImage {
+
+ protected String src;
+ protected String filename;
+ protected String framework;
+ protected NSData data;
+ protected String mimeType;
+
+ protected WOBody() {
+ super();
+ }
+
+ public WOBody(String aName, NSDictionary aMap, WOElement template) {
+ super(aName, aMap, template);
+ }
+
+ void ensureAwakeInContext (WOContext aContext)
+ {
+ if ( rootElement != null )
+ {
+ rootElement.ensureAwakeInContext( aContext );
+ }
+ }
+
+ public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
+ {
+ if ( rootElement != null )
+ {
+ rootElement.takeValuesFromRequest( aRequest, aContext );
+ }
+ }
+
+ public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
+ {
+ if ( rootElement != null )
+ {
+ return rootElement.invokeAction( aRequest, aContext );
+ }
+ return null;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c)
+ {
+ r.appendContentString("<body background=\"");
+ r.appendContentString(sourceURL(c));
+ r.appendContentString("\"");
+ r.appendContentString(additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "src", "filename", "framework", "data", "mimeType" })));
+ r.appendContentString(">");
+ if ( rootElement != null )
+ {
+ rootElement.appendToResponse( r, c );
+ }
+ r.appendContentString("</body>");
+ }
+
+}
+
+/*
+ * $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.4 2003/08/07 00:15:14 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.3 2003/01/27 15:57:28 mpowers
+ * Now participates in all parts of request/response cycle.
+ *
+ * Revision 1.2 2003/01/27 15:08:23 mpowers
+ * Now appending to response.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java
new file mode 100644
index 0000000..5d22d36
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCheckBox.java
@@ -0,0 +1,81 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+
+public class WOCheckBox extends WOInput {
+
+ protected boolean checked = false;
+
+ public WOCheckBox() {
+ super();
+ }
+
+ public WOCheckBox(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ protected String inputType() {
+ return "CHECKBOX";
+ }
+
+ protected Object value(WOContext c) {
+ Object val = null;
+ boolean checked = false;
+ if (associations.objectForKey("value") != null) {
+ val = valueForProperty("value", c.component());
+ Object sel = valueForProperty("selection", c.component());
+ if (sel != null && val != null && sel.equals(val))
+ checked = true;
+ }
+ if (val == null) {
+ val = c.elementID();
+ }
+ return val;
+ }
+
+ protected void appendExtras(WOResponse r, WOContext c) {
+ checked |= booleanForProperty("checked", c.component());
+ if (checked)
+ r.appendContentString(" CHECKED");
+ }
+
+ protected NSMutableArray additionalAttributes() {
+ NSMutableArray a = super.additionalAttributes();
+ a.addObject("checked");
+ a.addObject("selection");
+ return a;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ checked = false;
+ super.appendToResponse(r, c);
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ if (disabled(c))
+ return;
+ NSArray values = r.formValuesForKey(inputName(c));
+ Object val = valueForProperty("value", c.component());
+ if (val == null)
+ val = c.elementID();
+ java.util.Enumeration enumerator = values.objectEnumerator();
+ checked = false;
+ while (enumerator.hasMoreElements()) {
+ Object nextval = enumerator.nextElement();
+ if (nextval.equals(val))
+ checked = true;
+ }
+ if (associations.objectForKey("value") != null && associations.objectForKey("selection") != null) {
+ if (checked)
+ setValueForProperty("selection", val, c.component());
+ else if (valueForProperty("selection", c.component()) != null)
+ setValueForProperty("selection", null, c.component());
+ }
+ if (associations.objectForKey("checked") != null)
+ setValueForProperty("checked", checked ? Boolean.TRUE : Boolean.FALSE, c.component());
+ }
+
+} \ No newline at end of file
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.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java
new file mode 100644
index 0000000..1544934
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentContent.java
@@ -0,0 +1,45 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+
+/**
+ * Used to include a component's content inside another component. This way, you can create reusable components
+ * with content that surrounds the component it's in. In reality all it does is forward the appendToResponse
+ * method to the WOElement containing the HTML between the WEBOBJECT tags that define this element.
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOComponentContent extends WODynamicElement {
+
+ public WOComponentContent() {
+ super();
+ }
+
+ public WOComponentContent(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ c.component().rootElement.appendToResponse(r, c);
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java
new file mode 100644
index 0000000..9f79987
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOComponentRequestHandler.java
@@ -0,0 +1,229 @@
+/*
+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;
+
+
+/**
+* An implementation of WORequestHandler that dispatches Component actions.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOComponentRequestHandler
+ extends WORequestHandler
+{
+ public WOResponse handleRequest( WORequest aRequest )
+ {
+ WOApplication application = aRequest.application();
+ WOContext context = WOContext.contextWithRequest( aRequest );
+ WOResponse response = null;
+
+ // no concurrent access is allowed to a user's session:
+ // a user/browser/machine with multiple requests will have to wait
+ synchronized ( aRequest.request.getSession() )
+ {
+ try
+ {
+ application.awake();
+
+ WOSession session = null;
+ /*
+ // the NeXT way
+ String sessionID = aRequest.sessionID();
+ if ( sessionID != null )
+ {
+ session = application.restoreSessionWithID( sessionID, context );
+ if ( session == null )
+ {
+ response = application.handleSessionRestorationErrorInContext( context );
+ }
+ }
+ else
+ {
+ session = application.createSessionForRequest( aRequest );
+ if ( session == null )
+ {
+ response = application.handleSessionCreationErrorInContext( context );
+ }
+ else
+ {
+ session.setContext( context );
+ }
+ }
+ */
+ // the servlet way
+ String sessionID = aRequest.sessionID();
+ if ( sessionID != null )
+ {
+ session = application.restoreSessionWithID( sessionID, context );
+ }
+
+ if ( session == null )
+ {
+ session = application.createSessionForRequest( aRequest );
+ if ( session == null )
+ {
+ response = application.handleSessionCreationErrorInContext( context );
+ }
+ else
+ {
+ session.setContext( context );
+ }
+ }
+
+ context.setSession( session );
+
+ session.awake();
+
+ if ( response == null )
+ {
+
+ WOComponent page;
+ String contextID = aRequest.contextID();
+
+ if ( contextID != null )
+ {
+ page = session.restorePageForContextID( contextID );
+ }
+ else
+ {
+ page = application.pageWithName( aRequest.pageName(), context );
+ }
+
+ if ( page == null )
+ {
+ //FIXME: should we call this a restoration error, or do we
+ // allow a bookmark with an expired context id to resume gracefully?
+ page = application.pageWithName( aRequest.pageName(), context );
+ //!response = application.handlePageRestorationErrorInContext( context );
+ }
+ //!else
+ {
+ context.pushElement( page ); //? needed?
+ page.ensureAwakeInContext( context ); //? shouldn't this be in WOApplication?
+
+ // only take values from request if there are values in the request
+ System.out.println("should I takeValuesFromRequest ? " + ( aRequest.formValueKeys().count() > 0 ));
+ if ( aRequest.formValueKeys().count() > 0 )
+ {
+ application.takeValuesFromRequest( aRequest, context );
+ }
+
+ // only invoke action if there is a sender id to invoke
+ WOActionResults result;
+ System.out.println("senderID: " + aRequest.senderID());
+ if ( aRequest.senderID() != null )
+ {
+ result = application.invokeAction( aRequest, context );
+ }
+ else
+ {
+ result = null;
+ }
+
+ if ( result == null || result == page ) // same page is returned
+ {
+ result = page;
+
+ // generate response
+ response = new WOResponse();
+ application.appendToResponse( response, context );
+ page.sleep();
+ }
+ else // different page is returned
+ if ( result instanceof WOComponent )
+ {
+ page.sleep();
+ page = (WOComponent) result;
+ page.ensureAwakeInContext( context );
+ context.popElement(); // removes page
+ context.pushElement( page );
+
+ // generate response
+ response = new WOResponse();
+ application.appendToResponse( response, context );
+ page.sleep();
+ }
+ else // WOResponse was returnd
+ {
+ response = (WOResponse) result;
+ }
+
+ context.popElement();
+ session.sleep();
+ session.savePage( page );
+ session.setContext( null );
+ application.saveSessionForContext( context );
+ }
+ }
+ }
+ catch ( Throwable t )
+ {
+ response = application.handleException( t, context );
+ }
+
+ application.sleep();
+ }
+ return response;
+ }
+}
+
+/*
+ * $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.8 2003/08/07 00:15:15 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.7 2003/01/17 20:34:57 mpowers
+ * Better handling for components and parents in the context's element stack.
+ *
+ * Revision 1.6 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.4 2003/01/14 15:51:09 mpowers
+ * No longer calling takeValues or invokeAction if there are no request params
+ *
+ * Revision 1.3 2003/01/13 22:24:34 mpowers
+ * Request-response cycle is working with session and page persistence.
+ *
+ * Revision 1.1 2003/01/09 16:13:59 mpowers
+ * Implemented WOComponentRequestHandler:
+ * Bringing the request-response cycle more into conformance.
+ *
+ * Revision 1.2 2002/12/17 14:57:44 mpowers
+ * Minor corrections to WORequests's parsing, and updated javadocs.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:53:19 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:50 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java
new file mode 100644
index 0000000..124ca11
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOConditional.java
@@ -0,0 +1,99 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+
+/**
+* WOConditional renders whatever is inside its opening and closing tags
+ * only if a condition is met.
+ * Bindings are:
+ * <ul>
+ * <li>condition: a boolean property that indicates whether the contents of the element
+ * should be displayed, invoked, or passed the request form values.</li>
+ * <li>negate: if this is true, then the behavior of the element is reversed, showing its
+ * contents only if the condition is NOT met.</li>
+ * </ul>
+ *
+ * @author ezamudio@nasoft.com
+ * @author $Author: cgruber $
+ * @version $Revision: 893 $
+ */
+public class WOConditional extends WODynamicElement {
+
+ public boolean condition;
+ public boolean negate;
+
+ protected WOConditional() {
+ super();
+ }
+
+ public WOConditional(String aName, NSDictionary aMap, WOElement template) {
+ super(aName, aMap, template);
+ }
+
+ public void setCondition(boolean value) {
+ condition = value;
+ }
+ public boolean condition() {
+ return condition;
+ }
+
+ public void setNegate(boolean value) {
+ negate = value;
+ }
+ public boolean negate() {
+ return negate;
+ }
+
+ protected void pullValuesFromParent(WOComponent c) {
+ condition = booleanForProperty("condition", c);
+ negate = booleanForProperty("negate", c);
+ }
+
+ public void takeValuesFromRequest(WORequest aRequest, WOContext aContext) {
+ if (rootElement == null)
+ return;
+ pullValuesFromParent(aContext.component());
+ if ((condition && !negate) || (!condition && negate)) {
+ rootElement.takeValuesFromRequest(aRequest, aContext);
+ }
+ }
+
+ public WOActionResults invokeAction(WORequest aRequest, WOContext aContext) {
+ if (rootElement == null)
+ return null;
+ pullValuesFromParent(aContext.component());
+ WOActionResults el = null;
+ if ((condition && !negate) || (!condition && negate)) {
+ el = rootElement.invokeAction(aRequest, aContext);
+ }
+ return el;
+ }
+
+ public void appendToResponse(WOResponse aResponse, WOContext aContext) {
+ if (rootElement == null)
+ return;
+ pullValuesFromParent(aContext.component());
+ if ((condition && !negate) || (!condition && negate)) {
+ rootElement.appendToResponse(aResponse, aContext);
+ }
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java
new file mode 100644
index 0000000..4f774e6
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOContext.java
@@ -0,0 +1,572 @@
+/*
+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.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+* A pure java implementation of WOContext.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOContext
+{
+ private WOSession session;
+ private WORequest request;
+ private WOResponse response;
+ private List elementStack;
+ private boolean isInForm;
+ private boolean isDistributionEnabled;
+ private static final String EMPTY = "";
+ private static final String SEP = ".";
+ private static final String ZERO = "0";
+ private static final String HTTP = "http://";
+ private static final String HTTPS = "https://";
+
+ // package access
+ WOComponent page;
+ WOComponent component;
+ StringBuffer elementID;
+
+ private static volatile int contextCounter = 0;
+ private String contextID;
+
+ /**
+ * Default constructor performs necessary initialization.
+ * Subclassers should override the static factory method below.
+ */
+ public WOContext ()
+ {
+ contextID = null;
+ elementID = new StringBuffer();
+ elementStack = new LinkedList();
+ }
+
+ /**
+ * Preferred constructor performs necessary initialization.
+ * Subclassers should override this method.
+ */
+ public WOContext (WORequest aRequest)
+ {
+ this();
+ request = aRequest;
+ response = new WOResponse();
+ }
+
+ /**
+ * Simply calls the preferred constructor.
+ * Included for compatibility.
+ */
+ public static WOContext contextWithRequest (WORequest aRequest)
+ {
+ String id = Integer.toString( contextCounter++ );
+ WOContext result = new WOContext( aRequest );
+ result.contextID = id;
+ return result;
+ }
+
+ /**
+ * Returns the context id.
+ */
+ public String contextID ()
+ {
+ return contextID;
+ }
+
+ /**
+ * Returns the sender id, or null if it doesn't exist.
+ */
+ public String senderID ()
+ {
+ return request.senderID();
+ }
+
+ /**
+ * Returns the element id, or null if it doesn't exist.
+ */
+ public String elementID ()
+ {
+ return elementID.toString();
+ }
+
+ /**
+ * Returns this context's application, or null if it doesn't exist.
+ */
+ public WOApplication application ()
+ {
+ return request.application();
+ }
+
+ /**
+ * Returns whether a session has been created for the associated request.
+ */
+ public boolean hasSession ()
+ {
+ return ( session != null );
+ }
+
+ /**
+ * Returns this context's session, creating one if it doesn't exist.
+ */
+ public WOSession session ()
+ {
+ if ( session == null )
+ {
+ // so far we can't figure out how the direct action handler can avoid creating a session
+ throw new RuntimeException( "WOContext.session: Lazy instantiation not yet implemented." );
+/*
+ session = application().restoreSessionWithID(
+ request.sessionID(), this );
+ if ( session == null )
+ {
+ session = application().createSessionForRequest( request );
+ session.setContext( this );
+ }
+*/
+ }
+ return session;
+ }
+
+ /**
+ * Package access only.
+ */
+ void setSession( WOSession aSession )
+ {
+ session = aSession;
+ }
+
+ /**
+ * Returns this context's request.
+ */
+ public WORequest request ()
+ {
+ return request;
+ }
+
+ /**
+ * Returns this context's response.
+ */
+ public WOResponse response ()
+ {
+ return response;
+ }
+
+ /**
+ * Returns the current page.
+ */
+ public WOComponent page ()
+ {
+ return (WOComponent) elementStack.get( elementStack.size()-1 );
+ }
+
+ /**
+ * Returns the current component.
+ */
+ public WOComponent component ()
+ {
+ Object o;
+ Iterator i = elementStack.iterator();
+ while ( i.hasNext() )
+ {
+ o = i.next();
+ if ( o instanceof WOComponent ) return (WOComponent) o;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the current component's parent.
+ */
+ WOComponent parent ()
+ {
+ Object o;
+ Iterator i = elementStack.iterator();
+ if ( i.hasNext() )
+ {
+ // skip current component
+ o = i.next();
+ }
+ while ( i.hasNext() )
+ {
+ o = i.next();
+ if ( o instanceof WOComponent )
+ {
+ return (WOComponent) o;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Pushes an element onto the stack.
+ * Package access only.
+ */
+ void pushElement( WOElement aComponent )
+ {
+ elementStack.add( 0, aComponent );
+ }
+
+ /**
+ * Pops an element off the stack.
+ * Package access only.
+ */
+ WOElement popElement()
+ {
+ return (WOElement) elementStack.remove(0);
+ }
+
+ /**
+ * Returns whether the current context is in a form.
+ */
+ public boolean isInForm ()
+ {
+ return isInForm;
+ }
+
+ /**
+ * Sets whether the current context is in a WOForm.
+ */
+ public void setInForm (boolean inForm)
+ {
+ isInForm = inForm;
+ }
+
+ /**
+ * Appends the specified string to the end of the element id.
+ * For example, if the element id is "0.1", sending "Test"
+ * changes the element id to "0.1.Test".
+ */
+ public void appendElementIDComponent (String aString)
+ {
+ if ( elementID.length() > 0 )
+ {
+ elementID.append( SEP );
+ }
+ elementID.append( aString );
+ }
+
+ /**
+ * Appends a zero to the element id to represent the first
+ * new child component. For example, if the element id is "0.1",
+ * calling this changes the element id to "0.1.0".
+ */
+ public void appendZeroElementIDComponent ()
+ {
+ if ( elementID.length() > 0 )
+ {
+ elementID.append( SEP );
+ }
+ elementID.append( ZERO );
+ }
+
+ /**
+ * Increments the last component of the element id.
+ * For example, if the element id is "0.1", calling this
+ * changes the element id to "0.2".
+ */
+ public void incrementLastElementIDComponent ()
+ {
+ String last;
+ String id = elementID.toString();
+ int index = id.lastIndexOf( SEP );
+ if ( index == -1 )
+ {
+ last = id;
+ }
+ else
+ {
+ last = id.substring( index + 1 );
+ }
+
+ deleteLastElementIDComponent();
+
+ try
+ {
+ appendElementIDComponent(
+ Integer.toString( Integer.parseInt( last ) + 1 ) );
+ }
+ catch ( Exception exc )
+ {
+ System.err.println( "Error parsing id: " + last );
+ appendZeroElementIDComponent();
+ }
+ //System.out.println( "WOContext: " + elementID );
+ }
+
+ /**
+ * Deletes the last component of the element id.
+ * For example, if the element id is "0.1", callling this
+ * changes the element id to "0".
+ */
+ public void deleteLastElementIDComponent ()
+ {
+ int index = elementID.toString().lastIndexOf( SEP );
+ if ( index == -1 )
+ {
+ elementID.setLength( 0 );
+ }
+ else
+ {
+ elementID.setLength( index );
+ }
+ }
+
+ /**
+ * Deletes all components of the element id.
+ * This makes the element id an empty string.
+ */
+ public void deleteAllElementIDComponents ()
+ {
+ elementID.setLength( 0 );
+ }
+
+ /**
+ * Returns a URL for the named action with query parameters
+ * as specified by the dictionary.
+ */
+ public String directActionURLForActionNamed (
+ String anActionName, Map aQueryDict)
+ {
+ StringBuffer query = new StringBuffer();
+
+ try
+ {
+ String key;
+ Iterator i = aQueryDict.keySet().iterator();
+ while ( i.hasNext() )
+ {
+ key = i.next().toString();
+ query.append( URI.encode( key,
+ URI.allowed_within_query ) );
+ query.append( '=' );
+ query.append( URI.encode( aQueryDict.get( key ).toString(),
+ URI.allowed_within_query ) );
+ if ( i.hasNext() )
+ {
+ query.append( '&' );
+ }
+ }
+ }
+ catch ( IOException exc )
+ {
+ // report error
+ System.err.println(
+ "directActionURLForActionNamed: " + anActionName + " : " + aQueryDict );
+ System.err.println( exc );
+
+ // delete query string
+ query = new StringBuffer();
+ }
+
+ return urlWithRequestHandlerKey(
+ WOApplication.directActionRequestHandlerKey(),
+ anActionName, query.toString() );
+ }
+
+ /**
+ * Returns the complete URL for the current component action.
+ */
+ public String componentActionURL ()
+ {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append( request().applicationName() );
+ buffer.append( '/' );
+ buffer.append( WOApplication.application().componentRequestHandlerKey() );
+ buffer.append( '/' );
+ buffer.append( page().name() );
+ buffer.append( '/' );
+ buffer.append( contextID );
+ buffer.append( '/' );
+ buffer.append( elementID );
+ return buffer.toString();
+ }
+
+ /**
+ * Returns a URL relative to the servlet for the specified
+ * request handler, action, and query string.
+ */
+ public String urlWithRequestHandlerKey (
+ String aRequestHandlerKey, String aPath, String aQueryString)
+ {
+ StringBuffer buffer = new StringBuffer();
+ buffer.append( request().applicationName() );
+ buffer.append( '/' );
+ buffer.append( aRequestHandlerKey );
+ buffer.append( '/' );
+ buffer.append( aPath );
+ if ( aQueryString != null && aQueryString.trim().length() > 0 )
+ {
+ buffer.append( '?' );
+ buffer.append( aQueryString );
+ }
+ return buffer.toString();
+ }
+
+ /**
+ * Returns the complete URL for the specified request handler,
+ * path, and query string. isSecure determines the protocol:
+ * http or https. port is appended to the protocol, if zero,
+ * the port is omitted from the URL.
+ */
+ public String completeURLWithRequestHandlerKey (
+ String aRequestHandlerKey, String aPath, String aQueryString,
+ boolean isSecure, int port)
+ {
+ StringBuffer buffer = new StringBuffer();
+
+ if ( isSecure )
+ {
+ buffer.append( HTTPS );
+ }
+ else
+ {
+ buffer.append( HTTP );
+ }
+
+ buffer.append( request.applicationHost() );
+
+ if ( port != 0 && port != 80 )
+ {
+ buffer.append( ':' );
+ buffer.append( Integer.toString( port ) );
+ }
+
+ buffer.append( urlWithRequestHandlerKey(
+ aRequestHandlerKey, aPath, aQueryString ) );
+
+ return buffer.toString();
+ }
+
+ /**
+ * Sets whether distribution is enabled.
+ */
+ public void setDistributionEnabled (boolean distributionEnabled)
+ {
+ isDistributionEnabled = distributionEnabled;
+ }
+
+ /**
+ * Returns whether distribution is enabled.
+ */
+ public boolean isDistributionEnabled ()
+ {
+ return isDistributionEnabled;
+ }
+
+ /**
+ * This method is not included in the WOContext specification.
+ * This implementation returns "" since only cookie ids are
+ * currently supported.
+ */
+ String urlSessionPrefix ()
+ {
+ return EMPTY; // "/" + sessionid;
+ }
+
+ /**
+ * Returns the relative URL for the current page.
+ */
+ String url ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ public String toString()
+ {
+ return "[WOContext@"+Integer.toHexString(System.identityHashCode(this))
+ +":id=" + contextID +":page=" + page + ":component=" + component + ":element=" + elementID + "]";
+ }
+}
+
+/*
+ * $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.19 2003/08/07 00:15:15 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.18 2003/02/21 16:40:23 mpowers
+ * Now reading port and smtp host from system properties.
+ * Implemented WOApplication.main.
+ *
+ * Revision 1.17 2003/01/24 20:12:54 mpowers
+ * Better parent determination.
+ *
+ * Revision 1.16 2003/01/21 22:27:02 mpowers
+ * Corrected context id usage.
+ * Implemented backtracking.
+ *
+ * Revision 1.15 2003/01/17 20:58:19 mpowers
+ * Fixed up WOHyperlink.
+ *
+ * Revision 1.14 2003/01/17 20:34:57 mpowers
+ * Better handling for components and parents in the context's element stack.
+ *
+ * Revision 1.13 2003/01/14 19:48:36 mpowers
+ * - fixes to property synchronization
+ * - forms now pass takeValuesFromRequest to children
+ * - fixes to action invocation
+ * - now supporting multipleSubmit in forms
+ *
+ * Revision 1.12 2003/01/13 22:24:44 mpowers
+ * Request-response cycle is working with session and page persistence.
+ *
+ * Revision 1.11 2003/01/10 20:17:41 mpowers
+ * Component action urls are now working.
+ *
+ * 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:59 mpowers
+ * Implemented WOComponentRequestHandler:
+ * Bringing the request-response cycle more into conformance.
+ *
+ * Revision 1.4 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/12/17 14:57:42 mpowers
+ * Minor corrections to WORequests's parsing, and updated javadocs.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:53:04 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.3 2000/12/20 16:25:49 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java
new file mode 100644
index 0000000..78427f0
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOCookie.java
@@ -0,0 +1,203 @@
+/*
+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 net.wotonomy.foundation.NSDate;
+
+/**
+* A pure java implementation of WOCookie that extends
+* javax.servlet.httpd.Cookie for greater compatibility.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOCookie
+ extends javax.servlet.http.Cookie
+{
+ /**
+ * Default constructor.
+ */
+ public WOCookie ()
+ {
+ super( "", "" );
+ }
+
+ /**
+ * Constructs a cookie with the specified name and value.
+ */
+ public WOCookie( String aName, String aValue )
+ {
+ super( aName, aValue );
+ }
+
+ /**
+ * Constructs a cookie with the specified name and value.
+ * Also sets the path to the current application's path.
+ */
+ public static WOCookie cookieWithName (String aName, String aValue)
+ {
+ WOCookie result = new WOCookie( aName, aValue );
+ //TODO: Set the path to the current application's path.
+ return result;
+ }
+
+ /**
+ * Constructs a cookie with the specified attributes.
+ */
+ public static WOCookie cookieWithName (String aName, String aValue,
+ String aPath, String aDomain, NSDate expirationDate, boolean secure)
+ {
+ WOCookie result = new WOCookie( aName, aValue );
+ result.setPath( aPath );
+ result.setDomain( aDomain );
+ result.setExpires( expirationDate );
+ result.setSecure( secure );
+ return result;
+ }
+
+ /**
+ * Returns the name of the cookie.
+ */
+ public String name ()
+ {
+ return this.getName();
+ }
+
+ /**
+ * Sets the name of the cookie.
+ */
+ public void setName (String aString)
+ {
+ // super.setName( aString );
+ throw new RuntimeException( "Not yet implemented." );
+ }
+
+ /**
+ * Returns the value of the cookie.
+ */
+ public String value ()
+ {
+ return this.getValue();
+ }
+
+ /**
+ * Sets the value of the cookie.
+ */
+ public void setValue (String aString)
+ {
+ super.setValue( aString );
+ }
+
+ /**
+ * Gets the domain of the cookie.
+ */
+ public String domain ()
+ {
+ return this.getDomain();
+ }
+
+ /**
+ * Sets the domain of the cookie.
+ */
+ public void setDomain (String aString)
+ {
+ super.setDomain( aString );
+ }
+
+ /**
+ * Gets the path of the cookie.
+ */
+ public String path ()
+ {
+ return this.getPath();
+ }
+
+ /**
+ * Sets the path of the cookie.
+ */
+ public void setPath (String aString)
+ {
+ super.setPath( aString );
+ }
+
+ /**
+ * Gets the expiration date of the cookie.
+ * If in the past, the cookie will persist until browser shutdown.
+ */
+ public NSDate expires ()
+ {
+ return new NSDate( this.getMaxAge() );
+ }
+
+ /**
+ * Sets the expiration date of the cookie.
+ */
+ public void setExpires (NSDate aDate)
+ {
+ this.setMaxAge( (int) aDate.timeIntervalSinceNow() );
+ }
+
+ /**
+ * Returns whether the cookie will only be sent over a secure protocol.
+ */
+ public boolean isSecure ()
+ {
+ return this.getSecure();
+ }
+
+ /**
+ * Sets whether the cookie will only be sent over a secure protocol.
+ */
+ public void setIsSecure (boolean isSecure)
+ {
+ this.setSecure( isSecure );
+ }
+
+ /**
+ * Returns the string as it appears in the HTTP header of the response.
+ * This would normally be called by WOResponse, but is handled automatically
+ * by the servlet implementation.
+ */
+ public String headerString ()
+ {
+ new RuntimeException( "Not implemented yet." );
+ return null;
+ }
+}
+
+/*
+ * $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.1.1.1 2000/12/21 15:53:04 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:50 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java
new file mode 100644
index 0000000..09d0131
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectAction.java
@@ -0,0 +1,317 @@
+/*
+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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.servlet.http.HttpSession;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.Introspector;
+import net.wotonomy.foundation.internal.ValueConverter;
+
+/**
+* A pure java implementation of WODirectAction.
+* This class provides a number of services to subclasses,
+* including access to the query parameters, the request,
+* the session, and logging and debugging facilities.
+* A new instance of the class is created and then destroyed
+* for every request-response cycle.<br><br>
+*
+* Subclass this to implement direct actions for your
+* application. Subclasses would typically override the
+* constructor to initialize state based on the request,
+* and then provide additional methods that would be invoked
+* based on the value at the end of the URI. <br><br>
+*
+* Example: "http://www/MyApp.woa/MyActions/search" would call
+* the method named "searchAction" on the DirectAction subclass
+* named "MyActions". If the subclass name is omitted, a subclass
+* named "DirectAction" is assumed to exist within the application.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WODirectAction
+{
+ private WORequest request;
+ WOContext context;
+
+ /**
+ * Default constructor. This is called implicitly by
+ * subclasses in all cases. Package access only.
+ */
+ WODirectAction ()
+ {
+
+ }
+
+ /**
+ * Request constructor. This is the constructor used
+ * to create an action in response to a user request.
+ * Override to perform any necessary initialization in
+ * your subclass.
+ */
+ public WODirectAction (WORequest aRequest)
+ {
+ this();
+ request = aRequest;
+ }
+
+ /**
+ * Returns the response from the component named "Main".
+ */
+ public WOActionResults defaultAction()
+ {
+ return pageWithName("Main").generateResponse();
+ }
+
+ /**
+ * Returns the WORequest object for the current request.
+ */
+ public WORequest request ()
+ {
+ return request;
+ }
+
+ /**
+ * Returns the existing session, or null if no session exists.
+ */
+ public WOSession existingSession ()
+ {
+ //FIXME: this is incorrect
+ HttpSession session = request.servletRequest().getSession();
+ if ( session == null ) return null;
+ WOSession wosession = new WOSession();
+ wosession.setServletSession( session );
+ return wosession;
+ }
+
+ /**
+ * Returns the existing session if it exists. If no session
+ * exists, returns a newly created session. Note that if the
+ * user client does not support session ids/cookies, this will
+ * create a new session with each request.
+ */
+ public WOSession session ()
+ {
+ //FIXME: this is incorrect
+ WOSession wosession = new WOSession();
+ wosession.setServletSession(
+ request.servletRequest().getSession( true ) );
+ return wosession;
+ }
+
+ /**
+ * Returns the named WOComponent.
+ */
+ public WOComponent pageWithName (String aString)
+ {
+ return request.application().pageWithName( aString, context );
+ }
+
+ /**
+ * Appends "Action" to the specified string and tries to invoke
+ * method with that name and no arguments. Returns null if
+ * the method does not exist. If anAction is null, "defaultAction"
+ * will be assumed.
+ */
+ public WOActionResults performActionNamed (String anAction)
+ {
+ if ( anAction == null ) anAction = "default";
+ try
+ {
+ NSSelector sel = new NSSelector( anAction+"Action" );
+ return (WOActionResults) sel.invoke( this );
+ }
+ catch ( NoSuchMethodException exc )
+ {
+ // returns below
+ }
+ catch ( InvocationTargetException exc )
+ {
+ Throwable e = exc.getTargetException();
+ exc.printStackTrace();
+ throw new RuntimeException( e.toString() );
+ }
+ catch ( Exception exc )
+ {
+ exc.printStackTrace();
+ throw new RuntimeException( exc.toString() );
+ }
+ WOResponse error = new WOResponse();
+ error.setStatus( 404 ); // not found
+ error.appendContentString(
+ "Could not find method named \"" + anAction + "Action\"." );
+ return error;
+ }
+
+ /**
+ * Assigns the arrays of form values for the specified keys
+ * to properties on this object with matching names whose type
+ * is NSArray or is convertable from a Collection.
+ */
+ public void takeFormValueArraysForKeyArray (NSArray anArray)
+ {
+ if ( anArray == null ) return;
+
+ Method m;
+ Object key;
+ Object value;
+ java.util.Enumeration e = anArray.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ key = e.nextElement();
+ value = request.formValuesForKey( key.toString() );
+ try
+ {
+ // obtain setter method for this class
+ m = Introspector.getPropertyWriteMethod(
+ this.getClass(), key.toString(),
+ new Class[] { Introspector.WILD } );
+ if ( m != null )
+ {
+ // if value isn't null, try to convert it to type
+ if ( value != null )
+ {
+ Class[] paramTypes = m.getParameterTypes();
+ if ( ! paramTypes[0].isAssignableFrom(
+ value.getClass() ) )
+ {
+ //TODO: find a constructor whose parameter
+ // is assignable from value.getClass()
+ // and instantiate it in the place of value.
+ }
+ }
+ // set property to value
+ m.invoke( this, new Object[] { value } );
+ }
+ }
+ catch ( Exception exc )
+ {
+ // show error and continue
+ debugString( "WODirectAction.takeFormValuesForKeyArray: " + exc );
+ }
+ }
+
+ }
+
+ /**
+ * Assigns the form values for the specified keys to properties
+ * on this object with matching names.
+ */
+ public void takeFormValuesForKeyArray (NSArray anArray)
+ {
+ if ( anArray == null ) return;
+
+ Method m;
+ Object key;
+ Object value;
+ java.util.Enumeration e = anArray.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ key = e.nextElement();
+ value = request.formValueForKey( key.toString() );
+ try
+ {
+ // obtain setter method for this class
+ m = Introspector.getPropertyWriteMethod(
+ this.getClass(), key.toString(),
+ new Class[] { Introspector.WILD } );
+ if ( m != null )
+ {
+ // if value isn't null, try to convert it to type
+ if ( value != null )
+ {
+ Class[] paramTypes = m.getParameterTypes();
+ Object convertedValue =
+ ValueConverter.convertObjectToClass(
+ value, paramTypes[0] );
+ if ( convertedValue != null )
+ {
+ value = convertedValue;
+ }
+ }
+ // set property to value
+ m.invoke( this, new Object[] { value } );
+ }
+ }
+ catch ( Exception exc )
+ {
+ // show error and continue
+ debugString( "WODirectAction.takeFormValuesForKeyArray: " + exc );
+ }
+ }
+
+ }
+
+ /**
+ * Writes a message to the standard error stream.
+ */
+ public static void logString (String aString)
+ {
+ System.err.println( aString );
+ }
+
+ /**
+ * Writes a message to the standard error stream
+ * if debugging is activated.
+ */
+ public static void debugString (String aString)
+ {
+ // TODO: Check to see if debugging is enabled.
+ System.err.println( aString );
+ }
+}
+
+/*
+ * $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.5 2003/01/13 22:24:51 mpowers
+ * Request-response cycle is working with session and page persistence.
+ *
+ * Revision 1.4 2003/01/09 21:16:48 mpowers
+ * Bringing request-response cycle more into conformance.
+ *
+ * Revision 1.3 2003/01/07 15:58:11 mpowers
+ * Implementing the request-response cycle.
+ * WOSession refactored to support distribution.
+ *
+ * Revision 1.2 2002/12/17 14:57:43 mpowers
+ * Minor corrections to WORequests's parsing, and updated javadocs.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:53:18 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:50 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java
new file mode 100644
index 0000000..eac826b
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODirectActionRequestHandler.java
@@ -0,0 +1,221 @@
+/*
+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 net.wotonomy.foundation.NSArray;
+
+/**
+* An implementation of WORequestHandler that dispatches
+* DirectActions. <br><br>
+*
+* See WODirectAction for the rules for parsing a request.
+* In short, className defaults to "DirectAction", and the
+* action defaults to "default". The action class is expected
+* to have a constructor that takes a WORequest parameter.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WODirectActionRequestHandler
+ extends WORequestHandler
+{
+ public WOResponse handleRequest (WORequest aRequest)
+ {
+ WOResponse response = null;
+
+ // no concurrent access is allowed to a user's session:
+ // a user/browser/machine with multiple requests will have to wait.
+ // NOTE: this forces a session creation for any direct action (!)
+ synchronized ( aRequest.request.getSession() )
+ {
+ WOApplication application = aRequest.application();
+ WOContext context = WOContext.contextWithRequest( aRequest );
+
+ String className = "DirectAction";
+ String actionName = "default";
+ NSArray path = aRequest.requestHandlerPathArray();
+ if ( path.count() > 0 )
+ {
+ className = path.objectAtIndex( 0 ).toString();
+ if ( path.count() > 1 )
+ {
+ actionName = path.objectAtIndex( path.count() - 1 ).toString();
+ }
+ if ( path.count() > 2 )
+ {
+ for ( int i = 1; i < path.count()-1; i++ )
+ {
+ className = className + "." +
+ path.objectAtIndex( i ).toString();
+ }
+ }
+ }
+
+ //FIXME: sessions are supposed to be lazily created for direct actions
+
+ WOSession session = null;
+
+ String sessionID = aRequest.sessionID();
+ if ( sessionID != null )
+ {
+ session = application.restoreSessionWithID( sessionID, context );
+ }
+
+ if ( session == null )
+ {
+ session = application.createSessionForRequest( aRequest );
+ if ( session == null )
+ {
+ response = application.handleSessionCreationErrorInContext( context );
+ }
+ else
+ {
+ session.setContext( context );
+ }
+ }
+
+ context.setSession( session );
+
+ application.awake();
+ session.awake();
+
+ try
+ {
+
+ if ( response == null )
+ {
+ Class c = null;
+ c = application.getLocalClass( className );
+ if ( ( c == null ) && ( path.count() == 1 ) )
+ {
+ actionName = className;
+ className = "DirectAction";
+ c = application.getLocalClass( className );
+ }
+ if ( c == null )
+ {
+ throw new RuntimeException(
+ "Could not find class named \"" + className
+ + "\": " );
+ }
+ java.lang.reflect.Constructor ctor =
+ c.getConstructor( new Class[] { WORequest.class } );
+
+ WODirectAction action = (WODirectAction)
+ ctor.newInstance( new Object[] { aRequest } );
+ action.context = context; //HACK: how else can action call pageWithName?
+
+ WOActionResults result = action.performActionNamed( actionName );
+ if ( result instanceof WOComponent )
+ {
+ //HACK: I'm not sure where this should have gone: seems hackish here.
+ context.pushElement( (WOComponent) result );
+ ((WOComponent)result).ensureAwakeInContext( context );
+ //context.popElement();
+ }
+ response = result.generateResponse(); // calls session.savePage (?)
+ if ( result instanceof WOComponent )
+ {
+ //HACK: I'm not sure where this should have gone: seems hackish here.
+ ((WOComponent)result).sleep();
+ }
+ }
+ }
+ catch ( NoSuchMethodException exc )
+ {
+ WOResponse error = new WOResponse();
+ exc.printStackTrace();
+ error.setStatus( 404 ); // not found
+ error.appendContentString(
+ "Could not find request constructor for class named \""
+ + className + "\": " );
+ response = error;
+ }
+ catch ( Exception exc )
+ {
+ WOResponse error = new WOResponse();
+ error.setStatus( 500 ); // error
+ if ( exc.getMessage() != null )
+ {
+ error.appendContentString( exc.getMessage() );
+ exc.printStackTrace();
+ }
+ else
+ {
+ error.appendContentString( exc.toString() );
+ exc.printStackTrace();
+ }
+ response = error;
+ }
+
+ session.sleep();
+ session.setContext( null );
+ application.saveSessionForContext( context );
+ application.sleep();
+ }
+ return response;
+ }
+
+}
+
+/*
+ * $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.10 2003/03/28 17:26:18 mpowers
+ * Implemented package support: Applications can now live in packages.
+ * Better support for locating package local classes.
+ *
+ * Revision 1.9 2003/01/19 22:33:26 mpowers
+ * Fixed problems with classpath and dynamic class loading.
+ * Dynamic elements now pass on ensureAwakeInContext.
+ * Parser how handles <standalone/> tags.
+ *
+ * Revision 1.8 2003/01/17 20:34:57 mpowers
+ * Better handling for components and parents in the context's element stack.
+ *
+ * Revision 1.7 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.5 2003/01/13 22:25:00 mpowers
+ * Request-response cycle is working with session and page persistence.
+ *
+ * Revision 1.4 2003/01/09 21:16:49 mpowers
+ * Bringing request-response cycle more into conformance.
+ *
+ * Revision 1.2 2002/12/17 14:57:44 mpowers
+ * Minor corrections to WORequests's parsing, and updated javadocs.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:53:19 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:50 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java
new file mode 100644
index 0000000..bda1dd5
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODisplayGroup.java
@@ -0,0 +1,2455 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+
+import net.wotonomy.control.EODataSource;
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.EOKeyValueCoding;
+import net.wotonomy.control.EOKeyValueCodingSupport;
+import net.wotonomy.control.EOObjectStore;
+import net.wotonomy.control.EOObserverCenter;
+import net.wotonomy.control.EOObserving;
+import net.wotonomy.control.EOQualifier;
+import net.wotonomy.control.EOSortOrdering;
+import net.wotonomy.control.OrderedDataSource;
+import net.wotonomy.control.PropertyDataSource;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.NSNotification;
+import net.wotonomy.foundation.NSNotificationCenter;
+import net.wotonomy.foundation.NSRange;
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.Duplicator;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* WODisplayGroup manages a set of objects,
+* allowing them to be sorted, batched, and filtered.
+* WODisplay also acts as a bridge to the wotonomy's
+* control package, including WODisplayGroup and
+* EOEditingContext.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WODisplayGroup extends Observable
+ implements EOObserving, EOEditingContext.Editor,
+ java.io.Serializable
+{
+ /**
+ * Notification sent when the display group is about to fetch.
+ */
+ public static final String DisplayGroupWillFetchNotification
+ = "DisplayGroupWillFetchNotification";
+
+ private static boolean
+ globalDefaultForValidatesChangesImmediately = true;
+ private static String
+ globalDefaultStringMatchFormat = "caseInsensitiveLike";
+ private static String
+ globalDefaultStringMatchOperator = "%@*";
+
+ protected NSMutableArray allObjects;
+ protected NSArray allObjectsProxy;
+ protected NSMutableArray displayedObjects;
+ protected NSArray displayedObjectsProxy;
+ protected NSMutableArray selectedObjects;
+ protected NSArray selectedObjectsProxy;
+ protected NSMutableArray selectedIndexes;
+
+ private String defaultStringMatchOperator;
+ private String defaultStringMatchFormat;
+
+ private boolean validatesChangesImmediately;
+ private Object delegate;
+ private EODataSource dataSource;
+ private EOQualifier qualifier;
+ private NSMutableArray sortOrderings;
+ private NSArray sortOrderingsProxy;
+
+ private NSArray localKeys;
+ private NSDictionary insertedObjectDefaultValues;
+ private boolean fetchesOnLoad;
+ private boolean selectsFirstObjectAfterFetch;
+ private boolean usesOptimisticRefresh;
+ private boolean inQueryMode;
+
+ // change detection: package access for helper classes
+ boolean selectionChanged;
+ int updatedObjectIndex;
+
+ // batching
+ private int batchIndex;
+ private int batchSize;
+
+ // this property is not in the spec
+ private boolean compareByReference = false;
+
+ private EOObserving lastGroupObserver;
+
+ /**
+ * Creates a new display group.
+ */
+ public WODisplayGroup()
+ {
+ validatesChangesImmediately =
+ globalDefaultForValidatesChangesImmediately();
+ defaultStringMatchOperator =
+ globalDefaultStringMatchFormat();
+ defaultStringMatchFormat =
+ globalDefaultStringMatchOperator();
+
+ allObjects = new ObservableArray( this );
+ allObjectsProxy = NSArray.arrayBackedByList( allObjects );
+ displayedObjects = new NSMutableArray();
+ displayedObjectsProxy = NSArray.arrayBackedByList( displayedObjects );
+ selectedObjects = new NSMutableArray();
+ selectedObjectsProxy = NSArray.arrayBackedByList( selectedObjects );
+ sortOrderings = new NSMutableArray();
+ sortOrderingsProxy = NSArray.arrayBackedByList( sortOrderings );
+ selectedIndexes = new NSMutableArray();
+
+ delegate = null;
+ dataSource = null;
+ qualifier = null;
+
+ localKeys = new NSArray(); // not implemented
+ insertedObjectDefaultValues = new NSDictionary();
+ fetchesOnLoad = false; // not implemented
+ selectsFirstObjectAfterFetch = false;
+ usesOptimisticRefresh = false;
+ inQueryMode = false; // not implemented
+
+ selectionChanged = false;
+ updatedObjectIndex = -1;
+
+ batchIndex = 0;
+ batchSize = 0;
+ }
+
+
+
+ // specify optional data source
+
+ /**
+ * Sets the data source that will be used by
+ * this display group.
+ */
+ public void setDataSource ( EODataSource aDataSource )
+ {
+ if ( ( dataSource != null )
+ && ( dataSource.editingContext() != null ) )
+ {
+ // un-register for notifications from existing parent store
+ NSNotificationCenter.defaultCenter().removeObserver(
+ this, null, dataSource.editingContext() );
+ dataSource.editingContext().removeEditor( this );
+ if ( dataSource.editingContext().messageHandler() == this )
+ {
+ dataSource.editingContext().setMessageHandler( null );
+ }
+
+ }
+
+ dataSource = aDataSource;
+
+ if ( ( dataSource != null )
+ && ( dataSource.editingContext() != null ) )
+ {
+ // register for notifications from parent store
+ NSNotificationCenter.defaultCenter().addObserver(
+ this, new NSSelector( "objectsInvalidatedInEditingContext",
+ new Class[] { NSNotification.class } ),
+ null, dataSource.editingContext() );
+
+ // add ourselves as editor
+ dataSource.editingContext().addEditor( this );
+
+ // add ourselves as message handler if no such handler exists
+ if ( dataSource.editingContext().messageHandler() == null )
+ {
+ dataSource.editingContext().setMessageHandler( this );
+ }
+ }
+ }
+
+ /**
+ * Returns the current data source backing this display group,
+ * or null if no dataSource is currently used.
+ */
+ public EODataSource dataSource()
+ {
+ return dataSource;
+ }
+
+ /**
+ * Returns the key by which this display group is bound a master
+ * display group, or null if this is not a detail display group.
+ */
+ public String detailKey()
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ return ((PropertyDataSource)dataSource).key();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the key by which this display group is bound a master
+ * display group. Does nothing if this is not a detail display group.
+ */
+ public void setDetailKey( String aKey )
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ ((PropertyDataSource)dataSource).setKey( aKey );
+ }
+ }
+
+ /**
+ * Returns whether the data source is a detail data source,
+ * suggesting that this is a detail display group.
+ */
+ public boolean hasDetailDataSource()
+ {
+ return ( dataSource instanceof PropertyDataSource );
+ }
+
+ /**
+ * Returns the selected object in the master display group,
+ * or null if this is not a detail display group.
+ */
+ public Object masterObject()
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ return ((PropertyDataSource)dataSource).source();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the master object in the detail data source.
+ * Does nothing if there is no detail data source.
+ */
+ public void setMasterObject( Object anObject )
+ {
+ if ( dataSource instanceof PropertyDataSource )
+ {
+ ((PropertyDataSource)dataSource).setSource( anObject );
+ }
+ }
+
+ // specify optional delegate
+
+ /**
+ * Sets the display group delegate that
+ * will be used by this display group.
+ */
+ public void setDelegate ( Object aDelegate )
+ {
+ delegate = aDelegate;
+ }
+
+ /**
+ * Returns the current delegate for this display group,
+ * or null if no delegate is currently set.
+ */
+ public Object delegate()
+ {
+ return delegate;
+ }
+
+
+
+ // display group configuration
+
+ /**
+ * Returns the current string matching format.
+ * If not set, defaults to "%@*".
+ */
+ public String defaultStringMatchFormat()
+ {
+ return defaultStringMatchFormat;
+ }
+
+ /**
+ * Returns the current string matching operator.
+ * If not set, defaults to "caseInsensitiveLike".
+ */
+ public String defaultStringMatchOperator()
+ {
+ return defaultStringMatchOperator;
+ }
+
+ /**
+ * Sets the display group and associations to edit a
+ * "query by example" query object. This method is
+ * used for target/action connections.
+ */
+ public void enterQueryMode ( Object aSender )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether this display group should immediate
+ * fetch when loaded.
+ */
+ public boolean fetchesOnLoad()
+ {
+ return fetchesOnLoad;
+ }
+
+ /**
+ * Returns whether this display group is in "query by
+ * example" mode.
+ */
+ public boolean inQueryMode()
+ {
+ return inQueryMode;
+ }
+
+ /**
+ * Returns a Map of default values that are applied
+ * to new objects that are inserted into the list.
+ */
+ public NSDictionary insertedObjectDefaultValues()
+ {
+ return insertedObjectDefaultValues;
+ }
+
+ /**
+ * Returns the keys that were declared when read from
+ * an external resource file.
+ */
+ public NSArray localKeys()
+ {
+ return localKeys;
+ }
+
+ /**
+ * Sets whether this display group will select the
+ * first object in the list after a fetch.
+ */
+ public boolean selectsFirstObjectAfterFetch()
+ {
+ return selectsFirstObjectAfterFetch;
+ }
+
+ /**
+ * Sets the default string matching format that
+ * will be used by this display group.
+ */
+ public void setDefaultStringMatchFormat ( String aFormat )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the default string matching operator that
+ * will be used by this display group.
+ */
+ public void setDefaultStringMatchOperator ( String anOperator )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets whether this display group will fetch objects
+ * from its data source on load.
+ */
+ public void setFetchesOnLoad ( boolean willFetch )
+ {
+ fetchesOnLoad = willFetch;
+ }
+
+ /**
+ * Sets whether this display group is in "query by example"
+ * mode. If true, all associations will bind to a special
+ * "example" object.
+ */
+ public void setInQueryMode ( boolean isInQueryMode )
+ {
+ inQueryMode = isInQueryMode;
+ }
+
+ /**
+ * Sets the mapping that contains the values that will
+ * be applied to new objects inserted into the display group.
+ */
+ public void setInsertedObjectDefaultValues ( Map aMap )
+ {
+ insertedObjectDefaultValues = new NSDictionary( aMap );
+ }
+
+ /**
+ * Sets the keys that are declared when instantiated from
+ * an external resource file.
+ */
+ public void setLocalKeys ( List aKeyList )
+ {
+ localKeys = new NSArray( (Collection) aKeyList );
+ }
+
+ /**
+ * Sets whether the first object in the list will be
+ * selected after a fetch.
+ */
+ public void setSelectsFirstObjectAfterFetch (
+ boolean selectsFirst )
+ {
+ selectsFirstObjectAfterFetch = selectsFirst;
+ }
+
+ /**
+ * Sets the order of the keys by which this display group
+ * will be ordered after a fetch or after a call to
+ * updateDisplayedObjects(). The elements in the display
+ * group will be sorted first by the first key, within
+ * the first key, by the second key, and so on.
+ */
+ public void setSortOrderings ( List aList )
+ {
+ sortOrderings.removeAllObjects();
+
+ Object o;
+ Iterator it = aList.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ // handle the convenience of specifying just a key
+ if ( ! ( o instanceof EOSortOrdering ) )
+ {
+ o = new EOSortOrdering(
+ o.toString(), EOSortOrdering.CompareAscending );
+ }
+ sortOrderings.add( o );
+ }
+ }
+
+ /**
+ * Sets whether only changed objects are refreshed (optimistic),
+ * or whether all objects are refreshed (pessimistic, default).
+ * By default, when the display group receives notification that
+ * one of its objects has changed, updateDisplayedObjects is called.
+ */
+ public void setUsesOptimisticRefresh ( boolean isOptimistic )
+ {
+ usesOptimisticRefresh = isOptimistic;
+ }
+
+ /**
+ * Sets whether changes made by associations are validated
+ * immediately, or when changes are saved.
+ */
+ public void setValidatesChangesImmediately (
+ boolean validatesImmediately )
+ {
+ validatesChangesImmediately = validatesImmediately;
+ }
+
+ /**
+ * Returns a read-only List of sort orderings for this display group.
+ */
+ public NSArray sortOrderings()
+ {
+ return sortOrderingsProxy;
+ }
+
+ /**
+ * Returns whether this display group refreshes only
+ * the changed objects or all objects on refresh.
+ */
+ public boolean usesOptimisticRefresh()
+ {
+ return usesOptimisticRefresh;
+ }
+
+ /**
+ * Returns whether this display group validates changes
+ * immediately. Otherwise, validation should occur when
+ * changes are saved. Default is the global default,
+ * which is initially true.
+ */
+ public boolean validatesChangesImmediately()
+ {
+ return validatesChangesImmediately;
+ }
+
+
+ // qualification
+
+ /**
+ * Returns a qualifier that will be applied all the objects
+ * in this display group to determine which objects will
+ * be displayed.
+ */
+ public EOQualifier qualifier()
+ {
+ return qualifier;
+ }
+
+ /**
+ * Returns a new qualifier built from the three query
+ * value maps: greater than, equal to, and less than.
+ */
+ public EOQualifier qualifierFromQueryValues()
+ {
+ //TODO: assemble qualifier from query values
+
+ return new EOQualifier()
+ {
+ // use inner class until we actually implement one
+ public EOQualifier qualifierWithBindings(
+ Map aMap,
+ boolean requireAll )
+ {
+ return null;
+ }
+ public Throwable
+ validateKeysWithRootClassDescription( Class aClass )
+ {
+ return null;
+ }
+ public boolean evaluateWithObject(Object o)
+ {
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Calls qualifierFromQueryValues(), applies the result
+ * to the data source, and calls fetch().
+ */
+ public void qualifyDataSource()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Calls qualifierFromQueryValues(), sets the qualifier
+ * with setQualifier(), and calls updateDisplayedObjects().
+ */
+ public void qualifyDisplayGroup()
+ {
+ setQualifier( qualifierFromQueryValues() );
+ updateDisplayedObjects();
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values.
+ */
+ public NSDictionary queryBindingValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values.
+ */
+ public NSMutableDictionary queryBindings()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values for a matching query.
+ */
+ public NSMutableDictionary queryMatch()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values for a minimum value query.
+ */
+ public NSMutableDictionary queryMin()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to binding query values for a maximum value query.
+ */
+ public NSMutableDictionary queryMax()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to operator values.
+ */
+ public NSMutableDictionary queryOperator()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a list containing all supported qualifier operators.
+ */
+ public NSArray allQualifierOperators()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to operator values.
+ */
+ public NSDictionary queryOperatorValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the qualifier that will be used by
+ * updateDisplayedObjects() to filter displayed objects.
+ */
+ public void setQualifier ( EOQualifier aQualifier )
+ {
+ qualifier = aQualifier;
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to binding values.
+ */
+ public void setQueryBindingValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to operator values.
+ */
+ public void setQueryOperatorValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+
+
+ // qualifier query values
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for equality.
+ */
+ public NSDictionary equalToQueryValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for greater value.
+ */
+ public NSDictionary greaterThanQueryValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns a Map containing the mappings of keys
+ * to query values that will be used to test for lesser value.
+ */
+ public NSDictionary lessThanQueryValues()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the Map that contains the mappings of keys
+ * to query values that will be used to test for equality.
+ */
+ public void setEqualToQueryValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to query values that will be used to test for greater value.
+ */
+ public void setGreaterThanQueryValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets the mapping that contains the mappings of keys
+ * to query values that will be used to test for lesser value.
+ */
+ public void setLessThanQueryValues ( Map aMap )
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Deprecated: returns true.
+ */
+ public boolean endEditing()
+ {
+ return true;
+ }
+
+
+ // object management
+
+ /**
+ * Returns a read-only List containing all objects managed by the display group.
+ * This includes those objects not visible due to disqualification.
+ */
+ public NSArray allObjects()
+ { //System.out.println( "avoided allocation: allObjects" );
+ return allObjectsProxy;
+ }
+
+ /**
+ * Returns the total number of batches held by this display group.
+ */
+ public int batchCount()
+ {
+ if ( batchSize < 1 ) return 1;
+ int count = displayedObjects.count();
+ if ( count == 0 ) return 1;
+ return ((count-1) / batchSize) + 1;
+ }
+
+ /**
+ * Returns the index of the currently displayed batch.
+ */
+ public int currentBatchIndex()
+ {
+ return batchIndex;
+ }
+
+ /**
+ * Sets the index of the currently displayed batch.
+ */
+ public void setCurrentBatchIndex( int aBatchIndex )
+ {
+ batchIndex = aBatchIndex;
+ updateDisplayedObjects();
+ }
+
+ /**
+ * Sets the displayed objects to the batch containing
+ * the first selected object, and updates the current
+ * batch index, and returns null to force a page reload.
+ * Displays the first batch is there is no selection.
+ */
+ public Object displayBatchContainingSelectedObject()
+ {
+ if ( batchSize < 1 ) return null;
+ NSArray indexes = selectionIndexes();
+ if ( indexes.count() > 0 )
+ {
+ batchIndex =
+ ((Number)indexes.objectAtIndex( 0 )).intValue() / batchSize;
+ }
+ else
+ {
+ batchIndex = 0;
+ }
+ updateDisplayedObjects();
+ return null;
+ }
+
+ /**
+ * Sets the displayed objects to the next batch
+ * and updates the current batch index, and returns null
+ * to force a page reload. If there is no next batch
+ * the first batch is displayed.
+ */
+ public Object displayNextBatch()
+ {
+ batchIndex = (batchIndex + 1) % batchCount();
+ updateDisplayedObjects();
+ return null;
+ }
+
+ /**
+ * Sets the displayed objects to the next batch
+ * and updates the current batch index, and returns null
+ * to force a page reload. If there is no previous
+ * batch, the last batch is displayed.
+ */
+ public Object displayPreviousBatch()
+ {
+ batchIndex--;
+ if ( batchIndex < 0 ) batchIndex = batchCount() - 1;
+ updateDisplayedObjects();
+ return null;
+ }
+
+ /**
+ * Returns whether the displayed objects have been batched.
+ */
+ public boolean hasMultipleBatches()
+ {
+ return batchCount() > 1;
+ }
+
+ /**
+ * Returns the one-based index within the displayed objects list
+ * of the first displayed object in the current batch.
+ */
+ public int indexOfFirstDisplayedObject()
+ {
+ if ( batchSize < 1 ) return 1;
+ return batchIndex * batchSize + 1;
+ }
+
+ /**
+ * Returns the one-based index within the displayed objects list
+ * of the first last object in the current batch.
+ */
+ public int indexOfLastDisplayedObject()
+ {
+ if ( batchSize < 1 ) return displayedObjects.count();
+ return Math.min(
+ ((batchIndex+1) * batchSize),
+ displayedObjects.count() );
+ }
+
+ /**
+ * Returns the number of objects per batch.
+ */
+ public int numberOfObjectsPerBatch()
+ {
+ return batchSize;
+ }
+
+ /**
+ * Returns the number of objects per batch.
+ */
+ public void setNumberOfObjectsPerBatch( int aSize )
+ {
+ batchSize = aSize;
+ updateDisplayedObjects();
+ }
+
+ /**
+ * Clears the current selection.
+ * @return True is the selection was cleared,
+ * False if the selection could not be cleared
+ * @see #setSelectionIndexes
+ */
+ public boolean clearSelection()
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldChangeSelection",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, new NSArray( selectedObjects ) } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return false;
+ }
+
+ selectionChanged = true;
+ willChange();
+
+ selectedObjects.removeAllObjects();
+ selectedIndexes.removeAllObjects();
+
+ notifyDelegate(
+ "displayGroupDidChangeSelection",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ notifyDelegate(
+ "displayGroupDidChangeSelectedObjects",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+
+ return true;
+ }
+
+ /**
+ * Convenience for binding to a component action:
+ * calls deleteSelection() and then displayBatchContainingSelectedObject()
+ * and returns null, which is a suitable result from a component action.
+ */
+ public Object delete()
+ {
+ deleteSelection();
+ displayBatchContainingSelectedObject();
+ return null;
+ }
+
+ /**
+ * Deletes the object at the specified index,
+ * notifying the delegate before and after the operation,
+ * and then updating the selection if needed.
+ * @return True if delete was successful, false if the
+ * object was not deleted.
+ */
+ public boolean deleteObjectAtIndex ( int anIndex )
+ {
+ Object target = displayedObjects.objectAtIndex( anIndex );
+
+ Object result = notifyDelegate(
+ "displayGroupShouldDeleteObject",
+ new Class[] { WODisplayGroup.class, Object.class },
+ new Object[] { this, target } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return false;
+ }
+
+ deleteObjectAtIndexNoNotify( anIndex );
+
+ if ( dataSource != null )
+ {
+ dataSource.deleteObject( target );
+ }
+
+ notifyDelegate(
+ "displayGroupDidDeleteObject",
+ new Class[] { WODisplayGroup.class, Object.class },
+ new Object[] { this, target } );
+
+ return true;
+ }
+
+ private void deleteObjectAtIndexNoNotify ( int anIndex )
+ {
+ Object target = displayedObjects.objectAtIndex( anIndex );
+
+ int i;
+
+ // remove from selected objects if necessary
+ i = indexOf( selectedObjects, target );
+ if ( i != NSArray.NotFound )
+ {
+ selectionChanged = true;
+ willChange(); // notify before removing
+ selectedObjects.removeObjectAtIndex( i );
+ selectedIndexes.remove( new Integer( i ) ); // comps by value
+ }
+ else // notify - no selection change needed
+ {
+ willChange();
+ }
+
+ // remove from all objects
+ i = indexOf( allObjects, target );
+ if ( i != NSArray.NotFound )
+ {
+ allObjects.removeObjectAtIndex( i );
+ }
+ else // otherwise should never happen
+ {
+// throw new WotonomyException(
+// "Displayed object not found in allObjects" );
+ }
+
+ // remove from displayed objects
+ displayedObjects.removeObjectAtIndex( anIndex );
+ }
+
+ /**
+ * Deletes the currently selected objects.
+ * This implementation calls deleteObjectAtIndex() for
+ * each index in the selection list, immediately returning
+ * false if any delete operation fails.
+ * @return True if all selected objects were deleted,
+ * false if any deletion failed.
+ */
+ public boolean deleteSelection()
+ {
+ int i;
+ boolean result = true;
+
+ Enumeration e = new NSArray( selectedObjects ).objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ i = indexOf( displayedObjects, e.nextElement() );
+ if ( i == NSArray.NotFound )
+ {
+ // should never happen
+ throw new WotonomyException(
+ "Selected object not found in displayedObjects" );
+ }
+ result = result && deleteObjectAtIndex( i );
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns a read-only List of all objects in the display group
+ * that are currently displayed by the associations.
+ */
+ public NSArray displayedObjects()
+ { // System.out.println( "avoided allocation: displayedObjects" );
+ if ( batchSize < 1 ) return displayedObjectsProxy;
+ return displayedObjectsProxy.subarrayWithRange(
+ new NSRange( batchIndex * batchSize, batchSize ) );
+ }
+
+ /**
+ * Requests a list of objects from the DataSource
+ * and calls setObjectArray to populate the list.
+ * More specifically, calls endEditing(), asks the
+ * delegate, fetches the objects, notifies the delegate,
+ * and populates the list. Returns null to force a
+ * page reload.
+ */
+ public Object fetch()
+ {
+ endEditing();
+
+ if ( dataSource == null )
+ {
+ return null;
+ }
+
+ Object result = notifyDelegate(
+ "displayGroupShouldFetch",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return null;
+ }
+
+ NSNotificationCenter.defaultCenter().postNotification(
+ DisplayGroupWillFetchNotification, this, new NSDictionary() );
+
+ NSArray objectList = dataSource.fetchObjects();
+
+ notifyDelegate(
+ "displayGroupDidFetchObjects",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, objectList } );
+
+ setObjectArray( objectList );
+
+ if ( ( selectsFirstObjectAfterFetch ) && ( displayedObjects.size() > 0 ) )
+ {
+ setSelectionIndexes( new NSArray( new Integer( 0 ) ) );
+ }
+
+ return null;
+ }
+
+ /**
+ * Convenience to call insertNewObjectAtIndex with the current selection plus one,
+ * or at the end of the list if there is no selection.
+ * Returns null to force a page reload.
+ */
+ public Object insert()
+ {
+ NSArray indexes = selectionIndexes();
+ int size = indexes.count();
+ if ( size == 0 )
+ {
+ insertNewObjectAtIndex( displayedObjects.count() );
+ }
+ else
+ {
+ insertNewObjectAtIndex(
+ ((Number)selectedIndexes.objectAtIndex( size-1 )).intValue()+1 );
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new object at the specified index.
+ * Calls insertObjectAtIndex() with the result
+ * from sending createObject() to the data source.
+ * @return the newly created object.
+ */
+ public Object insertNewObjectAtIndex ( int anIndex )
+ {
+ Object result = null;
+ if ( dataSource != null )
+ {
+ result = dataSource.createObject();
+ }
+ if ( result != null )
+ {
+ if ( insertedObjectDefaultValues != null )
+ {
+ Duplicator.writePropertiesForObject(
+ insertedObjectDefaultValues, result );
+ }
+ insertObjectAtIndex( result, anIndex );
+ }
+ else // create failed
+ {
+ if ( delegate() != null )
+ {
+ NSSelector selector = new NSSelector(
+ "displayGroupCreateObjectFailed",
+ new Class[] { WODisplayGroup.class, EODataSource.class } );
+ if ( selector.implementedByObject( delegate() ) )
+ {
+ try
+ {
+ selector.invoke( delegate(), new Object[] { this, dataSource } );
+ return result;
+ }
+ catch ( Exception exc )
+ {
+ System.err.println( "Error notifying delegate: displayGroupCreateObjectFailed" );
+ exc.printStackTrace();
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Inserts the specified object into the list at
+ * the specified index.
+ */
+ public void insertObjectAtIndex ( Object anObject, int anIndex )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldInsertObject",
+ new Class[] { WODisplayGroup.class, Object.class, int.class },
+ new Object[] { this, anObject, new Integer(anIndex) } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ return;
+ }
+
+ updatedObjectIndex = anIndex;
+ willChange();
+
+ int i;
+
+ // add to all objects
+ if ( anIndex == displayedObjects.size() )
+ {
+ allObjects.addObject( anObject );
+ }
+ else // insert before same object
+ {
+ Object target = displayedObjects.objectAtIndex( anIndex );
+ int targetIndex = indexOf( allObjects, target );
+ if ( targetIndex != NSArray.NotFound )
+ {
+ allObjects.insertObjectAtIndex( anObject, targetIndex );
+ }
+ else // should never happen
+ {
+ throw new WotonomyException(
+ "Could not find displayed object in all objects list: "
+ + target );
+ }
+ }
+
+ // add to displayed objects
+ displayedObjects.insertObjectAtIndex( anObject, anIndex );
+
+ if ( dataSource != null )
+ {
+ if ( dataSource instanceof OrderedDataSource )
+ {
+ ((OrderedDataSource)dataSource).insertObjectAtIndex(
+ anObject, anIndex );
+ }
+ else
+ {
+ dataSource.insertObject( anObject );
+ }
+ }
+
+ notifyDelegate(
+ "displayGroupDidInsertObject",
+ new Class[] { WODisplayGroup.class, Object.class },
+ new Object[] { this, anObject } );
+ }
+
+ /**
+ * Sets contentsChanged to true and notifies all observers.
+ */
+ public void redisplay()
+ {
+ willChange();
+ }
+
+ /**
+ * Sets the selection to the next displayed object after the current
+ * selection. If the last object is selected, or if no object
+ * is selected, then the first object becomes selected.
+ * If multiple items are selected, the first selected item is
+ * considered the selected item for the purposes of this method.
+ * Does not call redisplay().
+ * @return null to force a page reload.
+ */
+ public Object selectNext()
+ {
+ int count = displayedObjects.count();
+ if ( count == 0 ) return null;
+ if ( count == 1 )
+ {
+ selectObject( displayedObjects.objectAtIndex( 0 ) );
+ return null;
+ }
+
+ int i = -1;
+ Object selectedObject = selectedObject();
+ if ( selectedObject != null )
+ {
+ i = indexOf( displayedObjects, selectedObject );
+ }
+ if ( i == NSArray.NotFound ) i = -1;
+
+ // select next object
+ i++;
+ if ( i != displayedObjects.count() )
+ {
+ // set to next object
+ selectedObject = displayedObjects.objectAtIndex( i );
+ }
+ else // out of range
+ {
+ // set to null
+ selectedObject = displayedObjects.objectAtIndex( 0 );
+ }
+
+ selectObject( selectedObject );
+ return null;
+ }
+
+ /**
+ * Sets the selection to the specified object.
+ * If the specified object is null or does not exist
+ * in the list of displayed objects, the selection
+ * will be cleared.
+ * @return true if the object was selected.
+ */
+ public boolean selectObject ( Object anObject )
+ {
+ if ( ( anObject == null ) ||
+ ( indexOf( displayedObjects, anObject )
+ == NSArray.NotFound ) )
+ {
+ clearSelection();
+ return false;
+ }
+
+ selectObjectsIdenticalTo( new NSArray( new Object[] { anObject } ) );
+ return true;
+ }
+
+ /**
+ * Sets the selection to the specified objects.
+ * If the specified list is null or if none of the objects
+ * in the list exist in the list of displayed objects, the
+ * selection will be cleared.
+ * @return true if all specified objects were selected.
+ */
+ public boolean selectObjectsIdenticalTo ( List anObjectList )
+ {
+ // optimization: check for resetting of selection
+ if ( ( anObjectList != null ) && ( selectedObjects.size() == anObjectList.size() ) )
+ {
+ boolean identical = true;
+ int size = selectedObjects.size();
+ for ( int i = 0; ( i < size ) && identical; i++ )
+ {
+ // compare by reference
+ if ( anObjectList.get( i ) != selectedObjects.get( i ) )
+ {
+ identical = false;
+ }
+ else if ( displayedObjects.indexOfIdenticalObject(
+ anObjectList.get( i ) ) == NSArray.NotFound )
+ {
+ identical = false;
+ }
+ }
+ if ( identical )
+ {
+ return true;
+ }
+ }
+
+ Object result = notifyDelegate(
+ "displayGroupShouldChangeSelection",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, anObjectList } );
+ if ( ( result != null ) && ( Boolean.FALSE.equals( result ) ) )
+ {
+ // need to notify the calling component
+ // to revert back to the previous selection
+ selectionChanged = true;
+ willChange();
+ return false;
+ }
+
+ int i;
+ selectionChanged = true;
+ willChange();
+ Object o;
+ selectedObjects.removeAllObjects();
+ selectedIndexes.removeAllObjects();
+ Iterator it = anObjectList.iterator();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
+ != NSArray.NotFound )
+ {
+ selectedObjects.addObject( o );
+ selectedIndexes.addObject( new Integer( i ) );
+ }
+ }
+
+ notifyDelegate(
+ "displayGroupDidChangeSelection",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ notifyDelegate(
+ "displayGroupDidChangeSelectedObjects",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+
+ return true;
+ }
+
+ /**
+ * Calls selectObjectsIdenticalTo and if false is returned
+ * and selectFirstIfNoMatch is true, selects the first object.
+ */
+ public boolean selectObjectsIdenticalToSelectFirstOnNoMatch(
+ List anObjectList, boolean selectFirstIfNoMatch )
+ {
+ if ( selectObjectsIdenticalTo( anObjectList ) )
+ {
+ return true;
+ }
+ if ( selectFirstIfNoMatch )
+ {
+ clearSelection();
+ selectNext();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Sets the selection to the previous displayed object before the current
+ * selection. If the first object is selected, or if no object
+ * is selected, then the last object becomes selected.
+ * If multiple items are selected, the first selected item is
+ * considered the selected item for the purposes of this method.
+ * Does not call redisplay().
+ * @return null to force a page reload.
+ */
+ public Object selectPrevious()
+ {
+ int i = displayedObjects.count();
+ if ( i == 0 ) return null;
+ if ( i == 1 )
+ {
+ selectObject( displayedObjects.objectAtIndex( 0 ) );
+ return null;
+ }
+
+ Object selectedObject = selectedObject();
+ if ( selectedObject != null )
+ {
+ i = indexOf( displayedObjects, selectedObject );
+ }
+ if ( i == NSArray.NotFound ) i = displayedObjects.count();
+
+ // select next object
+ i--;
+ if ( i < 0 )
+ {
+ // out of range - select last object
+ i = displayedObjects.count() - 1;
+ }
+
+ selectObject( displayedObjects.objectAtIndex( i ) );
+ return null;
+ }
+
+ /**
+ * Returns the currently selected object, or null if
+ * there is no selection.
+ */
+ public Object selectedObject()
+ {
+ if ( selectedObjects.count() == 0 )
+ {
+ return null;
+ }
+ return selectedObjects.objectAtIndex( 0 );
+ }
+
+ /**
+ * Returns a read-only List containing all selected objects, if any.
+ * Returns an empty list if no objects are selected.
+ */
+ public NSArray selectedObjects()
+ { // System.out.println( "avoided allocation: selectedObjects" );
+ return selectedObjectsProxy;
+ }
+
+ /**
+ * Returns a read-only List containing the indexes of all selected
+ * objects, if any. The list contains instances of
+ * java.lang.Number; call intValue() to retrieve the index.
+ */
+ public NSArray selectionIndexes()
+ {
+// return selectedIndexes;
+ int i;
+ NSMutableArray result = new NSMutableArray();
+ Enumeration e = selectedObjects.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ i = indexOf( displayedObjects, e.nextElement() );
+ if ( i != NSArray.NotFound )
+ {
+ result.addObject( new Integer( i ) );
+ }
+ else
+ {
+ System.err.println(
+ "Should never happen: selected objects not in displayed objects" );
+ new RuntimeException().printStackTrace( System.err );
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Sets the objects managed by this display group.
+ * updateDisplayedObjects() is called to filter the
+ * display objects. The previous selection will be
+ * maintained if possible. The data source is not
+ * notified.
+ */
+ public void setObjectArray ( List anObjectList )
+ {
+ if ( anObjectList == null ) anObjectList = new NSArray();
+
+ Object result = notifyDelegate(
+ "displayGroupDisplayArrayForObjects",
+ new Class[] { WODisplayGroup.class, List.class },
+ new Object[] { this, anObjectList } );
+ if ( result != null )
+ {
+ anObjectList = (List) result;
+ }
+
+ willChange();
+
+ NSArray oldSelectedObjects = new NSArray( selectedObjects ); // copy
+
+ // reset allObjects to new list
+ allObjects.removeAllObjects();
+ allObjects.addObjectsFromArray( anObjectList );
+
+ // update the displayed object list
+ updateDisplayedObjects();
+
+ // restore the selection if possible
+ selectObjectsIdenticalTo( oldSelectedObjects );
+
+ batchIndex = 0;
+ displayBatchContainingSelectedObject();
+ }
+
+ /**
+ * Sets the currently selected object, or clears the
+ * selection if the object is not found or is null.
+ * Note: it's not clear how this differs from
+ * selectObject in the spec. It is recommended that
+ * you call selectObject for now.
+ */
+ public void setSelectedObject ( Object anObject )
+ {
+ selectObject( anObject );
+ }
+
+ /**
+ * Sets the current selection to the specified objects.
+ * The previous selection is cleared, and any objects
+ * in the display group that are in the specified list
+ * are then selected. If no items in the specified list
+ * are found in the display group, then the selection is
+ * effectively cleared.
+ * Note: it's not clear how this differs from
+ * selectObjectsIdenticalTo in the spec.
+ * It is recommended that you call that method for now.
+ */
+ public void setSelectedObjects ( List aList )
+ {
+ selectObjectsIdenticalTo( aList );
+ }
+
+ /**
+ * Sets the current selection to the objects at the
+ * specified indexes. Items in the list are assumed
+ * to be instances of java.lang.Number.
+ * The previous selection is cleared, and any objects
+ * in the display group that are in the specified list
+ * are then selected. If no items in the specified list
+ * are found in the display group, then the selection is
+ * effectively cleared.
+ */
+ public boolean setSelectionIndexes ( List aList )
+ {
+ Object o;
+ int index;
+ NSMutableArray objects = new NSMutableArray();
+ Iterator it = aList.iterator();
+ while ( it.hasNext() )
+ {
+ index = ((Number)it.next()).intValue();
+ if ( index < displayedObjects.count() )
+ {
+ o = displayedObjects.objectAtIndex( index );
+ if ( o != null )
+ {
+ objects.add( o );
+ }
+ }
+ }
+ return selectObjectsIdenticalTo( objects );
+ }
+
+ /**
+ * Applies the qualifier to all objects and sorts
+ * the results to update the list of displayed objects.
+ * Observing associations are notified to reflect the changes.
+ */
+ public void updateDisplayedObjects()
+ {
+ updatedObjectIndex = -1;
+ willChange();
+
+ displayedObjects.removeAllObjects();
+
+ displayedObjects.addObjectsFromArray( allObjects );
+
+ // apply qualifier, if any
+ if ( qualifier != null )
+ {
+ EOQualifier.filterArrayWithQualifier(
+ displayedObjects, qualifier );
+ }
+
+ // apply sort orderings, if any
+ NSArray orderings = sortOrderings();
+ if ( orderings != null )
+ {
+ if ( orderings.count() > 0 )
+ {
+ selectionChanged = true;
+ willChange();
+ EOSortOrdering.sortArrayUsingKeyOrderArray(
+ displayedObjects, orderings );
+ }
+ }
+
+ // make sure the selectedObjects is a subset of displayedObjects
+ int i;
+ Object o;
+ Iterator it = new LinkedList( selectedObjects ).iterator();
+ boolean removeflag = false;
+ selectedIndexes.removeAllObjects();
+ while ( it.hasNext() )
+ {
+ o = it.next();
+ if ( ( i = displayedObjects.indexOfIdenticalObject( o ) )
+ == NSArray.NotFound )
+ {
+ selectedObjects.removeIdenticalObject( o );
+ removeflag = true;
+ }
+ else
+ {
+ selectedIndexes.addObject( new Integer( i ) );
+ }
+ }
+
+ //Note: it is important to put the
+ //selectionChanged = true line below remove.
+ if (removeflag)
+ {
+ selectionChanged = true;
+ willChange();
+
+ notifyDelegate(
+ "displayGroupDidChangeSelection",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ notifyDelegate(
+ "displayGroupDidChangeSelectedObjects",
+ new Class[] { WODisplayGroup.class },
+ new Object[] { this } );
+ }
+ }
+
+ /**
+ * Returns the index of the changed object. If more than
+ * one object has changed, -1 is returned.
+ */
+ public int updatedObjectIndex()
+ {
+ return updatedObjectIndex;
+ }
+
+ // getting and setting values in objects
+
+ /**
+ * Returns a value on the selected object for the specified key.
+ */
+ public Object selectedObjectValueForKey ( String aKey )
+ {
+ Object selectedObject = selectedObject();
+ if ( selectedObject == null ) return null;
+ return valueForObject( selectedObject, aKey );
+ }
+
+ /**
+ * Sets the specified value for the specified key on
+ * all selected objects.
+ */
+ public boolean setSelectedObjectValue (
+ Object aValue, String aKey )
+ {
+ Object selectedObject = selectedObject();
+ if ( selectedObject == null ) return false;
+ return setValueForObject( aValue, selectedObject, aKey );
+ }
+
+ /**
+ * Sets the specified value for the specified key on
+ * the specified object. Validations may be triggered,
+ * and error dialogs may appear to the user.
+ * @return True if the value was set successfully,
+ * false if the value could not be set and the update
+ * operation should not continue.
+ */
+ public boolean setValueForObject (
+ Object aValue, Object anObject, String aKey )
+ {
+ // notify object's observers:
+ // this includes us, and will notify our observers
+ EOObserverCenter.notifyObserversObjectWillChange( anObject );
+
+ //TODO: if key is null, need to remove old object
+ // and add new object instead of simply replacing it.
+
+ try
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ ((EOKeyValueCoding)anObject).takeValueForKey( aValue, aKey );
+ }
+ else
+ {
+ EOKeyValueCodingSupport.takeValueForKey( anObject, aValue, aKey );
+ }
+ }
+ catch ( RuntimeException exc )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldDisplayAlert",
+ new Class[] { WODisplayGroup.class, String.class, String.class },
+ new Object[] { this, "Error", exc.getMessage() } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ throw exc;
+ }
+ return false;
+ }
+
+ notifyDelegate(
+ "displayGroupDidSetValueForObject",
+ new Class[] { WODisplayGroup.class, Object.class, Object.class, String.class },
+ new Object[] { this, aValue, anObject, aKey } );
+
+ return true;
+ }
+
+ /**
+ * Calls setValueForObject() for the object at
+ * the specified index.
+ */
+ public boolean setValueForObjectAtIndex (
+ Object aValue, int anIndex, String aKey )
+ {
+ return setValueForObject(
+ aValue, displayedObjects.objectAtIndex( anIndex ), aKey );
+ }
+
+ /**
+ * Returns the value for the specified key on the specified object.
+ */
+ public Object valueForObject ( Object anObject, String aKey )
+ {
+ // empty string is considered the identity property
+ if ( aKey == null ) return anObject;
+ if ( aKey.equals( "" ) ) return anObject;
+
+ try
+ {
+ if ( anObject instanceof EOKeyValueCoding )
+ {
+ return ((EOKeyValueCoding)anObject).valueForKey( aKey );
+ }
+ else
+ {
+ return EOKeyValueCodingSupport.valueForKey( anObject, aKey );
+ }
+ }
+ catch ( RuntimeException exc )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldDisplayAlert",
+ new Class[] { WODisplayGroup.class, String.class, String.class },
+ new Object[] { this, "Error", exc.getMessage() } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ throw exc;
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Calls valueForObject() for the object at the specified index.
+ * Returns null if out of bounds.
+ */
+ public Object valueForObjectAtIndex ( int anIndex, String aKey )
+ {
+ if ( displayedObjects.count() <= anIndex ) return null;
+ Object o = displayedObjects.objectAtIndex( anIndex );
+ return valueForObject( o, aKey );
+ }
+
+ /**
+ * Prints out the list of displayed objects.
+ */
+ public String toString()
+ {
+ return displayedObjects.toString();
+ }
+
+
+ /**
+ * Handles notifications from the data source's editing context,
+ * looking for InvalidatedAllObjectsInStoreNotification and
+ * ObjectsChangedInEditingContextNotification, refetching in
+ * the former case and updating displayed objects in the latter.
+ * Note: This method is not in the public specification.
+ */
+ public void objectsInvalidatedInEditingContext( NSNotification aNotification )
+ {
+ if ( EOObjectStore.InvalidatedAllObjectsInStoreNotification
+ .equals( aNotification.name() ) )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldRefetch",
+ new Class[] { WODisplayGroup.class, NSNotification.class },
+ new Object[] { this, aNotification } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ fetch();
+ }
+ }
+ else
+ if ( EOEditingContext.ObjectsChangedInEditingContextNotification
+ .equals( aNotification.name() ) )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldRedisplay",
+ new Class[] { WODisplayGroup.class, NSNotification.class },
+ new Object[] { this, aNotification } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ int index;
+ Enumeration e;
+ boolean didChange = false;
+ NSDictionary userInfo = aNotification.userInfo();
+
+ // inserts are ignored
+
+ // mark updated objects as updated
+ NSArray updates = (NSArray) userInfo.objectForKey(
+ EOObjectStore.UpdatedKey );
+ e = updates.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ index = indexOf( displayedObjects, e.nextElement() );
+ if ( index != NSArray.NotFound )
+ {
+ //System.out.println( "WODisplayGroup: updated: " + index );
+ if ( ! didChange )
+ {
+ didChange = true;
+ willChange();
+ updatedObjectIndex = index;
+ }
+ else
+ {
+ updatedObjectIndex = -1;
+ }
+ }
+ }
+
+ // treat invalidated objects as updated
+ NSArray invalidates = (NSArray) userInfo.objectForKey(
+ EOObjectStore.InvalidatedKey );
+ e = invalidates.objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ index = indexOf( displayedObjects, e.nextElement() );
+ if ( index != NSArray.NotFound )
+ {
+ //System.out.println( "WODisplayGroup: invalidated: " + index );
+ if ( ! didChange )
+ {
+ didChange = true;
+ willChange();
+ updatedObjectIndex = index;
+ }
+ else
+ {
+ updatedObjectIndex = -1;
+ }
+ }
+ }
+
+ // remove deletes from display group if they exist
+ NSArray deletes = (NSArray) userInfo.objectForKey(
+ EOObjectStore.DeletedKey );
+ e = deletes.objectEnumerator();
+ Object o;
+ while ( e.hasMoreElements() )
+ {
+ o = e.nextElement();
+ index = indexOf( displayedObjects, o );
+ if ( index != NSArray.NotFound )
+ {
+ //System.out.println( "WODisplayGroup: deleted: " + o );
+ deleteObjectAtIndexNoNotify( index );
+ }
+ }
+
+ if ( !usesOptimisticRefresh() )
+ {
+ updateDisplayedObjects();
+ }
+ }
+ }
+
+ }
+
+ // static methods
+
+ /**
+ * Specifies the default behavior for whether changes
+ * should be validated immediately for all display groups.
+ */
+ public static boolean
+ globalDefaultForValidatesChangesImmediately()
+ {
+ return globalDefaultForValidatesChangesImmediately;
+ }
+
+ /**
+ * Specifies the default string matching format for all
+ * display groups.
+ */
+ public static String globalDefaultStringMatchFormat()
+ {
+ return globalDefaultStringMatchFormat;
+ }
+
+ /**
+ * Specifies the default string matching operator for all
+ * display groups.
+ */
+ public static String globalDefaultStringMatchOperator()
+ {
+ return globalDefaultStringMatchOperator;
+ }
+
+ /**
+ * Sets the default behavior for validating changes
+ * for all display groups.
+ */
+ public static void
+ setGlobalDefaultForValidatesChangesImmediately (
+ boolean validatesImmediately )
+ {
+ globalDefaultForValidatesChangesImmediately =
+ validatesImmediately;
+ }
+
+ /**
+ * Sets the default string matching format that
+ * will be used by all display groups.
+ */
+ public static void
+ setGlobalDefaultStringMatchFormat ( String aFormat )
+ {
+ globalDefaultStringMatchFormat = aFormat;
+ }
+
+ /**
+ * Sets the default string matching operator that
+ * will be used by all display groups.
+ */
+ public static void
+ setGlobalDefaultStringMatchOperator ( String anOperator )
+ {
+ globalDefaultStringMatchOperator = anOperator;
+ }
+
+ /**
+ * Needed because we don't inherit from NSObject.
+ * Calls EOObserverCenter.notifyObserversObjectWillChange.
+ */
+ protected void willChange()
+ {
+ EOObserverCenter.notifyObserversObjectWillChange( this );
+ }
+
+ /**
+ * Returns the index of the specified object in the
+ * specified NSArray, comparing by value or by reference
+ * as determined by the private instance variable
+ * compareByReference. If not found, returns NSArray.NotFound.
+ */
+ private int indexOf( NSArray anArray, Object anObject )
+ {
+ if ( compareByReference )
+ {
+ return anArray.indexOfIdenticalObject( anObject );
+ }
+ else
+ {
+ return anArray.indexOf( anObject );
+ }
+ }
+
+ // interface EOObserving
+
+ /**
+ * Receives notifications of changes from objects that
+ * are managed by this display group. This implementation
+ * sets updatedObjectIndex as appropriate.
+ */
+ public void objectWillChange(Object anObject)
+ {
+ int index = indexOf( displayedObjects, anObject );
+ if ( index != NSArray.NotFound )
+ {
+ updatedObjectIndex = index;
+ willChange();
+ }
+ }
+
+ // interface EOEditingContext.Editor
+
+ /**
+ * Called before the editing context begins to save changes.
+ * This implementation calls endEditing().
+ */
+ public void editingContextWillSaveChanges(
+ EOEditingContext anEditingContext )
+ {
+ endEditing();
+ }
+
+ /**
+ * Called to determine whether this editor has changes
+ * that have not been committed to the object in the context.
+ * This implementation returns false.
+ */
+ public boolean editorHasChangesForEditingContext(
+ EOEditingContext anEditingContext )
+ {
+ return false;
+ }
+
+ // interface EOEditingContext.MessageHandler
+
+ /**
+ * Called to display a message for an error that occurred
+ * in the specified editing context. If the delegate allows,
+ * this implementation writes a message to the standard output.
+ * Override to customize.
+ */
+ public void editingContextPresentErrorMessage(
+ EOEditingContext anEditingContext,
+ String aMessage )
+ {
+ Object result = notifyDelegate(
+ "displayGroupShouldDisplayAlert",
+ new Class[] { WODisplayGroup.class, String.class, String.class },
+ new Object[] { this, "Error", aMessage } );
+ if ( ( result == null ) || ( Boolean.TRUE.equals( result ) ) )
+ {
+ System.out.println( aMessage );
+ }
+ }
+
+ /**
+ * Called by the specified object store to determine whether
+ * fetching should continue, where count is the current count
+ * and limit is the limit as specified by the fetch specification.
+ * This implementation returns true. Override to customize.
+ */
+ public boolean editingContextShouldContinueFetching(
+ EOEditingContext anEditingContext,
+ int count,
+ int limit,
+ EOObjectStore anObjectStore )
+ {
+ return true;
+ }
+
+ /**
+ * Sends the specified message to the delegate.
+ * Returns the return value of the method,
+ * or null if no return value or no delegate
+ * or no implementation.
+ */
+ private Object notifyDelegate(
+ String aMethodName, Class[] types, Object[] params )
+ {
+ try
+ {
+ Object delegate = delegate();
+ if ( delegate == null ) return null;
+ return NSSelector.invoke(
+ aMethodName, types, delegate, params );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ // ignore: not implemented
+ }
+ catch ( Exception exc )
+ {
+ // log to standard error
+ System.err.println(
+ "Error while messaging delegate: " +
+ delegate + " : " + aMethodName );
+ exc.printStackTrace();
+ }
+
+ return null;
+ }
+
+ /**
+ * DisplayGroups can delegate important decisions to a Delegate.
+ * Note that DisplayGroup doesn't require its delegates to implement
+ * this interface: rather, this interface defines the methods that
+ * DisplayGroup will attempt to invoke dynamically on its delegate.
+ * The delegate may choose to implement only a subset of the methods
+ * on the interface.
+ */
+ public interface Delegate
+ {
+ /**
+ * Called when the specified data source fails
+ * to create an object for the specified display group.
+ */
+ void displayGroupCreateObjectFailed (
+ WODisplayGroup aDisplayGroup,
+ EODataSource aDataSource );
+
+ /**
+ * Called after the specified display group's
+ * data source is changed.
+ */
+ void displayGroupDidChangeDataSource (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called after a change occurs in the specified
+ * display group's selected objects.
+ */
+ void displayGroupDidChangeSelectedObjects (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called after the specified display group's
+ * selection has changed.
+ */
+ void displayGroupDidChangeSelection (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called after the specified display group has
+ * deleted the specified object.
+ */
+ void displayGroupDidDeleteObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject );
+
+ /**
+ * Called after the specified display group
+ * has fetched the specified object list.
+ */
+ void displayGroupDidFetchObjects (
+ WODisplayGroup aDisplayGroup,
+ List anObjectList );
+
+ /**
+ * Called after the specified display group
+ * has inserted the specified object into
+ * its internal object list.
+ */
+ void displayGroupDidInsertObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject );
+
+ /**
+ * Called after the specified display group
+ * has set the specified value for the specified
+ * object and key.
+ */
+ void displayGroupDidSetValueForObject (
+ WODisplayGroup aDisplayGroup,
+ Object aValue,
+ Object anObject,
+ String aKey );
+
+ /**
+ * Called by the specified display group to
+ * determine what objects should be displayed
+ * for the objects in the specified list.
+ * @return An NSArray containing the objects
+ * to be displayed for the objects in the
+ * specified list.
+ */
+ NSArray displayGroupDisplayArrayForObjects (
+ WODisplayGroup aDisplayGroup,
+ List aList );
+
+ /**
+ * Called by the specified display group before
+ * it attempts to change the selection.
+ * @return True to allow the selection to change,
+ * false otherwise.
+ */
+ boolean displayGroupShouldChangeSelection (
+ WODisplayGroup aDisplayGroup,
+ List aSelectionList );
+
+ /**
+ * Called by the specified display group before
+ * it attempts to delete the specified object.
+ * @return True to allow the object to be deleted
+ * false to prevent the deletion.
+ */
+ boolean displayGroupShouldDeleteObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject );
+
+ /**
+ * Called by the specified display group before
+ * it attempts display the specified alert to
+ * the user.
+ * @return True to allow the message to be
+ * displayed, false if you want to handle the
+ * alert yourself and suppress the display group's
+ * notification.
+ */
+ boolean displayGroupShouldDisplayAlert (
+ WODisplayGroup aDisplayGroup,
+ String aTitle,
+ String aMessage );
+
+ /**
+ * Called by the specified display group before
+ * it attempts fetch objects.
+ * @return True to allow the fetch to take place,
+ * false to prevent the fetch.
+ */
+ boolean displayGroupShouldFetch (
+ WODisplayGroup aDisplayGroup );
+
+ /**
+ * Called by the specified display group before
+ * it attempts to insert the specified object.
+ * @return True to allow the object to be inserted
+ * false to prevent the insertion.
+ */
+ boolean displayGroupShouldInsertObject (
+ WODisplayGroup aDisplayGroup,
+ Object anObject,
+ int anIndex );
+
+ /**
+ * Called by the specified display group when
+ * it receives the specified
+ * ObjectsChangedInEditingContextNotification.
+ * @return True to allow the display group to
+ * update the display (recommended), false
+ * to prevent the update.
+ */
+ boolean displayGroupShouldRedisplay (
+ WODisplayGroup aDisplayGroup,
+ NSNotification aNotification );
+
+ /**
+ * Called by the specified display group when
+ * it receives the specified
+ * InvalidatedAllObjectsInStoreNotification.
+ * @return True to allow the display group to
+ * refetch (recommended), false to prevent the
+ * refetch.
+ */
+ boolean displayGroupShouldRefetch (
+ WODisplayGroup aDisplayGroup,
+ NSNotification aNotification );
+
+ }
+
+}
+
+/*
+ * $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.6 2003/08/07 00:15:15 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.5 2003/01/22 23:01:06 mpowers
+ * Better handling for index out of bounds.
+ *
+ * Revision 1.4 2003/01/21 22:27:02 mpowers
+ * Corrected context id usage.
+ * Implemented backtracking.
+ *
+ * Revision 1.3 2003/01/21 17:54:01 mpowers
+ * Batch indices are one-based, not zero-based.
+ *
+ * Revision 1.2 2003/01/21 14:38:42 mpowers
+ * Fixed batching.
+ *
+ * Revision 1.1 2003/01/18 23:30:42 mpowers
+ * WODisplayGroup now compiles.
+ *
+ * Revision 1.46 2002/10/24 21:15:36 mpowers
+ * New implementations of NSArray and subclasses.
+ *
+ * Revision 1.45 2002/10/24 18:20:20 mpowers
+ * Because NSArray is read-only, we are returning our internal representations
+ * to callers of allObjects(), displayedObjects(), and selectedObjects().
+ *
+ * Revision 1.44 2002/08/06 18:20:25 mpowers
+ * Now posting DisplayGroupWillFetch notifications before fetch.
+ * Implemented support for usesOptimisticRefresh.
+ * No longer supporting inserted/updated/deleted lists: not part of spec.
+ *
+ * Revision 1.43 2002/05/17 15:01:49 mpowers
+ * Implemented dynamic lookup of delegate methods so delegates no longer
+ * need to implement the DisplayGroup.Delegate interface.
+ *
+ * Revision 1.42 2002/03/26 21:46:06 mpowers
+ * Contributing EditingContext as a java-friendly convenience.
+ *
+ * Revision 1.41 2002/03/11 03:17:56 mpowers
+ * Provided control point for coalesced changes.
+ *
+ * Revision 1.40 2002/03/05 23:18:28 mpowers
+ * Added documentation.
+ * Added isSelectionPaintedImmediate and isSelectionTracking attributes
+ * to TableAssociation.
+ * Added getTableAssociation to TableColumnAssociation.
+ *
+ * Revision 1.39 2002/02/19 22:26:04 mpowers
+ * Implemented EOEditingContext.MessageHandler support.
+ *
+ * Revision 1.38 2002/02/19 16:37:38 mpowers
+ * Implemented support for EOEditingContext.Editor
+ *
+ * Revision 1.37 2001/12/11 22:17:48 mpowers
+ * Now properly handling exceptions in valueForObject.
+ * No longer trying to retain selection based only on index.
+ *
+ * Revision 1.36 2001/11/08 21:42:00 mpowers
+ * Now we know what to do with shouldRefetch and shouldRedisplay.
+ *
+ * Revision 1.35 2001/11/04 18:26:58 mpowers
+ * Fixed bug where exceptions were not properly reported when updating
+ * a value and the display group did not have a delegate.
+ *
+ * Revision 1.34 2001/11/02 20:59:36 mpowers
+ * Now correctly ensuring selected objects are a subset of displayed objects.
+ *
+ * Revision 1.33 2001/10/30 22:56:45 mpowers
+ * Added support for EOQualifier.
+ *
+ * Revision 1.32 2001/10/23 22:27:53 mpowers
+ * Now running at ObserverPrioritySixth.
+ *
+ * Revision 1.31 2001/10/23 18:45:05 mpowers
+ * Rolling back changes.
+ *
+ * Revision 1.28 2001/08/22 19:23:41 mpowers
+ * No longer asserting objects in all objects list.
+ *
+ * Revision 1.27 2001/07/30 16:17:01 mpowers
+ * Minor code cleanup.
+ *
+ * Revision 1.26 2001/07/10 22:49:07 mpowers
+ * Fixed bug in optimization for selectObjectsIdenticalTo (found by Dongzhi).
+ *
+ * Revision 1.25 2001/06/19 15:40:21 mpowers
+ * Now only changing the selection if the new selection is different
+ * from the old.
+ *
+ * Revision 1.24 2001/05/24 17:36:15 mpowers
+ * Fixed problem with selectedObjectsIdenticalTo: it was using compare
+ * by value instead of compare by reference.
+ *
+ * Revision 1.23 2001/05/18 21:09:19 mpowers
+ * Now throwing exceptions if the delegate cannot handle error from update.
+ *
+ * Revision 1.22 2001/05/14 15:26:12 mpowers
+ * Now checking for null delegate before and after selection change.
+ *
+ * Revision 1.21 2001/05/08 18:47:34 mpowers
+ * Minor fixes for d3.
+ *
+ * Revision 1.20 2001/04/29 22:02:45 mpowers
+ * Work on id transposing between editing contexts.
+ *
+ * Revision 1.19 2001/04/13 16:38:09 mpowers
+ * Alpha3 release.
+ *
+ * Revision 1.18 2001/04/03 20:36:01 mpowers
+ * Fixed refaulting/reverting/invalidating to be self-consistent.
+ *
+ * Revision 1.17 2001/03/29 03:31:13 mpowers
+ * No longer using Introspector.
+ *
+ * Revision 1.16 2001/02/27 03:32:18 mpowers
+ * Implemented default values for new objects.
+ *
+ * Revision 1.15 2001/02/27 02:11:17 mpowers
+ * Now throwing exception when cloning fails.
+ * Removed debugging printlns.
+ *
+ * Revision 1.14 2001/02/26 22:41:51 mpowers
+ * Implemented null placeholder classes.
+ * Duplicator now uses NSNull.
+ * No longer catching base exception class.
+ *
+ * Revision 1.13 2001/02/26 15:53:22 mpowers
+ * Fine-tuning notification firing.
+ * Child display groups now update properly after parent save or invalidate.
+ *
+ * Revision 1.12 2001/02/22 20:55:06 mpowers
+ * Implemented notification handling.
+ *
+ * Revision 1.11 2001/02/21 20:40:42 mpowers
+ * setObjectArray now falls back to index when trying to retain the
+ * same selection.
+ *
+ * Revision 1.10 2001/02/20 16:38:55 mpowers
+ * MasterDetailAssociations now observe their controlled display group's
+ * objects for changes to that the parent object will be marked as updated.
+ * Before, only inserts and deletes to an object's items are registered.
+ * Also, moved ObservableArray to package access.
+ *
+ * Revision 1.9 2001/02/17 17:23:49 mpowers
+ * More changes to support compiling with jdk1.1 collections.
+ *
+ * Revision 1.8 2001/02/17 16:52:05 mpowers
+ * Changes in imports to support building with jdk1.1 collections.
+ *
+ * Revision 1.7 2001/01/24 16:35:37 mpowers
+ * Improved documentation on TreeAssociation.
+ * SortOrderings are now inherited from parent nodes.
+ * Updates after sorting are still lost on TreeController.
+ *
+ * Revision 1.6 2001/01/24 14:23:05 mpowers
+ * Added support for OrderedDataSource.
+ *
+ * Revision 1.5 2001/01/12 17:21:37 mpowers
+ * Implicit creation of EOSortOrderings now happens in setSortOrderings.
+ *
+ * Revision 1.4 2001/01/11 20:34:26 mpowers
+ * Implemented EOSortOrdering and added support in framework.
+ * Added header-click to sort table columns.
+ *
+ * Revision 1.3 2001/01/10 22:49:44 mpowers
+ * Implemented similarly named selection methods instead of
+ * throwing exceptions.
+ *
+ * Revision 1.2 2001/01/09 20:12:52 mpowers
+ * Moved inner classes to package access.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:48:20 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.21 2000/12/20 16:25:39 michael
+ * Added log to all files.
+ *
+ * Revision 1.20 2000/12/15 15:04:42 michael
+ * Added doc.
+ *
+ * Revision 1.19 2000/12/11 13:32:48 michael
+ * Finish the much better TreeAssociation implementation.
+ * TreeAssociation now has no gui dependencies.
+ *
+ * Revision 1.18 2000/12/05 17:41:46 michael
+ * Broadcasts selection change after delegate refuses selection change
+ * so the initiating association gets refreshed.
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java
new file mode 100644
index 0000000..6e449c3
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WODynamicElement.java
@@ -0,0 +1,208 @@
+/*
+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.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+* The base class for dynamic WOElements. Dynamic elements
+* are expected to do something useful with user-entered data
+* in the request and with any binding associations with the
+* context's current WOComponent.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public abstract class WODynamicElement
+ extends WOElement
+{
+ protected String name;
+ protected WOElement rootElement;
+ protected NSDictionary associations;
+
+ /**
+ * The default constructor.
+ */
+ protected WODynamicElement ()
+ {
+ name = null;
+ associations = new NSMutableDictionary();
+ rootElement = null;
+ }
+
+ /**
+ * Required constructor specifying the class name of the component,
+ * a map of associations, and the root element of the tree that
+ * contains this element (which may be null). The map keys
+ * correspond to properties of this element, and the values are
+ * associations to be applied to the context's current component.
+ */
+ public WODynamicElement (
+ String aName, NSDictionary anAssociationMap, WOElement aRootElement)
+ {
+ this();
+ name = aName;
+ associations = anAssociationMap;
+ rootElement = aRootElement;
+ }
+
+ /**
+ * 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)
+ {
+ if ( rootElement != null )
+ {
+ rootElement.ensureAwakeInContext( aContext );
+ }
+ }
+
+ /**
+ * Use this method to get a map with the properties that start with
+ * a question mark. These are supposed to go at the end of a URL, and it is
+ * very useful for components that generate URLs, specially with direct
+ * actions.
+ * @param c The component where the values of the properties have to be
+ * retrieved from.
+ */
+ Map urlFields(WOComponent c) {
+ HashMap map = new HashMap(associations.count());
+ Enumeration enumeration = associations.keyEnumerator();
+ while (enumeration.hasMoreElements()) {
+ String key = (String)enumeration.nextElement();
+ if (key.charAt(0) == '?') {
+ map.put(key.substring(1), valueForProperty(key, c));
+ }
+ }
+ return map;
+ }
+
+ /** Convenience method for getting the value of an association. */
+ Object valueForProperty(String key, WOComponent c) {
+ WOAssociation a = (WOAssociation)associations.objectForKey(key);
+ if (a != null)
+ return a.valueInComponent(c);
+ return null;
+ }
+
+ /** Convenience method for getting the string value of an association. */
+ String stringForProperty(String key, WOComponent c) {
+ WOAssociation a = (WOAssociation)associations.objectForKey(key);
+ Object result = null;
+ if (a != null) result = a.valueInComponent(c);
+ if ( result == null ) return null;
+ return result.toString();
+ }
+
+ /** Convenience method for getting the string value of an association. */
+ boolean booleanForProperty(String key, WOComponent c) {
+ WOAssociation a = (WOAssociation)associations.objectForKey(key);
+ Object result = null;
+ if (a != null) result = a.valueInComponent(c);
+ if ( result == null ) return false;
+ if ( result.toString().toLowerCase().equals( "true" ) ) return true;
+ return Boolean.TRUE.equals( result );
+ }
+
+ /** Convenience method for setting the value of an association. */
+ void setValueForProperty(String key, Object value, WOComponent c) {
+ WOAssociation a = (WOAssociation)associations.objectForKey(key);
+ if ( a != null && a.isValueSettable() )
+ a.setValue(value, c);
+ }
+
+ /** this method composes a String suitable for inclusion inside a HTML tag. It includes
+ the key-value pairs of all the associations not mentioned in the standardProperties
+ parameter. This is very useful for including extra properties in tags without having to worry
+ if the HTML specification has changed or if non-standard tags are being used.
+ @param c The component where the associations' values should be retrieved from.
+ @param standardProperties An array of Strings with all the associations that should be
+ excluded from the resulting string. */
+ String additionalHTMLProperties(WOComponent c, NSArray standardProperties) {
+ Enumeration enumeration = associations.keyEnumerator();
+ StringBuffer buf = new StringBuffer();
+ while (enumeration.hasMoreElements()) {
+ String key = (String)enumeration.nextElement();
+ if (!(standardProperties.containsObject(key) || key.charAt(0)=='?')) {
+ buf.append(' ');
+ buf.append(key);
+ buf.append("=\"");
+ buf.append(valueForProperty(key, c));
+ buf.append('\"');
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * This method is called to retrieve user-entered data from
+ * the request. WOElements should retrieve data from the
+ * request based on their elementID and set values in the
+ * context's current WOComponent, typically those values that
+ * are associated with the element in the binding. This
+ * implementation does nothing.
+ */
+ public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
+ {
+
+ }
+
+ /**
+ * This method is called on all objects and elements of the
+ * application until a non-null value is returned.
+ * WOElements should first check to see if they are the
+ * target of an action by checking the WOContext's senderID
+ * to see if it matches this element's elementID.
+ * If this element is the target, it should perform an
+ * appropriate action on the context's current WOComponent,
+ * usually the action specified in the binding, and return
+ * the result of that action. This implementation returns null.
+ */
+ public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
+ {
+ return null;
+ }
+
+ /**
+ * This method is called on all elements of the content tree
+ * to build a response to a user request. The message should
+ * be forwarded to any child elements so that the entire tree
+ * is traversed. This implementation does nothing.
+ */
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ // does nothing
+ }
+
+ public WOResponse generateResponse()
+ {
+ return null;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java
new file mode 100644
index 0000000..11944d3
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOElement.java
@@ -0,0 +1,97 @@
+/*
+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.Serializable;
+
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* This class represents a static or dynamic portion of the
+* content returned to a request. Each request walks a tree
+* of WOElements to generate a response.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public abstract class WOElement implements WOActionResults, Serializable
+{
+ NSDictionary associations;
+
+ /**
+ * Default constructor. Performs necessary initialization.
+ */
+ public WOElement()
+ {
+ }
+
+ /**
+ * This method is called to retrieve user-entered data from
+ * the request. WOElements should retrieve data from the
+ * request based on their elementID and set values in the
+ * context's current WOComponent, typically those values that
+ * are associated with the element in the binding. This
+ * implementation does nothing.
+ */
+ public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
+ {
+ // does nothing
+ }
+
+ /**
+ * This method is called on all objects and elements of the
+ * application until a non-null value is returned.
+ * WOElements should first check to see if they are the
+ * target of an action by checking the WOContext's senderID
+ * to see if it matches this element's elementID.
+ * If this element is the target, it should perform an
+ * appropriate action on the context's current WOComponent,
+ * usually the action specified in the binding, and return
+ * the result of that action. This implementation returns null.
+ */
+ public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
+ {
+ return null;
+ }
+
+ /**
+ * This method is called on all elements of the content tree
+ * to build a response to a user request. The message should
+ * be forwarded to any child elements so that the entire tree
+ * is traversed. This implementation does nothing.
+ */
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ // does nothing
+ }
+
+ /**
+ * 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)
+ {
+ // does nothing
+ }
+
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java
new file mode 100644
index 0000000..887d1a3
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOForm.java
@@ -0,0 +1,124 @@
+/*
+ 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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* Implements a FORM element with dynamic bindings.
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOForm extends WODynamicElement {
+
+ public WOForm() {
+ super();
+ }
+
+ public WOForm(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ public String href(WOContext c) {
+ return (String)valueForProperty("href", c.component());
+ }
+
+ public String directActionName(WOContext c) {
+ return (String)valueForProperty("directAction", c.component());
+ }
+
+ public String directActionClass(WOContext c) {
+ return (String)valueForProperty("actionClass", c.component());
+ }
+
+ public boolean multipleSubmit(WOContext c) {
+ return booleanForProperty("multipleSubmit", c.component());
+ }
+
+ public void takeValuesFromRequest (
+ WORequest aRequest, WOContext aContext)
+ {
+ if ( rootElement != null )
+ {
+ rootElement.takeValuesFromRequest( aRequest, aContext );
+ }
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ //Append the opening tag
+ r.appendContentString("<FORM");
+ String link = href(c);
+ //Append the href, if present
+ if (link != null) {
+ r.appendContentString(" HREF=\"");
+ r.appendContentString(link);
+ r.appendContentString("\"");
+ link = null;
+ } else {
+ link = directActionName(c);
+ }
+
+ //otherwise, append Direct Action
+ if (link != null) {
+ r.appendContentString(" HREF=\"");
+ if (directActionClass(c) != null)
+ link = directActionClass(c) + "/" + link;
+ r.appendContentString(c.directActionURLForActionNamed(link, urlFields(c.component())));
+ r.appendContentString("\"");
+ link = null;
+// } else if (associations.objectForKey("action") != null) {
+ } else {
+ //finally, append action
+ r.appendContentString(" action=\"");
+ r.appendContentString(c.componentActionURL());
+ r.appendContentString("\"");
+ } //else
+// now defaulting to action if not specified(?): WOBuilder does generate WOForms without any bindings(!)
+// throw new IllegalArgumentException("You must use one of directActionName, action, or href.");
+
+ //Append any additional properties
+ link = additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "href", "action", "directActionName", "actionClass", "multipleSubmit" }));
+ if (link.length() > 0)
+ r.appendContentString(link);
+ r.appendContentString(">");
+ //Notify that we're inside a form now
+ c.setInForm(true);
+ //Append the inner template
+ rootElement.appendToResponse(r, c);
+ //Close the tag
+ r.appendContentString("</FORM>");
+// c.deleteLastElementIDComponent();
+ c.setInForm(false);
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ //We only process the request if it's not a multipleSubmit (otherwise we leave it to the buttons)
+ if (!multipleSubmit(c)
+ && c.elementID().equals(c.senderID())
+ && associations.objectForKey("action") != null ) {
+ return (WOActionResults)valueForProperty("action", c.component());
+ }
+ WOActionResults res = rootElement.invokeAction(r, c);
+ return res;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java
new file mode 100644
index 0000000..30dd4bc
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOFrame.java
@@ -0,0 +1,60 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+public class WOFrame extends WODynamicElement {
+
+ public WOFrame() {
+ super();
+ }
+
+ public WOFrame(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ public String frameName(WOContext c) {
+ String x = (String)valueForProperty("name", c.component());
+ if (x != null)
+ return x;
+ return c.elementID();
+ }
+
+ public String url(WOContext c) {
+ //Check if the href property is set
+ String href = stringForProperty("href", c.component());
+ if (href != null)
+ return href;
+ href = stringForProperty("pageName", c.component());
+ if (href != null || associations.objectForKey("action") != null) { //write this component's URL
+ return c.componentActionURL();
+ }
+ href = stringForProperty("directActionName", c.component());
+ if (href != null) { //compose the direct action URL
+ String fullActionName = stringForProperty("actionClass", c.component());
+ if (fullActionName != null)
+ fullActionName = fullActionName + "/" + href;
+ else
+ fullActionName = href;
+ return c.directActionURLForActionNamed(fullActionName,
+ urlFields(c.component()));
+ }
+ //Coded needed here to support filename/framework and data/mimeType.
+ return null;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ r.appendContentString("<FRAME NAME=\"");
+ r.appendContentString(frameName(c));
+ r.appendContentString("\" SRC=\"");
+ r.appendContentString(url(c));
+ r.appendContentString("\"");
+ String moreFields = additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "name", "href", "pageName", "directActionName", "actionClass" }));
+ if (moreFields != null && moreFields.length() > 0)
+ r.appendContentString(moreFields);
+ r.appendContentString(">");
+ }
+
+} \ No newline at end of file
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java
new file mode 100644
index 0000000..9af5460
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericContainer.java
@@ -0,0 +1,61 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+
+/**
+ * Used to dynamically generate HTML containers (elements with opening and closing tags and something in between).
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOGenericContainer extends WOGenericElement {
+
+ public WOGenericContainer() {
+ super();
+ }
+
+ public WOGenericContainer(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ super.appendToResponse(r, c);
+ rootElement.appendToResponse(r, c);
+ r.appendContentString("</");
+ r.appendContentString(elementName(c));
+ r.appendContentString(">");
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ super.takeValuesFromRequest( r, c );
+ rootElement.takeValuesFromRequest(r, c);
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ WOActionResults result = super.invokeAction( r, c );
+ if ( result == null )
+ {
+ result = rootElement.invokeAction(r, c);
+ }
+ return result;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java
new file mode 100644
index 0000000..8894428
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOGenericElement.java
@@ -0,0 +1,95 @@
+/*
+ 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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+ * Used to generate any HTML element dynamically. It only creates the opening tag without a closing tag.
+ * To generate HTML elements that have opening and closing tags, as well as some content in between,
+ * use WOGenericContainer instead.
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOGenericElement extends WODynamicElement {
+
+ static NSArray bindings = new NSArray( new Object[]
+ { "elementName", "omitTags", "elementID", "otherTagString",
+ "formValue", "formValues", "invokeAction" } );
+
+ public WOGenericElement() {
+ super();
+ }
+
+ public WOGenericElement(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ public String elementName(WOContext c) {
+ String x = (String)valueForProperty("elementName", c.component());
+ if (x != null)
+ return x;
+ return c.elementID();
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ if ( c.elementID().equals( c.senderID() ) )
+ {
+ Object value;
+ value = r.formValueForKey( c.elementID() );
+ setValueForProperty( "formValue", value, c.component() );
+ value = r.formValuesForKey( c.elementID() );
+ setValueForProperty( "formValues", value, c.component() );
+ }
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c)
+ {
+ WOActionResults result = null;
+ String action = stringForProperty( "invokeAction", c.component() );
+ if ( action != null && c.elementID().equals( c.senderID() ) )
+ {
+ result = c.component().performAction( action );
+ }
+ return result;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ WOComponent component = c.component();
+ if ( !booleanForProperty( "omitTags", component ) )
+ {
+ r.appendContentString("<");
+ r.appendContentString(elementName(c));
+ String other = stringForProperty( "otherTagString", component );
+ if ( other != null )
+ {
+ r.appendContentString( " " );
+ r.appendContentString( other );
+ }
+ String add = additionalHTMLProperties(component, bindings);
+ if (add.length() > 0)
+ r.appendContentString(add);
+ r.appendContentString(">");
+ }
+ setValueForProperty( "elementID", c.elementID(), component );
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java
new file mode 100644
index 0000000..c5d5711
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHiddenField.java
@@ -0,0 +1,42 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+
+/**
+* Used to dynamically generate a hidden field within a form.
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOHiddenField extends WOTextField {
+
+ public WOHiddenField() {
+ super();
+ }
+
+ public WOHiddenField(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java
new file mode 100644
index 0000000..d0f3ff7
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOHyperlink.java
@@ -0,0 +1,249 @@
+/*
+ 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.util.Iterator;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* WOHyperlink renders a dynamically generated hyperlink in the output.
+ * Bindings are:
+ * <ul>
+ * <li>string: a string to be included between the hyperlink tags (optional).</li>
+ * <li>escapeHTML: a property returning a value convertable to a Boolean
+ * indicating whether the any html characters in the output should be
+ * escaped so they are shown as html characters rather than interpreted
+ * as html.</li>
+ * <li>href: The URL that the hyperlink should point to.</li>
+ * <li>pageName: The name of the WOComponent that the hyperlink should point to.</li>
+ * <li>directActionName: The name of the direct action to call when the link is activated.</li>
+ * <li>actionClass: The name of the WODirectAction subclass where the direct action resides.</li>
+ * <li>anchorName: The name of the link, for anchor tags.</li>
+ * <li>action: A pointer to a method on the component that contains this element. If the link is activated,
+ * the method will be called.
+ * <li>ref: The name of the anchor to go to inside the resulting page.</li>
+ * </ul>
+ *
+ * The href, pageName and directActionName/actionClass and name properties are mutually exclusive and you should
+ * only use at most one of them simultaneously.
+ *
+ * @author ezamudio@nasoft.com
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOHyperlink extends WODynamicElement {
+
+ protected String string;
+ protected String href;
+ protected String pageName;
+ protected String directActionName;
+ protected String actionClass;
+ protected String action;
+ protected boolean escapeHTML;
+ protected String anchorName;
+ protected String ref;
+
+ protected WOHyperlink() {
+ super();
+ }
+
+ public WOHyperlink(String aName, NSDictionary aMap, WOElement aRootElement) {
+ super(aName, aMap, aRootElement);
+ escapeHTML = true;
+ }
+
+ public void setString(String value) {
+ string = value;
+ }
+ public String string() {
+ return string;
+ }
+
+ public void setHref(String value) {
+ href = value;
+ }
+ public String href() {
+ return href;
+ }
+
+ public void setAnchorName(String value) {
+ anchorName = value;
+ }
+ public String anchorName() {
+ return anchorName;
+ }
+
+ public void setPageName(String value) {
+ pageName = value;
+ }
+ public String pageName() {
+ return pageName;
+ }
+
+ public void setDirectActionName(String value) {
+ directActionName = value;
+ }
+ public String directActionName() {
+ return directActionName;
+ }
+
+ public void setActionClass(String value) {
+ actionClass = value;
+ }
+ public String actionClass() {
+ return actionClass;
+ }
+
+ /** Sets the escapeHTML property. */
+ public void setEscapeHTML(boolean escape) {
+ escapeHTML = escape;
+ }
+
+ /** If true, inserts escape codes in to the <B>string</B> string so
+ * that HTML special characters (greater-than, less-than, etc.)
+ * appear correctly. If false, those characters will get
+ * interpreted by the browser. Defaults to true.
+ */
+ public boolean escapeHTML() {
+ return escapeHTML;
+ }
+
+ public String actionURL(WOContext c) {
+ //Check if the href property is set
+ if (href() != null) {
+ return href();
+ } else if (pageName() != null || associations.objectForKey("action") != null) { //write this component's URL
+ StringBuffer retval = new StringBuffer(c.componentActionURL());
+ Map addFields = urlFields(c.component());
+ if (addFields.size() > 0) {
+ Iterator enumeration = addFields.keySet().iterator();
+ retval.append('?');
+ while (enumeration.hasNext()) {
+ String encoding = c.response() != null ? c.response().contentEncoding() : c.request().contentEncoding();
+ String key = (String)enumeration.next();
+ try {
+ retval.append(java.net.URLEncoder.encode(key, encoding));
+ } catch (java.io.UnsupportedEncodingException ex) {
+ retval.append(key);
+ }
+ retval.append("=");
+ try {
+ retval.append(java.net.URLEncoder.encode(addFields.get(key).toString(), encoding));
+ } catch (java.io.UnsupportedEncodingException e) {
+ retval.append(addFields.get(key).toString());
+ }
+ if (enumeration.hasNext())
+ retval.append('&');
+ }
+ }
+ return retval.toString();
+ } else if (directActionName() != null) { //compose the direct action URL
+ String fullActionName = null;
+ if (actionClass() != null )
+ fullActionName = actionClass() + "/" + directActionName();
+ else
+ fullActionName = directActionName();
+ return c.directActionURLForActionNamed(fullActionName, urlFields(c.component()));
+ }
+ return null;
+ }
+
+ protected void pullValuesFromParent(WOComponent c) {
+ string = stringForProperty("string", c);
+ href = stringForProperty("href", c);
+ pageName = stringForProperty("pageName", c);
+ directActionName = stringForProperty("directActionName", c);
+ actionClass = stringForProperty("actionClass", c);
+ //action = stringForProperty("action", c);
+ escapeHTML = booleanForProperty("escapeHTML", c);
+ anchorName = stringForProperty("anchorName", c);
+ ref = stringForProperty("ref", c);
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ pullValuesFromParent( c.component() );
+ r.appendContentString("<A");
+ boolean closeQuotes = false;
+ //Check if the href property is set
+ String _href = actionURL(c);
+ if (_href != null) {
+ closeQuotes = true;
+ r.appendContentString(" HREF=\"");
+ r.appendContentString(_href);
+ } else if (anchorName() != null) {
+ r.appendContentString(" NAME=\"");
+ r.appendContentString(anchorName());
+ closeQuotes = true;
+ }
+ if (ref != null) {
+ if (!closeQuotes) {
+ r.appendContentString(" HREF=\"#");
+ closeQuotes = true;
+ } else
+ r.appendContentString("#");
+ r.appendContentString(ref);
+ }
+ if (closeQuotes)
+ r.appendContentString("\"");
+ r.appendContentString(additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "name", "href", "pageName", "action", "directActionName", "actionClass", "anchorName",
+ "escapeHTML", "string" })));
+ r.appendContentString(">");
+ //Append the string if present
+ if (string() != null) {
+ if (escapeHTML())
+ r.appendContentHTMLString(string());
+ else
+ r.appendContentString(string());
+ }
+ //If there is a template, call appendToResponse on it
+ if (rootElement != null) {
+ rootElement.appendToResponse(r, c);
+ }
+ //Close the tag
+ r.appendContentString("</A>");
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ System.out.println("invoke action with elementID=" + c.elementID() + " senderID=" + c.senderID());
+ //Check if this element is the target
+ if (c.senderID().equals(c.elementID())) {
+ if (pageName() != null)
+ {
+ return WOApplication.application().pageWithName(pageName(), r);
+ }
+ else
+ {
+ WOAssociation ass = (WOAssociation) associations.objectForKey("action");
+ if ( ass != null && ass.path != null ) //??
+ return (WOActionResults)c.component().performAction( ass.path );
+ }
+ }
+ return null;
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ System.out.println("takeValuesFromRequest elementID=" + c.elementID() + " senderID=" + c.senderID());
+ super.takeValuesFromRequest(r, c);
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java
new file mode 100644
index 0000000..2673cd1
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImage.java
@@ -0,0 +1,150 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* WOImage renders a dynamically generated IMG tag. The URL for the image SRC can be
+* static, or it can be generated dynamically to return a NSData object (with a mime-type)
+* or even return the contents of a file (not implemented yet).
+*
+* Bindings are:
+* <UL><LI>src: A static URL for the image source.</li>
+* <li>data: A NSData object with the image content. Must be used with mimeType.</li>
+* <li>mimeType: The MIME type for the image data. Can be used with filename or data bindings.</li>
+* <li>filename: The path to a file containing an image.</li>
+* <li>framework: The optional framework from whence the image should be retrieved (used in conjunction with filename).</li>
+*
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOImage extends WODynamicElement {
+
+ protected String src;
+ protected String filename;
+ protected String framework;
+ protected NSData data;
+ protected String mimeType;
+
+ protected WOImage() {
+ super();
+ }
+
+ public WOImage(String aName, NSDictionary aMap, WOElement template) {
+ super(aName, aMap, template);
+ }
+
+ public void setSrc(String value) {
+ src = value;
+ }
+ public String src() {
+ return src;
+ }
+
+ public void setFilename(String value) {
+ filename = value;
+ }
+
+ public String filename() {
+ return filename;
+ }
+
+ public void setFramework(String value) {
+ framework = value;
+ }
+ public String framework() {
+ return framework;
+ }
+
+ public void setData(NSData value) {
+ data = value;
+ }
+ public NSData data() {
+ return data;
+ }
+
+ public void setMimeType(String value) {
+ mimeType = value;
+ }
+ public String mimeType() {
+ return mimeType();
+ }
+
+ public String sourceURL(WOContext c) {
+ if (associations.objectForKey("src") != null)
+ return (String)valueForProperty("src", c.component());
+ if (associations.objectForKey("data") != null) {
+ return c.componentActionURL();
+ }
+ if (associations.objectForKey("filename") != null) {
+ WOComponent component = c.component();
+
+ String framework = stringForProperty("framework", component);
+ String filename = stringForProperty( "filename", component );
+ if ( filename != null && framework == null )
+ {
+ if ( filename.startsWith("/" ) )
+ {
+ int i = filename.lastIndexOf( "/" );
+ if ( i > 0 )
+ {
+ framework = filename.substring( 0, i );
+ if ( i < filename.length() )
+ {
+ filename = filename.substring( i+1 );
+ }
+ }
+ }
+ else
+ {
+ // just until we figure out how we're handling bundles/localization
+ framework = component.frameworkName();
+ if ( framework != null )
+ {
+ framework = framework + '/' + component.name() + ".wo";
+ }
+ else
+ {
+ framework = '/' + component.name() + ".wo";
+ }
+ }
+ }
+ return WOApplication.application().resourceManager().urlForResourceNamed(
+ filename, framework, c.request().browserLanguages(), c.request() );
+ }
+ return "NO SOURCE";
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ r.appendContentString("<IMG SRC=\"");
+ r.appendContentString(sourceURL(c));
+ r.appendContentString("\"");
+ r.appendContentString(additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "src", "filename", "framework", "data", "mimeType" })));
+ r.appendContentString(">");
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ if (c.senderID().equals(c.elementID())) {
+ Object data = valueForProperty("data", c.component());
+ if (data instanceof byte[]) data = new NSData( (byte[]) data );
+ if (data instanceof NSData) {
+ String mt = stringForProperty("mimeType", c.component());
+ if (mt == null)
+ throw new IllegalArgumentException("WOImage: No mimeType specified for data.");
+ WOResponse img = new WOResponse();
+ img.setContent((NSData)data);
+ img.setHeader(mt, "content-type");
+ return img;
+ } else if (filename() != null) {
+ //will this thing use frameworks, or regular JAR files with resources?
+ // mp: both/either, depending on which resource manager implementation
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java
new file mode 100644
index 0000000..7c9f22e
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOImageButton.java
@@ -0,0 +1,58 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+ * WOImageButton renders a dynamically generated IMG tag or an INPUT tag, depending on whether the
+ * element is inside a WOForm (in which case an INPUT of type IMAGE is generated) or not (in which
+ * case the equivalent of a WOActiveImage)
+ *
+ * Bindings are:
+ * <UL>
+ * <LI>src: A static URL for the image source.</li>
+ * <li>data: A NSData object with the image content. Must be used with mimeType.</li>
+ * <li>mimeType: The MIME type for the image data. Can be used with filename or data bindings.</li>
+ * <li>filename: The path to a file containing an image.</li>
+ * <li>framework: The framework where the image should be retrieved from (used in conjunction with filename).</li>
+ *
+ * @author ezamudio@nasoft.com
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOImageButton extends WOImage {
+
+ protected WOImageButton() {
+ super();
+ }
+
+ public WOImageButton(String aName, NSDictionary aMap, WOElement template) {
+ super(aName, aMap, template);
+ }
+
+ public String buttonName(WOContext c) {
+ String x = (String)valueForProperty("name", c.component());
+ if (x != null)
+ return x;
+ return c.elementID();
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ if (c.isInForm()) {
+ //generate an INPUT
+ r.appendContentString("<INPUT TYPE=IMAGE NAME=\"");
+ r.appendContentString(buttonName(c));
+ r.appendContentString("\" SRC=\"");
+ r.appendContentString(sourceURL(c));
+ r.appendContentString("\"");
+ r.appendContentString(additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "name", "action", "src", "filename", "framework", "data", "mimeType" })));
+ r.appendContentString(">");
+ } else {
+ //generate a WOActiveImage
+ new WOActiveImage(name, associations, rootElement).appendToResponse(r, c);
+ }
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java
new file mode 100644
index 0000000..a8c7daa
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOInput.java
@@ -0,0 +1,102 @@
+
+package net.wotonomy.web;
+
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+
+public abstract class WOInput extends WODynamicElement {
+
+ public WOInput() {
+ super();
+ }
+
+ public WOInput(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ protected abstract String inputType();
+ protected abstract Object value(WOContext c);
+
+ protected NSMutableArray additionalAttributes() {
+ return new NSMutableArray(new Object[]{
+ "disabled", "type", "value", "name"
+ });
+ }
+
+ public String inputName(WOContext c) {
+ String x = (String)valueForProperty("name", c.component());
+ if (x != null)
+ return x;
+ return c.elementID();
+ }
+
+ protected boolean disabled(WOContext c) {
+ return booleanForProperty("disabled", c.component());
+ }
+
+ protected void appendExtras(WOResponse r, WOContext c) {
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ r.appendContentString("<INPUT TYPE=\"");
+ r.appendContentString(inputType());
+ r.appendContentString("\" NAME=\"");
+ r.appendContentString(inputName(c));
+ r.appendContentString("\" VALUE=\"");
+ r.appendContentString(value(c).toString());
+ r.appendContentString("\"");
+ String moreFields = additionalHTMLProperties(c.component(), additionalAttributes());
+ if (moreFields != null && moreFields.length() > 0)
+ r.appendContentString(moreFields);
+ appendExtras(r, c);
+ if (disabled(c)) {
+ r.appendContentString(" DISABLED");
+ }
+ r.appendContentString(">");
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ if (disabled(c))
+ return;
+ Object val = r.formValueForKey(inputName(c));
+ WOAssociation va = (WOAssociation)associations.objectForKey("value");
+ if (val != null && va != null && va.isValueSettable())
+ setValueForProperty("value", val, c.component());
+ }
+
+ /** Formats a value as a date or number. Checks for
+ * numberformat or dateformat associations; if one of them
+ * exists, the value is formatter using the specified pattern.
+ * @param value The value to format.
+ * @return The original object, or a date or number if the
+ * receiver has a numberformat or dateformat association.
+ */
+ protected Object formattedValue(Object value, WOComponent c) {
+ //Format the value in case of number
+ String pattern = (String)valueForProperty("numberformat", c);
+ if (pattern != null) {
+ DecimalFormat fmt = new DecimalFormat(pattern);
+ try {
+ return fmt.parse(value.toString());
+ } catch (ParseException e) {
+ return value;
+ }
+ }
+ //Format the value in case of date
+ pattern = (String)valueForProperty("dateformat", c);
+ if (pattern != null) {
+ SimpleDateFormat fmt = new SimpleDateFormat(pattern);
+ try {
+ return fmt.parse(value.toString());
+ } catch (ParseException e) {
+ return value;
+ }
+ }
+ return value;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java
new file mode 100644
index 0000000..eccc489
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMailDelivery.java
@@ -0,0 +1,54 @@
+/*
+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.util.List;
+
+/**
+* A pure java implementation of WOMailDelivery.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public abstract class WOMailDelivery
+{
+ private static WOMailDelivery sharedInstance;
+
+ protected WOMailDelivery ()
+ {
+ }
+
+ public static WOMailDelivery sharedInstance ()
+ {
+ if ( sharedInstance == null )
+ {
+// sharedInstance = new WOMailDelivery();
+ }
+ return sharedInstance;
+ }
+
+ public abstract String composePlainTextEmail (
+ String aSender, List aToList, List aCcList,
+ String aSubject, String aMessage, boolean sendImmediately );
+ public abstract String composeComponentEmail (
+ String aSender, List aToList, List aCcList,
+ String aSubject, WOComponent aComponent, boolean sendImmediately );
+ public abstract void sendEmail (String aMailMessage);
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java
new file mode 100644
index 0000000..be76be1
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOMessage.java
@@ -0,0 +1,333 @@
+/*
+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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableData;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+* A pure java implementation of WOResponse.
+*
+* @author ezamudio@nasoft.com
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOMessage {
+
+ protected String _contentEncoding = "ISO8859_1";
+ protected NSMutableDictionary _headers = new NSMutableDictionary();
+ protected NSMutableDictionary _cookies = new NSMutableDictionary();
+ private NSMutableDictionary _userInfo = new NSMutableDictionary();
+ protected NSMutableData _contentData = new NSMutableData();
+
+ public WOMessage() {
+ super();
+ }
+
+ /**
+ * Sets the content encoding for the response.
+ */
+ public void setContentEncoding (String encoding)
+ {
+ _contentEncoding = encoding;
+ }
+
+ /**
+ * Gets the current content encoding for the response.
+ */
+ public String contentEncoding () {
+ return _contentEncoding;
+ }
+
+ /**
+ * Sets the specified array of values as headers under
+ * the specified key.
+ */
+ public void setHeaders (NSArray headerArray, String aKey) {
+ _headers.setObjectForKey( headerArray, aKey );
+ }
+
+ /**
+ * Sets the specified header value for the specified key.
+ */
+ public void setHeader (String aValue, String aKey) {
+ _headers.setObjectForKey( new NSArray( aValue ), aKey );
+ }
+
+ /**
+ * Returns an array of all the header keys that have been
+ * set in the response.
+ */
+ public NSArray headerKeys () {
+ return _headers.allKeys();
+ }
+
+ /**
+ * Returns an array of all the header values for the specified key,
+ * or null if the key does not exist.
+ */
+ public NSArray headersForKey (String aKey) {
+ return (NSArray)_headers.objectForKey( aKey );
+ }
+
+ /**
+ * Returns one header value for the specified key.
+ * Provided as a convenience, since most header keys
+ * will have a single value.
+ */
+ public String headerForKey (String aKey) {
+ NSArray values = (NSArray)_headers.objectForKey( aKey );
+ if ( values != null && values.count() > 0 ) {
+ return values.objectAtIndex( 0 ).toString();
+ }
+ return null;
+ }
+
+ /**
+ * Sets the content of the response to the bytes represented
+ * by the specified data object.
+ */
+ public void setContent(NSData aData) {
+ _contentData.setData( aData );
+ setHeader(Integer.toString(aData.length()), "content-length");
+ }
+
+ /**
+ * Retrieves the current content of the response.
+ */
+ public NSData content() {
+ return _contentData;
+ }
+
+ /**
+ * Sets the current user info dictionary. These values
+ * are for application-specific uses and are available to
+ * other actions and components in the request-response cycle.
+ */
+ public void setUserInfo (NSDictionary aDict) {
+ _userInfo = new NSMutableDictionary( aDict );
+ }
+
+ /**
+ * Gets the current user info dictionary. These values
+ * are for application-specific uses are are available to
+ * other actions and components in the request-response cycle.
+ */
+ public NSDictionary userInfo () {
+ return new NSDictionary(_userInfo);
+ }
+
+ /**
+ * Appends the bytes in the specified data object to the response.
+ */
+ public void appendContentData (NSData aData)
+ {
+ _contentData.appendData( aData );
+ setHeader(Integer.toString(_contentData.length()), "content-length");
+ }
+
+ /**
+ * Appends the specified byte to the response.
+ */
+ public void appendContentCharacter (char character) {
+ _contentData.appendByte((byte)character);
+ setHeader(Integer.toString(_contentData.length()), "content-length");
+ }
+
+ /**
+ * Appends the specified string to the response.
+ * Any special characters will not be escaped.
+ * The string will be encoded in the current content encoding.
+ */
+ public void appendContentString (String aString)
+ {
+ _contentData.appendData( new NSData( aString.getBytes() ) );
+ setHeader(Integer.toString(_contentData.length()), "content-length");
+ }
+
+ /**
+ * Appends the specified string containing HTML to the response.
+ * Any special characters will be escaped appropriately.
+ * The string will be encoded in the current content encoding.
+ */
+ public void appendContentHTMLString (String aString)
+ {
+ _contentData.appendData(
+ new NSData( stringByEscapingHTMLString(
+ aString ).getBytes() ) );
+ setHeader(Integer.toString(_contentData.length()), "content-length");
+ }
+
+ /**
+ * Appends the specified string containing HTML to the response.
+ * Any special characters will be escaped appropriately.
+ * This method escapes tabs and new-line characters as well.
+ * The string will be encoded in the current content encoding.
+ */
+ public void appendContentHTMLAttributeValue (String aString)
+ {
+ _contentData.appendData (
+ new NSData( stringByEscapingHTMLAttributeValue(
+ aString ).getBytes() ) );
+ setHeader(Integer.toString(_contentData.length()), "content-length");
+ }
+
+ /**
+ * Adds the specified cookie to the response.
+ */
+ public void addCookie (WOCookie aCookie)
+ {
+ _cookies.setObjectForKey( aCookie, aCookie.name() );
+ }
+
+ /**
+ * Removes the specified cookie from the response.
+ */
+ public void removeCookie (WOCookie aCookie)
+ {
+ _cookies.removeObjectForKey( aCookie.name() );
+ }
+
+ /**
+ * Returns an array of cookies currently being sent with the response.
+ * Contains whatever cookies have previously been set in this response.
+ */
+ public NSArray cookies ()
+ {
+ return _cookies.allValues();
+ }
+
+ /**
+ * Sets the HTTP version header in the response.
+ */
+ public void setHTTPVersion (String aString)
+ {
+ setHeader( aString, "Protocol");
+ }
+
+ /**
+ * Gets the current HTTP version header for the response.
+ * Because servlet responses do not allow read access
+ * to headers, this method returns null if setHTTPVersion
+ * has not been called.
+ */
+ public String httpVersion ()
+ {
+ return headerForKey( "Protocol" );
+ }
+
+ /**
+ * Returns a sting containing the contents of the specified
+ * string after escaping all special HTML characters.
+ */
+ public static String stringByEscapingHTMLString
+ (String aString)
+ {
+ int len = aString.length();
+ StringBuffer result = new StringBuffer();
+ char[] buf = new char[ len ];
+ aString.getChars( 0, len, buf, 0 );
+ for ( int i = 0; i < len; i++ )
+ {
+ if ( buf[i] == '&' )
+ {
+ result.append( "&amp;" );
+ }
+ else
+ if ( buf[i] == '\\' )
+ {
+ result.append( "&quot;" );
+ }
+ else
+ if ( buf[i] == '<' )
+ {
+ result.append( "&lt;" );
+ }
+ else
+ if ( buf[i] == '>' )
+ {
+ result.append( "&gt;" );
+ }
+ else
+ {
+ result.append( buf[i] );
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Returns a sting containing the contents of the specified
+ * string after escaping all special HTML characters.
+ * This method escapes tabs and new-line characters as well.
+ */
+ public static String stringByEscapingHTMLAttributeValue
+ (String aString)
+ {
+ int len = aString.length();
+ StringBuffer result = new StringBuffer();
+ char[] buf = new char[ len ];
+ aString.getChars( 0, len, buf, 0 );
+ for ( int i = 0; i < len; i++ )
+ {
+ if ( buf[i] == '&' )
+ {
+ result.append( "&amp;" );
+ }
+ else
+ if ( buf[i] == '\\' )
+ {
+ result.append( "&quot;" );
+ }
+ else
+ if ( buf[i] == '<' )
+ {
+ result.append( "&lt;" );
+ }
+ else
+ if ( buf[i] == '>' )
+ {
+ result.append( "&gt;" );
+ }
+ else
+ if ( buf[i] == '\t' )
+ {
+ result.append( "&#9;" );
+ }
+ else
+ if ( buf[i] == '\n' )
+ {
+ result.append( "&#10;" );
+ }
+ else
+ if ( buf[i] == '\r' )
+ {
+ result.append( "&#13;" );
+ }
+ else
+ {
+ result.append( buf[i] );
+ }
+ }
+ return result.toString();
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java
new file mode 100644
index 0000000..40ee618
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOParentElement.java
@@ -0,0 +1,178 @@
+/*
+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.util.Iterator;
+import java.util.List;
+
+import net.wotonomy.foundation.NSMutableArray;
+
+/**
+* This class represents a parent node in an element tree.
+* It has no content in itself, and exists only to forward
+* messages to each of its children, in turn.
+* Package access only, as it is not in the specification.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+class WOParentElement extends WOElement
+{
+ NSMutableArray children;
+
+ /**
+ * Default constructor.
+ */
+ public WOParentElement()
+ {
+ children = new NSMutableArray();
+ }
+
+ /**
+ * Returns an element with the specified children.
+ */
+ public WOParentElement( List childElements )
+ {
+ this();
+ children.addAll( childElements );
+ }
+
+ /**
+ * 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)
+ {
+ WOElement element;
+ Iterator it = children.iterator();
+ while ( it.hasNext() )
+ {
+ element = (WOElement) it.next();
+ aContext.pushElement( element );
+ element.ensureAwakeInContext( aContext );
+ aContext.popElement();
+ }
+ }
+
+ /**
+ * Forwards this message to all child elements.
+ */
+ public void takeValuesFromRequest (
+ WORequest aRequest, WOContext aContext)
+ {
+ WOElement element;
+
+// aContext.incrementLastElementIDComponent();
+ aContext.appendZeroElementIDComponent();
+
+ Iterator it = children.iterator();
+ while ( it.hasNext() )
+ {
+ element = (WOElement) it.next();
+ aContext.pushElement( element );
+ aContext.incrementLastElementIDComponent();
+ element.takeValuesFromRequest( aRequest, aContext );
+ aContext.popElement();
+ }
+
+ aContext.deleteLastElementIDComponent();
+ }
+
+ /**
+ * Forwards this message to all child elements,
+ * returning the first non-null result.
+ */
+ public WOActionResults invokeAction (
+ WORequest aRequest, WOContext aContext)
+ {
+ WOElement element;
+
+// aContext.incrementLastElementIDComponent();
+ aContext.appendZeroElementIDComponent();
+
+ WOActionResults result = null;
+ Iterator it = children.iterator();
+ while ( it.hasNext() )
+ {
+ element = (WOElement) it.next();
+ aContext.pushElement( element );
+ aContext.incrementLastElementIDComponent();
+ result = element.invokeAction( aRequest, aContext );
+ aContext.popElement();
+ if ( result != null ) break;
+ }
+
+ aContext.deleteLastElementIDComponent();
+ return result;
+ }
+
+ /**
+ * Forwards this message to all child elements.
+ */
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ WOElement element;
+
+// aContext.incrementLastElementIDComponent();
+ aContext.appendZeroElementIDComponent();
+
+ // for each child element
+ Iterator it = children.iterator();
+ while ( it.hasNext() )
+ {
+ element = (WOElement) it.next();
+ aContext.pushElement( element );
+ aContext.incrementLastElementIDComponent();
+
+ // forward the message
+ element.appendToResponse(
+ aResponse, aContext );
+
+ aContext.popElement();
+
+ }
+ aContext.deleteLastElementIDComponent();
+ }
+
+ public WOResponse generateResponse()
+ {
+ WOResponse r = new WOResponse();
+ return r;
+ }
+
+ public String toString()
+ {
+ StringBuffer result = new StringBuffer();
+ result.append( "[WOParentElement: " );
+ // for each child element
+ Iterator it = children.iterator();
+ while ( it.hasNext() )
+ {
+ result.append( "[ " );
+ result.append( it.next().toString() );
+ result.append( " ]" );
+ }
+ result.append( " ]" );
+ return result.toString();
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java
new file mode 100644
index 0000000..2d4f16e
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPasswordField.java
@@ -0,0 +1,20 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSDictionary;
+
+public class WOPasswordField extends WOTextField {
+
+ public WOPasswordField() {
+ super();
+ }
+
+ public WOPasswordField(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ protected String inputType() {
+ return "PASSWORD";
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java
new file mode 100644
index 0000000..a73636c
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOPopUpButton.java
@@ -0,0 +1,135 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSKeyValueCodingAdditions;
+
+public class WOPopUpButton extends WOInput {
+
+ public WOPopUpButton() {
+ super();
+ }
+
+ public WOPopUpButton(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ protected String inputType() {
+ return "SELECT";
+ }
+
+ protected int inputSize() {
+ return 1;
+ }
+
+ protected NSArray list(WOContext c) {
+ NSArray l = (NSArray)valueForProperty("list", c.component());
+ if (l == null)
+ l = NSArray.EmptyArray;
+ return l;
+ }
+
+ protected void setItem(Object v, WOContext c) {
+ if (associations.objectForKey("item") == null)
+ return;
+ setValueForProperty("item", v, c.component());
+ }
+ protected Object item(WOContext c) {
+ return valueForProperty("item", c.component());
+ }
+
+ protected void setSelection(Object v, WOContext c) {
+ if (associations.objectForKey("selection") == null)
+ return;
+ setValueForProperty("selection", v, c.component());
+ }
+ protected Object selection(WOContext c) {
+ return valueForProperty("selection", c.component());
+ }
+
+ public Object value(WOContext c) {
+ return null;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ r.appendContentString("<SELECT NAME=\"");
+ r.appendContentString(inputName(c));
+ r.appendContentString("\" SIZE=");
+ r.appendContentString(Integer.toString(inputSize()));
+ r.appendContentString(">");
+ java.util.Enumeration numerador = list(c).objectEnumerator();
+ String displayKey = stringForProperty("displayString", c.component());
+ String valueKey = stringForProperty("value", c.component());
+ Object sel = selection(c);
+ if (sel == null)
+ sel = item(c);
+ int pos = 0;
+ while (numerador.hasMoreElements()) {
+ Object item = numerador.nextElement();
+ setItem(item, c);
+ r.appendContentString("<OPTION ");
+ //Append the "SELECTED" attribute if it's the selected item
+ if (sel != null && item.equals(sel))
+ r.appendContentString("SELECTED ");
+ r.appendContentString("VALUE=\"");
+ //Append the value
+ if (valueKey != null && item instanceof NSKeyValueCodingAdditions) {
+ Object val = ((NSKeyValueCodingAdditions)item).valueForKeyPath(valueKey);
+ if (val == null)
+ val = "null";
+ r.appendContentString(val.toString());
+ } else
+ r.appendContentString(Integer.toString(pos));
+ r.appendContentString("\">");
+ //Append display string
+ if (displayKey != null && item instanceof NSKeyValueCodingAdditions) {
+ Object ds = ((NSKeyValueCodingAdditions)item).valueForKeyPath(displayKey);
+ if (ds == null)
+ ds = "";
+ r.appendContentString(ds.toString());
+ } else
+ r.appendContentString(item.toString());
+ r.appendContentString("\n");
+ pos++;
+ }
+ r.appendContentString("</SELECT>");
+ }
+
+ protected void select(Object v, WOContext c) {
+ if (associations.objectForKey("selection") != null) {
+ setSelection(v, c);
+ return;
+ }
+ if (associations.objectForKey("item") != null) {
+ setItem(v,c);
+ }
+ }
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ Object val = r.formValueForKey(inputName(c));
+ if (val == null)
+ return;
+ NSArray list = list(c);
+ String valueKey = stringForProperty("value", c.component());
+ //If no value binding, just get the index
+ if (valueKey == null) {
+ int pos = Integer.parseInt(val.toString());
+ val = list.objectAtIndex(pos);
+ select(val, c);
+ return;
+ }
+ //If value binding is present, lookup the value
+ java.util.Enumeration numerador = list.objectEnumerator();
+ while (numerador.hasMoreElements()) {
+ Object o = numerador.nextElement();
+ if (o instanceof NSKeyValueCodingAdditions) {
+ Object x = ((NSKeyValueCodingAdditions)o).valueForKeyPath(valueKey);
+ if (x != null && x.equals(val)) {
+ select(o, c);
+ return;
+ }
+ }
+ }
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java
new file mode 100644
index 0000000..386218c
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORadioButton.java
@@ -0,0 +1,20 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSDictionary;
+
+public class WORadioButton extends WOCheckBox {
+
+ public WORadioButton() {
+ super();
+ }
+
+ public WORadioButton(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ protected String inputType() {
+ return "RADIO";
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java
new file mode 100644
index 0000000..afa118a
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORepetition.java
@@ -0,0 +1,179 @@
+/*
+ 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.util.Collection;
+import java.util.Iterator;
+
+import net.wotonomy.foundation.NSDictionary;
+
+public class WORepetition extends WODynamicElement {
+
+ protected int count;
+ protected int index;
+ protected Object item;
+ protected Collection list;
+ protected Iterator iterator;
+
+ protected WORepetition() {
+ super();
+ }
+
+ public WORepetition(String aName, NSDictionary aMap, WOElement template) {
+ super(aName, aMap, template);
+ }
+
+ public void setCount(int value) {
+ count = value;
+ }
+
+ public int count() {
+ return count;
+ }
+
+ public void setIndex(int value) {
+ index = value;
+ }
+ public int index() {
+ return index;
+ }
+
+ public void setItem(Object value) {
+ item = value;
+ }
+ public Object item() {
+ return item;
+ }
+
+ public void setList(Collection value) {
+ list = value;
+ }
+ public Collection list() {
+ return list;
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ c.appendZeroElementIDComponent();
+ pullValuesFromParent(c.component());
+ index = 0;
+ WOAssociation countAsoc = (WOAssociation)associations.objectForKey("count");
+ item = getNextItem();
+ while (item != null) {
+ pushValuesToParent(c.component());
+ rootElement.takeValuesFromRequest(r, c);
+ index++;
+ c.incrementLastElementIDComponent();
+ if (countAsoc != null && index >= count())
+ item = null;
+ else
+ item = getNextItem();
+ }
+ iterator = null;
+ c.deleteLastElementIDComponent();
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ c.appendZeroElementIDComponent();
+ pullValuesFromParent(c.component());
+ index = 0;
+ WOAssociation countAsoc = (WOAssociation)associations.objectForKey("count");
+ item = getNextItem();
+ while (item != null) {
+ pushValuesToParent(c.component());
+ WOActionResults e = rootElement.invokeAction(r, c);
+ if (e != null)
+ {
+ iterator = null;
+ return e;
+ }
+ index++;
+ c.incrementLastElementIDComponent();
+ if (countAsoc != null && index >= count())
+ item = null;
+ else
+ item = getNextItem();
+ }
+ iterator = null;
+ c.deleteLastElementIDComponent();
+ return null;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ c.appendZeroElementIDComponent();
+ pullValuesFromParent(c.component());
+ index = 0;
+ WOAssociation countAsoc = (WOAssociation)associations.objectForKey("count");
+ item = getNextItem();
+ while (item != null) {
+ pushValuesToParent(c.component());
+ rootElement.appendToResponse(r, c);
+ index++;
+ c.incrementLastElementIDComponent();
+ if (countAsoc != null || index < count())
+ item = null;
+ else
+ item = getNextItem();
+ }
+ iterator = null;
+ c.deleteLastElementIDComponent();
+ }
+
+ protected Object getNextItem() {
+ if ( iterator == null )
+ {
+ if ( list == null ) return null;
+ iterator = list.iterator();
+ }
+ if ( iterator.hasNext() )
+ {
+ return iterator.next();
+ }
+ return null;
+ }
+
+ protected void pullValuesFromParent(WOComponent c) {
+ Object value;
+
+ value = valueForProperty("count", c);
+ if ( value instanceof Number )
+ {
+ setCount( ((Number)value).intValue() );
+ }
+ else
+ {
+ setCount( -1 );
+ }
+
+ value = valueForProperty("list", c);
+ if ( value instanceof Collection )
+ {
+ setList( (Collection) value );
+ }
+ else
+ {
+ setList( null );
+ }
+ }
+
+ protected void pushValuesToParent(WOComponent c) {
+ setValueForProperty("index", new Integer(index), c);
+ setValueForProperty("item", item, c);
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java
new file mode 100644
index 0000000..7d71223
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequest.java
@@ -0,0 +1,586 @@
+/*
+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.InputStream;
+import java.util.Enumeration;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableData;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+* A pure java implementation of WORequest.
+* This implementation is backed by an HttpServletRequest,
+* and thus does not support application-specific subclassing.
+* Future implementations may remove this limitation.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WORequest extends WOResponse
+{
+ HttpServletRequest request;
+ private WOApplication application;
+ private NSArray languages;
+ private String requestHandlerKey;
+ private String requestHandlerPath;
+ private String pageName;
+ private String contextID;
+ private String senderID;
+ private String defaultFormValueEncoding;
+
+ /**
+ * Parameterless constructor which should not be called.
+ */
+ public WORequest ()
+ {
+ throw new RuntimeException(
+ "This constructor is not yet supported." );
+ }
+
+ /**
+ * Standard constructor. Method and URL are required.
+ * HeaderMap is a map of header names to arrays containing
+ * one or more values. Data is the content of the request.
+ * UserInfo contains application-defined paramters that get
+ * passed to all objects involved in dispatching the request.
+ */
+ public WORequest
+ (String aMethod, String aURL, String aProtocolName,
+ NSDictionary headerMap, NSData aData, NSDictionary userInfo)
+ {
+ throw new RuntimeException(
+ "This constructor is not yet supported." );
+ }
+
+ /**
+ * The only supported constructor for this implementation.
+ * This WORequest will wrap the specified servlet request.
+ */
+ public WORequest( HttpServletRequest aRequest, WOApplication anApplication )
+ {
+ request = aRequest;
+ application = anApplication;
+
+ languages = null;
+ senderID = null;
+ pageName = null;
+ contextID = null;
+ defaultFormValueEncoding = "ISO8859_1";
+ requestHandlerKey = WOApplication.directActionRequestHandlerKey();
+ requestHandlerPath = request.getServletPath(); // request.getPathInfo();
+ String remainder = requestHandlerPath;
+ if ( requestHandlerPath == null ) requestHandlerPath = "";
+ if ( requestHandlerPath.length() > 0 )
+ {
+ int i;
+
+ // get request handler key
+ i = requestHandlerPath.indexOf( "/", 1 );
+ if ( i != -1 )
+ {
+ requestHandlerKey = requestHandlerPath.substring( 1, i );
+ requestHandlerPath = requestHandlerPath.substring( i );
+ }
+
+ Enumeration e = requestHandlerPathArray().objectEnumerator();
+ // skipping session ID for now
+ if ( e.hasMoreElements() )
+ {
+ pageName = e.nextElement().toString();
+ }
+ if ( e.hasMoreElements() )
+ {
+ contextID = e.nextElement().toString();
+ }
+ if ( e.hasMoreElements() )
+ {
+ senderID = e.nextElement().toString();
+ }
+ }
+ }
+
+ /**
+ * Returns the HTTP method of the request: "GET" or "POST",
+ * or possibly "PUT".
+ */
+ public String method ()
+ {
+ return request.getMethod();
+ }
+
+ /**
+ * Returns the Uniform Resource Identifier, which is the part
+ * of the request URL after the protocol name (after the port
+ * number) and before the query parameters (before the question mark).
+ */
+ public String uri ()
+ {
+ return request.getRequestURI();
+ }
+
+ /**
+ * Returns the name of the protocol (presumably HTTP) and the
+ * version used by the client as sent in the request headers.
+ */
+ public String httpVersion ()
+ {
+ return request.getProtocol();
+ }
+
+ /**
+ * Returns an array of the header names in this request
+ * in no particular order.
+ */
+ public NSArray headerKeys ()
+ {
+ return arrayFromEnumeration( request.getHeaderNames() );
+ }
+
+ /**
+ * Returns an array of the header values for the specified key
+ * in no particular order.
+ */
+ public NSArray headersForKey (String aString)
+ {
+ return arrayFromEnumeration( request.getHeaders( aString ) );
+ }
+
+ /**
+ * Returns one value for the specified header key.
+ * Provided as a convenience since most headers have
+ * only one value. Returns null if not found.
+ */
+ public String headerForKey (String aKey)
+ {
+ return request.getHeader( aKey );
+ }
+
+ /**
+ * Returns the content of the request, or null if no content.
+ * The type of the content is determined by the "content-type" header.
+ * On error, null is returned.
+ */
+ public NSData content ()
+ {
+ //TODO: This is broken!
+ NSMutableData data = new NSMutableData();
+ try
+ {
+ byte[] buf = new byte[ request.getContentLength() ];
+ InputStream is = request.getInputStream();
+
+ // this is so very not efficient
+ int len;
+ while ( ( len = is.read( buf ) ) > -1 )
+ {
+ // copies data twice...
+ data.appendData( new NSData( buf, 0, len ) );
+ }
+ }
+ catch ( Exception exc )
+ {
+ return null;
+ }
+ return data;
+ }
+
+ /**
+ * Returns the application-specific userInfo dictionary.
+ * This implementation returns the servlet attribute map.
+ */
+ public NSDictionary userInfo ()
+ {
+ //TODO: Test this logic.
+ Object key, value;
+ NSMutableDictionary info = new NSMutableDictionary();
+ java.util.Enumeration e = request.getAttributeNames();
+ while ( e.hasMoreElements() )
+ {
+ key = e.nextElement();
+ value = request.getAttribute( e.nextElement().toString() );
+ if ( value != null )
+ {
+ info.setObjectForKey( value, key );
+ }
+ }
+ return info;
+ }
+
+ /**
+ * Returns the items in the request handler path parsed
+ * by the "/" delimiter and put into an array.
+ */
+ public NSArray requestHandlerPathArray ()
+ {
+ return NSArray.componentsSeparatedByString( requestHandlerPath(), "/" );
+ }
+
+ /**
+ * Returns the client's preferred languages in decreasing
+ * order of preference. The strings are returned in java
+ * Locale format, meaning dashes (-) are converted to
+ * underbars (_): see java.util.Locale.toString().
+ */
+ public NSArray browserLanguages ()
+ {
+ if ( languages == null )
+ {
+ languages = arrayFromEnumeration( request.getLocales() );
+ }
+ return languages;
+ }
+
+ /**
+ * Returns the portion of the URI specifying the engine within which
+ * this web application is running. This is important for generating
+ * URLs to be used in the content of the response.
+ */
+ public String adaptorPrefix ()
+ {
+ //TODO: Test this logic.
+ String name = request.getServletPath();
+ return name;
+
+// String uri = request.getRequestURI();
+// int end = uri.indexOf( request.getPathInfo() );
+// return uri.substring( request.getContextPath().length(), end );
+ }
+
+ /**
+ * Returns the application name as specified in the request URI.
+ * Note that wotonomy web applications do not typically have a .woa
+ * extension, but the extension will be included for those that do.
+ */
+ public String applicationName ()
+ {
+ return request.getContextPath();
+ }
+
+ /**
+ * Returns the id of the application instance that is needed to
+ * service this request. -1 indicates that the request is not
+ * specific to a particular instance of the application.
+ * This implementation currently returns -1.
+ */
+ public int applicationNumber ()
+ {
+ return -1;
+ }
+
+ /**
+ * Returns the portion of the URI that specifies which request handler
+ * should handle the request.
+ */
+ public String requestHandlerKey ()
+ {
+ return requestHandlerKey;
+ }
+
+ /**
+ * Returns the portion of the URI that specifies path information
+ * for the request, not including the query string.
+ */
+ public String requestHandlerPath ()
+ {
+ return requestHandlerPath;
+ }
+
+ /**
+ * Returns the unique identifier for the sessions associated with
+ * this request, or null if no session is found.
+ */
+ public String sessionID ()
+ {
+ HttpSession session = request.getSession( false );
+ if ( session == null ) return null;
+ return session.getId();
+ }
+
+ /**
+ * Returns an array containing all the form keys in the request.
+ */
+ public NSArray formValueKeys ()
+ {
+ return arrayFromEnumeration( request.getParameterNames() );
+ }
+
+ /**
+ * Returns an array of the values for the specified key in
+ * no particular order.
+ */
+ public NSArray formValuesForKey (String aKey)
+ {
+ NSMutableArray result = new NSMutableArray();
+ String[] values = request.getParameterValues( aKey );
+ if ( values != null )
+ {
+ for ( int i = 0; i < values.length; i++ )
+ {
+ result.addObject( values[i] );
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns one value for the specified key.
+ * Provided as a convenience since most keys have
+ * only one value. Returns null if not found.
+ */
+ public Object formValueForKey (String aKey)
+ {
+ return request.getParameter( aKey );
+ }
+
+ /**
+ * Returns the value for the specified key, as a String.
+ */
+ public String stringFormValueForKey(String key) {
+ Object x = formValueForKey(key);
+ if (x != null)
+ return x.toString();
+ return null;
+ }
+
+ /**
+ * Returns a dictionary containing all the key-value
+ * mappings in the request.
+ */
+ public NSDictionary formValues ()
+ {
+ NSMutableDictionary result = new NSMutableDictionary ();
+ java.util.Enumeration e = request.getParameterNames();
+ String key;
+ while ( e.hasMoreElements() )
+ {
+ key = e.nextElement().toString();
+ result.setObjectForKey( formValuesForKey( key ), key );
+ }
+ return result;
+ }
+
+ /**
+ * Returns whether the request is from a java-based client component.
+ * This implementation returns false.
+ */
+ public boolean isFromClientComponent ()
+ {
+ return false;
+ }
+
+ /**
+ * Returns an array of cookie values for the specified key in
+ * no particular order.
+ */
+ public NSArray cookieValuesForKey (String aKey)
+ {
+ // TODO: Test this logic.
+ NSMutableArray result = new NSMutableArray();
+ Cookie[] cookies = request.getCookies();
+ if ( cookies == null ) return result;
+
+ for ( int i = 0; i < cookies.length; i++ )
+ {
+ if ( cookies[i].getName().equals( aKey ) )
+ {
+ result.addObject( cookies[i].getValue() );
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns one value for the specified cookie key.
+ * Provided as a convenience since most cookies have
+ * only one value. Returns null if not found.
+ */
+ public String cookieValueForKey (String aKey)
+ {
+ // TODO: Test this logic.
+ Cookie[] cookies = request.getCookies();
+ if ( cookies == null ) return null;
+
+ for ( int i = 0; i < cookies.length; i++ )
+ {
+ if ( cookies[i].getName().equals( aKey ) )
+ {
+ return cookies[i].getValue();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a dictionary of cookie key-value mappings.
+ */
+ public NSDictionary cookieValues ()
+ {
+ // TODO: Test this logic.
+ NSMutableDictionary result = new NSMutableDictionary();
+ Cookie[] cookies = request.getCookies();
+ if ( cookies == null ) return result;
+
+ NSMutableArray value;
+ for ( int i = 0; i < cookies.length; i++ )
+ {
+ value = (NSMutableArray) result.objectForKey(
+ cookies[i].getName() );
+ if ( value == null )
+ {
+ value = new NSMutableArray();
+ result.setObjectForKey(
+ cookies[i].getValue(), cookies[i].getName() );
+ }
+ value.addObject( cookies[i].getValue() );
+ }
+
+ return result;
+ }
+
+ /**
+ * Sets the default character encoding.
+ */
+ public void setDefaultFormValueEncoding (String encoding)
+ {
+ defaultFormValueEncoding = encoding;
+ }
+
+ /**
+ * Returns the default form value encoding ("ISO8859_1").
+ */
+ public String defaultFormValueEncoding ()
+ {
+ return defaultFormValueEncoding;
+ }
+
+ /**
+ * Sets whether the appropriate encoding scheme for decoding
+ * the form values will be automatically determined.
+ */
+ public void setFormValueEncodingDetectionEnabled (boolean enabled)
+ {
+ throw new RuntimeException( "Not yet implemented." );
+ }
+
+ /**
+ * Gets whether the appropriate encoding scheme for decoding
+ * the form values is currently automatically determined.
+ */
+ public boolean isFormValueEncodingDetectionEnabled ()
+ {
+ throw new RuntimeException( "Not yet implemented." );
+ }
+
+ /**
+ * Gets the current method used for decoding form values.
+ */
+ public int formValueEncoding ()
+ {
+ throw new RuntimeException( "Not yet implemented." );
+ }
+
+ /**
+ * Returns the host name of the server that is the target of this request.
+ * NOTE: This method is not published in the WORequest specification.
+ */
+ String applicationHost ()
+ {
+ // FIXME: this should call WOApplication.hostname();
+ return request.getServerName();
+ }
+
+ /**
+ * Returns the port of the server that is the target of this request.
+ * NOTE: This method is not published in the WORequest specification.
+ */
+ int port()
+ {
+ // FIXME: this should call WOApplication.port();
+ return request.getServerPort();
+ }
+
+ /**
+ * Returns the backing HttpServletRequest.
+ */
+ HttpServletRequest servletRequest ()
+ {
+ return request;
+ }
+
+ /**
+ * Returns the application that was the target of this request.
+ * This method is not published in the WORequest specification.
+ */
+ WOApplication application ()
+ {
+ return application;
+ }
+
+ /**
+ * This method is not published in the WORequest specification.
+ * This sender id from the URI, which is the id of the element
+ * that generated this request.
+ */
+ String senderID ()
+ {
+ return senderID;
+ }
+
+ /**
+ * This method is not published in the WORequest specification.
+ * This returns the context id from the URI.
+ */
+ String contextID ()
+ {
+ return contextID;
+ }
+
+ /**
+ * This method is not published in the WORequest specification.
+ */
+ String pageName ()
+ {
+ return pageName;
+ }
+
+ /**
+ * Convenience method to populate an NSArray from an Enumeration.
+ */
+ private static NSArray arrayFromEnumeration( java.util.Enumeration e )
+ {
+ NSMutableArray result = new NSMutableArray();
+ while ( e.hasMoreElements() )
+ {
+ result.addObject( e.nextElement() );
+ }
+ return result;
+ }
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java
new file mode 100644
index 0000000..957cf25
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WORequestHandler.java
@@ -0,0 +1,68 @@
+/*
+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 net.wotonomy.foundation.internal.NetworkClassLoader;
+
+/**
+* A pure java implementation that corresponds
+* to the abstract class WORequestHandler.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public abstract class WORequestHandler
+{
+ private NetworkClassLoader loader;
+
+ /**
+ * Default constructor.
+ */
+ public WORequestHandler()
+ {
+ loader = new NetworkClassLoader( WOApplication.application().getClass().getClassLoader() );
+ }
+
+ /**
+ * Dispatches the request and returns the response.
+ */
+ abstract public WOResponse handleRequest (WORequest aRequest);
+
+ /**
+ * Gets the specified class, loading it if it has not already been
+ * loaded or if it has been modified. Returns null if not found.
+ * Because this method is not in the specification, it has only
+ * package access.
+ */
+ Class loadClass( String aName )
+ {
+ try
+ {
+ return loader.loadClass( aName, true );
+ }
+ catch ( ClassNotFoundException cnfe )
+ {
+ return null;
+ }
+ }
+
+// void lock ()
+// void unlock ()
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java
new file mode 100644
index 0000000..8428681
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResetButton.java
@@ -0,0 +1,50 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+
+/**
+* Implements a reset button with dynamic bindings.
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOResetButton extends WOInput {
+
+ public WOResetButton() {
+ super();
+ }
+
+ public WOResetButton(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ protected String inputType() {
+ return "RESET";
+ }
+
+ protected Object value(WOContext c) {
+ Object v = valueForProperty("value", c.component());
+ if (v == null)
+ return "Reset";
+ return v;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java
new file mode 100644
index 0000000..c69c9db
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceManager.java
@@ -0,0 +1,489 @@
+/*
+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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSKeyValueCoding;
+import net.wotonomy.foundation.NSMutableDictionary;
+import net.wotonomy.foundation.internal.PropertyListParser;
+
+/**
+* Manages all resources vended by the application.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOResourceManager
+{
+ private NSMutableDictionary resourceCache;
+ private NSMutableDictionary dynamicDataCache;
+ private NSMutableDictionary stringTableCache;
+ private Map localeCache; // used for wo-style i18n
+ private Locale californiaLocale; // used for wo-style i18n
+
+ /**
+ * Constructor is only accessible to subclasses.
+ */
+ protected WOResourceManager()
+ {
+ resourceCache = new NSMutableDictionary();
+ dynamicDataCache = new NSMutableDictionary();
+ stringTableCache = new NSMutableDictionary();
+ localeCache = new HashMap();
+ californiaLocale = new Locale( "en", "US" );
+ localeCache.put( "en", californiaLocale );
+ }
+
+ /**
+ * Returns the raw data corresponding to the specified resource.
+ * Any data retrieved by this method will be placed in the
+ * resource manager's global cache.
+ */
+ public byte[] bytesForResourceNamed(String aFileName,
+ String aFrameworkName,
+ NSArray aLanguagesList)
+ {
+ String mash = aFileName + aFrameworkName;
+ if ( aLanguagesList != null )
+ {
+ mash = mash + aLanguagesList.componentsJoinedByString(":");
+ }
+
+ byte[] result = (byte[]) resourceCache.objectForKey( mash );
+ if ( result == null )
+ {
+ InputStream input = inputStreamForResourceNamed(
+ aFileName, aFrameworkName, aLanguagesList );
+ if ( input != null )
+ {
+ try
+ {
+ int c;
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ while ( ( c = input.read() ) != -1 )
+ {
+ output.write( c );
+ }
+ output.flush();
+ input.close();
+ output.close();
+ result = output.toByteArray();
+ synchronized ( resourceCache )
+ {
+ resourceCache.setObjectForKey( result, mash );
+ }
+ }
+ catch ( Throwable t )
+ {
+ System.err.println( "WOResourceManager: Error reading bytes: " + aFileName );
+ t.printStackTrace();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the content type corresponding to the specified resource.
+ * This implementation recognizes gif, jpg, png, html, and xml extensions.
+ * Otherwise, "text/plain" is returned.
+ */
+ public String contentTypeForResourceNamed(String aResourcePath)
+ {
+ if ( aResourcePath.endsWith( ".gif" ) ) return "image/gif";
+ if ( aResourcePath.endsWith( ".jpg" ) ) return "image/jpeg";
+ if ( aResourcePath.endsWith( ".png" ) ) return "image/png";
+ if ( aResourcePath.endsWith( ".html" ) ) return "text/html";
+ if ( aResourcePath.endsWith( ".xml" ) ) return "text/xml";
+ return "text/plain";
+ }
+
+ /**
+ * Returns a url to be used when errors occur while retrieving a resource.
+ */
+ public String errorMessageUrlForResourceNamed(String aResourceName,
+ String aFrameworkName)
+ {
+ if ( aResourceName == null ) aResourceName = "null";
+ if ( aFrameworkName == null )
+ {
+ return "/ERROR/NOT_FOUND/app=" +
+ WOApplication.application().name() +
+ "/filename=" + aResourceName;
+ }
+ else
+ {
+ return "/ERROR/NOT_FOUND/framework=" +
+ aFrameworkName + "/filename=" + aResourceName;
+ }
+
+ }
+
+ /**
+ * Clears all cached system-wide resource data.
+ */
+ public void flushDataCache()
+ {
+ synchronized ( resourceCache )
+ {
+ resourceCache.removeAllObjects();
+ }
+ synchronized ( dynamicDataCache )
+ {
+ dynamicDataCache.removeAllObjects();
+ }
+ synchronized ( stringTableCache )
+ {
+ stringTableCache.removeAllObjects();
+ }
+ }
+
+ /**
+ * Returns the file-system path for the specified resource.
+ * Deprecated and not implemented.
+ * @deprecated Use inputStreamForResourceNamed instead.
+ */
+ public String pathForResourceNamed(String aResourceName,
+ String aFrameworkName,
+ NSArray aLanguagesList)
+ {
+ throw new RuntimeException( "ResourceManager.pathForResourceNamed: deprecated" );
+ }
+
+ /**
+ * Removes the data from the dynamic data cache for the specified session.
+ * If aSession is null, the data is removed from the application-wide
+ * data cache.
+ */
+ public void removeDataForKey(String aKey,
+ WOSession aSession)
+ {
+ if ( aSession != null )
+ {
+ if ( aSession.dynamicDataCache != null )
+ {
+ aSession.dynamicDataCache.removeObjectForKey( aKey );
+ }
+ }
+ else
+ {
+ synchronized ( dynamicDataCache )
+ {
+ dynamicDataCache.removeObjectForKey( aKey );
+ }
+ }
+ }
+
+ /**
+ * Sets the data in the dynamic data cache for the specified session.
+ * If aSession is null, the data is placed in the application-wide
+ * data cache. If the key is a system-generated key, the data will
+ * be removed by calling removeData() the next time it is requested.
+ */
+ public void setData(NSData someData,
+ String key,
+ String type,
+ WOSession aSession)
+ {
+ if ( aSession != null )
+ {
+ if ( aSession.dynamicDataCache != null )
+ {
+ aSession.dynamicDataCache.setObjectForKey(
+ new TypedData( type, someData ), key );
+ }
+ }
+ else
+ {
+ synchronized ( dynamicDataCache )
+ {
+ dynamicDataCache.setObjectForKey(
+ new TypedData( type, someData ), key );
+ }
+ }
+ }
+
+ /**
+ * Returns a localized string from a property list for
+ * a given key. If the key doesn't exist, aDefaultValue
+ * is returned.
+ */
+ public String stringForKey(String aKey,
+ String aFileName,
+ String aDefaultValue,
+ String aFrameworkName,
+ NSArray aLanguagesList)
+ {
+
+ String mash = aFileName + aFrameworkName;
+ if ( aLanguagesList != null )
+ {
+ mash = mash + aLanguagesList.componentsJoinedByString(":");
+ }
+
+ Object table = stringTableCache.objectForKey( mash );
+ if ( table == null )
+ {
+ try
+ {
+ InputStream input = (InputStream) inputStreamForResourceNamed(
+ aFileName, aFrameworkName, aLanguagesList);
+ if ( input != null )
+ {
+ Reader reader =
+ new BufferedReader(new InputStreamReader(input));
+ table = PropertyListParser.propertyListFromReader( reader );
+ synchronized ( stringTableCache )
+ {
+ stringTableCache.setObjectForKey( table, mash );
+ }
+ }
+ }
+ catch ( IOException ioe )
+ {
+ System.err.println( "WOResourceManager: error reading: " + aFileName );
+ ioe.printStackTrace();
+ }
+ catch ( Throwable t )
+ {
+ // could not parse
+ System.err.println( "WOResourceManager: could not parse: " + aFileName );
+ System.err.println( t );
+ }
+ }
+
+ Object result = null;
+ if ( table != null )
+ {
+ result = NSKeyValueCoding.DefaultImplementation.valueForKey( table, aKey );
+ }
+ if ( result == null )
+ {
+ result = aDefaultValue;
+ }
+ else
+ {
+ result = result.toString();
+ }
+
+ return (String) result;
+ }
+
+ /**
+ * Returns a url that invokes the resource manager for the
+ * specified resource.
+ */
+ public String urlForResourceNamed(String aResourceName,
+ String aFrameworkName,
+ NSArray aLanguagesList,
+ WORequest aRequest)
+ {
+ StringBuffer buffer = new StringBuffer();
+ if ( aFrameworkName == null )
+ {
+ aFrameworkName = "application";
+ }
+ buffer.append( aRequest.applicationName() );
+ buffer.append( '/' );
+ buffer.append( WOApplication.resourceRequestHandlerKey() );
+ if ( !aFrameworkName.startsWith("/") )
+ {
+ buffer.append( '/' );
+ }
+ buffer.append( aFrameworkName );
+ buffer.append( '/' );
+ buffer.append( aResourceName );
+ return buffer.toString();
+ }
+
+ /**
+ * Returns an input for the raw resource. Data returned by
+ * this method will not be put in the resource manager's global cache.
+ */
+ public InputStream inputStreamForResourceNamed(String aResourceName,
+ String aFrameworkName,
+ NSArray aLanguagesList)
+ {
+ if ( aResourceName == null ) return null;
+ InputStream result = null;
+
+ StringBuffer path = new StringBuffer();
+ path.append( '/' );
+ if ( aFrameworkName != null )
+ {
+ path.append( aFrameworkName ).append( '/' );
+ }
+
+ int i = aResourceName.lastIndexOf( "." );
+ if ( i != -1 )
+ path.append( aResourceName.substring( 0, i ) );
+ else
+ path.append( aResourceName );
+
+ String location = path.toString();
+ if ( aLanguagesList != null )
+ {
+ String language;
+ Locale locale;
+ HashSet tried = new HashSet(5);
+ Enumeration e = aLanguagesList.objectEnumerator();
+ while ( e.hasMoreElements() && result == null )
+ {
+ language = e.nextElement().toString();
+
+ // look for java-style localization
+ if ( i != -1 )
+ {
+ result = getStream( location + '_'
+ + language + aResourceName.substring( i ) );
+ }
+ else // no dot extension
+ {
+ result = getStream( location + '_' + language );
+ }
+
+ // look for wo-style localization
+ if ( result == null )
+ {
+ locale = (Locale) localeCache.get( language );
+ if ( locale == null )
+ {
+ if ( language.length() == 5 )
+ {
+ locale = new Locale(
+ language.substring( 0, 2 ),
+ language.substring( 3, 5 ) );
+ }
+ else
+ {
+ locale = new Locale( language, "" );
+ }
+ synchronized ( localeCache )
+ {
+ localeCache.put( language, locale );
+ }
+ }
+
+ language = '/'+locale.getDisplayLanguage( californiaLocale )+".lproj";
+ if ( !tried.contains( language ) )
+ {
+ if ( aFrameworkName != null )
+ {
+ int j = aFrameworkName.length()+1;
+ path.insert( j, language );
+ result = getStream( path.toString() + aResourceName.substring( i ) );
+ path.delete( j, j+language.length() );
+ }
+ else
+ {
+ result = getStream( language + path.toString() + aResourceName.substring( i ) );
+ }
+ tried.add( language );
+ }
+ }
+ }
+ }
+
+ // look for file in package
+ if ( result == null )
+ {
+ if ( i != -1 )
+ {
+ result = getStream( path.append(
+ aResourceName.substring( i ) ).toString() );
+ }
+ else // no dot extension
+ {
+ result = getStream( location );
+ }
+ }
+
+ return result;
+ }
+
+ private static final InputStream getStream( String path )
+ { //System.out.println( "getStream: " + path );
+ InputStream input =
+ WOApplication.application().getClass().getResourceAsStream( path );
+ if ( input == null )
+ {
+ // in case the local class loader doesn't delegate to its parent
+ input = ClassLoader.getSystemResourceAsStream( path );
+ }
+ return input;
+ }
+
+ private static final class TypedData
+ {
+ String type;
+ NSData data;
+
+ public TypedData( String aType, NSData aData )
+ {
+ type = aType;
+ data = aData;
+ }
+ }
+}
+
+/*
+ * $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.5 2003/08/07 00:15:15 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.4 2003/02/28 22:58:57 mpowers
+ * Added support for wo-style localization (*.lproj).
+ *
+ * Revision 1.3 2003/02/21 16:40:24 mpowers
+ * Now reading port and smtp host from system properties.
+ * Implemented WOApplication.main.
+ *
+ * Revision 1.2 2003/01/28 19:33:52 mpowers
+ * Implemented the rest of WOResourceManager.
+ * Implemented support for java-style i18n.
+ * Components now use the resource manager to load templates.
+ *
+ * Revision 1.1 2003/01/27 15:08:00 mpowers
+ * Implemented WOResourceManager, using java resources for now.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java
new file mode 100644
index 0000000..4d25128
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceRequestHandler.java
@@ -0,0 +1,122 @@
+/*
+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.util.Enumeration;
+
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+* An implementation of WORequestHandler that
+* retrieves resources from the deployed application.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOResourceRequestHandler
+ extends WORequestHandler
+{
+ private NSMutableDictionary resourceCache;
+ private WOResourceManager resourceManager;
+
+ public WOResourceRequestHandler()
+ {
+ //TODO: should probably have some kind of limit on the cache
+ resourceCache = new NSMutableDictionary();
+ resourceManager = WOApplication.application().resourceManager();
+ }
+
+ public WOResponse handleRequest( WORequest aRequest )
+ {
+ WOResponse response = new WOResponse();
+
+ StringBuffer buf = new StringBuffer();
+
+ //TODO: this is just to get things working...
+ String framework = null;
+ Enumeration e = aRequest.requestHandlerPathArray().objectEnumerator();
+ if ( e.hasMoreElements() )
+ {
+ framework = e.nextElement().toString();
+ if ( framework.equals( "application" ) )
+ {
+ buf.append('/').append( framework );
+ framework = null;
+ }
+ }
+ if ( e.hasMoreElements() )
+ {
+ buf.append( e.nextElement() );
+ }
+ while ( e.hasMoreElements() )
+ {
+ buf.append('/').append( e.nextElement() );
+ }
+
+ String resource;
+ if ( buf.length() > 0 )
+ {
+ resource = buf.toString();
+ byte[] data = resourceManager.bytesForResourceNamed(
+ resource, framework, aRequest.browserLanguages() );
+ if ( data != null )
+ {
+ response.setHeader(
+ resourceManager.contentTypeForResourceNamed( resource ),
+ "Content-Type" );
+ response.setContent( new NSData( data ) );
+ return response;
+ }
+ }
+ response.setStatus( 404 ); // not found
+ return response;
+ }
+}
+
+/*
+ * $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.6 2003/08/07 00:15:15 chochos
+ * general cleanup (mostly removing unused imports)
+ *
+ * Revision 1.5 2003/01/27 15:08:00 mpowers
+ * Implemented WOResourceManager, using java resources for now.
+ *
+ * Revision 1.4 2003/01/22 23:01:36 mpowers
+ * Better handling for request handler path.
+ * Better support for resources with absolute path.
+ *
+ * Revision 1.3 2003/01/20 16:18:22 mpowers
+ * Fixed class loading issues with resource retrieval.
+ *
+ * Revision 1.2 2003/01/17 20:58:20 mpowers
+ * Fixed up WOHyperlink.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java
new file mode 100644
index 0000000..862c6dc
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResourceURL.java
@@ -0,0 +1,31 @@
+
+package net.wotonomy.web;
+
+import net.wotonomy.foundation.NSData;
+import net.wotonomy.foundation.NSDictionary;
+
+public class WOResourceURL extends WODynamicElement {
+
+ public WOResourceURL() {
+ super();
+ }
+
+ public WOResourceURL(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ String fname = stringForProperty("filename", c.component());
+ if (fname != null) {
+ String fwk = stringForProperty("framework", c.component());
+ return;
+ }
+ NSData data = (NSData)valueForProperty("data", c.component());
+ if (data != null) {
+ String mime = stringForProperty("mimeType", c.component());
+ String key = stringForProperty("key", c.component());
+ return;
+ }
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java
new file mode 100644
index 0000000..d9fbade
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOResponse.java
@@ -0,0 +1,184 @@
+/*
+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.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import net.wotonomy.foundation.NSArray;
+
+/**
+* A pure java implementation of WOResponse.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOResponse extends WOMessage
+ implements WOActionResults
+{
+ protected static String defaultEncoding;
+ private static SimpleDateFormat htmlDateFormat;
+ static
+ {
+ defaultEncoding = "ISO8859_1";
+ htmlDateFormat = new SimpleDateFormat(
+ "EEE, dd MMM yyyy HH:mm:ss z" );
+ htmlDateFormat.setTimeZone(
+ TimeZone.getTimeZone( "GMT" ) );
+ }
+
+ private int status;
+
+ /**
+ * Parameterless constructor which should not be called.
+ */
+ public WOResponse ()
+ {
+ status = -1; // -1 indicates not yet set
+ }
+
+ /**
+ * Sets the status code of the response.
+ * You should use the constants defined in HttpServletResponse.
+ */
+ public void setStatus (int code)
+ {
+ status = code;
+ }
+
+ /**
+ * Gets the current status code for the response.
+ */
+ public int status ()
+ {
+ return status;
+ }
+
+ /**
+ * Sets a header in the response to disable client caching.
+ * (Whether this works depends on the client implementation.)
+ */
+ public void disableClientCaching ()
+ {
+ String dateString = htmlDateFormat.format( new Date() );
+ setHeader( dateString, "Date" );
+ setHeader( dateString, "Expires" );
+ setHeader( "no-cache", "Pragma" );
+ setHeaders( new NSArray( new Object[] {
+ "private", "no-cache", "max-age = 0" } ), "Cache-Control" );
+ //System.out.println( "disableClientCaching: " + dateString );
+ }
+
+ /**
+ * Returns this object. (Implements the WOActionResults interface.)
+ */
+ public WOResponse generateResponse()
+ {
+ return this;
+ }
+
+ /**
+ * Generates the response using the specified servlet response,
+ * but does not flush the buffer. The caller is responsible
+ * for sending the response to the client. <br><br>
+ * Note that this method is currently responsible for setting
+ * the content type to "text/html". As far as I can tell, WORequests
+ * have no way to set the content type and therefore must always
+ * be of type "text/html".
+ */
+ void generateServletResponse
+ (javax.servlet.http.HttpServletResponse response)
+ {
+ if ( WOApplication.application().isPageRefreshOnBacktrackEnabled() )
+ {
+ disableClientCaching();
+ }
+
+ String key;
+ java.util.Enumeration e, f;
+
+ // set content type: might be overwritten by headers below
+ response.setContentType( "text/html" );
+
+ // set status
+ if ( status != -1 )
+ {
+ response.setStatus( status );
+ }
+
+ // set headers
+ f = _headers.allKeys().objectEnumerator();
+ while ( f.hasMoreElements() )
+ {
+ key = f.nextElement().toString();
+ e = ((NSArray)_headers.objectForKey( key )).objectEnumerator();
+ if ( e.hasMoreElements() )
+ {
+ // overwrite existing header
+ response.setHeader( key, e.nextElement().toString() );
+ }
+ while ( e.hasMoreElements() )
+ {
+ response.addHeader( key, e.nextElement().toString() );
+ }
+ }
+
+ // set cookies
+ e = _cookies.allValues().objectEnumerator();
+ while ( e.hasMoreElements() )
+ {
+ response.addCookie( (WOCookie) e.nextElement() );
+ }
+
+ try
+ {
+ // write content
+ response.getOutputStream().write(_contentData.bytes() );
+ if ( status > 299 )
+ {
+ response.sendError( status );
+ }
+ }
+ catch ( Exception exc )
+ {
+ throw new RuntimeException(
+ "Error writing response: " + exc );
+ }
+ }
+
+
+ /**
+ * Returns the current default encoding seting.
+ */
+ public static String defaultEncoding ()
+ {
+ return defaultEncoding;
+ }
+
+ /**
+ * Sets the default encoding setting.
+ */
+ public static void setDefaultEncoding (String encoding)
+ {
+ defaultEncoding = encoding;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java
new file mode 100644
index 0000000..0f2898c
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOServletSessionStore.java
@@ -0,0 +1,188 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2003 Intersect Software Corp.
+
+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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamClass;
+
+import javax.servlet.http.HttpSession;
+
+
+/**
+* An implementation of WOSessionStore that stores WOSessions
+* inside of HttpSessions, to take advantage of the servlet
+* containers built-in distribution capabilities.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOServletSessionStore extends WOSessionStore
+{
+ private static final String ServletSessionKey = "WOServletSessionStoreKey";
+
+ /**
+ * This implementation currently does nothing and returns null, as we rely on the
+ * servlet container to dispose of the HttpSession which contains our WOSession.
+ */
+ public WOSession removeSessionWithID(String sessionID)
+ {
+ return null;
+ }
+
+ /**
+ * Returns the WOSession for the specified ID from the store.
+ * The sessionID parameter is ignored, and the session is removed from
+ * the store until saveSessionForContext is called.
+ */
+ public WOSession restoreSessionWithID(String sessionID, WORequest aRequest)
+ {
+ WOSession result = null;
+ HttpSession servletSession = aRequest.servletRequest().getSession();
+ if ( servletSession != null )
+ {
+ try
+ {
+ result = (WOSession) servletSession.getAttribute( ServletSessionKey );
+ if ( result != null )
+ {
+ // if the servlet's class loader has changed, we need to reload
+ if ( result.getClass().getClassLoader() !=
+ WOApplication.application().getClass().getClassLoader() )
+ {
+ throw new ClassCastException( result.getClass().toString() );
+ }
+ }
+ }
+ catch ( ClassCastException exc )
+ {
+ // we're having an issue with the container's class loader:
+ // try serializing and deserializing to see if it is reloaded correctly
+ try
+ {
+
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ ObjectOutputStream output = new ObjectOutputStream( bytes );
+ Object o = servletSession.getAttribute( ServletSessionKey );
+ output.writeObject( o );
+ output.flush();
+ output.close();
+
+ System.out.println(
+ "WOServletSessionStore: reloaded session with size: " +
+ bytes.toByteArray().length );
+
+ ObjectInputStream input = new CustomObjectInputStream(
+ new ByteArrayInputStream( bytes.toByteArray() ),
+ WOApplication.application().getClass().getClassLoader() );
+
+ o = input.readObject();
+ input.close();
+
+ // try it again
+ result = (WOSession) o;
+ }
+ catch ( Exception e )
+ {
+ e.printStackTrace();
+ // give up: remove the attribute and allow a new session
+ servletSession.removeAttribute( ServletSessionKey );
+ }
+ }
+
+ if ( result != null )
+ {
+ servletSession.removeAttribute( ServletSessionKey );
+ }
+ }
+ //System.out.println( "restoreSessionWithID: " + sessionID + " : " + result );
+ return result;
+ }
+
+ /**
+ * Places the context's session into the store.
+ */
+ public void saveSessionForContext(WOContext context)
+ {
+ //System.out.println( "saveSessionForContext: " + context + " : " + context.session() );
+ context.request().servletRequest().getSession( true ).setAttribute(
+ ServletSessionKey, context.session() );
+ }
+
+ /**
+ * Needed to specify a class loader which may be different that the
+ * one used to load the ObjectInputStream class.
+ */
+ private static class CustomObjectInputStream extends ObjectInputStream
+ {
+ ClassLoader loader;
+
+ public CustomObjectInputStream(
+ InputStream anInputStream, ClassLoader aClassLoader )
+ throws java.io.IOException, java.io.StreamCorruptedException
+ {
+ super( anInputStream );
+ loader = aClassLoader;
+ }
+
+ protected Class resolveClass(ObjectStreamClass v)
+ throws IOException, ClassNotFoundException
+ {
+ return loader.loadClass( v.getName() );
+ }
+ }
+}
+
+/*
+ * $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.7 2003/01/22 23:00:32 mpowers
+ * Now keeping the most recent page around.
+ *
+ * Revision 1.6 2003/01/21 17:53:16 mpowers
+ * Now handling reloading when wotonomy is on the system class path.
+ *
+ * Revision 1.5 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.3 2003/01/14 16:05:19 mpowers
+ * Removed extraneous printlns.
+ *
+ * Revision 1.2 2003/01/13 22:25:07 mpowers
+ * Request-response cycle is working with session and page persistence.
+ *
+ * Revision 1.1 2003/01/07 20:48:24 mpowers
+ * Implemented WOSessionStore and WOServletSessionStore.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java
new file mode 100644
index 0000000..e187567
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSession.java
@@ -0,0 +1,528 @@
+/*
+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.Serializable;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import net.wotonomy.control.EOEditingContext;
+import net.wotonomy.control.KeyValueCodingUtilities;
+import net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDate;
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSKeyValueCodingAdditions;
+import net.wotonomy.foundation.NSKeyValueCodingSupport;
+import net.wotonomy.foundation.NSMutableArray;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+/**
+* A pure java implementation of WOSession.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOSession implements Serializable, NSKeyValueCodingAdditions
+{
+ //NOTE: need to set this when deserialized and on creation
+ transient private HttpSession session;
+
+ // the current context
+ transient private WOContext context;
+
+ // the last requested page: an optimization
+ transient private WOComponent currentPage;
+ // the last requested page's context id
+ transient private String currentContextID;
+
+ //FIXME: transient until ec's implement serializable
+ private transient EOEditingContext defaultEditingContext;
+
+ private NSMutableDictionary state;
+ private NSMutableDictionary pages;
+ private NSMutableDictionary permanentPages;
+ private NSMutableArray stateStack;
+ private NSMutableArray pageStack;
+ private NSMutableArray permanentPageStack;
+ private boolean terminating;
+
+ // used by WOResourceManager to cache dynamic resources
+ transient NSMutableDictionary dynamicDataCache;
+
+ public static final String WOSessionDidTimeOutNotification
+ = "WOSessionDidTimeOutNotification";
+ public static final String WOSessionDidRestoreNotification
+ = "WOSessionDidRestoreNotification";
+ public static final String WOSessionDidCreateNotification
+ = "WOSessionDidCreateNotification";
+
+ /**
+ * Default constructor. This is called implicitly by
+ * subclasses in all cases.
+ */
+ public WOSession ()
+ {
+ session = null;
+ state = new NSMutableDictionary();
+ pages = new NSMutableDictionary();
+ permanentPages = new NSMutableDictionary();
+ stateStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() );
+ pageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() );
+ permanentPageStack = NSMutableArray.mutableArrayBackedByList( new LinkedList() );
+ defaultEditingContext = null;
+ terminating = false;
+ }
+
+ /**
+ * Package method to initialize the backing session.
+ */
+ void setServletSession( HttpSession aSession )
+ {
+ session = aSession;
+ }
+
+ /**
+ * Package method to set the current context.
+ */
+ void setContext( WOContext aContext )
+ {
+ context = aContext;
+ }
+
+ /**
+ * Returns the id of the current session. If no session
+ * currently exists, return null.
+ */
+ public String sessionID ()
+ {
+ if ( session != null )
+ {
+ return session.getId();
+ }
+ return null;
+ }
+
+ /**
+ * Sets whether distribution is currently enabled.
+ * This method is not implemented by this implementation
+ * as the servlet container manages distribution.
+ */
+ public void setDistributionEnabled (boolean enabled)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether the session is part of a distributed application.
+ * This implementation always returns false.
+ */
+ public boolean isDistributionEnabled ()
+ {
+ return false;
+ }
+
+ /**
+ * Sets whether session ids should be stored in cookies.
+ * This method is not implemented in this implementation
+ * as the servlet container manages sessions with cookies.
+ */
+ public void setStoresIDsInCookies (boolean cookies)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether session ids are currently stored in cookies.
+ * This implementation always returns true.
+ */
+ public boolean storesIDsInCookies ()
+ {
+ return true;
+ }
+
+ /**
+ * Returns the current expiration date for cookies that store session ids.
+ */
+ public NSDate expirationDateForIDCookies ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Sets whether session ids should be stored in urls.
+ * This method is not implemented in this implementation
+ * as the servlet container manages sessions with cookies.
+ */
+ public void setStoresIDsInURLs (boolean urls)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns whether session ids are currently stored in urls.
+ * This implementation always returns false.
+ */
+ public boolean storesIDsInURLs ()
+ {
+ return false;
+ }
+
+ /**
+ * Returns the current domain for cookies containing session ids.
+ */
+ public String domainForIDCookies ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Terminates this session after the completion of the current response.
+ */
+ public void terminate ()
+ {
+ terminating = true;
+ session.invalidate();
+ }
+
+ /**
+ * Returns whether the current session will terminate at the completion
+ * of the current response.
+ */
+ public boolean isTerminating ()
+ {
+ return terminating;
+ }
+
+ /**
+ * Sets the number of seconds after the last request before
+ * the session should be terminated.
+ */
+ public void setTimeOut (double timeout)
+ {
+ session.setMaxInactiveInterval( (int) timeout );
+ }
+
+ /**
+ * Returns the number of seconds after the last request before
+ * the session should be terminated.
+ */
+ public double timeOut ()
+ {
+ return session.getMaxInactiveInterval();
+ }
+
+ /**
+ * Sets the languages for which this session has been localized,
+ * in order of preference. The application will be responsible for
+ * localizing the content based on the languages found in this array.
+ */
+ public void setLanguages (NSArray anArray)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Returns the languages for which this session has been localized,
+ * in order of preference. The application will be responsible for
+ * localizing the content based on the languages found in this array.
+ */
+ public NSArray languages ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Stores the specified key-value pair in the session.
+ */
+ public void setObjectForKey (Object anObject, String aKey)
+ {
+ state.setObjectForKey( anObject, aKey );
+ }
+
+ /**
+ * Returns the session value associated with the specified key.
+ */
+ public Object objectForKey (String aKey)
+ {
+ return state.objectForKey( aKey );
+ }
+
+ /**
+ * Removes the session value associated with the specified key.
+ */
+ public void removeObjectForKey (String aKey)
+ {
+ state.removeObjectForKey( aKey );
+ }
+
+ /**
+ * Returns the context for the current request.
+ */
+ public WOContext context ()
+ {
+ return context;
+ }
+
+ /**
+ * Invoked at the beginning of the request-response cycle.
+ * Override to perform any kind of initialization at the
+ * start of a request. This implementation does nothing.
+ */
+ public void awake ()
+ {
+
+ }
+
+ /**
+ * Invoked by the Application to extract user-assigned balues
+ * and assign them to attributes. This implementation calls
+ * takeValuesFromRequest on the top-level component.
+ */
+ public void takeValuesFromRequest (WORequest aRequest, WOContext aContext)
+ {
+ context().component().takeValuesFromRequest( aRequest, aContext );
+ }
+
+ /**
+ * Invoked by the Application to determine which component is the
+ * intended recipient of the user's action. This implementation calls
+ * invokeAction on the top-level component.
+ */
+ public WOActionResults invokeAction (WORequest aRequest, WOContext aContext)
+ {
+ return context().component().invokeAction( aRequest, aContext );
+ }
+
+ /**
+ * Invoked by the Application to generate the content of the response.
+ * This implementation calls appendToResponse on the top-level component.
+ */
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ context().component().appendToResponse( aResponse, aContext );
+ }
+
+ /**
+ * Invoked at the end of the request-response cycle.
+ * Override to perform any kind of clean-up at the
+ * end of a request. This implementation does nothing.
+ */
+ public void sleep ()
+ {
+
+ }
+
+ /**
+ * Returns a list of pages accessed by this session in order
+ * of their access and named by calling WOComponent.descriptionForResponse.
+ */
+ public NSArray statistics ()
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+ /**
+ * Puts this page in the session's page cache using the current
+ * context id as the key.
+ */
+ public void savePage (WOComponent aComponent)
+ {
+ currentPage = aComponent;
+ currentContextID = context.contextID();
+
+ if ( pages.objectForKey( currentContextID ) == null )
+ {
+ byte[] data = KeyValueCodingUtilities.freeze(
+ aComponent, defaultEditingContext(), aComponent, true );
+ System.out.println( "WOSession.savePage: " + currentContextID + " : " + data.length );
+
+ pages.setObjectForKey( data, currentContextID );
+ pageStack.addObject( currentContextID );
+ if ( pageStack.count() > context().application().pageCacheSize() )
+ {
+ String id = pageStack.remove( 0 ).toString(); // removeObjectAtIndex
+ System.out.println( "WOSession.savePage: removing from cache: " + id );
+ pages.removeObjectForKey( id );
+ }
+ }
+ //System.out.println( "savePage: " + this + " : " + id + " : " + pages );
+ }
+
+ /**
+ * Returns the page in the session's page cache corresponding to
+ * the specified context id. Any special permanent caches are
+ * searched before the standard page cache.
+ */
+ public WOComponent restorePageForContextID (String anID)
+ {
+ if ( anID == null ) return null;
+ if ( anID.equals( currentContextID ) ) return currentPage;
+
+ WOComponent result = null;
+ byte[] data = (byte[]) permanentPages.objectForKey( anID );
+ if ( data == null ) data = (byte[]) pages.objectForKey( anID );
+ if ( data != null )
+ {
+ System.out.println( "WOSession.restorePageForContextID: " + anID + " : " + data.length );
+ result = (WOComponent) KeyValueCodingUtilities.thaw(
+ data, defaultEditingContext(),
+ WOApplication.application().getClass().getClassLoader(), true );
+ }
+ //System.out.println( "restorePageForContextID: " + this + " : " + anID + " : " + result + " : " + pages );
+ return result;
+ }
+
+ /**
+ * Puts this page in the special cache is will not get automatically
+ * flushed like the session page cache. Use this if the page
+ * is likely to be around for a while, specifically pages within
+ * frames.
+ */
+ public void savePageInPermanentCache (WOComponent aComponent)
+ {
+ currentPage = aComponent;
+ currentContextID = context.contextID();
+
+ if ( permanentPages.objectForKey( currentContextID ) == null )
+ {
+ byte[] data = KeyValueCodingUtilities.freeze(
+ aComponent, defaultEditingContext(), aComponent, true );
+ //System.out.println( "WOSession.savePageInPermanentCache: "
+ // + currentContextID + " : " + data.length );
+
+ permanentPages.setObjectForKey( data, currentContextID );
+ permanentPageStack.addObject( currentContextID );
+ if ( permanentPageStack.count() > context().application().pageCacheSize() )
+ {
+ String id = permanentPageStack.remove( 0 ).toString(); // removeObjectAtIndex
+ permanentPages.removeObjectForKey( id );
+ }
+ }
+ }
+
+ /**
+ * Writes a message to the standard error stream.
+ */
+ public static void logString (String aString)
+ {
+ System.err.println( aString );
+ }
+
+ /**
+ * Writes a message to the standard error stream
+ * if debugging is activated.
+ */
+ public static void debugString (String aString)
+ {
+ // TODO: Check to see if debugging is enabled.
+ System.err.println( aString );
+ }
+
+ /**
+ * Returns the default editing context used by this session.
+ * Defaults to null.
+ */
+ public EOEditingContext defaultEditingContext ()
+ {
+ return defaultEditingContext;
+ }
+
+ /**
+ * Sets the default editing context used by this session.
+ */
+ public void setDefaultEditingContext (EOEditingContext aContext)
+ {
+ defaultEditingContext = aContext;
+ }
+
+ // interface NSKeyValueCodingAdditions
+
+ 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 );
+ Object result = objectForKey( aKey );
+ if ( result == null )
+ result = NSKeyValueCodingSupport.valueForKey( this, aKey );
+ return result;
+ }
+
+ public void takeValueForKey (Object aValue, String aKey)
+ { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this );
+ setObjectForKey( aValue, aKey );
+ }
+
+ public Object storedValueForKey (String aKey)
+ {
+ Object result = objectForKey( aKey );
+ if ( result == null )
+ NSKeyValueCodingSupport.storedValueForKey( this, aKey );
+ return result;
+ }
+
+ public void takeStoredValueForKey (Object aValue, String aKey)
+ {
+ setObjectForKey( aValue, aKey );
+ }
+
+ public Object handleQueryWithUnboundKey (String aKey)
+ {
+ return NSKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey );
+ }
+
+ public void handleTakeValueForUnboundKey (Object aValue, String aKey)
+ {
+ NSKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey );
+ }
+
+ public void unableToSetNullForKey (String aKey)
+ {
+ NSKeyValueCodingSupport.unableToSetNullForKey( this, aKey );
+ }
+
+ public Object validateTakeValueForKeyPath (Object aValue, String aKey)
+ {
+ throw new RuntimeException( "Not implemented yet." );
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java
new file mode 100644
index 0000000..f91a433
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSessionStore.java
@@ -0,0 +1,93 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2003 Intersect Software Corp.
+
+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;
+
+/**
+* An abstract class defining the requirements for persisting
+* session state across user transactions. Used by WOApplication
+* to persist sessions between requests.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+public abstract class WOSessionStore
+{
+ private static WOSessionStore serverSessionStore = null;
+
+ /**
+ * Returns the default session store used by WOApplication.
+ */
+ public static WOSessionStore serverSessionStore()
+ {
+ if ( serverSessionStore == null )
+ {
+ serverSessionStore = new WOServletSessionStore();
+ }
+ return serverSessionStore;
+ }
+
+ /**
+ * Called by WOApplication after the request-response cycle has ended.
+ * The context's session will again be available for subsequent requests.
+ */
+ public final void checkInSessionForContext(WOContext aContext)
+ {
+ saveSessionForContext( aContext );
+ }
+
+ /**
+ * Returns the session with the specified id for the specified request,
+ * or null if none exist. Subsequent calls for the same id will return
+ * null until the session is checked in again.
+ * Called by WOApplication before the request-response cycle starts.
+ */
+ public final WOSession checkOutSessionWithID(String sessionID, WORequest aRequest)
+ {
+ return restoreSessionWithID( sessionID, aRequest );
+ }
+
+ /**
+ * Removes the WOSession for the specified ID from the store and returns it.
+ */
+ public abstract WOSession removeSessionWithID(String sessionID);
+
+ /**
+ * Returns the WOSession for the specified ID from the store.
+ */
+ public abstract WOSession restoreSessionWithID(String sessionID,
+ WORequest aRequest);
+
+ /**
+ * Places the context's session into the store.
+ */
+ public abstract void saveSessionForContext(WOContext context);
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/16 13:22:22 cgruber
+ * Check in all sources in eclipse-friendly maven-enabled packages.
+ *
+ * Revision 1.1 2003/01/07 20:48:29 mpowers
+ * Implemented WOSessionStore and WOServletSessionStore.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java
new file mode 100644
index 0000000..308cd20
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOStaticElement.java
@@ -0,0 +1,70 @@
+/*
+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;
+
+/**
+* This class represents a static portion of a web page.
+* Package access only, as it is not in the specification.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 893 $
+*/
+class WOStaticElement extends WOElement
+{
+ String content;
+
+ /**
+ * Default constructor.
+ */
+ public WOStaticElement()
+ {
+ content = null;
+ }
+
+ /**
+ * Returns a static element representing the specified content.
+ */
+ public WOStaticElement( String aContentString )
+ {
+ this();
+ content = aContentString;
+ }
+
+ /**
+ * Overridden to append the content string..
+ */
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ aResponse.appendContentString( content );
+ }
+
+
+ public WOResponse generateResponse()
+ {
+ WOResponse r = new WOResponse();
+ if (content != null)
+ {
+ r.appendContentString(content);
+ }
+ return r;
+ }
+
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java
new file mode 100644
index 0000000..ef5b771
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOString.java
@@ -0,0 +1,136 @@
+/*
+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.text.Format;
+
+import net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSNumberFormatter;
+import net.wotonomy.foundation.NSTimestampFormatter;
+
+/**
+* WOString renders a dynamically generated string in the output.
+* Bindings are:
+* <ul>
+* <li>value: a property returning a value which will be rendered as the
+* output. If a formatter is not used, then the value must be convertable
+* to a String with toString().</li>
+* <li>escapeHTML: a property returning a value convertable to a Boolean
+* indicating whether the any html characters in the output should be
+* escaped so they are shown as html characters rather than interpreted
+* as html.</li>
+* <li>formatter: a property returning a Format object that will be
+* used to format the value into a String.</li>
+* <li>dateformat: a property returning a DateFormat object that will be
+* used to format the value into a String.</li>
+* <li>numberformat: a property returning a NumberFormat object that will be
+* used to format the value into a String. Not yet implemented.</li>
+* <li>valueWhenEmpty: a property returning a String that will be used
+* in place of an empty or null value. Not yet implemented.</li>
+* </ul>
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOString extends WODynamicElement
+{
+ protected Object value;
+ protected boolean escapeHTML;
+ protected Format formatter;
+ protected String dateformat;
+ protected String numberformat;
+ protected Object valueWhenEmpty;
+
+ /**
+ * The default constructor.
+ */
+ protected WOString ()
+ {
+ }
+
+ public WOString (
+ String aName, NSDictionary anAssociationMap, WOElement aRootElement)
+ {
+ super( aName, anAssociationMap, aRootElement );
+ escapeHTML = true;
+ }
+
+ public void appendToResponse (WOResponse aResponse, WOContext aContext)
+ {
+ WOComponent c = aContext.component();
+ numberformat = stringForProperty("numberformat", c );
+ dateformat = stringForProperty("dateformat", c );
+ formatter = (Format) valueForProperty("formatter", c );
+ escapeHTML = booleanForProperty("escapeHTML", c );
+ value = valueForProperty("value", c );
+ valueWhenEmpty = valueForProperty("valueWhenEmpty", c );
+
+ Object result = value;
+ if ( result != null )
+ {
+ if ( formatter != null )
+ {
+ try
+ {
+ result = formatter.format( result );
+ }
+ catch ( IllegalArgumentException exc )
+ {
+ }
+ }
+ if ( dateformat != null )
+ {
+ try
+ {
+ result = new NSTimestampFormatter( dateformat ).format( result );
+ }
+ catch ( IllegalArgumentException exc )
+ {
+ }
+ }
+ if ( numberformat != null )
+ {
+ try
+ {
+ result = new NSNumberFormatter( numberformat ).format( result );
+ }
+ catch ( IllegalArgumentException exc )
+ {
+ }
+ }
+ }
+ if ( result == null )
+ {
+ result = valueWhenEmpty;
+ if ( result == null )
+ {
+ result = "nil";
+ }
+ }
+ if ( escapeHTML )
+ {
+ aResponse.appendContentHTMLString( result.toString() );
+ }
+ else
+ {
+ aResponse.appendContentString( result.toString() );
+ }
+ }
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java
new file mode 100644
index 0000000..05824f3
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSubmitButton.java
@@ -0,0 +1,70 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+
+/**
+* Implements a submit button with dynamic bindings.
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOSubmitButton extends WOInput {
+
+ public WOSubmitButton() {
+ super();
+ }
+
+ public WOSubmitButton(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ protected String inputType() {
+ return "SUBMIT";
+ }
+
+ protected Object value(WOContext c) {
+ Object v = valueForProperty("value", c.component());
+ if (v == null) {
+ return "Submit";
+ }
+ return v;
+ }
+
+ protected NSMutableArray additionalAttributes() {
+ NSMutableArray a = super.additionalAttributes();
+ a.add("action");
+ return a;
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c) {
+ if (disabled(c))
+ return null;
+ //It's useless to check the senderID here because it's going to be for the form always.
+ //So we check if the formValues contain the button's name (which means it was pressed)
+ if (r.formValueForKey(inputName(c)) != null) {
+ if (associations.objectForKey("action") != null)
+ return (WOActionResults)valueForProperty("action", c.component());
+ }
+ return null;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java
new file mode 100644
index 0000000..d2e373a
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOSwitchComponent.java
@@ -0,0 +1,112 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableDictionary;
+
+public class WOSwitchComponent extends WODynamicElement {
+
+ private NSMutableDictionary elements;
+ private String currentName;
+ private WOElement currentElement;
+
+ protected WOSwitchComponent()
+ {
+ super();
+ elements = new NSMutableDictionary();
+ }
+
+ public WOSwitchComponent(String aName, NSDictionary aMap, WOElement template)
+ {
+ super(aName, aMap, template);
+ elements = new NSMutableDictionary();
+ }
+
+ private WOElement getCurrentElement( WOContext c )
+ {
+ String name = stringForProperty( "WOComponentName", c.component() );
+ if ( name == null ) return null;
+
+ if ( currentElement != null && name.equals( currentName ) ) return currentElement;
+
+ currentName = name;
+ currentElement = (WOElement) elements.objectForKey( name );
+ if ( currentElement == null )
+ {
+ currentElement = WOApplication.application().pageWithName( name, c );
+ if ( currentElement != null )
+ {
+ currentElement.associations = associations;
+ elements.setObjectForKey( currentElement, name );
+ }
+ }
+
+ return currentElement;
+ }
+
+ void ensureAwakeInContext (WOContext aContext)
+ {
+ if ( aContext != null )
+ {
+ WOElement element = getCurrentElement( aContext );
+ if ( element != null )
+ {
+ aContext.pushElement( element );
+ element.ensureAwakeInContext( aContext );
+ aContext.popElement();
+ }
+ }
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c)
+ {
+ WOElement element = getCurrentElement( c );
+ if ( element != null )
+ {
+ c.pushElement( element );
+ element.takeValuesFromRequest( r, c );
+ c.popElement();
+ }
+ }
+
+ public WOActionResults invokeAction(WORequest r, WOContext c)
+ {
+ WOActionResults result = null;
+ WOElement element = getCurrentElement( c );
+ if ( element != null )
+ {
+ c.pushElement( element );
+ result = element.invokeAction( r, c );
+ c.popElement();
+ }
+ return result;
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c)
+ {
+ WOElement element = getCurrentElement( c );
+ if ( element != null )
+ {
+ c.pushElement( element );
+ element.appendToResponse( r, c );
+ c.popElement();
+ }
+ }
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java
new file mode 100644
index 0000000..008fda8
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOText.java
@@ -0,0 +1,72 @@
+/*
+ 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 net.wotonomy.foundation.NSArray;
+import net.wotonomy.foundation.NSDictionary;
+
+/**
+* Implements a TEXTAREA element, with dynamic bindings.
+ * @author michael@mpowers.net
+ * @author $Author: cgruber $
+ * @version $Revision: 905 $
+ */
+public class WOText extends WOInput {
+
+ public WOText() {
+ super();
+ }
+
+ public WOText(String n, NSDictionary m, WOElement t) {
+ super(n, m, t);
+ }
+
+ protected String inputType() {
+ return "TEXTAREA";
+ }
+
+ protected Object value(WOContext c) {
+ Object fieldValue = valueForProperty("value", c.component());
+ if (fieldValue == null) {
+ fieldValue = "";
+ }
+ return formattedValue(fieldValue, c.component());
+ }
+
+ public void takeValuesFromRequest(WORequest r, WOContext c) {
+ Object val = r.formValueForKey(inputName(c));
+ if ( val != null )
+ setValueForProperty("value", formattedValue(val, c.component()), c.component());
+ }
+
+ public void appendToResponse(WOResponse r, WOContext c) {
+ r.appendContentString("<TEXTAREA NAME=\"");
+ r.appendContentString(inputName(c));
+ r.appendContentString("\"");
+ String moreFields = additionalHTMLProperties(c.component(), new NSArray(new Object[]{
+ "name", "value" }));
+ if (moreFields != null && moreFields.length() > 0)
+ r.appendContentString(moreFields);
+ r.appendContentString(">");
+ moreFields = value(c).toString();
+ if (moreFields != null && moreFields.length() > 0)
+ r.appendContentString(moreFields);
+ r.appendContentString("</TEXTAREA>");
+ }
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java
new file mode 100644
index 0000000..4233ad4
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/WOTextField.java
@@ -0,0 +1,59 @@
+/*
+ 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 net.wotonomy.foundation.NSDictionary;
+import net.wotonomy.foundation.NSMutableArray;
+
+/**
+* Implements an INPUT tag of type TEXT, with dynamic bindings.
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class WOTextField extends WOInput {
+
+ public WOTextField() {
+ super();
+ }
+
+ public WOTextField(String aName, NSDictionary assocs, WOElement template) {
+ super(aName, assocs, template);
+ }
+
+ protected String inputType() {
+ return "TEXT";
+ }
+
+ protected Object value(WOContext c) {
+ Object fieldValue = valueForProperty("value", c.component());
+ if (fieldValue == null) {
+ fieldValue = "";
+ }
+ return formattedValue(fieldValue, c.component());
+ }
+
+ protected NSMutableArray additionalAttributes() {
+ NSMutableArray a = super.additionalAttributes();
+ a.addObject("dateformat");
+ a.addObject("numberformat");
+ return a;
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/package.html b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/package.html
new file mode 100644
index 0000000..10bef50
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/package.html
@@ -0,0 +1,11 @@
+<body>
+<p>
+An implementation of the WebObjects
+framework. It tries to be API-compatible to the
+maximum extent possible.
+</p>
+<p>
+This package has dependencies on the foundation
+and util packages.
+</p>
+</body>
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java
new file mode 100644
index 0000000..777d4a1
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java
@@ -0,0 +1,665 @@
+//package edu.stanford.ejalbert;
+package net.wotonomy.web.util;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/** <p> BrowserLauncher is a class that provides one static method,
+ * openURL, which opens the default web browser for the current user
+ * of the system to the given URL. It may support other protocols
+ * depending on the system -- mailto, ftp, etc. -- but that has not
+ * been rigorously tested and is not guaranteed to work. </p>
+ *
+ * <p> Yes, this is platform-specific code, and yes, it may rely on
+ * classes on certain platforms that are not part of the standard JDK.
+ * What we're trying to do, though, is to take something that's
+ * frequently desirable but inherently platform-specific -- opening a
+ * default browser -- and allow programmers (you, for example) to do
+ * so without worrying about dropping into native code or doing
+ * anything else similarly evil. </p>
+ *
+ * <p> Anyway, this code is completely in Java and will run on all JDK
+ * 1.1-compliant systems without modification or a need for additional
+ * libraries. All classes that are required on certain platforms to
+ * allow this to run are dynamically loaded at runtime via reflection
+ * and, if not found, will not cause this to do anything other than
+ * returning an error when opening the browser. </p>
+ *
+ * <p> There are certain system requirements for this class, as it's
+ * running through Runtime.exec(), which is Java's way of making a
+ * native system call. Currently, this requires that a Macintosh have
+ * a Finder which supports the GURL event, which is true for Mac OS
+ * 8.0 and 8.1 systems that have the Internet Scripting AppleScript
+ * dictionary installed in the Scripting Additions folder in the
+ * Extensions folder (which is installed by default as far as I know
+ * under Mac OS 8.0 and 8.1), and for all Mac OS 8.5 and later
+ * systems. On Windows, it only runs under Win32 systems (Windows 95,
+ * 98, and NT 4.0, as well as later versions of all). On other
+ * systems, this drops back from the inherently platform-sensitive
+ * concept of a default browser and simply attempts to launch Netscape
+ * via a shell command. </p>
+ *
+ * <p> This code is Copyright 1999-2001 by Eric Albert
+ * (ejalbert@cs.stanford.edu) and may be redistributed or modified in
+ * any form without restrictions as long as the portion of this
+ * comment from this paragraph through the end of the comment is not
+ * removed. The author requests that he be notified of any
+ * application, applet, or other binary that makes use of this code,
+ * but that's more out of curiosity than anything and is not required.
+ * This software includes no warranty. The author is not repsonsible
+ * for any loss of data or functionality or any adverse or unexpected
+ * effects of using this software. </p>
+ *
+ * Credits: <br> Steven Spencer, JavaWorld magazine (<a
+ * href="http://www.javaworld.com/javaworld/javatips/jw-javatip66.html">Java
+ * Tip 66</a>) <br> Thanks also to Ron B. Yeh, Eric Shapiro, Ben
+ * Engber, Paul Teitlebaum, Andrea Cantatore, Larry Barowski, Trevor
+ * Bedzek, Frank Miedrich, and Ron Rabakukk
+ *
+ *
+ * original author Eric Albert (<a
+ * href="mailto:ejalbert@cs.stanford.edu">ejalbert@cs.stanford.edu</a>)
+ * author's version 1.4b1 (Released June 20, 2001)
+ * @author Copyright 2001, Intersect Software Corporation
+ * @version $Revision: 905 $ $Date: 2006-02-18 20:44:03 -0500 (Sat, 18 Feb 2006) $
+ */
+
+public class BrowserLauncher {
+ /** The Java virtual machine that we are running on. Actually, in
+ * most cases we only care about the operating system, but some
+ * operating systems require us to switch on the VM. */
+ private static int jvm;
+
+ /** The browser for the system */
+ private static Object browser;
+
+ /** Caches whether any classes, methods, and fields that are not
+ * part of the JDK and need to be dynamically loaded at runtime
+ * loaded successfully. <p> Note that if this is
+ * <code>false</code>, <code>openURL()</code> will always return
+ * an IOException. */
+ private static boolean loadedWithoutErrors;
+
+ /** The com.apple.mrj.MRJFileUtils class */
+ private static Class mrjFileUtilsClass;
+
+ /** The com.apple.mrj.MRJOSType class */
+ private static Class mrjOSTypeClass;
+
+ /** The com.apple.MacOS.AEDesc class */
+ private static Class aeDescClass;
+
+ /** The <init>(int) method of com.apple.MacOS.AETarget */
+ private static Constructor aeTargetConstructor;
+
+ /** The <init>(int, int, int) method of com.apple.MacOS.AppleEvent */
+ private static Constructor appleEventConstructor;
+
+ /** The <init>(String) method of com.apple.MacOS.AEDesc */
+ private static Constructor aeDescConstructor;
+
+ /** The findFolder method of com.apple.mrj.MRJFileUtils */
+ private static Method findFolder;
+
+ /** The getFileCreator method of com.apple.mrj.MRJFileUtils */
+ private static Method getFileCreator;
+
+ /** The getFileType method of com.apple.mrj.MRJFileUtils */
+ private static Method getFileType;
+
+ /** The openURL method of com.apple.mrj.MRJFileUtils */
+ private static Method openURL;
+
+ /** The makeOSType method of com.apple.MacOS.OSUtils */
+ private static Method makeOSType;
+
+ /** The putParameter method of com.apple.MacOS.AppleEvent */
+ private static Method putParameter;
+
+ /** The sendNoReply method of com.apple.MacOS.AppleEvent */
+ private static Method sendNoReply;
+
+ /** Actually an MRJOSType pointing to the System Folder on a Macintosh */
+ private static Object kSystemFolderType;
+
+ /** The keyDirectObject AppleEvent parameter type */
+ private static Integer keyDirectObject;
+
+ /** The kAutoGenerateReturnID AppleEvent code */
+ private static Integer kAutoGenerateReturnID;
+
+ /** The kAnyTransactionID AppleEvent code */
+ private static Integer kAnyTransactionID;
+
+ /** The linkage object required for JDirect 3 on Mac OS X. */
+ private static Object linkage;
+
+ /** The framework to reference on Mac OS X */
+ private static final String JDirect_MacOSX = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox";
+
+ /** JVM constant for MRJ 2.0 */
+ private static final int MRJ_2_0 = 0;
+
+ /** JVM constant for MRJ 2.1 or later */
+ private static final int MRJ_2_1 = 1;
+
+ /** JVM constant for Java on Mac OS X 10.0 (MRJ 3.0) */
+ private static final int MRJ_3_0 = 3;
+
+ /** JVM constant for MRJ 3.1 */
+ private static final int MRJ_3_1 = 4;
+
+ /** JVM constant for any Windows NT JVM */
+ private static final int WINDOWS_NT = 5;
+
+ /** JVM constant for any Windows 9x JVM */
+ private static final int WINDOWS_9x = 6;
+
+ /** JVM constant for any other platform */
+ private static final int OTHER = -1;
+
+ /** The file type of the Finder on a Macintosh. Hardcoding
+ * "Finder" would keep non-U.S. English systems from working
+ * properly. */
+ private static final String FINDER_TYPE = "FNDR";
+
+ /** The creator code of the Finder on a Macintosh, which is needed
+ * to send AppleEvents to the application. */
+ private static final String FINDER_CREATOR = "MACS";
+
+ /** The name for the AppleEvent type corresponding to a GetURL event. */
+ private static final String GURL_EVENT = "GURL";
+
+ /** The first parameter that needs to be passed into
+ * Runtime.exec() to open the default web browser on Windows. */
+ private static final String FIRST_WINDOWS_PARAMETER = "/c";
+
+ /** The second parameter for Runtime.exec() on Windows. */
+ private static final String SECOND_WINDOWS_PARAMETER = "start";
+
+ /** The third parameter for Runtime.exec() on Windows. This is a
+ * "title" parameter that the command line expects. Setting this
+ * parameter allows URLs containing spaces to work. */
+ private static final String THIRD_WINDOWS_PARAMETER = "\"\"";
+
+ /** The shell parameters for Netscape that opens a given URL in an
+ * already-open copy of Netscape on many command-line systems. */
+ private static final String NETSCAPE_REMOTE_PARAMETER = "-remote";
+ //private static final String NETSCAPE_OPEN_PARAMETER_START = "'openURL(";
+ //private static final String NETSCAPE_OPEN_PARAMETER_END = ")'";
+ private static final String NETSCAPE_OPEN_PARAMETER_START = "openURL(";
+ private static final String NETSCAPE_OPEN_PARAMETER_END = ")";
+
+ /** The message from any exception thrown throughout the
+ * initialization process. */
+ private static String errorMessage;
+
+ /** An initialization block that determines the operating system
+ * and loads the necessary runtime data. */
+ static {
+ loadedWithoutErrors = true;
+ String osName = System.getProperty("os.name");
+ if (osName.startsWith("Mac OS")) {
+ String mrjVersion = System.getProperty("mrj.version");
+ String majorMRJVersion = mrjVersion.substring(0, 3);
+ try {
+ double version = Double.valueOf(majorMRJVersion).doubleValue();
+ if (version == 2) {
+ jvm = MRJ_2_0;
+ } else if (version >= 2.1 && version < 3) {
+ // Assume that all 2.x versions of MRJ work the
+ // same. MRJ 2.1 actually works via
+ // Runtime.exec() and 2.2 supports that but has an
+ // openURL() method as well that we currently
+ // ignore.
+ jvm = MRJ_2_1;
+ } else if (version == 3.0) {
+ jvm = MRJ_3_0;
+ } else if (version >= 3.1) {
+ // Assume that all 3.1 and later versions of MRJ
+ // work the same.
+ jvm = MRJ_3_1;
+ } else {
+ loadedWithoutErrors = false;
+ errorMessage = "Unsupported MRJ version: " + version;
+ }
+ } catch (NumberFormatException nfe) {
+ loadedWithoutErrors = false;
+ errorMessage = "Invalid MRJ version: " + mrjVersion;
+ }
+ } else if (osName.startsWith("Windows")) {
+ if (osName.indexOf("9") != -1) {
+ jvm = WINDOWS_9x;
+ } else {
+ jvm = WINDOWS_NT;
+ }
+ } else {
+ jvm = OTHER;
+ }
+
+ if (loadedWithoutErrors) { // if we haven't hit any errors yet
+ loadedWithoutErrors = loadClasses();
+ }
+ }
+
+ /** This class should be never be instantiated; this just ensures so. */
+ private BrowserLauncher() { }
+
+ /** Called by a static initializer to load any classes, fields,
+ * and methods required at runtime to locate the user's web
+ * browser.
+ * @return <code>true</code> if all intialization succeeded
+ * <code>false</code> if any portion of the initialization failed */
+ private static boolean loadClasses() {
+ switch (jvm) {
+ case MRJ_2_0:
+ try {
+ Class aeTargetClass = Class.forName("com.apple.MacOS.AETarget");
+ Class osUtilsClass = Class.forName("com.apple.MacOS.OSUtils");
+ Class appleEventClass = Class.forName
+ ("com.apple.MacOS.AppleEvent");
+ Class aeClass = Class.forName("com.apple.MacOS.ae");
+ aeDescClass = Class.forName("com.apple.MacOS.AEDesc");
+
+ aeTargetConstructor = aeTargetClass.getDeclaredConstructor
+ (new Class [] { int.class });
+ appleEventConstructor = appleEventClass.getDeclaredConstructor
+ (new Class[] { int.class, int.class, aeTargetClass,
+ int.class, int.class });
+ aeDescConstructor = aeDescClass.getDeclaredConstructor
+ (new Class[] { String.class });
+
+ makeOSType = osUtilsClass.getDeclaredMethod
+ ("makeOSType", new Class [] { String.class });
+ putParameter = appleEventClass.getDeclaredMethod
+ ("putParameter", new Class[] { int.class, aeDescClass });
+ sendNoReply = appleEventClass.getDeclaredMethod
+ ("sendNoReply", new Class[] { });
+
+ Field keyDirectObjectField = aeClass.getDeclaredField
+ ("keyDirectObject");
+ keyDirectObject = (Integer) keyDirectObjectField.get(null);
+ Field autoGenerateReturnIDField = appleEventClass
+ .getDeclaredField("kAutoGenerateReturnID");
+ kAutoGenerateReturnID = (Integer) autoGenerateReturnIDField
+ .get(null);
+ Field anyTransactionIDField = appleEventClass.getDeclaredField
+ ("kAnyTransactionID");
+ kAnyTransactionID = (Integer) anyTransactionIDField.get(null);
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ } catch (NoSuchFieldException nsfe) {
+ errorMessage = nsfe.getMessage();
+ return false;
+ } catch (IllegalAccessException iae) {
+ errorMessage = iae.getMessage();
+ return false;
+ }
+ break;
+ case MRJ_2_1:
+ try {
+ mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
+ mrjOSTypeClass = Class.forName("com.apple.mrj.MRJOSType");
+ Field systemFolderField = mrjFileUtilsClass.getDeclaredField
+ ("kSystemFolderType");
+ kSystemFolderType = systemFolderField.get(null);
+ findFolder = mrjFileUtilsClass.getDeclaredMethod
+ ("findFolder", new Class[] { mrjOSTypeClass });
+ getFileCreator = mrjFileUtilsClass.getDeclaredMethod
+ ("getFileCreator", new Class[] { File.class });
+ getFileType = mrjFileUtilsClass.getDeclaredMethod
+ ("getFileType", new Class[] { File.class });
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchFieldException nsfe) {
+ errorMessage = nsfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ } catch (SecurityException se) {
+ errorMessage = se.getMessage();
+ return false;
+ } catch (IllegalAccessException iae) {
+ errorMessage = iae.getMessage();
+ return false;
+ }
+ break;
+ case MRJ_3_0:
+ try {
+ Class linker = Class.forName("com.apple.mrj.jdirect.Linker");
+ Constructor constructor = linker.getConstructor
+ (new Class[]{ Class.class });
+ linkage = constructor.newInstance(new Object[]
+ { BrowserLauncher.class });
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ } catch (InvocationTargetException ite) {
+ errorMessage = ite.getMessage();
+ return false;
+ } catch (InstantiationException ie) {
+ errorMessage = ie.getMessage();
+ return false;
+ } catch (IllegalAccessException iae) {
+ errorMessage = iae.getMessage();
+ return false;
+ }
+ break;
+ case MRJ_3_1:
+ try {
+ mrjFileUtilsClass = Class.forName("com.apple.mrj.MRJFileUtils");
+ openURL = mrjFileUtilsClass.getDeclaredMethod
+ ("openURL", new Class[] { String.class });
+ } catch (ClassNotFoundException cnfe) {
+ errorMessage = cnfe.getMessage();
+ return false;
+ } catch (NoSuchMethodException nsme) {
+ errorMessage = nsme.getMessage();
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ /** Attempts to locate the default web browser on the local
+ * system. Caches results so it only locates the browser once *
+ * for each use of this class per JVM instance.
+ * @return The browser for the system. Note that this may not be
+ * what you would consider to be a standard web browser; instead,
+ * it's the application that gets called to open the default web
+ * browser. In some cases, this will be a non-String object that
+ * provides the means of calling the default browser. */
+ private static Object locateBrowser() {
+ if (browser != null) {
+ return browser;
+ }
+ switch (jvm) {
+ case MRJ_2_0:
+ try {
+ Integer finderCreatorCode = (Integer) makeOSType.invoke
+ (null, new Object[] { FINDER_CREATOR });
+ Object aeTarget = aeTargetConstructor.newInstance
+ (new Object[] { finderCreatorCode });
+ Integer gurlType = (Integer) makeOSType.invoke
+ (null, new Object[] { GURL_EVENT });
+ Object appleEvent = appleEventConstructor.newInstance
+ (new Object[] { gurlType, gurlType, aeTarget,
+ kAutoGenerateReturnID, kAnyTransactionID });
+ // Don't set browser = appleEvent because then the
+ // next time we call locateBrowser(), we'll get the
+ // same AppleEvent, to which we'll already have added
+ // the relevant parameter. Instead, regenerate the
+ // AppleEvent every time. There's probably a way to
+ // do this better; if any has any ideas, please let me
+ // know.
+ return appleEvent;
+ } catch (IllegalAccessException iae) {
+ browser = null;
+ errorMessage = iae.getMessage();
+ return browser;
+ } catch (InstantiationException ie) {
+ browser = null;
+ errorMessage = ie.getMessage();
+ return browser;
+ } catch (InvocationTargetException ite) {
+ browser = null;
+ errorMessage = ite.getMessage();
+ return browser;
+ }
+ case MRJ_2_1:
+ File systemFolder;
+ try {
+ systemFolder = (File) findFolder.invoke(null, new Object[]
+ { kSystemFolderType });
+ } catch (IllegalArgumentException iare) {
+ browser = null;
+ errorMessage = iare.getMessage();
+ return browser;
+ } catch (IllegalAccessException iae) {
+ browser = null;
+ errorMessage = iae.getMessage();
+ return browser;
+ } catch (InvocationTargetException ite) {
+ browser = null;
+ errorMessage = ite.getTargetException().getClass() + ": " +
+ ite.getTargetException().getMessage();
+ return browser;
+ }
+ String[] systemFolderFiles = systemFolder.list();
+ // Avoid a FilenameFilter because that can't be stopped mid-list
+ for(int i = 0; i < systemFolderFiles.length; i++) {
+ try {
+ File file = new File(systemFolder, systemFolderFiles[i]);
+ if (!file.isFile()) {
+ continue;
+ }
+ // We're looking for a file with a creator code of
+ // 'MACS' and a type of 'FNDR'. Only requiring
+ // the type results in non-Finder applications
+ // being picked up on certain Mac OS 9 systems,
+ // especially German ones, and sending a GURL
+ // event to those applications results in a logout
+ // under Multiple Users.
+ Object fileType = getFileType.invoke
+ (null, new Object[] { file });
+ if (FINDER_TYPE.equals(fileType.toString())) {
+ Object fileCreator = getFileCreator.invoke
+ (null, new Object[] { file });
+ if (FINDER_CREATOR.equals(fileCreator.toString())) {
+ browser = file.toString(); // Actually the
+ // Finder, but that's OK
+ return browser;
+ }
+ }
+ } catch (IllegalArgumentException iare) {
+ //WTF? browser = browser;
+ errorMessage = iare.getMessage();
+ return null;
+ } catch (IllegalAccessException iae) {
+ browser = null;
+ errorMessage = iae.getMessage();
+ return browser;
+ } catch (InvocationTargetException ite) {
+ browser = null;
+ errorMessage = ite.getTargetException().getClass() + ": "
+ + ite.getTargetException().getMessage();
+ return browser;
+ }
+ }
+ browser = null;
+ break;
+ case MRJ_3_0:
+ case MRJ_3_1:
+ browser = ""; // Return something non-null
+ break;
+ case WINDOWS_NT:
+ browser = "cmd.exe";
+ break;
+ case WINDOWS_9x:
+ browser = "command.com";
+ break;
+ case OTHER:
+ default:
+ browser = "netscape";
+ break;
+ }
+ return browser;
+ }
+
+ /** Attempts to open the default web browser to the given URL.
+ * @param url The URL to open
+ * @throws IOException If the web browser could not be located or
+ * does not run */
+ public static void openURL(String url) throws IOException {
+ if (!loadedWithoutErrors) {
+ throw new IOException("Exception in finding browser: "
+ + errorMessage);
+ }
+ Object browser = locateBrowser();
+ if (browser == null) {
+ throw new IOException("Unable to locate browser: " + errorMessage);
+ }
+
+ switch (jvm) {
+ case MRJ_2_0:
+ Object aeDesc = null;
+ try {
+ aeDesc = aeDescConstructor.newInstance(new Object[] { url });
+ putParameter.invoke(browser, new Object[]
+ { keyDirectObject, aeDesc });
+ sendNoReply.invoke(browser, new Object[] { });
+ } catch (InvocationTargetException ite) {
+ throw new IOException("InvocationTargetException while creating"
+ +" AEDesc: " + ite.getMessage());
+ } catch (IllegalAccessException iae) {
+ throw new IOException("IllegalAccessException while building "
+ + "AppleEvent: " + iae.getMessage());
+ } catch (InstantiationException ie) {
+ throw new IOException("InstantiationException while creating "
+ + "AEDesc: " + ie.getMessage());
+ } finally {
+ aeDesc = null; // Encourage it to get disposed if it
+ // was created
+ browser = null; // Ditto
+ }
+ break;
+ case MRJ_2_1:
+ Runtime.getRuntime().exec(new String[] { (String) browser, url } );
+ break;
+ case MRJ_3_0:
+ int[] instance = new int[1];
+ int result = ICStart(instance, 0);
+ if (result == 0) {
+ int[] selectionStart = new int[] { 0 };
+ byte[] urlBytes = url.getBytes();
+ int[] selectionEnd = new int[] { urlBytes.length };
+ result = ICLaunchURL(instance[0], new byte[] { 0 }, urlBytes,
+ urlBytes.length, selectionStart,
+ selectionEnd);
+ if (result == 0) {
+ // Ignore the return value; the URL was launched
+ // successfully regardless of what happens here.
+ ICStop(instance);
+ } else {
+ throw new IOException("Unable to launch URL: " + result);
+ }
+ } else {
+ throw new IOException("Unable to create an Internet Config "
+ + "instance: " + result);
+ }
+ break;
+ case MRJ_3_1:
+ try {
+ openURL.invoke(null, new Object[] { url });
+ } catch (InvocationTargetException ite) {
+ throw new IOException("InvocationTargetException while calling "
+ + "openURL: " + ite.getMessage());
+ } catch (IllegalAccessException iae) {
+ throw new IOException("IllegalAccessException while calling "
+ + "openURL: " + iae.getMessage());
+ }
+ break;
+ case WINDOWS_NT:
+ // Add quotes around the URL to allow ampersands and other special
+ // characters to work.
+ Process process = Runtime.getRuntime().exec(new String[]
+ { (String) browser, FIRST_WINDOWS_PARAMETER,
+ SECOND_WINDOWS_PARAMETER, THIRD_WINDOWS_PARAMETER,
+ '"' + url + '"' });
+ // This avoids a memory leak on some versions of Java on
+ // Windows. That's hinted at in
+ // <http://developer.java.sun.com/developer/qow/archive/68/>.
+ try {
+ process.waitFor();
+ process.exitValue();
+ } catch (InterruptedException ie) {
+ throw new IOException("InterruptedException while launching "
+ + "browser: " + ie.getMessage());
+ }
+ break;
+ case WINDOWS_9x:
+ // Add quotes around the URL to allow ampersands and other special
+ // characters to work.
+ // Note: windows 98 doesn't expect the THIRD_WINDOWS_PARAMETER for
+ // its title.
+ process = Runtime.getRuntime().exec(new String[]
+ { (String) browser, FIRST_WINDOWS_PARAMETER,
+ SECOND_WINDOWS_PARAMETER, '"' + url + '"' });
+ // This avoids a memory leak on some versions of Java on
+ // Windows. That's hinted at in
+ // <http://developer.java.sun.com/developer/qow/archive/68/>.
+ try {
+ process.waitFor();
+ process.exitValue();
+ } catch (InterruptedException ie) {
+ throw new IOException("InterruptedException while launching "
+ + "browser: " + ie.getMessage());
+ }
+ break;
+ case OTHER:
+ // Assume that we're on Unix and that Netscape is installed
+
+ // First, attempt to open the URL in a currently running
+ // session of Netscape
+ process = Runtime.getRuntime().exec(new String[]
+ {(String)browser, NETSCAPE_REMOTE_PARAMETER,
+ NETSCAPE_OPEN_PARAMETER_START + url +
+ NETSCAPE_OPEN_PARAMETER_END });
+ try {
+ int exitCode = process.waitFor();
+ if (exitCode != 0) { // if the command had an error
+ Runtime.getRuntime().exec(new String[]
+ { (String) browser, url } );
+ } else if(process.getErrorStream() != null) {
+ // Netscape may not be open, so the command may not have an
+ // error, it just wouldn't have a process to attach to...
+ BufferedReader reader = new BufferedReader
+ (new InputStreamReader(process.getErrorStream()));
+ String errorStr = reader.readLine();
+
+ if ( errorStr != null ) {
+ // Command failed, start up the browser
+ process = Runtime.getRuntime().exec(new String[] {
+ (String) browser, url });
+ }
+ }
+ } catch (InterruptedException ie) {
+ throw new IOException("InterruptedException while launching "
+ + "browser: " + ie.getMessage());
+ }
+ break;
+ default:
+ // This should never occur, but if it does, we'll try the
+ // simplest thing possible
+ Runtime.getRuntime().exec(new String[] { (String) browser, url });
+ break;
+ }
+ }
+
+ /** Methods required for Mac OS X. The presence of native methods
+ * does not cause any problems on other platforms. */
+ private native static int ICStart(int[] instance, int signature);
+ private native static int ICStop(int[] instance);
+ private native static int ICLaunchURL
+ (int instance, byte[] hint, byte[] data, int len, int[] selectionStart,
+ int[] selectionEnd);
+}
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java
new file mode 100644
index 0000000..8da71fe
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoder.java
@@ -0,0 +1,114 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Intersect Software Corporation
+
+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.xml;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import net.wotonomy.foundation.internal.WotonomyException;
+import net.wotonomy.foundation.xml.XMLDecoder;
+
+import org.xml.sax.SAXException;
+
+/**
+* An implementation of XMLDecoder that reads objects from
+* XMLRPC format.
+* This implementation is not thread-safe, so a new instances
+* should be created to accomodate multiple threads.
+*/
+public class XMLRPCDecoder implements XMLDecoder
+{
+ XMLRPCDecoderHelper helper = new XMLRPCDecoderHelper();
+
+ /**
+ * Decodes an object in XML-RPC format from the specified input stream.
+ * @param anInputStream The input stream from which to read.
+ * The stream will be read fully.
+ * @param aDescription A description to accompany error messages
+ * for the stream, typically a file name.
+ * @param aURL A URL against which relative references within the
+ * XML will be resolved.
+ * @return The object that was constructed from the XML content,
+ * or null if no object could be constructed.
+ */
+ public Object decode(
+ InputStream anInputStream, String aDescription, URL aURL )
+ {
+ Object result = null;
+
+ try {
+ SAXParser sparser = SAXParserFactory.newInstance().newSAXParser();
+ sparser.parse(anInputStream,helper,aURL.toExternalForm());
+ result = helper.getResult();
+ } catch (ParserConfigurationException e) {
+ throw new WotonomyException("Problem in parser configuration", e );
+ } catch (IOException e) {
+ throw new WotonomyException("IOException thrown while parsing", e );
+ } catch (SAXException e) {
+ throw new WotonomyException("SAXException thrown while parsing", e );
+ }
+ helper.reset();
+ return result;
+ }
+
+ /**
+ * Decodes an XML-RPC message from the specified input stream.
+ * Stand-alone values not wrapped in "methodCall" or "param"
+ * tags will be treated as a response.
+ * @param anInputStream The input stream from which to read.
+ * The stream will be read fully.
+ * @param aReceiver an XMLRPCReceiver that will be invoked with
+ * the appropriate method: request, response, or fault.
+ */
+ public void decode(
+ InputStream anInputStream, XMLRPCReceiver aReceiver )
+ {
+ try
+ {
+ SAXParser sparser = SAXParserFactory.newInstance().newSAXParser();
+ sparser.parse(anInputStream,helper);
+
+ if ( helper.isRequest() )
+ {
+ aReceiver.request( helper.getMethodName(), helper.getParameters() );
+ }
+ else
+ if ( helper.isFault() )
+ {
+ aReceiver.fault( helper.getFaultCode(), helper.getFaultString() );
+ }
+ else // all else is considered a response
+ {
+ aReceiver.response( helper.getResult() );
+ }
+ } catch (ParserConfigurationException e) {
+ throw new WotonomyException("Problem in parser configuration", e );
+ } catch (IOException e) {
+ throw new WotonomyException("IOException thrown while parsing", e );
+ } catch (SAXException e) {
+ throw new WotonomyException("SAXException thrown while parsing", e );
+ }
+ helper.reset();
+ }
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java
new file mode 100644
index 0000000..2368672
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCDecoderHelper.java
@@ -0,0 +1,534 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Michael Powers
+
+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.xml;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+
+import net.wotonomy.foundation.internal.Introspector;
+import net.wotonomy.foundation.internal.ValueConverter;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+* Used by XMLDecoder to implement the necessary interfaces
+* required by the jclark xp parser.
+* This class is not thread safe.
+*/
+class XMLRPCDecoderHelper extends DefaultHandler
+{
+ protected final Object nilMarker = new Object();
+ protected Stack valueStack;
+ protected String methodName;
+ protected int faultCode;
+ protected String faultString;
+ protected List parameters;
+ protected StringBuffer cdataBuffer;
+
+
+
+ public XMLRPCDecoderHelper()
+ {
+ valueStack = new Stack();
+ parameters = new LinkedList();
+ cdataBuffer = new StringBuffer();
+ reset();
+ }
+
+ public void reset()
+ {
+ valueStack.clear();
+ parameters.clear();
+ cdataBuffer.setLength( 0 );
+ methodName = null;
+ faultCode = 0;
+ faultString = null;
+ }
+
+ public boolean isRequest()
+ {
+ return ( methodName != null );
+ }
+
+ public boolean isResponse()
+ {
+ return ( methodName == null );
+ }
+
+ public boolean isFault()
+ {
+ // faults are responses
+ return ( isResponse() ) && ( faultString != null );
+ }
+
+ public int getFaultCode()
+ {
+ return faultCode;
+ }
+
+ public String getFaultString()
+ {
+ return faultString;
+ }
+
+ public String getMethodName()
+ {
+ return methodName;
+ }
+
+ public Object[] getParameters()
+ {
+ return parameters.toArray();
+ }
+
+ public void endDocument() throws SAXException {
+ // TODO Auto-generated method stub
+ super.endDocument();
+ }
+
+ public void startDocument() throws SAXException {
+ // TODO Auto-generated method stub
+ super.startDocument();
+ reset();
+ }
+
+ public Object getResult()
+ {
+ if ( valueStack.empty() ) return null;
+ Object result = valueStack.peek();
+ if ( result == nilMarker ) result = null;
+ return result;
+ }
+
+
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ super.startElement(uri, localName, qName, attributes);
+ if ( XMLRPCEncoder.VALUE.equals( localName ) )
+ {
+ ValueMarker marker = new ValueMarker();
+ String classname = attributes.getValue( uri, XMLRPCEncoder.CLASS );
+ if ( classname != null )
+ {
+ try
+ {
+ Class c = Class.forName( classname );
+ if ( c != null )
+ {
+ marker.setMarkerClass( c );
+ }
+ }
+ catch ( Exception exc )
+ {
+ System.out.println( "XMLRPCDecoderHelper.startElement: " +
+ "Can't find class: " + classname );
+ }
+ }
+ valueStack.push( marker );
+ }
+ }
+
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ super.characters(ch, start, length);
+ char[] someChars = new char[((start+length)<=ch.length) ? length : ch.length-start];
+ for (int i = 0; i < someChars.length; i++ ) {
+ someChars[i] = ch[start+i];
+ }
+ cdataBuffer.append(someChars);
+ }
+
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ super.endElement(uri, localName, qName);
+
+ // if any cdata is buffered or if string value
+ if ( ( XMLRPCEncoder.STRING.equals( localName ) )
+ || ( cdataBuffer.length() > 0 ) )
+ {
+ // push value on the stack
+ valueStack.push( cdataBuffer.toString() );
+ cdataBuffer.setLength( 0 );
+ }
+
+ if ( XMLRPCEncoder.VALUE.equals( localName ) )
+ {
+ Object value = valueStack.pop();
+ try
+ {
+// ValueMarker marker = (ValueMarker) valueStack.pop();
+ ValueMarker marker = null;
+ Object markerValue = valueStack.pop();
+ if ( markerValue instanceof ValueMarker )
+ {
+ marker = (ValueMarker) markerValue;
+ }
+ else
+ {
+ throw new WotonomyException( "Expected value marker, found"
+ + markerValue.getClass() + " : " + markerValue );
+ }
+
+//System.out.println( "getMarkerClass: " + marker.getMarkerClass() + " : " + value );
+//System.out.println( valueStack );
+ if ( marker.getMarkerClass() != null )
+ {
+ // apply introspection
+ if ( value instanceof Map )
+ {
+ Map map = (Map)value;
+ Map.Entry entry;
+ value = marker.getMarkerClass().newInstance();
+ Iterator it = map.entrySet().iterator();
+ Object entryValue;
+ while( it.hasNext() )
+ {
+ entry = (Map.Entry) it.next();
+ entryValue = entry.getValue();
+ if ( entryValue == nilMarker ) entryValue = null;
+ Introspector.set(
+ value, entry.getKey().toString(), entryValue );
+ }
+ }
+ if ( ! ( value.getClass().equals( marker.getMarkerClass() ) ) )
+ {
+ Object converted =
+ ValueConverter.convertObjectToClass(
+ value, marker.getMarkerClass() );
+ if ( converted != null )
+ {
+ value = converted;
+ }
+ }
+ }
+ }
+ catch ( Exception exc )
+ {
+ // fall back on unconverted value
+ }
+
+ valueStack.push( value );
+// System.out.println( "convertedValue: " + value + "("+ value.getClass() +")" );
+ }
+ else
+ if ( XMLRPCEncoder.MEMBER.equals( localName ) ) // Map.Entry
+ {
+ // leave key and value to be handled by struct
+ }
+ else
+ if ( XMLRPCEncoder.STRUCT.equals( localName ) ) // write Entries to map or object
+ {
+ // write values to array (reverse the order)
+ Object value;
+ Map map = new HashMap();
+ while ( ( ! valueStack.empty() )
+ && ( ! ( valueStack.peek() instanceof ValueMarker ) ) )
+ {
+ value = valueStack.pop();
+ map.put( valueStack.pop(), value );
+ }
+ // push the list on the stack
+ valueStack.push( map );
+ }
+ else
+ if ( XMLRPCEncoder.ARRAY.equals( localName ) )
+ {
+//System.out.println( "ended ARRAY: " + valueStack.size() );
+ // write values to array (reverse the order)
+ Object value;
+ LinkedList list = new LinkedList();
+ while ( ( ! valueStack.empty() )
+ && ( ! ( valueStack.peek() instanceof ValueMarker ) ) )
+ {
+ value = valueStack.pop();
+ if ( value == nilMarker ) value = null;
+ list.addFirst( value );
+ }
+ // push the list on the stack
+ valueStack.push( list );
+ }
+ else
+ if ( XMLRPCEncoder.INT.equals( localName ) )
+ {
+ Object value = valueStack.pop();
+ try
+ {
+ valueStack.push(
+ new Integer( value.toString() ) );
+ }
+ catch ( NumberFormatException exc )
+ {
+ throw new WotonomyException(
+ "Invalid double format: " + value.toString() );
+ }
+ }
+ else
+ if ( XMLRPCEncoder.I4.equals( localName ) )
+ {
+ Object value = valueStack.pop();
+ try
+ {
+ valueStack.push(
+ new Integer( value.toString() ) );
+ }
+ catch ( NumberFormatException exc )
+ {
+ throw new WotonomyException(
+ "Invalid double format: " + value.toString() );
+ }
+ }
+ else
+ if ( XMLRPCEncoder.NIL.equals( localName ) )
+ {
+ valueStack.push( nilMarker );
+ }
+ else
+ if ( XMLRPCEncoder.DOUBLE.equals( localName ) )
+ {
+ Object value = valueStack.pop();
+ try
+ {
+ valueStack.push(
+ new Double( value.toString() ) );
+ }
+ catch ( NumberFormatException exc )
+ {
+ throw new WotonomyException(
+ "Invalid double format: " + value.toString() );
+ }
+ }
+ else
+ if ( XMLRPCEncoder.DATE.equals( localName ) )
+ {
+ Object value = valueStack.pop();
+ try
+ {
+ valueStack.push(
+ XMLRPCEncoder.DATEFORMAT8601.parseObject(
+ value.toString() ) );
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException(
+ "Invalid date format: " + value );
+ }
+ }
+ else
+ if ( XMLRPCEncoder.BOOLEAN.equals( localName ) )
+ {
+ Object value = valueStack.pop();
+ if ( XMLRPCEncoder.TRUE.equals( value ) )
+ {
+ valueStack.push( Boolean.TRUE );
+ }
+ else
+ if ( XMLRPCEncoder.FALSE.equals( value ) )
+ {
+ valueStack.push( Boolean.FALSE );
+ }
+ else
+ {
+ throw new WotonomyException(
+ "Invalid boolean format: " + value );
+ }
+ }
+ else
+ if ( XMLRPCEncoder.BASE64.equals( localName ) )
+ {
+ throw new WotonomyException( "Not implemented yet." );
+ }
+ else
+ if ( XMLRPCEncoder.FAULT.equals( localName ) )
+ {
+ Map faultMap = (Map) valueStack.pop();
+ try
+ {
+ faultCode = ((Integer)
+ faultMap.get( XMLRPCEncoder.FAULTCODE )).intValue();
+ faultString = (String) faultMap.get( XMLRPCEncoder.FAULTSTRING );
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException(
+ "Invalid fault format: " + faultMap );
+ }
+ }
+ else
+ if ( XMLRPCEncoder.METHODNAME.equals( localName ) )
+ {
+ methodName = (String) valueStack.pop();
+ }
+ else
+ if ( XMLRPCEncoder.PARAM.equals( localName ) )
+ {
+ //NOTE: this leaves the parameter on the stack
+ parameters.add( getResult() );
+ }
+ }
+
+
+
+ public void endPrefixMapping(String prefix) throws SAXException {
+ // TODO Auto-generated method stub
+ super.endPrefixMapping(prefix);
+ }
+
+ public void error(SAXParseException e) throws SAXException {
+ // TODO Auto-generated method stub
+ super.error(e);
+ }
+
+ public void fatalError(SAXParseException e) throws SAXException {
+ // TODO Auto-generated method stub
+ super.fatalError(e);
+ }
+
+ public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
+ // TODO Auto-generated method stub
+ super.ignorableWhitespace(ch, start, length);
+ }
+
+ public void notationDecl(String name, String publicId, String systemId) throws SAXException {
+ // TODO Auto-generated method stub
+ super.notationDecl(name, publicId, systemId);
+ }
+
+ public void processingInstruction(String target, String data) throws SAXException {
+ // TODO Auto-generated method stub
+ super.processingInstruction(target, data);
+ }
+
+ public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
+ // NOTE: Sun accepted an incompatible api difference. The (false) should be tossed by hotspot
+ try {
+ if (false) throw new IOException("Fake exception to make it compile in both 1.4 and 1.5");
+ return super.resolveEntity(publicId, systemId);
+ } catch (IOException e) {
+ throw new SAXException(e.getClass().getName() + " thrown while resolving entity.",e);
+ }
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ // TODO Auto-generated method stub
+ super.setDocumentLocator(locator);
+ }
+
+ public void skippedEntity(String name) throws SAXException {
+ // TODO Auto-generated method stub
+ super.skippedEntity(name);
+ }
+
+
+
+ public void startPrefixMapping(String prefix, String uri) throws SAXException {
+ // TODO Auto-generated method stub
+ super.startPrefixMapping(prefix, uri);
+ }
+
+ public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException {
+ // TODO Auto-generated method stub
+ super.unparsedEntityDecl(name, publicId, systemId, notationName);
+ }
+
+ public void warning(SAXParseException e) throws SAXException {
+ // TODO Auto-generated method stub
+ super.warning(e);
+ }
+
+
+ // marker class
+
+ private class ValueMarker
+ {
+ private Class theClass;
+
+ public ValueMarker()
+ {
+ theClass = null;
+ }
+
+ public void setMarkerClass( Class aClass )
+ {
+ theClass = aClass;
+ }
+
+ public Class getMarkerClass()
+ {
+ return theClass;
+ }
+
+ public String toString()
+ {
+ return "[ValueMarker: " + theClass + "]";
+ }
+ }
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/19 01:44:03 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.7 2003/08/06 23:07:53 chochos
+ * general code cleanup (mostly, removing unused imports)
+ *
+ * Revision 1.6 2001/03/03 15:16:35 mpowers
+ * Fixed bug in decoding empty strings: string handler did nothing and
+ * empty cdatas were ignored, so no value was placed on the stack.
+ *
+ * Revision 1.5 2001/02/17 16:52:06 mpowers
+ * Changes in imports to support building with jdk1.1 collections.
+ *
+ * Revision 1.4 2001/02/09 15:51:39 mpowers
+ * Fixed a pernicious bug: I was using the character event entirely
+ * incorrectly, but the problem only exhibited itself with large data
+ * and even then only randomly. Now using a string buffer.
+ *
+ * Revision 1.3 2001/02/07 19:24:28 mpowers
+ * Moved XML classes to separate package.
+ *
+ * Revision 1.2 2001/02/06 14:34:23 mpowers
+ * Forgot to rename the package declarations.
+ *
+ * Revision 1.1 2001/02/06 14:31:19 mpowers
+ * Moving XML utilities from util to xml package.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:52:39 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:48 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java
new file mode 100644
index 0000000..3a63d45
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCEncoder.java
@@ -0,0 +1,526 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Intersect Software Corporation
+
+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.xml;
+
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.text.Format;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import net.wotonomy.foundation.internal.Introspector;
+import net.wotonomy.foundation.internal.WotonomyException;
+import net.wotonomy.foundation.xml.XMLEncoder;
+
+import org.dom4j.Element;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.XMLWriter;
+import org.dom4j.util.NonLazyElement;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+* An implementation of XMLEncoder that serializes objects
+* into XMLRPC format, which is found at http://xmlrpc.com/spec.
+* We extend that standard only in that we add a "class"
+* attribute to the "value" tag, so that a java-based decoder
+* can more closely reconstruct the original java data structure.
+* The class attribute can be safely ignored by clients.
+* This implementation is not thread-safe, so a new instances
+* should be created to accomodate multiple threads.
+*/
+public class XMLRPCEncoder implements XMLEncoder
+{
+ public static final String METHODCALL = "methodCall";
+ public static final String METHODNAME = "methodName";
+ public static final String METHODRESPONSE = "methodResponse";
+ public static final String PARAMS = "params";
+ public static final String PARAM = "param";
+ public static final String FAULT = "fault";
+ public static final String FAULTCODE = "faultCode";
+ public static final String FAULTSTRING = "faultString";
+
+ public static final String VALUE = "value";
+ public static final String CLASS = "class";
+
+ public static final String STRUCT = "struct";
+ public static final String MEMBER = "member";
+ public static final String NAME = "name";
+
+ public static final String ARRAY = "array";
+ public static final String DATA = "data";
+
+ public static final String NIL = "nil";
+ public static final String INT = "int";
+ public static final String I4 = "i4";
+ public static final String BOOLEAN = "boolean";
+ public static final String STRING = "string";
+ public static final String DOUBLE = "double";
+ public static final String DATE = "dateTime.iso8601";
+ public static final String BASE64 = "base64";
+
+ public static final String TRUE = "1";
+ public static final String FALSE = "0";
+
+ public static final Format DATEFORMAT8601 =
+ new SimpleDateFormat( "yyyyMMdd'T'HHmmss" );
+
+ /**
+ * Encodes an object to the specified output stream as XML.
+ * @param anObject The object to be serialized to XML format.
+ * @param anOutputStream The output stream to which the object
+ * will be written.
+ */
+ public void encode( Object anObject, OutputStream anOutputStream )
+ {
+ try
+ {
+
+ //XMLWriter writer = new UTF8XMLWriter( anOutputStream );
+ RPCXMLWriter writer = new RPCXMLWriter(anOutputStream,OutputFormat.createCompactFormat());
+ writeValueToXMLWriter( anObject, writer );
+ writer.flush();
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+ }
+
+ /**
+ * Encodes a method request in XML-RPC format in a "methodCall" tag,
+ * and writes the XML to the specified output stream.
+ * This method only writes XML: the caller is responsible for
+ * generating the appropriate header, if any, which should set
+ * the content type as "text/xml" and the content length as appropriate.
+ * The caller is also responsible for writing the xml version tag.
+ * @param aMethodName The method name to appear in the "methodName" tag.
+ * @param aParameterArray An array of objects, each of which will be
+ * encoded as values enclosed in a "param" tag, all of which will be
+ * enclosed in a "params" tag.
+ * @param anOutputStream The stream to which the XML will be written.
+ */
+ public void encodeRequest(
+ String aMethodName, Object[] aParameterArray,
+ OutputStream anOutputStream )
+ {
+ try
+ {
+ RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat());
+ writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
+ writer.startElement( METHODCALL );
+ writer.startElement( METHODNAME );
+ writer.write( aMethodName );
+ writer.endElement( METHODNAME );
+ writer.startElement( PARAMS );
+ for ( int i = 0; i < aParameterArray.length; i++ )
+ {
+ writer.startElement( PARAM );
+ writeValueToXMLWriter( aParameterArray[i], writer );
+ writer.endElement( PARAM );
+ }
+ writer.endElement( PARAMS );
+ writer.endElement( METHODCALL );
+ writer.flush();
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+
+ //TODO: should this return the content-length?
+ }
+
+ /**
+ * Encodes a method response in XML-RPC format in a "methodResponse" tag,
+ * and writes the XML to the specified output stream.
+ * This method only writes XML: the caller is responsible for
+ * generating the appropriate header, if any, which should set
+ * the content type as "text/xml" and the content length as appropriate.
+ * The caller is also responsible for writing the xml version tag.
+ * @param aResult A object which will be
+ * encoded as values enclosed in a "param" tag, all of which will be
+ * enclosed in a "params" tag.
+ * @param anOutputStream The stream to which the XML will be written.
+ */
+ public void encodeResponse(
+ Object aResult, OutputStream anOutputStream )
+ {
+ try
+ {
+ RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() );
+ writer.processingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" );
+ writer.startElement( METHODRESPONSE );
+ writer.startElement( PARAMS );
+ writer.startElement( PARAM );
+ writeValueToXMLWriter( aResult, writer );
+ writer.endElement( PARAM );
+ writer.endElement( PARAMS );
+ writer.endElement( METHODRESPONSE );
+ writer.flush();
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+
+ //TODO: should this return the content-length?
+ }
+
+ /**
+ * Encodes a fault response in XML-RPC format in a "methodResponse" tag,
+ * and writes the XML to the specified output stream.
+ * This method only writes XML: the caller is responsible for first
+ * generating the appropriate header, if any, which should set
+ * the content type as "text/xml" and the content length as appropriate.
+ * The caller is also responsible for writing the xml version tag.
+ * @param aFaultCode An application-defined error code.
+ * @param aFaultString A human-readable error description.
+ * @param anOutputStream The stream to which the XML will be written.
+ */
+ public void encodeFault(
+ int aFaultCode, String aFaultString, OutputStream anOutputStream )
+ {
+ try
+ {
+ RPCXMLWriter writer = new RPCXMLWriter( anOutputStream, OutputFormat.createCompactFormat() );
+ writer.processingInstruction( "xml", "version=\"1.0\"" );
+ writer.startElement( METHODRESPONSE );
+ writer.startElement( FAULT );
+ writer.startElement( VALUE );
+ writer.startElement( STRUCT );
+
+ writer.startElement( MEMBER );
+ writer.startElement( NAME );
+ writer.write( FAULTCODE );
+ writer.endElement( NAME );
+ writer.startElement( VALUE );
+ writer.startElement( INT );
+ writer.write( new Integer( aFaultCode ).toString() );
+ writer.endElement( INT );
+ writer.endElement( VALUE );
+ writer.endElement( MEMBER );
+
+ writer.startElement( MEMBER );
+ writer.startElement( NAME );
+ writer.write( FAULTSTRING );
+ writer.endElement( NAME );
+ writer.startElement( VALUE );
+ writer.startElement( STRING );
+ writer.write( aFaultString );
+ writer.endElement( STRING );
+ writer.endElement( VALUE );
+ writer.endElement( MEMBER );
+
+ writer.endElement( STRUCT );
+ writer.endElement( VALUE );
+ writer.endElement( FAULT );
+ writer.endElement( METHODRESPONSE );
+ writer.flush();
+ }
+ catch ( Exception exc )
+ {
+ throw new WotonomyException( exc );
+ }
+
+ //TODO: should this return the content-length?
+ }
+
+ /**
+ * Performs the actual writing of the file to XML.
+ */
+ private void writeValueToXMLWriter(
+ Object anObject, RPCXMLWriter writer )
+ {
+ try
+ {
+
+
+ if ( anObject == null )
+ {
+ writer.startElement( VALUE );
+ // write nil for null
+ Element nill = new NonLazyElement(NIL);
+ writer.write(nill);
+ }
+ else
+ if ( anObject instanceof Collection )
+ {
+ // write class so we can restore if possible
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ // write items in the order we get them from the iterator
+
+ writer.startElement( ARRAY );
+ writer.startElement( DATA );
+ Iterator it = ((Collection)anObject).iterator();
+ while ( it.hasNext() )
+ {
+ writeValueToXMLWriter( it.next(), writer );
+ }
+ writer.endElement( DATA );
+ writer.endElement( ARRAY );
+
+ }
+ else
+ if ( anObject instanceof Map )
+ {
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ // write items in the order we get them from the iterator
+ //FIXME: The method-based properties are being ignored!
+
+ Map.Entry entry;
+ writer.startElement( STRUCT );
+ writer.startElement( MEMBER );
+ Iterator it = ((Map)anObject).entrySet().iterator();
+ while ( it.hasNext() )
+ {
+ entry = (Map.Entry) it.next();
+ writer.startElement( NAME );
+ writeValueToXMLWriter( entry.getKey(), writer );
+ writer.endElement( NAME );
+ writeValueToXMLWriter( entry.getValue(), writer );
+ }
+ writer.endElement( MEMBER );
+ writer.endElement( STRUCT );
+ }
+ else // not a collection
+ {
+ // check for primitive types
+ if ( anObject instanceof String )
+ {
+ writer.startElement( VALUE );
+
+ writer.startElement( STRING );
+ writer.write( anObject.toString() );
+ writer.endElement( STRING );
+ }
+ else
+ if ( anObject instanceof StringBuffer )
+ {
+ // write class so we can restore if possible
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ writer.startElement( STRING );
+ writer.write( anObject.toString() );
+ writer.endElement( STRING );
+ }
+ else
+ if ( anObject instanceof Number )
+ {
+ // write class so we can restore if possible
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ if ( ( anObject instanceof Double )
+ || ( anObject instanceof Float ) )
+ {
+ writer.startElement( DOUBLE );
+ writer.write( anObject.toString() );
+ writer.endElement( DOUBLE );
+ }
+ else
+ {
+ writer.startElement( INT );
+ writer.write( anObject.toString() );
+ writer.endElement( INT );
+ }
+ }
+ else
+ if ( anObject instanceof Date )
+ {
+ // write class so we can restore if possible
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ writer.startElement( DATE );
+ writer.write( DATEFORMAT8601.format( anObject ) );
+ writer.endElement( DATE );
+ }
+ else
+ if ( anObject instanceof Boolean )
+ {
+ writer.startElement( BOOLEAN );
+ if ( ((Boolean)anObject).booleanValue() )
+ {
+ writer.write( "1" );
+ }
+ else
+ {
+ writer.write( "0" );
+ }
+ writer.endElement( BOOLEAN );
+ }
+ else
+ if ( anObject.getClass().isArray() )
+ {
+ // write class so we can restore if possible
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ writer.startElement( ARRAY );
+ writer.startElement( DATA );
+
+ int length = Array.getLength( anObject );
+ for ( int i = 0; i < length; i++ )
+ {
+ writeValueToXMLWriter( Array.get( anObject, i ), writer );
+ }
+
+ writer.endElement( DATA );
+ writer.endElement( ARRAY );
+ }
+ else // not primitive or collection, treat as struct
+ {
+ // write class so we can restore if possible
+ AttributesImpl a = new AttributesImpl();
+ a.addAttribute(null, CLASS, null, null, anObject.getClass().getName() );
+
+ writer.startElement( VALUE, a );
+
+ List readProperties = new ArrayList();
+ String[] read = Introspector.getReadPropertiesForObject( anObject );
+ for ( int i = 0; i < read.length; i++ )
+ {
+ readProperties.add( read[i] );
+ }
+
+ List properties = new ArrayList();
+ String[] write = Introspector.getWritePropertiesForObject( anObject );
+ for ( int i = 0; i < write.length; i++ )
+ {
+ properties.add( write[i] );
+ }
+
+ // only use readable properties
+ properties.retainAll( readProperties );
+
+// if ( properties.size() > 0 )
+// {
+ String key;
+ Object value;
+ Iterator it = properties.iterator();
+ writer.startElement( STRUCT );
+ while ( it.hasNext() )
+ {
+ key = (String) it.next();
+ value = Introspector.get( anObject, key );
+
+ writer.startElement( MEMBER );
+ writer.startElement( NAME );
+ writer.write( key );
+ writer.endElement( NAME );
+ writeValueToXMLWriter( value, writer );
+ writer.endElement( MEMBER );
+ }
+ writer.endElement( STRUCT );
+/*
+ }
+ else // no properties - write a converted string
+ {
+ writer.startElement( STRING );
+ Object converted =
+ ValueConverter.convertObjectToClass( anObject, String.class );
+ if ( converted != null )
+ {
+ writer.write( converted.toString() );
+ }
+ else
+ {
+ writer.write( anObject.toString() );
+ }
+ writer.endElement( STRING );
+ }
+*/
+ }
+ }
+
+ writer.endElement( VALUE );
+ }
+ catch ( Exception exc )
+ {
+ System.err.println( "XMLFileSoup.writeValueToXMLWriter: " + exc );
+ exc.printStackTrace();
+ }
+
+ }
+/*
+ public static void main( String[] argv )
+ {
+ System.out.println( "<test>" );
+ XMLRPCEncoder encoder = new XMLRPCEncoder();
+ encoder.encodeRequest( "systemObject.test", new Object[] {
+ new net.wotonomy.test.TestObject(),
+ new net.wotonomy.test.TestObject(),
+ new net.wotonomy.test.TestObject() }, System.out );
+ System.out.println();
+ System.out.println();
+ encoder.encodeResponse( new net.wotonomy.test.TestObject(), System.out );
+ System.out.println();
+ System.out.println();
+ encoder.encodeFault( -1, "This is a fault.", System.out );
+ System.out.println();
+ System.out.println();
+ System.out.println( "</test>" );
+ }
+*/
+
+ private class RPCXMLWriter extends XMLWriter {
+
+ public RPCXMLWriter(OutputStream arg0, OutputFormat arg1) throws UnsupportedEncodingException {
+ super(arg0, arg1);
+ }
+
+ public void endElement(String localname) throws SAXException {
+ super.endElement(null, localname, null);
+ }
+
+ public void startElement(String localname) throws SAXException {
+ this.startElement(localname, null);
+ }
+ public void startElement(String localname, Attributes attributes) throws SAXException {
+ this.startElement(null, localname, null, attributes);
+ }
+
+ }
+
+}
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java
new file mode 100644
index 0000000..48c9e41
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCReceiver.java
@@ -0,0 +1,72 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2000 Intersect Software Corporation
+
+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.xml;
+
+/**
+* A call-back interface that receives an XML-RPC transaction message.
+* Used by XMLRPCDecoder to return values from a message.
+*/
+public interface XMLRPCReceiver
+{
+ /**
+ * Receives an XML-RPC request.
+ * @param aMethodName The method name of the request.
+ * @param aParameterArray The objects contained in the request, in order.
+ */
+ void request( String aMethodName, Object[] aParameterArray );
+
+ /**
+ * Receives an XML-RPC response.
+ * @param aResult The object contained in the response.
+ */
+ void response( Object aResult );
+
+ /**
+ * Receives an XML-RPC fault response.
+ * @param aFaultCode The fault code contained in the response.
+ * @param aFaultString The fault string contained in the response.
+ */
+ void fault( int aFaultCode, String aFaultString );
+}
+
+/*
+ * $Log$
+ * Revision 1.1 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.2 2001/02/06 14:34:23 mpowers
+ * Forgot to rename the package declarations.
+ *
+ * Revision 1.1 2001/02/06 14:31:19 mpowers
+ * Moving XML utilities from util to xml package.
+ *
+ * Revision 1.1.1.1 2000/12/21 15:52:44 mpowers
+ * Contributing wotonomy.
+ *
+ * Revision 1.2 2000/12/20 16:25:49 michael
+ * Added log to all files.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java
new file mode 100644
index 0000000..7c1f104
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCSelector.java
@@ -0,0 +1,238 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Intersect Software Corporation
+
+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.xml;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* An NSSelector customized to invoke methods with XMLRPC
+* when a URL is passed in as the object to the invoke() method.
+* The method name and parameters will be marshalled and sent
+* as an XMLRPC request to the host specified by the URL. <br><br>
+*
+* To use this class simply as an XMLRPC client, just call
+* invoke() with a URL referencing the XMLRPC server and an
+* optional array of parameters.
+*
+* @author michael@mpowers.net
+* @author $Author: cgruber $
+* @version $Revision: 905 $
+*/
+public class XMLRPCSelector extends NSSelector
+{
+ private boolean copyStream = false;
+
+ /**
+ * Constructor specifying a method name.
+ */
+ public XMLRPCSelector (String aMethodName)
+ {
+ super( aMethodName, EMPTY_CLASS_ARRAY );
+ }
+
+ /**
+ * Constructor specifying a method name and an array of parameter types.
+ * When accessing XMLRPC servers, invoke() does require that the
+ * specified objects match the types in the parameter type array.
+ */
+ public XMLRPCSelector (String aMethodName, Class[] aParameterTypeArray)
+ {
+ super( aMethodName, aParameterTypeArray );
+ }
+
+ /**
+ * Invokes this selector's method on the specified object
+ * using the specified parameters.
+ */
+ public Object invoke (Object anObject, Object[] parameters)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException
+ {
+ if ( anObject instanceof URL )
+ {
+ Receiver receiver = new Receiver();
+ byte[] copyOfResponse = null;
+ try
+ {
+ URLConnection cn = ((URL)anObject).openConnection();
+
+ // set properties
+ cn.setDoOutput(true);
+ cn.setDoInput(true);
+ cn.setRequestProperty(
+ "content-type","text/xml");
+
+ // send parameters
+ OutputStream out = cn.getOutputStream();
+ new XMLRPCEncoder().encodeRequest(
+ name(), parameters, out );
+ out.flush();
+ out.close();
+
+ // get response: getInputStream initiates the request
+ InputStream input =
+ new BufferedInputStream( cn.getInputStream() );
+ if ( copyStream )
+ {
+ ByteArrayOutputStream byteArray =
+ new ByteArrayOutputStream();
+ int b;
+ while ( ( b = input.read() ) != -1 )
+ {
+ byteArray.write( b );
+ }
+ copyOfResponse = byteArray.toByteArray();
+ input = new ByteArrayInputStream( copyOfResponse );
+ }
+ new XMLRPCDecoder().decode( input, receiver );
+ }
+ catch ( FileNotFoundException exc )
+ {
+ throw new WotonomyException( "Server did not return a response." );
+ }
+ catch ( Exception exc )
+ {
+ if ( copyOfResponse != null )
+ {
+ System.out.println( new String( copyOfResponse ) );
+ exc.printStackTrace();
+ }
+ throw new InvocationTargetException( exc );
+ }
+
+ if ( receiver.faultString == null )
+ {
+ return receiver.result;
+ }
+ else
+ {
+ throw new InvocationTargetException(
+ new WotonomyException(
+ receiver.faultCode + ": " + receiver.faultString ) );
+ }
+ }
+
+ // else: not a URL
+ return super.invoke( anObject, parameters );
+ }
+
+ public static Object invoke
+ (String methodName, Class[] parameterTypes, Object anObject, Object[] parameters)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException
+ {
+ return new XMLRPCSelector( methodName, parameterTypes ).invoke( anObject, parameters );
+ }
+
+ public static Object invoke
+ (String methodName, Object anObject)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException
+ {
+ return XMLRPCSelector.invoke(
+ methodName, EMPTY_CLASS_ARRAY, anObject, EMPTY_OBJECT_ARRAY );
+ }
+
+ public static Object invoke
+ (String methodName, Class[] parameterTypes,
+ Object anObject, Object aParameter)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException
+ {
+ return XMLRPCSelector.invoke(
+ methodName, parameterTypes, anObject, new Object[] { aParameter } );
+ }
+
+ public static Object invoke
+ (String methodName, Class[] parameterTypes,
+ Object anObject, Object p1, Object p2)
+ throws IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException, NoSuchMethodException
+ {
+ return XMLRPCSelector.invoke(
+ methodName, parameterTypes, anObject, new Object[] { p1, p2 } );
+ }
+
+ private class Receiver implements XMLRPCReceiver
+ {
+ public Object result;
+ public int faultCode;
+ public String faultString;
+
+ public Receiver()
+ {
+ result = null;
+ faultCode = -1;
+ faultString = null;
+ }
+
+ public void request(
+ String aMethodName, Object[] aParameterArray )
+ {
+ throw new WotonomyException(
+ "Invalid response: Expected response but received request." );
+ }
+
+ public void response(
+ Object aResult )
+ {
+ result = aResult;
+ faultCode = -1;
+ faultString = null;
+ }
+
+ public void fault(
+ int aFaultCode, String aFaultString)
+ {
+ result = null;
+ faultCode = aFaultCode;
+ faultString = aFaultString;
+ }
+ }
+
+
+}
+
+/*
+ * $Log$
+ * Revision 1.1 2006/02/19 01:44:03 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.1 2001/02/07 19:24:28 mpowers
+ * Moved XML classes to separate package.
+ *
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java
new file mode 100644
index 0000000..a9981a4
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/XMLRPCServlet.java
@@ -0,0 +1,283 @@
+/*
+Wotonomy: OpenStep design patterns for pure Java applications.
+Copyright (C) 2001 Intersect Software Corporation
+
+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.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Hashtable;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.wotonomy.foundation.NSSelector;
+import net.wotonomy.foundation.internal.WotonomyException;
+
+/**
+* A servlet that can make any java object into an XML-RPC server.
+* Simply pass in the object to the constructor and XML-RPC requests
+* to this servlet will call the appropriate methods and convert
+* the results to an XML-RPC response. <br><br>
+*
+* Depending on your servlet container, it may be necessary to
+* create a simple subclass that creates your handler object
+* in its constructor and then calls setHandler(). <br><br>
+*
+* Responses are in the specification's standard response format.<br><br>
+*
+* Faults are returned if any exception is thrown in the method,
+* or if the specified method is not found on the object.
+* The fault string is the toString value of the exception,
+* and the fault code is the hasCode value of the exception. <br><br>
+*
+* Remember that this servlet only responds to POSTs,
+* per the XML-RPC spec.
+*/
+public class XMLRPCServlet extends HttpServlet
+{
+ protected Object handler;
+ protected boolean synchronizing;
+ private Hashtable selectorCache = new Hashtable(); // thread safe
+ private boolean copyStream = false;
+
+ /**
+ * Default constructor initializes internal state.
+ */
+ public XMLRPCServlet()
+ {
+ handler = null;
+ synchronizing = false;
+ }
+
+ /**
+ * Constructor takes any java object and allows its methods
+ * to be invoked via XMLRPC requests to this servlet.
+ * Simply calls setHandler().
+ */
+ public XMLRPCServlet( Object aHandler )
+ {
+ this();
+ setHandler( aHandler );
+ }
+
+ /**
+ * Gets the object whose methods will be invoked to
+ * handle incoming requests.
+ */
+ public Object getHandler()
+ {
+ return handler;
+ }
+
+ /**
+ * Sets the object whose methods will be invoked to
+ * handle incoming requests.
+ */
+ public void setHandler( Object aHandler )
+ {
+ handler = aHandler;
+ }
+
+ /**
+ * Gets whether the servlet should synchonize on the
+ * object before invoking methods on it.
+ * Defaults to false.
+ */
+ public boolean isSynchronizing()
+ {
+ return synchronizing;
+ }
+
+ /**
+ * Sets whether the servlet should synchonize on the
+ * object before invoking methods on it.
+ * Defaults to false.
+ */
+ public void setSynchronizing( boolean willSynchronize )
+ {
+ synchronizing = willSynchronize;
+ }
+
+ /**
+ * Overridden to service the request.
+ */
+ protected void doPost(
+ HttpServletRequest req, HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ if ( getHandler() != null )
+ {
+ InputStream input = req.getInputStream();
+ byte[] copyOfRequest = null;
+
+ if ( copyStream )
+ {
+ ByteArrayOutputStream byteArray =
+ new ByteArrayOutputStream();
+ int b;
+ while ( ( b = input.read() ) != -1 )
+ {
+ byteArray.write( b );
+ }
+ copyOfRequest = byteArray.toByteArray();
+ input = new ByteArrayInputStream( copyOfRequest );
+ }
+
+ try
+ {
+ new XMLRPCDecoder().decode( input,
+ new Receiver( this, resp ) );
+ }
+ catch ( WotonomyException exc )
+ {
+ if ( copyOfRequest != null )
+ {
+ System.out.println( new String( copyOfRequest ) );
+ exc.printStackTrace();
+ }
+ // catches io exceptions thrown in handleRequest.
+ Throwable t = exc.getWrappedThrowable();
+ if ( t instanceof IOException )
+ {
+ throw (IOException)t;
+ }
+ throw exc;
+ }
+ }
+ }
+
+ /**
+ * Called by doPost after parsing an incoming request,
+ * and is responsible for invoking the specified method
+ * with the specified parameters on the handler object.
+ * (This implementation calls getOutputStream() on the response.)
+ * Override to customize the handling of the request.
+ */
+ protected void handleRequest( String aMethodName,
+ Object[] aParameterArray, HttpServletResponse aResponse )
+ {
+ OutputStream output = null;
+
+ try
+ {
+ output = aResponse.getOutputStream();
+ aResponse.setStatus( HttpServletResponse.SC_OK ); // always 200
+ aResponse.setContentType( "text/xml" );
+ }
+ catch ( IOException exc )
+ {
+ // caught in doPost
+ throw new WotonomyException( exc );
+ }
+
+ // get the array of types
+ XMLRPCEncoder encoder = new XMLRPCEncoder();
+ Class[] types = new Class[ aParameterArray.length ];
+ for ( int i = 0; i < aParameterArray.length; i++ )
+ {
+ types[i] = aParameterArray[i].getClass();
+ }
+
+ //TODO: selectors should be cached if possible
+
+ Object handler = getHandler();
+ if ( isSynchronizing() )
+ {
+ synchronized ( handler )
+ {
+ execute( encoder, handler, output,
+ new NSSelector( aMethodName, types ), aParameterArray );
+ }
+ }
+ else
+ {
+ execute( encoder, handler, output,
+ new NSSelector( aMethodName, types ), aParameterArray );
+ }
+ }
+
+ private void execute( XMLRPCEncoder anEncoder, Object aHandler,
+ OutputStream output, NSSelector aSelector, Object[] aParameterArray )
+ {
+ try
+ {
+ Object result =
+ aSelector.invoke( aHandler, aParameterArray );
+ anEncoder.encodeResponse( result, output );
+ }
+ catch ( Exception exc )
+ {
+ anEncoder.encodeFault(
+ exc.hashCode(), exc.toString(), output );
+ }
+ }
+
+ private class Receiver implements XMLRPCReceiver
+ {
+ XMLRPCServlet controller;
+ HttpServletResponse response;
+
+ public Receiver(
+ XMLRPCServlet aController,
+ HttpServletResponse aResponse )
+ {
+ controller = aController;
+ response = aResponse;
+ }
+
+ public void request(
+ String aMethodName, Object[] aParameterArray )
+ {
+ controller.handleRequest(
+ aMethodName, aParameterArray, response );
+ }
+
+ public void response(
+ Object aResult )
+ {
+ // does nothing
+ }
+
+ public void fault(
+ int aFaultCode, String aFaultString)
+ {
+ // does nothing
+ }
+ }
+}
+
+/*
+ * $Log$
+ * Revision 1.1 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.1 2001/02/07 19:24:28 mpowers
+ * Moved XML classes to separate package.
+ *
+ */
+
diff --git a/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/package.html b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/package.html
new file mode 100644
index 0000000..4303e07
--- /dev/null
+++ b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/xml/package.html
@@ -0,0 +1,30 @@
+<body>
+<p>
+The XML object serialization framework, including
+an XML-RPC serializer implementation and an easy-to-use
+XML-RPC servlet and client.
+</p>
+<p>
+The primary serialization interfaces are XMLEncoder and XMLDecoder.
+</p>
+<p>
+The implementation of those interfaces is in XMLRPCEncoder and XMLRPCDecoder.
+In addition to serializing java objects to an XML-RPC struct format, these
+classes also define methods for generating and parsing XML-RPC requests,
+responses, and faults. Other implementations (like SOAP) may follow.
+</p>
+<p>
+The XMLRPCServlet utilizes the framework to allow you to turn any
+java object into an XML-RPC server with one line of code.
+</p>
+<p>
+XMLRPCSelector turns NSSelector into a XML-RPC client. If you
+pass in a URL as the target object, the selector will invoke its
+method on the specified XML-RPC server and return the result as
+a java object. The selector otherwise works normally.
+</p>
+<p>
+This package has dependencies on the foundation and util packages.
+The servlet has a dependency on javax.servlet.HttpServlet.
+</p>
+</body>