diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-20 17:58:16 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-20 17:58:16 -0400 |
| commit | 40a9d99496e098562f090fb7ffce9e749011b131 (patch) | |
| tree | 437df24d65470582e943e494a52db8ed65a881ae /projects/net.wotonomy.web/src/main/java/net/wotonomy | |
| parent | ff072dfe782f6f22123cd4ba050828d35c0d0fbd (diff) | |
Formatting pass
Diffstat (limited to 'projects/net.wotonomy.web/src/main/java/net/wotonomy')
59 files changed, 14030 insertions, 15825 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 index cef1372..1c91115 100644 --- 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 @@ -28,324 +28,272 @@ 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; - } - + * 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 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 ); - } + * 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 ); - } + * 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 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 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 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 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() ); - } - } + * 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; - + * 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() ) - { + 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 ); + 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() ); + 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--; + 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 ) ); + 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 ); - } + * 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 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 ); - } + * 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 ); - } - + * 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 ); - } + * 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 ); - } + * 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. + * $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 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.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.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/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. + * 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 index 41f77f5..ba608a4 100644 --- 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 @@ -59,7 +59,7 @@ * * [Additional notices, if required by prior licensing conditions] * - */ + */ // excellent class borrowed from Apache Commons project: //package org.apache.commons.httpclient; @@ -80,30 +80,39 @@ 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. + * 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. + * 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. + * 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> + * <p> + * <blockquote> + * + * <pre> * URI character sequence: char * octet sequence: byte * original character sequence: String - * </pre></blockquote><p> + * </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. + * 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> + * <p> + * <blockquote> + * + * <pre> * - In general, written as follows: * Absolute URI = <scheme>:<scheme-specific-part> * Generic URI = <scheme>://<authority><path>?<query> @@ -113,9 +122,13 @@ import sun.security.action.GetPropertyAction; * hier_part = ( net_path | abs_path ) [ "?" query ] * net_path = "//" authority [ abs_path ] * abs_path = "/" path_segments - * </pre></blockquote><p> + * </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 @@ -130,11 +143,14 @@ import sun.security.action.GetPropertyAction; * 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> + * <p> + * + * <pre> * For escaped URI forms * - URI(char[]) // constructor * - char[] getRawXxx() // method @@ -144,3321 +160,3489 @@ import sun.security.action.GetPropertyAction; * For unescaped URI forms * - URI(String) // constructor * - String getXXX() // method - * </pre><p> + * </pre> + * <p> * * @author <a href="mailto:jericho@apache.org">Sung-Gu</a> - * @version $Revision: 905 $ $Date: 2002/03/14 15:14:01 + * @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 <http://test.com/> - * - * @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 <http://test.com/> - * - * @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 = <scheme>:<scheme-specific-part># - * <fragment>. - * - * @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 = <scheme>:<path>?<query>#< - * fragment> and relative URI = <path>?<query>#<fragment - * >. - * - * @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 & 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 = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | - * "$" | "," - * </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 | - * ":" | "@" | "&" | "=" | "+" | "$" | "," - * </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 | ";" | "?" | ":" | "@" | - * "&" | "=" | "+" | "$" | "," - * </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 | - * ";" | ":" | "&" | "=" | "+" | "$" | "," ) - * </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 | "$" | "," | - * ";" | ":" | "@" | "&" | "=" | "+" ) - * </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 | - * ";" | "@" | "&" | "=" | "+" | "$" | "," ) - * </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 - * ("&", "=", "+", ",", 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 - } - - } + // ----------------------------------------------------------- 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 + * <http://test.com/> + * + * @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 <http://test.com/> + * + * @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 = <scheme>:<scheme-specific-part># + * <fragment>. + * + * @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 = <scheme>:<path>?<query>#< + * fragment> and relative URI = <path>?<query>#<fragment >. + * + * @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 & 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 = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," + * </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 | ":" | "@" | "&" | "=" | "+" | "$" | "," + * </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 | ";" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," + * </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 | + * ";" | ":" | "&" | "=" | "+" | "$" | "," ) + * </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 | "$" | "," | ";" | ":" | "@" | "&" | "=" | "+") + * </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 | ";" | "@" | "&" | "=" | "+" | "$" | ",") + * </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 ("&", "=", "+", ",", 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 index 377345c..cc95697 100644 --- 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 @@ -19,33 +19,27 @@ 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 (); + * 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. + * $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.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. + * 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 index 78191b6..86e7807 100644 --- 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 @@ -21,20 +21,23 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** - * This dynamic element renders only the URL of a hyperlink. - * Bindings are: + * 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>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. + * 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 $ @@ -42,16 +45,16 @@ import net.wotonomy.foundation.NSDictionary; */ public class WOActionURL extends WOHyperlink { - public WOActionURL() { - super(); - } + public WOActionURL() { + super(); + } - public WOActionURL(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOActionURL(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - public void appendToResponse(WOResponse r, WOContext c) { - r.appendContentString(actionURL(c)); - } + 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 index cad6f64..bf80b3e 100644 --- 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 @@ -7,8 +7,9 @@ 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. + * 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. * @@ -18,64 +19,64 @@ import net.wotonomy.foundation.NSMutableDictionary; */ public class WOActiveImage extends WODynamicElement { - protected WOActiveImage() { - super(); - } + protected WOActiveImage() { + super(); + } - public WOActiveImage(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } + 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 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); - } + 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); - } + 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 index 90e40c6..46b598c 100644 --- 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 @@ -37,1157 +37,982 @@ 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; + * 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 ); - } - + 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 ); - } + * 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 ); + * 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 singleton instance of this application. - */ - public static WOApplication application() - { - return (WOApplication) threadLocal.get(); - //return application; - } - + * 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 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 + * 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 /** - * 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 ); + * 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; } - /** - * 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 - { + /** + * 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; - } - + 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)); + } + /** - * 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 - { + * 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; - } + 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; + } -/* - // 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) - { + /** + * 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); -*/ + /** + * 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"); + } - /** - * 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(); - } - } + /** + * 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. + * $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 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.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.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.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.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.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.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.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.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.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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 608f9fa..89623a6 100644 --- 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 @@ -19,150 +19,138 @@ 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 -{ + * 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; + } + /** - * 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 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; - } + * 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 ); + * 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!" ); + 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 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 + "\"]"; - } -} + * 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. + * $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.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.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.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.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.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. + * 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 index 9f0707d..131d223 100644 --- 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 @@ -23,99 +23,91 @@ 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 $ -*/ + * 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 String src; + protected String filename; + protected String framework; + protected NSData data; + protected String mimeType; - protected WOBody() { - super(); - } + protected WOBody() { + super(); + } - public WOBody(String aName, NSDictionary aMap, WOElement template) { - super(aName, aMap, template); - } + public WOBody(String aName, NSDictionary aMap, WOElement template) { + super(aName, aMap, template); + } - void ensureAwakeInContext (WOContext aContext) - { - if ( rootElement != null ) - { - rootElement.ensureAwakeInContext( aContext ); - } - } + 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>"); - } + 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. + * $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 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.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.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. + * 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 index 5d22d36..8e706c1 100644 --- 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 @@ -7,75 +7,75 @@ import net.wotonomy.foundation.NSMutableArray; public class WOCheckBox extends WOInput { - protected boolean checked = false; + protected boolean checked = false; - public WOCheckBox() { - super(); - } + public WOCheckBox() { + super(); + } - public WOCheckBox(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WOCheckBox(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - protected String inputType() { - return "CHECKBOX"; - } + 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 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 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; - } + 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 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()); - } + 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 index 20d8b0a..c99768c 100644 --- 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 @@ -42,1271 +42,1037 @@ 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 -{ + * 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; + + 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(); + } /** - * 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 system-dependent file path to the current component directory, + * including the ".wo" extension. + */ + public String path() { + throw new RuntimeException("Not implemented yet."); + } + /** - * 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 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(); + } /** - * 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) - { + * 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(); - } + /** + * 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; - } - - /** - * 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) - { + } + + /** + * 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 ); - } + 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; - } + } + + 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() { + } /** - * 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 ); + * 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(); } - catch ( NoSuchMethodException exc ) - { + 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 ) - { + } catch (InvocationTargetException exc) { Throwable t = exc.getTargetException(); - exc.printStackTrace(); - throw new RuntimeException( t.toString() ); + exc.printStackTrace(); + throw new RuntimeException(t.toString()); + } catch (Exception exc) { + exc.printStackTrace(); + throw new RuntimeException(exc.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); } - 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 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 current context for this component. - */ - public WOContext context () - { - return context; - } + * Returns the application containing this instance of the class. + */ + public WOApplication application() { + return context.application(); + } /** - * 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() ); - } + * Returns whether a session has been created for this user. + */ + public boolean hasSession() { + return context.hasSession(); + } /** - * 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 - } + * Returns the current session object, creating it if it doesn't exist. + */ + public WOSession session() { + return context.session(); + } /** - * 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 the current context for this component. + */ + public WOContext context() { + return context; + } /** - * 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 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 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; - } + * 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." ); - } + * 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 ) ) ); - + /** + * 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 ); + 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() ) - { + while (it.hasNext()) { key = (String) it.next(); - context.append( " " + key + "=\"" + properties.get( key ) + "\"" ); + context.append(" " + key + "=\"" + properties.get(key) + "\""); } - - if ( body == null ) - { - context.append( "/>" ); + + if (body == null) { + context.append("/>"); return; } - - context.append( ">" + body + "</" + tagName + ">" ); + + 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 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 ); - } + + 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); } - catch ( IOException exc ) - { - throw new RuntimeException( - "Error while stripping comments from declaration: " + stripped ); + if (is == null) { + System.err.println("No resources found for: " + name); + return null; } - 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(); + + // 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"; + } } - else + // 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 { - System.err.println( "Could not parse declaration:" ); - System.err.println( content ); - throw new RuntimeException( - "Could not parse declaration: " + token ); - } + 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 ); + 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 + "'"); + } } - - 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; - } + 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. + * $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 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.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.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.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.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.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.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.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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 1544934..909212c 100644 --- 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 @@ -21,25 +21,28 @@ 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. + * 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() { + super(); + } - public WOComponentContent(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + 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); - } + 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 index 9f79987..9d35ab4 100644 --- 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 @@ -18,212 +18,168 @@ 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; - } + * 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. + * $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 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.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.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.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.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.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.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.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.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. + * 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 index 124ca11..d0ab35e 100644 --- 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 @@ -21,14 +21,13 @@ 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: + * 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> + * <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 @@ -37,63 +36,65 @@ import net.wotonomy.foundation.NSDictionary; */ 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); - } - } + 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 index 4f774e6..40c5f5c 100644 --- 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 @@ -25,548 +25,455 @@ 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 -{ + * 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 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://"; + 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 + "]"; - } + + 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. + * $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 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.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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 78427f0..4c5f498 100644 --- 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 @@ -21,183 +21,159 @@ 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 ); + * 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; - } + + /** + * 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. + * $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 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.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. + * 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 index 09d0131..08b591f 100644 --- 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 @@ -29,289 +29,241 @@ 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 -{ + * 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; + WOContext context; /** - * Default constructor. This is called implicitly by - * subclasses in all cases. Package access only. - */ - WODirectAction () - { - - } + * 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; - } + * 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 response from the component named "Main". + */ + public WOActionResults defaultAction() { + return pageWithName("Main").generateResponse(); + } /** - * 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 WORequest object for the current request. + */ + public WORequest request() { + return request; + } /** - * 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 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 named WOComponent. - */ - public WOComponent pageWithName (String aString) - { - return request.application().pageWithName( aString, context ); - } + * 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; + } /** - * 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 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 ) - { + } catch (InvocationTargetException exc) { Throwable e = exc.getTargetException(); - exc.printStackTrace(); - throw new RuntimeException( e.toString() ); - } - catch ( Exception exc ) - { - exc.printStackTrace(); - throw new RuntimeException( exc.toString() ); + 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; - } + 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. + * 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 ); - } + } + // 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 ); - } + * 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. + */ + 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 ); - } + * 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. + * $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 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.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.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.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.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.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. + * 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 index eac826b..228f387 100644 --- 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 @@ -21,201 +21,161 @@ 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; - } - + * 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. + * $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 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.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. + * 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.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.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.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.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.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.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. + * 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 index bda1dd5..0bdefdf 100644 --- 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 @@ -49,2407 +49,1950 @@ 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 - { + * 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() - { + } + + // 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 ); - - } + 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. + * $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 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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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. + * 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 index 6e449c3..6abb7d6 100644 --- 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 @@ -27,182 +27,175 @@ 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 -{ + * 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) - { - + 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); } - /** - * 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; - } + /** 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 index 11944d3..184eeea 100644 --- 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 @@ -23,75 +23,62 @@ 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 -{ + * 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() { + } + /** - * 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 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 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 - } + /** + * 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 index 887d1a3..b4dca91 100644 --- 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 @@ -21,104 +21,101 @@ 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 $ -*/ +/** + * 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; + 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 + } 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>"); + // 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; - } + 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 index 30dd4bc..06f4b7f 100644 --- 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 @@ -6,55 +6,54 @@ 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(">"); - } + 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 index 9af5460..b883b0f 100644 --- 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 @@ -21,41 +21,42 @@ 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). + * 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; - } + 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 index 8894428..92d1b42 100644 --- 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 @@ -22,74 +22,70 @@ 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. + * 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(); - } + static NSArray bindings = new NSArray(new Object[] { "elementName", "omitTags", "elementID", "otherTagString", + "formValue", "formValues", "invokeAction" }); - public WOGenericElement(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOGenericElement() { + super(); + } - public String elementName(WOContext c) { - String x = (String)valueForProperty("elementName", c.component()); - if (x != null) - return x; - return c.elementID(); - } + public WOGenericElement(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - 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 String elementName(WOContext c) { + String x = (String) valueForProperty("elementName", c.component()); + if (x != null) + return x; + return c.elementID(); + } - 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 ); - } + 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 index c5d5711..ca4e6c2 100644 --- 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 @@ -21,22 +21,23 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** -* Used to dynamically generate a hidden field within a form. + * 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() { + super(); + } - public WOHiddenField(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOHiddenField(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - public void takeValuesFromRequest(WORequest r, WOContext c) { - } + 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 index d0f3ff7..e2041d5 100644 --- 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 @@ -25,26 +25,30 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** -* WOHyperlink renders a dynamically generated hyperlink in the output. - * Bindings are: + * 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>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> + * 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>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>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. + * 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 $ @@ -52,198 +56,202 @@ import net.wotonomy.foundation.NSDictionary; */ 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 { + 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("="); + } 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); - } + 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 index 2673cd1..3f830b0 100644 --- 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 @@ -6,145 +6,146 @@ 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 $ -*/ + * 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; - } + 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 index 7c9f22e..ebb9ae1 100644 --- 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 @@ -5,17 +5,21 @@ 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) + * 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>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> + * <li>framework: The framework where the image should be retrieved from (used + * in conjunction with filename).</li> * * @author ezamudio@nasoft.com * @author $Author: cgruber $ @@ -23,36 +27,36 @@ import net.wotonomy.foundation.NSDictionary; */ 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); - } - } + 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 index a8c7daa..ddcdb9e 100644 --- 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 @@ -10,74 +10,75 @@ 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. + 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. + * @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); + // Format the value in case of number + String pattern = (String) valueForProperty("numberformat", c); if (pattern != null) { DecimalFormat fmt = new DecimalFormat(pattern); try { @@ -86,8 +87,8 @@ public abstract class WOInput extends WODynamicElement { return value; } } - //Format the value in case of date - pattern = (String)valueForProperty("dateformat", c); + // Format the value in case of date + pattern = (String) valueForProperty("dateformat", c); if (pattern != null) { SimpleDateFormat fmt = new SimpleDateFormat(pattern); try { 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 index eccc489..69939f9 100644 --- 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 @@ -21,34 +21,30 @@ 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 -{ + * 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 ) - { + + 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); + } + 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 index be76be1..f2310c2 100644 --- 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 @@ -24,12 +24,12 @@ 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 $ -*/ + * A pure java implementation of WOResponse. + * + * @author ezamudio@nasoft.com + * @author $Author: cgruber $ + * @version $Revision: 905 $ + */ public class WOMessage { protected String _contentEncoding = "ISO8859_1"; @@ -43,291 +43,233 @@ public class WOMessage { } /** - * Sets the content encoding for the response. - */ - public void setContentEncoding (String encoding) - { + * 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 () { + * 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 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 ); + * 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 () { + * 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 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(); + * 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. - */ + * Sets the content of the response to the bytes represented by the specified + * data object. + */ public void setContent(NSData aData) { - _contentData.setData( aData ); + _contentData.setData(aData); setHeader(Integer.toString(aData.length()), "content-length"); } /** - * Retrieves the current content of the response. - */ + * 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 ); + * 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 () { + * 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 ); + * 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); + * 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() ) ); + * 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() ) ); + * 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() ) ); + * 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() ); + * 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() ); + * 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(); + * 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"); + * 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" ); + * 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) - { + * 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( "&" ); - } - else - if ( buf[i] == '\\' ) - { - result.append( """ ); - } - else - if ( buf[i] == '<' ) - { - result.append( "<" ); + char[] buf = new char[len]; + aString.getChars(0, len, buf, 0); + for (int i = 0; i < len; i++) { + if (buf[i] == '&') { + result.append("&"); + } else if (buf[i] == '\\') { + result.append("""); + } else if (buf[i] == '<') { + result.append("<"); + } else if (buf[i] == '>') { + result.append(">"); + } else { + result.append(buf[i]); } - else - if ( buf[i] == '>' ) - { - result.append( ">" ); - } - else - { - result.append( buf[i] ); - } - } - return result.toString(); + } + 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) - { + * 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( "&" ); + char[] buf = new char[len]; + aString.getChars(0, len, buf, 0); + for (int i = 0; i < len; i++) { + if (buf[i] == '&') { + result.append("&"); + } else if (buf[i] == '\\') { + result.append("""); + } else if (buf[i] == '<') { + result.append("<"); + } else if (buf[i] == '>') { + result.append(">"); + } else if (buf[i] == '\t') { + result.append("	"); + } else if (buf[i] == '\n') { + result.append(" "); + } else if (buf[i] == '\r') { + result.append(" "); + } else { + result.append(buf[i]); } - else - if ( buf[i] == '\\' ) - { - result.append( """ ); - } - else - if ( buf[i] == '<' ) - { - result.append( "<" ); - } - else - if ( buf[i] == '>' ) - { - result.append( ">" ); - } - else - if ( buf[i] == '\t' ) - { - result.append( "	" ); - } - else - if ( buf[i] == '\n' ) - { - result.append( " " ); - } - else - if ( buf[i] == '\r' ) - { - result.append( " " ); - } - else - { - result.append( buf[i] ); - } - } - return result.toString(); + } + 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 index 40ee618..cf84b17 100644 --- 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 @@ -24,155 +24,137 @@ 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 -{ + * 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() - { + * Default constructor. + */ + public WOParentElement() { children = new NSMutableArray(); } - + /** - * Returns an element with the specified children. - */ - public WOParentElement( List childElements ) - { + * Returns an element with the specified children. + */ + public WOParentElement(List childElements) { this(); - children.addAll( childElements ); + 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; - + * 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.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.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(); - } - + 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 index 2d4f16e..4d81fe0 100644 --- 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 @@ -5,16 +5,16 @@ import net.wotonomy.foundation.NSDictionary; public class WOPasswordField extends WOTextField { - public WOPasswordField() { - super(); - } + public WOPasswordField() { + super(); + } - public WOPasswordField(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WOPasswordField(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - protected String inputType() { - return "PASSWORD"; - } + 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 index a73636c..a40c828 100644 --- 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 @@ -7,129 +7,132 @@ 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; - } - } - } - } + 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 index 386218c..b06b052 100644 --- 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 @@ -5,16 +5,16 @@ import net.wotonomy.foundation.NSDictionary; public class WORadioButton extends WOCheckBox { - public WORadioButton() { - super(); - } + public WORadioButton() { + super(); + } - public WORadioButton(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + public WORadioButton(String aName, NSDictionary assocs, WOElement template) { + super(aName, assocs, template); + } - protected String inputType() { - return "RADIO"; - } + 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 index afa118a..53615a4 100644 --- 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 @@ -25,155 +25,150 @@ 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); - } + 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 index 7d71223..34f7414 100644 --- 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 @@ -33,554 +33,479 @@ 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 ) - { + * 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(); - + 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 ) - { + 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; - + } + + /** + * 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 () - { + } + + /** + * 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() ); + } + + /** + * 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()); } - 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; - } + } + + 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 index 957cf25..7e3c624 100644 --- 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 @@ -21,44 +21,37 @@ 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 -{ + * 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() ); + * Default constructor. + */ + public WORequestHandler() { + loader = new NetworkClassLoader(WOApplication.application().getClass().getClassLoader()); } - + /** - * Dispatches the request and returns the response. - */ - abstract public WOResponse handleRequest (WORequest aRequest); - + * 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 ) - { + * 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; } } 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 index 8428681..f4fb49f 100644 --- 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 @@ -21,30 +21,31 @@ package net.wotonomy.web; import net.wotonomy.foundation.NSDictionary; /** -* Implements a reset button with dynamic bindings. + * 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() { + super(); + } - public WOResetButton(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOResetButton(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - protected String inputType() { - return "RESET"; - } + protected String inputType() { + return "RESET"; + } - protected Object value(WOContext c) { - Object v = valueForProperty("value", c.component()); - if (v == null) - return "Reset"; - return v; - } + 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 index c69c9db..5941ad2 100644 --- 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 @@ -37,453 +37,353 @@ 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; - } - } + * 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. + * $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 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.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.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.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.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. + * 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 index 4d25128..f611149 100644 --- 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 @@ -24,99 +24,81 @@ 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; - } + * 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. + * $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 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.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.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.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.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. + * 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 index 862c6dc..14c1c87 100644 --- 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 @@ -6,26 +6,26 @@ import net.wotonomy.foundation.NSDictionary; public class WOResourceURL extends WODynamicElement { - public WOResourceURL() { - super(); - } + public WOResourceURL() { + super(); + } - public WOResourceURL(String aName, NSDictionary assocs, WOElement template) { - super(aName, assocs, template); - } + 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; - } - } + 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 index d9fbade..6362a03 100644 --- 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 @@ -25,160 +25,133 @@ 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 -{ + * 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 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 () - { + * 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; + } /** - * 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; + } /** - * 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 ); + } /** - * 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; - } + * 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 ); + * 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 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 ); + // 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; - } + * 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; - } + * 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 index 0f2898c..647346c 100644 --- 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 @@ -28,161 +28,135 @@ 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() ); - } - } + * 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. + * $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 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.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.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.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.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.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. + * 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 index e187567..82df58b 100644 --- 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 @@ -36,493 +36,433 @@ 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." ); - } - + * 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 index f91a433..3008f21 100644 --- 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 @@ -19,75 +19,67 @@ 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; - } + * 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 ); - } + /** + * 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); + } - /** - * 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); + /** + * 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. + * $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. + * 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 index 308cd20..29a06c4 100644 --- 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 @@ -19,52 +19,44 @@ 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 -{ + * 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() - { + * Default constructor. + */ + public WOStaticElement() { content = null; } - + /** - * Returns a static element representing the specified content. - */ - public WOStaticElement( String aContentString ) - { + * 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; - } - - + /** + * 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 index ef5b771..0c471d7 100644 --- 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 @@ -25,112 +25,87 @@ 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 -{ + * 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; + protected Object valueWhenEmpty; /** - * The default constructor. - */ - protected WOString () - { - } - - public WOString ( - String aName, NSDictionary anAssociationMap, WOElement aRootElement) - { - super( aName, anAssociationMap, aRootElement ); - escapeHTML = true; - } + * 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); - 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() ); + 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 index 05824f3..a1ef303 100644 --- 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 @@ -22,49 +22,52 @@ 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 $ -*/ + * 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() { + super(); + } - public WOSubmitButton(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOSubmitButton(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } - protected String inputType() { - return "SUBMIT"; - } + protected String inputType() { + return "SUBMIT"; + } - protected Object value(WOContext c) { - Object v = valueForProperty("value", c.component()); - if (v == null) { - return "Submit"; - } - return v; - } + 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; - } + 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; - } + 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 index d2e373a..6f42199 100644 --- 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 @@ -23,90 +23,78 @@ 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(); - } - } + 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 index 008fda8..3b5dd1d 100644 --- 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 @@ -22,20 +22,21 @@ import net.wotonomy.foundation.NSArray; import net.wotonomy.foundation.NSDictionary; /** -* Implements a TEXTAREA element, with dynamic bindings. + * 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() { + super(); + } - public WOText(String n, NSDictionary m, WOElement t) { - super(n, m, t); - } + public WOText(String n, NSDictionary m, WOElement t) { + super(n, m, t); + } protected String inputType() { return "TEXTAREA"; @@ -49,24 +50,23 @@ public class WOText extends WOInput { 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 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>"); - } + 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 index 4233ad4..82f591a 100644 --- 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 @@ -22,38 +22,39 @@ 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 $ -*/ + * 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; - } + 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/util/BrowserLauncher.java b/projects/net.wotonomy.web/src/main/java/net/wotonomy/web/util/BrowserLauncher.java index 777d4a1..7a6a946 100644 --- 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 @@ -10,656 +10,644 @@ 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> + * 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> + * 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> + * 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> + * 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> + * <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 + * 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>) + * 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) $ + * @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 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; + /** 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; + /** + * 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.MRJFileUtils class */ + private static Class mrjFileUtilsClass; - /** The com.apple.mrj.MRJOSType class */ - private static Class mrjOSTypeClass; + /** The com.apple.mrj.MRJOSType class */ + private static Class mrjOSTypeClass; - /** The com.apple.MacOS.AEDesc class */ - private static Class aeDescClass; + /** 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) 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>(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 <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 findFolder method of com.apple.mrj.MRJFileUtils */ + private static Method findFolder; - /** The getFileCreator method of com.apple.mrj.MRJFileUtils */ - private static Method getFileCreator; + /** 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 getFileType method of com.apple.mrj.MRJFileUtils */ + private static Method getFileType; - /** The openURL method of com.apple.mrj.MRJFileUtils */ - private static Method openURL; + /** 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); + /** 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 index 8da71fe..54658c0 100644 --- 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 @@ -32,83 +32,73 @@ 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; - } + * 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); - /** - * 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(); - } + 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 index 2368672..0806d88 100644 --- 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 @@ -38,77 +38,64 @@ 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 { + * 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(); } @@ -118,47 +105,40 @@ class XMLRPCDecoderHelper extends DefaultHandler super.startDocument(); reset(); } - - public Object getResult() - { - if ( valueStack.empty() ) return null; - Object result = valueStack.peek(); - if ( result == nilMarker ) result = null; - return result; - } - - + + 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 ); - } + 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 { + + 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]; + 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); } @@ -166,231 +146,140 @@ class XMLRPCDecoderHelper extends DefaultHandler 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 - { + // 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 ); - } - + 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 ); + 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 ) ) - { + } 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() ); - } - } - - + // 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 @@ -423,12 +312,14 @@ class XMLRPCDecoderHelper extends DefaultHandler } public InputSource resolveEntity(String publicId, String systemId) throws SAXException { - // NOTE: Sun accepted an incompatible api difference. The (false) should be tossed by hotspot + // 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); + 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); + throw new SAXException(e.getClass().getName() + " thrown while resolving entity.", e); } } @@ -442,14 +333,13 @@ class XMLRPCDecoderHelper extends DefaultHandler 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 { + public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) + throws SAXException { // TODO Auto-generated method stub super.unparsedEntityDecl(name, publicId, systemId, notationName); } @@ -459,76 +349,65 @@ class XMLRPCDecoderHelper extends DefaultHandler 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 + "]"; - } - } - + // 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. + * $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 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.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.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.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.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.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.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 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.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. + * 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 index 3a63d45..d84f164 100644 --- 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 @@ -43,484 +43,398 @@ 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 ); - + * 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 { + 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 { + 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 index 48c9e41..5848cd0 100644 --- 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 @@ -19,54 +19,51 @@ 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 ); + * 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. + * $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 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.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 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.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. + * 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 index 7c1f104..599481f 100644 --- 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 @@ -32,207 +32,155 @@ 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 ); + * 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); } - 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 ); + /** + * 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, 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[] parameters) + throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException { + return new XMLRPCSelector(methodName, parameterTypes).invoke(anObject, parameters); } - 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, 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 p1, Object p2) - throws IllegalAccessException, IllegalArgumentException, - InvocationTargetException, NoSuchMethodException - { - return XMLRPCSelector.invoke( - methodName, parameterTypes, anObject, new Object[] { p1, p2 } ); + 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; + } } - - 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. + * $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 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. + * 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 index a9981a4..94b5680 100644 --- 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 @@ -34,250 +34,195 @@ 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; + * 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 - } - } + 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. + * $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 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. + * Revision 1.1 2001/02/07 19:24:28 mpowers Moved XML classes to separate + * package. * */ - |
