From aedc34d55462a75e329bbf342251ff6504cd117e Mon Sep 17 00:00:00 2001 From: Benjamin Culkin Date: Sun, 19 May 2024 17:56:33 -0400 Subject: Initial import from SVN --- .../wotonomy/foundation/internal/Duplicator.java | 299 +++++++ .../wotonomy/foundation/internal/Introspector.java | 941 +++++++++++++++++++++ .../foundation/internal/IntrospectorException.java | 32 + .../internal/MissingPropertyException.java | 32 + .../foundation/internal/NetworkClassLoader.java | 368 ++++++++ .../internal/NullPrimitiveException.java | 32 + .../foundation/internal/PropertyComparator.java | 100 +++ .../foundation/internal/PropertyListParser.java | 546 ++++++++++++ .../net/wotonomy/foundation/internal/QueueMap.java | 538 ++++++++++++ .../foundation/internal/URLResourceReader.java | 207 +++++ .../foundation/internal/ValueConverter.java | 718 ++++++++++++++++ .../foundation/internal/WotonomyException.java | 134 +++ 12 files changed, 3947 insertions(+) create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java create mode 100644 projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java (limited to 'projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal') diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java new file mode 100644 index 0000000..ddf347d --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Duplicator.java @@ -0,0 +1,299 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +import net.wotonomy.foundation.*; +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.WotonomyException; + +import java.io.*; +import java.util.*; //collections + +/** +* Duplicator makes use of Introspector to duplicate objects, +* either by shallow copy, deep copy, or by copying properties +* from one object to apply to another object. You may find this +* class useful because java.lang.Object.clone() only supports +* shallow copying. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 895 $ +*/ + +public class Duplicator +{ + /** + * Used to represent null values for properties in the + * maps returned by readProperties and cloneProperties + * and in the parameter to writeProperties. + * This actually references the NSNull instance. + */ + public static final Object NULL = NSNull.nullValue(); + private static NSSelector clone = new NSSelector( "clone" ); + + /** + * Returns a list of properties for the specified class + * that are both readable and writable. + */ + static public List editablePropertiesForObject( + Object anObject ) + { + 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 properties on both lists: read/write + properties.retainAll( readProperties ); + + return properties; + } + + /** + * Returns a Map containing only the mutable properties + * for the specified object and their values. + * Any null values for properties will be represented with + * the NULL object. + */ + static public Map readPropertiesForObject( + Object anObject ) + { + NSMutableDictionary result = new NSMutableDictionary(); + + String key; + Object value; + Iterator it = editablePropertiesForObject( anObject ).iterator(); + while ( it.hasNext() ) + { + key = it.next().toString(); + value = Introspector.get( anObject, key ); + if ( value == null ) value = NULL; + result.setObjectForKey( value, key ); + } + return result; + } + + /** + * Returns a Map containing only the mutable properties + * for the specified object and deep clones of their values. + * Nulls are represented by the NULL object. + */ + static public Map clonePropertiesForObject( + Object anObject ) + { + Object key, value; + Map result = readPropertiesForObject( anObject ); + Iterator it = result.keySet().iterator(); + while ( it.hasNext() ) + { + key = it.next(); + value = result.get( key ); + value = deepClone( value ); + result.put( key, value ); + } + return result; + } + + /** + * Applies the map of properties and values to the + * specified object. Null values for properties must + * be represented by the NULL object. + */ + static public void writePropertiesForObject( + Map aMap, Object anObject ) + { + String key; + Object value; + Iterator it = aMap.keySet().iterator(); + while ( it.hasNext() ) + { + key = it.next().toString(); + value = aMap.get( key ); + if ( NULL.equals( value ) ) value = null; + Introspector.set( anObject, key, value ); + } + } + + /** + * Creates a new copy of the specified object. + * This implementation tries to call clone(), + * and failing that, calls newInstance + * and then calls copy() to transfer the values. + * @throws WotonomyException if any operation fails. + */ + static public Object clone( + Object aSource ) + { + Object result = null; + if ( clone.implementedByObject( aSource ) ) + { + try + { + result = clone.invoke( aSource ); + return result; + } + catch ( Exception exc ) + { + // fall back on newInstance() + } + } + + Class c = aSource.getClass(); + try + { + result = c.newInstance(); + } + catch ( Exception exc ) + { + throw new WotonomyException( exc ); + } + return copy( aSource, result ); + } + + /** + * Creates a deep copy of the specified object. + * Every object in this objects graph will be + * duplicated with new instances. + * @throws WotonomyException if any operation fails. + */ + static public Object deepClone( + Object aSource ) + { + // the only known way to deep copy in + // java without native code is serialization + + try + { + ByteArrayOutputStream byteOutput = + new ByteArrayOutputStream(); + ObjectOutputStream objectOutput = + new ObjectOutputStream( byteOutput ); + + objectOutput.writeObject( aSource ); + objectOutput.flush(); + objectOutput.close(); + + ByteArrayInputStream byteInput = + new ByteArrayInputStream( byteOutput.toByteArray() ); + ObjectInputStream objectInput = + new ObjectInputStream( byteInput ); + return objectInput.readObject(); + } + catch ( Exception exc ) + { + throw new WotonomyException( "Error cloning object: " + aSource, exc ); + } + } + + /** + * Copies values from one object to another. + * Returns the destination object. + * @throws WotonomyException if any operation fails. + */ + static public Object copy( + Object aSource, Object aDestination ) + { + try + { + writePropertiesForObject( + readPropertiesForObject( aSource ), aDestination ); + } + catch ( RuntimeException exc ) + { + throw new WotonomyException( exc ); + } + return aDestination; + } + + /** + * Deeply clones the values from one object and applies them + * to another object. + * Returns the destination object. + * @throws WotonomyException if any operation fails. + */ + static public Object deepCopy( + Object aSource, Object aDestination ) + { + try + { + writePropertiesForObject( + clonePropertiesForObject( aSource ), aDestination ); + } + catch ( RuntimeException exc ) + { + throw new WotonomyException( exc ); + } + return aDestination; + } +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 16:52:12 cgruber + * Add cvsignore crap to find off checking in binary crap. + * + * Revision 1.1 2006/02/16 13:22:22 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.11 2001/08/22 19:24:26 mpowers + * Providing a more helpful error message for cloning exceptions. + * + * Revision 1.10 2001/03/29 03:30:36 mpowers + * Refactored duplicator a bit. + * Disabled MissingPropertyExceptions for now. + * + * Revision 1.9 2001/03/28 14:11:23 mpowers + * Removed debugging printlns. + * + * Revision 1.8 2001/03/27 23:25:48 mpowers + * Basically reverting to the previous version. + * + * Revision 1.7 2001/03/06 23:18:13 mpowers + * Clarified some comments. + * + * Revision 1.6 2001/03/01 20:36:35 mpowers + * Better error handling and better handling of nulls. + * + * Revision 1.5 2001/02/27 21:43:40 mpowers + * Removed NullMarker class in favor of NSNull. + * + * Revision 1.4 2001/02/26 22:41:51 mpowers + * Implemented null placeholder classes. + * Duplicator now uses NSNull. + * No longer catching base exception class. + * + * Revision 1.3 2001/02/23 21:07:46 mpowers + * Documented the NULL object. + * + * Revision 1.1 2001/02/16 22:51:29 mpowers + * Now deep-cloning objects passed between editing contexts. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java new file mode 100644 index 0000000..2b313d0 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/Introspector.java @@ -0,0 +1,941 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** +* This Introspector is a static utility class written to work +* around limitations in PropertyDescriptor and Introspector.

+* +* Of particular note are the get() and set() methods, which will attempt +* to get and set artibrary values on arbitrary objects to the best of its +* ability, converting values as appropriate. Properties of the form +* "property.nestedproperty.anotherproperty" are supported to get and set +* values on property values directly.

+* +* Note that for naming getter methods, this class supports "get", "is", +* and also the property name itself, which supports NeXT-style properties. +* Introspector supports Maps by treating the keys a property names, +* supports Lists by treating the indexes as property names.

+* +* Numeric and boolean types can be inverted by prepending a "!" before +* the name of the property, like "manager.!active" or "task.!lag". +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class Introspector +{ + // allows "hasProperty" or "property" forms + public static boolean strict = false; + + // print exception stack traces + private static boolean debug = true; + + // path separator + public static final String SEPARATOR = "."; + + // method cache - use hashtables for thread safety + private static Map getterMethods = new Hashtable(); + private static Map setterMethods = new Hashtable(); + + // wildcard value - using this class to represent a "wildcard" generic class. + // we have to do this when matching methods by parameter types and a + // null value is passed in - can't tell what class the null should be. + public static Class WILD = Introspector.class; + + // empty class array - prevents having to create one every time + private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + // use OGNL for property access + private static boolean useOGNL; + + static + { + try + { + useOGNL = ( Class.forName( "ognl.Ognl" ) != null ); + } + catch ( ClassNotFoundException t ) + { + useOGNL = false; + } + } + +/** +* Utility method to get the read method for a property belonging to a class. +* Will search for methods in the form of "getProperty" and failing that +* "isProperty" (to handle booleans). +* @param objectClass the class whose property methods will be retrieved. +* @param aProperty The property whose method will be retrieved. +* @param paramTypes An array of class objects representing the types of parameters. +* @return The appropriate method for the class, or null if not found. +*/ + static public Method getPropertyReadMethod( + Class objectClass, String aProperty, Class[] paramTypes ) + { + Method result = null; + + result = getMethodFromClass( objectClass, aProperty, paramTypes, true ); + + return result; + } + +/** +* Utility method to get the write method for a property belonging to a class. +* Will search for methods in the form of "setProperty". +* @param objectClass the class whose property methods will be retrieved. +* @param aProperty The property whose method will be retrieved. +* @param paramTypes An array of class objects representing the types of parameters. +* @return The appropriate method for the class, or null if not found. +*/ + static public Method getPropertyWriteMethod( + Class objectClass, String aProperty, Class[] paramTypes ) + { + Method result = null; + + result = getMethodFromClass( objectClass, aProperty, paramTypes, false ); + + return result; + } + +/** +* Gets a named method from a class. Using this method is preferred because +* the results are cached and should be faster than calling Class.getMethod(). +* Note that if an object has a "get" getter method and an "is" getter method +* with the same signature defined for a given property. The "get" method +* is called. +* @param objectClass the Class whose property methods will be retrieved. +* @param aMethodName A String containing the name of the desired method. +* @param paramTypes An array of class objects representing the types of parameters. +* @return The appropriate Method from the Class, or null if not found. +*/ + static private Method getMethodFromClass( + Class objectClass, String aProperty, Class[] paramTypes, boolean doGetter) + { // System.out.print( "Introspector.getMethodFromClass: " + aMethodName + " : " ); + + Map classesToMethods = (doGetter ? + getterMethods : + setterMethods); + + Map allMethods = (Map) classesToMethods.get( objectClass ); + if (allMethods == null) + { + // need to build maps for this class + mapPropertiesForClass( objectClass ); + // now the map should exist + allMethods = (Map) classesToMethods.get( objectClass ); + } + + Method[] methods = (Method[]) allMethods.get( aProperty ); + if ( methods == null ) + { + return null; // property doesn't exist + } + + methods_loop: // walks through all methods for name + for ( int i = 0; i < methods.length; i++ ) + { + Class[] types = methods[i].getParameterTypes(); + + // if parameter lengths don't match + if ( types.length != paramTypes.length ) + { + // System.out.println( aMethodName + " : " + types.length + " != " + paramTypes.length ); + continue methods_loop; // continue with outer loop + } + + // match up each parameter + for ( int j = 0; j < types.length; j++ ) + { + // convert primitives so they'll match - ugly + // (would have thought isAssignableFrom() would catch this) + if ( types[j].isPrimitive() ) + { + if ( types[j] == Boolean.TYPE ) + { + types[j] = Boolean.class; + } + else + if ( types[j] == Character.TYPE ) + { + types[j] = Character.class; + } + else + if ( types[j] == Byte.TYPE ) + { + types[j] = Byte.class; + } + else + if ( types[j] == Short.TYPE ) + { + types[j] = Short.class; + } + else + if ( types[j] == Integer.TYPE ) + { + types[j] = Integer.class; + } + else + if ( types[j] == Long.TYPE ) + { + types[j] = Long.class; + } + else + if ( types[j] == Float.TYPE ) + { + types[j] = Float.class; + } + else + if ( types[j] == Double.TYPE ) + { + types[j] = Double.class; + } + } + + // if parameters don't match + if ( ( paramTypes[j] != WILD ) && ( ! types[j].isAssignableFrom( paramTypes[j] ) ) ) + { +// System.out.println( "Introspector.getMethodFromClass: " + +// aProperty + " : " + types[j] + " != " + paramTypes[j] ); + continue methods_loop; // continue with outer loop + } + } + + // all params match + return methods[i]; + } + + // no match + return null; + } + + static private final Method[] getAllMethodsForClass( Class aClass ) + { + Method[] local = aClass.getDeclaredMethods(); // only local + Method[] all = aClass.getMethods(); // all public + Method[] result = new Method[ local.length + all.length ]; + System.arraycopy( local, 0, result, 0, local.length ); + System.arraycopy( all, 0, result, local.length, all.length ); + return result; + } + + /** + * Generates a map of properties to both getter or setter methods for the given class. + * Then assigned those maps into the appropriate getterMethods and setterMethods maps + * keyed by the specified class. Even on error, this method will at least place empty + * property maps into each of the methods maps. + */ + static private void mapPropertiesForClass( Class objectClass ) + { + try + { + Map readProperties = new HashMap(); + getterMethods.put( objectClass, readProperties ); + Map writeProperties = new HashMap(); + setterMethods.put( objectClass, writeProperties ); + + String name, property; + Method[] methods = getAllMethodsForClass( objectClass ); // throws SecurityException + for ( int i = 0; i < methods.length; i++ ) + { + name = methods[i].getName(); + methods[i].setAccessible( true ); // throws SecurityException + if ( name.startsWith( "set" ) ) + { + name = name.substring( 3 ); + if ( ! "".equals( name ) ) // excludes "set()" + { + putMethodIntoPropertyMap( name, methods[i], writeProperties ); + } + } + else + if ( methods[i].getReturnType() != void.class ) + { + String fullname = name; + if ( name.startsWith( "get" ) ) + { + name = name.substring( 3 ); + } + else + if ( name.startsWith( "is" ) ) + { + name = name.substring( 2 ); + } + else + if ( name.startsWith( "has" ) && ( !strict ) ) // what about hashCode()? + { + name = name.substring( 3 ); + } + + if ( ! "".equals( name ) && ( !strict ) ) // excludes "get()", "has()", and "is()" + { + putMethodIntoPropertyMap( name, methods[i], readProperties ); + if ( fullname != name ) + { // allows us to match properties that include the get/set prefix as well + putMethodIntoPropertyMap( fullname, methods[i], readProperties ); + } + } + } + } + } + catch ( SecurityException se ) + { + System.out.println( "Introspector.getMethodFromClass: " + se ); + // this class will show up with empty getter/setter maps + } + } + + /** + * Places a property-method pair into one of the properties maps. + * This in effect maps a property to an array of methods. + */ + private static void putMethodIntoPropertyMap( String aProperty, Method aMethod, Map aMap ) + { + // ensure first character is lower case + StringBuffer buffer = new StringBuffer( aProperty ); + buffer.setCharAt(0, Character.toLowerCase(buffer.charAt(0))); + String key = buffer.toString(); + + // build array of methods for property + Method[] result = (Method[]) aMap.get( key ); + if ( result == null ) + { + result = new Method[] { aMethod }; + } + else + { + // create new array that's larger by one and copy + int i; + Method[] enlarged = new Method[ result.length + 1 ]; + for ( i = 0; i < result.length; i ++ ) + { + enlarged[i] = result[i]; + } + // add the new method to end + enlarged[i] = aMethod; + result = enlarged; + } + aMap.put( key, result ); + } + +/** +* Utility method to get a method for a property belonging to a class. +* Use this if you don't feel like making the Class array from the parameters +* you will be using - pass in the parameters themselves. +* @param objectClass the Class whose property methods will be retrieved. +* @param aProperty The property whose method will be retrieved. +* @param params An array of parameters to be used. +* @return The appropriate method for the class, or null if not found. +*/ + static public Method getPropertyReadMethod( + Class objectClass, String aProperty, Object[] params ) + { + // optimization: avoid allocating class array for common case + if ( params.length == 0 ) + { + return getPropertyReadMethod( + objectClass, aProperty, EMPTY_CLASS_ARRAY ); + } + + Class[] paramList = new Class[ params.length ]; + for ( int i = 0; i < params.length; i++ ) + { + if ( params[i] != null ) + { + paramList[i] = params[i].getClass(); + } + else + { + paramList[i] = WILD; + } + } + return getPropertyReadMethod( objectClass, aProperty, paramList ); + } + +/** +* Utility method to get a method for a property belonging to a class. +* Use this if you don't feel like making the Class array from the parameters +* you will be using - pass in the parameters themselves. +* @param objectClass the Class whose property methods will be retrieved. +* @param aProperty The property whose method will be retrieved. +* @param params An array of parameters to be used. +* @return The appropriate method for the class, or null if not found. +*/ + static public Method getPropertyWriteMethod( + Class objectClass, String aProperty, Object[] params ) + { + Class[] paramList = new Class[ params.length ]; + for ( int i = 0; i < params.length; i++ ) + { + if ( params[i] != null ) + { + paramList[i] = params[i].getClass(); + } + else + { + paramList[i] = WILD; + } + } + return getPropertyWriteMethod( objectClass, aProperty, paramList ); + } + + /** + * Gets a list of the readable properties for the given class. + * Note that readable properties may not be writable - see getWriteProperties(). + * @return An array of property names in no particular order + * where each name is a string with the first character in lower case. + */ + public static String[] getReadPropertiesForClass( Class objectClass ) + { + Map properties = (Map) getterMethods.get( objectClass ); + if ( properties == null ) + { + // need to build maps for this class + mapPropertiesForClass( objectClass ); + // now the map should exist + properties = (Map) getterMethods.get( objectClass ); + } + + // put property names into string array + Set keys = properties.keySet(); + Iterator it = keys.iterator(); + int len = keys.size(); + String[] result = new String[ len ]; + for ( int i = 0; i < len; i++ ) + { + result[i] = (String) it.next(); + } + return result; + } + + /** + * Gets a list of the writable properties for the given class. + * Note that writable properties may not be writable - see getReadProperties(). + * @return An array of property names in no particular order + * where each name is a string with the first character in lower case. + */ + public static String[] getWritePropertiesForClass( Class objectClass ) + { + Map properties = (Map) setterMethods.get( objectClass ); + if ( properties == null ) + { + // need to build maps for this class + mapPropertiesForClass( objectClass ); + // now the map should exist + properties = (Map) setterMethods.get( objectClass ); + } + + // put property names into string array + Set keys = properties.keySet(); + Iterator it = keys.iterator(); + int len = keys.size(); + String[] result = new String[ len ]; + for ( int i = 0; i < len; i++ ) + { + result[i] = (String) it.next(); + } + return result; + } + + /** + * Gets a list of the readable properties for the given object, which may + * not be null. This method is more useful than getReadPropertiesForClass + * in that Maps will return their keys as properties and Lists will return + * their element indices as properties. + * Note that readable properties may not be writable - see getWriteProperties(). + * @return An array of property names in no particular order + * where each name is a string with the first character in lower case. + */ + public static String[] getReadPropertiesForObject( Object anObject ) + { + List properties = new ArrayList(); + String[] classProperties = + getReadPropertiesForClass( anObject.getClass() ); + if ( anObject instanceof List ) + { + properties.addAll( getPropertiesForList( (List) anObject ) ); + } + if ( anObject instanceof Map ) + { + properties.addAll( getPropertiesForMap( (Map) anObject ) ); + } + int i; + int len = classProperties.length + properties.size(); + String[] result = new String[ len ]; + for ( i = 0; i < classProperties.length; i++ ) + { + result[i] = classProperties[i]; + } + Iterator it = properties.iterator(); + while ( it.hasNext() ) + { + result[i++] = it.next().toString(); + } + return result; + } + + /** + * Gets a list of the writable properties for the given object, which may + * not be null. This method is more useful than getWritePropertiesForClass + * in that Maps will return their keys as properties and Lists will return + * their element indices as properties. + * Note that writable properties may not be writable - see getReadProperties(). + * @return An array of property names in no particular order + * where each name is a string with the first character in lower case. + */ + public static String[] getWritePropertiesForObject( Object anObject ) + { + List properties = new ArrayList(); + String[] classProperties = + getWritePropertiesForClass( anObject.getClass() ); + if ( anObject instanceof List ) + { + properties.addAll( getPropertiesForList( (List) anObject ) ); + } + if ( anObject instanceof Map ) + { + properties.addAll( getPropertiesForMap( (Map) anObject ) ); + } + + int i; + int len = classProperties.length + properties.size(); + String[] result = new String[ len ]; + for ( i = 0; i < classProperties.length; i++ ) + { + result[i] = classProperties[i]; + } + Iterator it = properties.iterator(); + while ( it.hasNext() ) + { + result[i++] = it.next().toString(); + } + return result; + } + + private static List getPropertiesForList( List aList ) + { + List result = new ArrayList(); + int len = aList.size(); + for ( int i = 0; i < len; i++ ) + { + result.add( new Integer( i ).toString() ); + } + return result; + } + + private static List getPropertiesForMap( Map aMap ) + { + List result = new ArrayList(); + Iterator it = ((Map)aMap).keySet().iterator(); + while ( it.hasNext() ) + { + result.add( it.next().toString() ); + } + return result; + } + + private static Object[] EMPTY_ARRAY = new Object[0]; + + /** + * Convenience to get a value for a property from an object. + * An empty property string is considered the identity property + * and simply returns the object. + * @throws MissingPropertyException if the property cannot be + * found on the object. + */ + public static Object getValueForObject( Object anObject, String aProperty ) + { + if ( ( aProperty == null ) || ( "".equals( aProperty ) ) ) + { + return anObject; + } + + if ( useOGNL && aProperty.startsWith( "ognl:" ) ) + { + try + { + return ognl.Ognl.getValue( aProperty, anObject ); + } + catch ( Throwable t ) + { + if ( debug ) + { + System.err.println( + "Introspector.getValueForObject: " + + anObject + "' ( " + anObject.getClass() + " )" + + ", ognl:" + aProperty ); + System.err.println( t ); + } + return null; + } + } + + boolean invert = false; + if ( aProperty.startsWith( "!" ) ) + { + aProperty = aProperty.substring(1); + invert = true; + } + + Object result = null; + try + { + Method m = Introspector.getPropertyReadMethod( + anObject.getClass(), aProperty, EMPTY_ARRAY ); + if ( m != null ) + { + result = m.invoke( anObject, EMPTY_ARRAY ); + } + else // no method, try for field + { + try + { + Field field = anObject.getClass().getDeclaredField( aProperty ); + if ( field != null ) + { + field.setAccessible( true ); // throws SecurityException + result = field.get( anObject ); + } + } + catch ( Throwable t ) + { + // ignore for now + } + } + + if ( result == null ) + { + if ( anObject instanceof Map ) + { + result = ((Map)anObject).get( aProperty ); + } + else + if ( anObject instanceof List ) + { + result = ((List)anObject).get( Integer.parseInt( aProperty ) ); + } + } + + if ( invert ) + { + Object inverted = ValueConverter.invert( result ); + if ( inverted != null ) result = inverted; + } + //System.out.println( "getValueForObject: " + anObject + " : " + aProperty + " : " + result ); + return result; + } + catch ( Throwable exc ) + { + if ( exc instanceof InvocationTargetException ) + { + exc = ((InvocationTargetException)exc).getTargetException(); + } + if ( exc instanceof RuntimeException ) + { + throw (RuntimeException)exc; + } + if ( debug ) + { + System.out.println( + "Introspector.getValueForObject: " + + anObject + "' ( " + anObject.getClass() + " )" + + ", " + aProperty + ": " ); + } + throw new WotonomyException( exc ); + } +//! throw new MissingPropertyException(); + } + + /** + * Convenience to set a value for a property from an object. + * Returns the return value from executing the specified method, + * or null if the method returns type void. + * @throws MissingPropertyException if the property cannot be + * found on the object. + * @throws NullPrimitiveException if the property is of primitive + * type and the value is null. + */ + public static Object setValueForObject( + Object anObject, String aProperty, Object aValue ) + { + if ( useOGNL && aProperty.startsWith( "ognl:" ) ) + { + try + { + ognl.Ognl.setValue( aProperty, anObject, aValue ); + } + catch ( Throwable t ) + { + if ( debug ) + { + System.err.println( + "Introspector.setValueForObject: " + + anObject + "' ( " + anObject.getClass() + " )" + + ", ognl:" + aProperty + " : " + aValue ); + System.err.println( t ); + } + } + return null; + } + + try + { + if ( aProperty.startsWith( "!" ) ) + { + aProperty = aProperty.substring(1); + Object inverted = ValueConverter.invert( aValue ); + if ( inverted != null ) aValue = inverted; + } + + Method m = null; + if ( aValue != null ) + { + m = Introspector.getPropertyWriteMethod( + anObject.getClass(), aProperty, new Class[] { aValue.getClass() } ); + } + if ( m == null ) + { + m = Introspector.getPropertyWriteMethod( + anObject.getClass(), aProperty, new Class[] { WILD } ); + if ( ( m != null ) && ( aValue != null ) ) + { + // check for null primitive + if ( ( aValue == null ) && + ( m.getParameterTypes()[0].isPrimitive() ) ) + { + throw new NullPrimitiveException(); + } + + // convert if possible + Object o = ValueConverter.convertObjectToClass( + aValue, m.getParameterTypes()[0] ); + if ( o != null ) + { + aValue = o; + } + } + } + if ( m != null ) + { + return m.invoke( anObject, new Object[] { aValue } ); + } + else // no method, try for field + { + try + { + Field field = anObject.getClass().getDeclaredField( aProperty ); + if ( field != null ) + { + field.setAccessible( true ); // throws SecurityException + field.set( anObject, aValue ); + return null; + } + } + catch ( Throwable t ) + { + // ignore for now + } + } + + if ( anObject instanceof Map ) + { + return ((Map)anObject).put( aProperty, aValue ); + } + if ( anObject instanceof List ) + { + List list = (List) anObject; + int i = Integer.parseInt( aProperty ); + if ( list.size() < i+1 ) + { + // expand list as necessary + for ( int j = list.size(); j <= i; j++ ) + { + list.add( new Object() ); // placeholder + } + } + return list.set( i, aValue ); + } + } + catch ( Throwable exc ) + { + if ( exc instanceof IllegalArgumentException ) + { + System.out.println( + "Introspector.setValueForObject: " + + anObject + " , " + aProperty + " , '" + + aValue + "' ):" ); + System.out.println( exc ); + } + else + if ( exc instanceof InvocationTargetException ) + { + exc = ((InvocationTargetException)exc).getTargetException(); + } + if ( exc instanceof RuntimeException ) + { + throw (RuntimeException)exc; + } + if ( debug ) + { + System.out.println( + "Introspector.setValueForObject: " + + anObject + " , " + aProperty + " , '" + + aValue + "' ):" ); + } + throw new WotonomyException( exc ); + } + return null; +//! throw new MissingPropertyException(); + } + + /** + * Gets a value from an object or any of its child objects. + * This will parse the property string for "."'s and get + * values for each successive object's property in the path. + * An empty property string is considered the identity property + * and simply returns the object. + */ + public static Object get( Object anObject, String aProperty ) + { + int i = aProperty.indexOf( SEPARATOR ); + if ( i == -1 ) return getValueForObject( anObject, aProperty ); + + String pathElement = aProperty.substring( 0, i ); + String remainder = aProperty.substring( i+1 ); + + Object result = getValueForObject( anObject, pathElement ); + if ( result == null ) return null; + return get( result, remainder ); + } + + /** + * Sets a value in an object or any of its child objects. + * This will parse the property string for "."'s and set + * values for each successive object's property in the path.

+ * + * If a property is not found, this method will try to + * implicitly create hash maps (if possible) to fill out the path. + * This is useful when dealing with trees of nested maps. + */ + public static Object set( Object anObject, String aProperty, Object aValue ) + { + int i = aProperty.indexOf( SEPARATOR ); + if ( i == -1 ) return setValueForObject( anObject, aProperty, aValue ); + + String pathElement = aProperty.substring( 0, i ); + String remainder = aProperty.substring( i+1 ); + + Object result = getValueForObject( anObject, pathElement ); + if ( result == null ) + { + result = new HashMap(2); + setValueForObject( anObject, pathElement, result ); + } + return set( result, remainder, aValue ); + } + + /** + * If set to true, exceptions printed to System.out.println. + * Defaults to true. + */ + public void setDebug( boolean isDebug ) + { + debug = isDebug; + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:11:47 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.19 2004/02/05 02:20:34 mpowers + * Added experimental ognl support (if ognl is present). + * + * Revision 1.18 2003/03/26 16:44:35 mpowers + * Now correctly reflecting on all methods, not just locally declared ones. + * + * Revision 1.17 2003/02/21 21:10:51 mpowers + * Now reaching package, protected, and private methods and fields. + * + * Revision 1.16 2003/01/28 22:11:59 mpowers + * Now more lenient in resolving properties starting with "is" "get" or "has". + * + * Revision 1.15 2003/01/27 15:10:54 mpowers + * Better handling for illegal argument exceptions. + * + * Revision 1.14 2003/01/18 23:30:42 mpowers + * WODisplayGroup now compiles. + * + * Revision 1.13 2002/10/11 15:35:12 mpowers + * Removed printlns. + * + * Revision 1.11 2001/05/02 17:58:41 mpowers + * Removed debugging code, added comments. + * + * Revision 1.10 2001/04/08 21:00:54 mpowers + * Changes to support new objectsForFetchSpecification scheme. + * + * Revision 1.9 2001/03/29 03:30:36 mpowers + * Refactored duplicator a bit. + * Disabled MissingPropertyExceptions for now. + * + * Revision 1.8 2001/03/28 17:52:45 mpowers + * Corrected the throws in the docs. + * + * Revision 1.7 2001/03/28 17:49:13 mpowers + * Better exception handling in Introspector. + * + * Revision 1.6 2001/03/13 21:40:20 mpowers + * Improved handling of runtime exceptions. + * + * Revision 1.5 2001/03/09 22:06:35 mpowers + * Now extracting the wrapped exception from InvocationTargetExceptions. + * + * Revision 1.4 2001/03/01 20:36:35 mpowers + * Better error handling and better handling of nulls. + * + * Revision 1.3 2001/01/17 16:20:57 mpowers + * Introspector now handles the identity property. + * + * Revision 1.2 2001/01/09 20:08:17 mpowers + * Slight optimization. + * + * Revision 1.1.1.1 2000/12/21 15:52:04 mpowers + * Contributing wotonomy. + * + * Revision 1.5 2000/12/20 16:25:46 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java new file mode 100644 index 0000000..b1ad824 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/IntrospectorException.java @@ -0,0 +1,32 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +/** +* A WotonomyException that is thrown by Introspector. +* This class serves as a base class for other exceptions. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class IntrospectorException extends WotonomyException +{ +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java new file mode 100644 index 0000000..c1e30d3 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/MissingPropertyException.java @@ -0,0 +1,32 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +/** +* A IntrospectorException that is thrown by Introspector when +* a property does not exist for an object. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class MissingPropertyException extends IntrospectorException +{ +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java new file mode 100644 index 0000000..43c14a5 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NetworkClassLoader.java @@ -0,0 +1,368 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package net.wotonomy.foundation.internal; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; + +/** + * The correct name for this class should be URLClassLoader. + * But there is already a class by that name in JDK1.2. + * + * I have had quite a few problems with URLClassLoader in + * past, so I ended up writing this ClassLoader. I found that + * the Java 2's URLClassLoader, does not close the Jar file once + * opened. It is a pretty good optimization step, but if you + * modify the class in the jar file, it does not pick it up. Some + * operating systems may not let you modify the jar file while it is + * still open. IMHO, it does make sense to close the jar file + * after you are done reading the class data. But this approach may not + * get you the performance of the URLClassLoader, but it works in all + * cases and also runs on JDK1.1. I have enhanced this class loader + * to read all the zip/jar entries once & cache the data, so that + * there is no overhead of opening/closing jar file to pick up + * each entry. + * + * + * @author Harish Prabandham + */ +public class NetworkClassLoader extends ClassLoader { + private ClassLoader parent = null; // parent classloader + private Hashtable classCache = new Hashtable(); + private Hashtable urlset = new Hashtable(); + + /** + * Creates a new instance of the class loader. + * @param delegate/parent class loader. + */ + public NetworkClassLoader(ClassLoader parent) { + setParent(parent); + } + + /** + * Sets the parent/delegate class loader. + * @param delegate/parent class loader. + */ + protected final void setParent(ClassLoader parent) { + this.parent = parent; + } + + /** + * Adds the given URL to this class loader. If the URL + * ends with "/", then it is assumed to be a directory + * otherwise, it is assumed to be a zip/jar file. If the + * same URL is added again, the URL is re-opened and this + * zip/jar file is used for serving any future class requests. + * @param URL where to look for the classes. + */ + public synchronized void addURL(URL url) { + // System.out.println("Adding url: " + url); + if(!urlset.containsKey(url)) { + try { + urlset.put(url, new URLResourceReader(url)); + }catch(IOException ioe){ + // Probably a bad url... + } + } else { + // remove the old one & add a new one... + try{ + URLResourceReader newu = new URLResourceReader(url); + URLResourceReader oldu = (URLResourceReader) urlset.get(url); + oldu.close(); + urlset.remove(url); + urlset.put(url, newu); + } catch (IOException ioe) { + } + } + } + + /** + * @return An enumeration of URLs where this class loader + * looks for classes. + */ + public Enumeration getURLs() { + return urlset.keys(); + } + + /** + * Call this to bypass the implementation of loadClass. + */ + public Class findClass(String name) { + byte[] b = loadClassData(name); + if ( b == null ) return null; + return defineClass(name, b, 0, b.length); + } + + protected byte[] loadResource(URL url, String resourceName) + throws IOException { + URLResourceReader urr = (URLResourceReader) urlset.get(url); + // System.out.println("Loading from " + urr + " " + resourceName); + if(urr != null) { + return urr.getResource(resourceName); + } + + return null; + } + + protected byte[] loadResource(String resource) { + byte[] barray = null; + for(Enumeration e = urlset.keys(); e.hasMoreElements();) { + URL url = (URL) e.nextElement(); + + try { + barray = loadResource(url, resource); + } catch(Exception ex) { + } finally { + if(barray != null) + break; + } + } + + return barray; + } + + protected byte[] loadClassData(String classname) { + String resourceName = classname.replace('.', '/') + ".class"; + return loadResource(resourceName); + } + + /** + * Overridden to search for a resource and return + * a "jar"-style URL or normal "file" URL as necessary. + */ + protected URL findResource(String name) + { //System.out.println( "findResource: " + name ); + URL url; + byte[] barray = null; + + for ( Enumeration e = urlset.keys(); e.hasMoreElements(); ) + { + url = (URL) e.nextElement(); + try + { + barray = loadResource(url, name); // loads fully: wasteful + } + catch(Exception ex) + { + // do nothing + } + if( barray != null ) + { + try + { + String ref = url.toString(); + if ( ref.endsWith( ".jar" ) ) + { + //System.out.println( "jar:" + ref + "!/" + name ); + return new URL( "jar:" + ref + "!/" + name ); + } + else + { + //System.out.println( new URL( url, name ).toString() ); + return new URL( url, name ); + } + } + catch ( Throwable t ) + { + t.printStackTrace(); + } + } + } + + return null; + } + + /** + * @return The resource as the input stream if such a resource + * exists, otherwise returns null. + */ + public InputStream getResourceAsStream(String name) { + //System.out.println( "getResourceAsStream: " + name ); + InputStream istream = null; + + // Algorithm: + // + // 1. first check the system path for the resource + // 2. next check the delegate/parent class loader for the resource + // 3. then attempt to get the resource from the url set. + // + + // Lets check the system path for the resource. + istream = getSystemResourceAsStream(name); + if(istream != null) + return istream; + + // Lets check the parent/delegate class loader for the resource. + if(parent != null) { + istream = parent.getResourceAsStream(name); + if(istream != null) + return istream; + } + + // Lets load it ourselves. + byte[] data = loadResource(name); + if(data != null) { + istream = new ByteArrayInputStream(data); + } + + return istream; + } + + /** + * java.lang.ClassLoader's defineClass method is final, so the + * its subclasses cannot override this method. But, this class + * calls this method in the loadClass() instead. + * @param The name of the class without ".class" extension. + * @param The class data bytes. + * @return The class object. + */ + protected Class defineClass(String classname, byte[] classdata) { + return defineClass(classname, classdata, 0, classdata.length); + } + + public synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + Class c = null; + + // Algorithm: (Please do not change the order; unless you + // have a good reason to do so). + // + // 1. first check the system class loader. + // 2. next check the delegate/parent class loader. + // 3. next check the class cache + // 4. then attempt to load classes from the URL set. + // + + // Lets see if the class is in system class loader. + try { + c = findSystemClass(name); + }catch(ClassNotFoundException cnfe) { + }finally { + if(c != null) + return c; + } + + // Lets see if the class is in parent class loader. + try { + if(parent != null) + c = parent.loadClass(name); + }catch(ClassNotFoundException cnfe) { + }finally { + if(c != null) + return c; + } + + // Lets see if the class is in the cache.. + c = (Class) classCache.get(name); + + if(c != null) + return c; + + + // Lets see if we find the class all by ourselves. + byte[] data = loadClassData(name); + + if(data != null) { + // we did !! + c = defineClass(name, data); + classCache.put(name, c); + if(resolve) + resolveClass(c); + } else { + // We are out of luck at this point... + throw new ClassNotFoundException(name); + } + + return c; + } + + /** + * This method resets this ClassLoader's state. It completely + * removes all the URLs and classes in this class loader cache. + */ + public final void clear() { + urlset.clear(); + classCache.clear(); + } + + /** + * This method resets this ClassLoader's state and resets the + * references for garbage collection. + */ + protected void finalize() throws Throwable { + // Cleanup real well. Otherwise, this can be + // a major source of memory leaks... + + // remove all the urls & class entries. + clear(); + + parent = null; + urlset = null; + classCache = null; + } +} + + + + + + + + + + + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java new file mode 100644 index 0000000..e367211 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/NullPrimitiveException.java @@ -0,0 +1,32 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +/** +* A IntrospectorException that is thrown by Introspector when +* trying to set a primitive type to null. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class NullPrimitiveException extends IntrospectorException +{ +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java new file mode 100644 index 0000000..abdc82f --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyComparator.java @@ -0,0 +1,100 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +import java.io.Serializable; +import java.util.Comparator; + + +/** +* A Comparator that will sort elements based on the +* property specified in the constructor. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class PropertyComparator implements Comparator, Serializable +{ + private String property; + +/** +* Standard constructor to configure the comparator. +* @param aProperty A property whose value is used to sort elements. +*/ + public PropertyComparator( String aProperty ) + { + property = aProperty; + } + + // interface Comparator + + public int compare(Object o1, Object o2) + { + Object v1 = Introspector.get( o1, property ); + Object v2 = Introspector.get( o2, property ); + if ( v1 instanceof Comparable ) + { + return ((Comparable)v1).compareTo( v2 ); + } + else + if ( v2 instanceof Comparable ) + { + return ((Comparable)v2).compareTo( v1 ); + } + else + { + if ( v1 == null ) + { + if ( v2 == null ) + { + return 0; // both nulls are equal + } + return -1; // null is less than any object + } + else + if ( v2 == null ) + { + return 1; // any object is greater than null + } + } + // last resort: compare string conversions + return v1.toString().compareTo( v2.toString() ); + } + + public boolean equals( Object obj ) + { + return ( obj instanceof PropertyComparator ); + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:11:47 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:52:07 mpowers + * Contributing wotonomy. + * + * Revision 1.2 2000/12/20 16:25:46 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java new file mode 100644 index 0000000..03231c7 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/PropertyListParser.java @@ -0,0 +1,546 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Blacksmith, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +import java.util.*; //collections +import java.io.*; + +/** + * PropertyListParser can parse a property list (plist) file or string, and + * return the top-level object represented by the plist.

+ * + * A property list is a heirarchical data structure containing only Maps, + * Lists, and Strings -- nothing else. In other words, a property list is + * either a Map, List, or String instance, with the restrictions that the + * collections may only contain Map, List, or String instances.

+ * + * This class can read a particularly-formatted string or file, and create + * the property list structure described. It provides a convenient means + * for having a structured data file, letting programs simply deal with the + * structure rather than having to do a lot of string parsing work as well. + * The concept is similar to Properties files, except that the values can + * be nested Maps or Lists instead of only Strings.

+ * + * A Map is specified in a file by key/value pairs surrounded by brace + * characters. An equal sign (=) must be between the key and value, and + * there must be a semicolon (;) following the value. + * + *

+ *     {
+ *         key1 = value1;
+ *         key2 = value2;
+ *         etc...
+ *     }
+ * 
+ * + * A List is specified by a comma-separated list of values surrounded by parentheses, like: + *
+ *     ( value1, value2, value3, etc... )
+ * 
+ * + * A String can either be quoted in the manner of a constant string in + * Java, or unquoted. If unquoted, the string can only contain + * alphanumerics, underscores (_), periods (.), dollar signs ($), colons + * (:), or forward slashes (/). If any other character appears in the + * string, it must be quoted (i.e., surrounded by " characters). + * Quoted strings may also contain \n, \t, \f, \v, \b, and \a escapes, + * octal escapes of the form \000, and unicode escapes of the form of \U + * followed by four hexadecimal characters. Any other character escaped + * by a backslash will be treated as that character, and the escaping + * backslash character will be omitted. Thus, to represent an actual + * backslash, it must appear as \\ in the quoted string.

+ * + * All whitespace between elements is ignored, and both //-style and + * /*-style comments are allowed to appear anywhere between elements.

+ * + * If there are any syntax errors encountered while parsing, + * RuntimeExceptions are thrown with the line number and column of the + * problem.

+ * + * Currenty, HashMaps and ArrayLists are the actual Map and List classes + * used when creating the property list.

+ * + * Examples:

+
+   // This plist file represents a Map, since it starts with a '{'.
+   {
+       Map1 = { subkey1 = "foo"; };
+       Map2 =
+       {
+           "key1"  = "This is a quoted string.";
+           "key 2" = "bar\nbaz";    // the value has a newline in it
+           key3    = ("a", b, c, "quux quux");   // a List of four Strings
+       };  // We need a semicolon here, since it's following the value of the "Map2" key
+
+       List1 = (foobar,foobaz,"foo,baz", (aa, ab, ac)); // a List of 3 Strings and a List
+
+       // And now a List of two Maps
+       List2 = (
+           {
+               key1 = value1;
+               key2 = "value 2";
+               key3 = (a,b,c,d);
+               key4 = ();
+           },  // We need the comma here
+           {
+               key1 = {};  // an empty Map
+               key2 = "another String value";
+           }
+       );
+   }
+ 
+
+ * For those wondering, this is essentially a re-implementation of + * NeXT/Apple's property lists, except that data values are not supported. + * + * @author clindberg@blacksmith.com + * @version $Revision: 899 $ + */ + +public class PropertyListParser +{ + private char buffer[]; + private int currIndex; + private int lineNumber; + private int currLineStartIndex; + + /** Reads an object (String, List, or Map) from plistString and returns it. + * RuntimeExceptions are raised if there are parse problems. + */ + public static Object propertyListFromString(String plistString) + { + PropertyListParser parser = new PropertyListParser(plistString); + return parser.readTopLevelObject(); + } + + /** + * Reads all remaining characters from the Reader, and returns the + * result of propertyListFromString(). RuntimeExceptions are raised if + * there are parse problems + */ + public static Object propertyListFromReader(Reader reader) throws IOException + { + char charBuffer[] = new char[2048]; + StringBuffer stringBuffer = new StringBuffer(); + int numRead = 0; + + while (numRead >= 0) + { + numRead = reader.read(charBuffer); + if (numRead > 0) stringBuffer.append(charBuffer, 0, numRead); + } + + return propertyListFromString(stringBuffer.toString()); + } + + /** + * Reads the contents of the specified file, and parses the contents. + * If any error occurs, prints out a message using System.out.println() + * and returns null. + */ + public static Object propertyListFromFile(String filename) + { + try { + FileInputStream stream = new FileInputStream(filename); + return propertyListFromReader(new InputStreamReader(stream)); + } catch (Exception exception) { + String errorMessage = exception.getMessage(); + System.out.println("Error parsing property list from "+filename+": "+errorMessage); + } + + return null; + } + + /** + * Creates a new PropertyListParser to parse the contents of the + * specified String. + */ + public PropertyListParser(String plistString) + { + this(plistString.toCharArray()); + } + + /** + * Creates a new PropertyListParser to parse the specified char array. + */ + public PropertyListParser(char[] charArray) + { + buffer = charArray; + lineNumber = 1; + currLineStartIndex = 1; + currIndex = 0; + } + + public Object readTopLevelObject() + { + Object plist = readObject(); + + skipCommentWhitespace(); + if (!isAtEnd()) + { + throwParseException("Extra characters in plist string after parsing object. A plist should only contain one top-level object."); + } + + return plist; + } + + private void throwParseException(String errorMessage) + { + int column = currIndex - currLineStartIndex + 1; + throw new RuntimeException(errorMessage + " (Line " + lineNumber + ", column " + column + ")"); + } + + private void updateLineNumberWithIndex(int lineStartIndex) + { + lineNumber++; + currLineStartIndex = lineStartIndex; + } + + private boolean isAtEnd() + { + return currIndex >= buffer.length; + } + + private void skipDoubleslashComment() + { + while (!isAtEnd() && buffer[currIndex] != '\n') { + currIndex++; + } + } + + private void skipStandardCComment() + { + currIndex++; //skip over the starting '/' + + while (!isAtEnd()) + { + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex+1); + + currIndex++; + + if (buffer[currIndex-2] == '*' && buffer[currIndex-1] == '/') + { + return; + } + } + + throwParseException("Input exhausted while parsing comment"); + } + + private void skipWhitespace() + { + while (!isAtEnd() && isWhitespace(buffer[currIndex])) + { + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex+1); + currIndex++; + } + } + + private void skipCommentWhitespace() + { + boolean done = false; + + while (!done) + { + done = true; + + skipWhitespace(); + if ((buffer.length - currIndex) > 1 && buffer[currIndex] == '/') + { + if (buffer[currIndex+1] == '/') { + done = false; //iterate again + skipDoubleslashComment(); + } + else if (buffer[currIndex+1] == '*') { + done = false; //iterate again + skipStandardCComment(); + } + } + } + } + + private Object readObject() + { + skipCommentWhitespace(); + if (isAtEnd()) return null; + + // Data (i.e. byte[]) not supported + if (buffer[currIndex] == '"') + return readQuotedString(); + if (buffer[currIndex] == '(') + return readList(); + if (buffer[currIndex] == '{') + return readMap(); + + return readUnquotedString(); + } + + private static final byte valueForHexDigit(char c) + { + if(c >= '0' && c <= '9') return (byte)(c - '0'); + if(c >= 'a' && c <= 'f') return (byte)((c - 'a') + 10); + if(c >= 'A' && c <= 'F') return (byte)((c - 'A') + 10); + + return 0; + } + + private static final boolean isOctalDigit(char c) + { + return c >= '0' && c <= '7'; + } + + private static final boolean isHexDigit(char c) + { + return (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'F'); + } + + private static String unquotedStringChars = "._$:/"; // chars allowed in unquoted strings + private static String whitespaceChars = " \t\n\r\f"; + + private static final boolean isWhitespace(char c) + { + return whitespaceChars.indexOf(c) >= 0; + } + + private static final boolean isValidUnquotedStringChar(char c) + { + return ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + unquotedStringChars.indexOf(c) >= 0); + } + + private String readUnquotedString() + { + int startIndex = currIndex; + + while (!isAtEnd() && isValidUnquotedStringChar(buffer[currIndex])) + currIndex++; + + if (startIndex == currIndex) + throwParseException("No allowable characters found to parse unquoted string"); + + return new String(buffer, startIndex, currIndex - startIndex); + } + + private String readQuotedString() + { + currIndex++; //skip over '"' + + StringBuffer stringBuffer = new StringBuffer(); + int startIndex = currIndex; + + while (!isAtEnd() && buffer[currIndex] != '"') + { + if (buffer[currIndex] != '\\') + { + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex+1); + + /* + * Just increment the index -- all these characters will be + * appended in chunks, either before an escape sequence or + * at the end. + */ + currIndex++; + } + else // it's an escape + { + /* Append anything scanned past before the '\\' */ + if (startIndex < currIndex) + stringBuffer.append(buffer, startIndex, currIndex - startIndex); + currIndex++; // skip over '\\' + + if (isAtEnd()) + throwParseException("Input exhausted while parsing escape sequence"); + + switch (buffer[currIndex]) + { + case 't': stringBuffer.append('\t'); currIndex++; break; // tab + case 'n': stringBuffer.append('\n'); currIndex++; break; // newline + case 'r': stringBuffer.append('\r'); currIndex++; break; // carriage return + case 'f': stringBuffer.append('\f'); currIndex++; break; // form feed + case 'b': stringBuffer.append('\b'); currIndex++; break; // backspace + case 'a': stringBuffer.append('\007'); currIndex++; break; // bell + case 'v': stringBuffer.append('\013'); currIndex++; break; // vertical tab + case 'U': + case 'u': + { + /* A Unicode escape. Always followed by 4 hex digits. */ + currIndex++; // skip past the 'U' + if ((currIndex+4) > buffer.length) + throwParseException("Not enough chars to parse \\U sequence"); + + if(!isHexDigit(buffer[currIndex]) || !isHexDigit(buffer[currIndex+1]) || + !isHexDigit(buffer[currIndex+2]) || !isHexDigit(buffer[currIndex+3])) + { + throwParseException("Four hex digits not found for \\U sequence"); + } + + byte byte3 = valueForHexDigit(buffer[currIndex]); + byte byte2 = valueForHexDigit(buffer[currIndex+1]); + byte byte1 = valueForHexDigit(buffer[currIndex+2]); + byte byte0 = valueForHexDigit(buffer[currIndex+3]); + char theChar = (char)((byte3 << 12) + (byte2 << 8) + (byte1 << 4) + byte0); + stringBuffer.append(theChar); + currIndex += 4; + break; + } + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + { + /* An octal escape. Expect 1, 2, or 3 octal digits. */ + int digits = 0; + int value = 0; + + do { + value *= 8; + value += (int)(buffer[currIndex] - '0'); + currIndex++; + digits++; + } while (digits <= 3 && !isAtEnd() && isOctalDigit(buffer[currIndex])); + + if (value > 255) + throwParseException("Value too large in octal escape sequence (> 0377)"); + + // This assumes value is in ISO Latin 1 encoding + stringBuffer.append((char)value); + break; + } + /* I guess plists can't have the \x{HEX}{HEX} escapes */ + default: + { + // Unknown escape sequence, just add the character. + // GCC warns if this isn't a '"', '\'', or '\\'... + stringBuffer.append(buffer[currIndex]); + if (buffer[currIndex] == '\n') + updateLineNumberWithIndex(currIndex+1); + currIndex++; + break; + } + } // end case + + /* Reset startIndex, so a verbatim copy will now start from this index */ + startIndex = currIndex; + + } //end '\\' escape + } + + if (isAtEnd()) + throwParseException("Input exhausted while parsing quoted string"); + if (startIndex < currIndex) + stringBuffer.append(buffer, startIndex, currIndex - startIndex); + currIndex++; //skip past '"' + + return stringBuffer.toString(); + } + + private List readList() + { + List newList = new ArrayList(); + + currIndex++; //skip over '(' + skipCommentWhitespace(); + while (!isAtEnd() && buffer[currIndex] != ')') + { + /* A comma is required between list elements */ + if (newList.size() > 0) + { + if (buffer[currIndex] != ',') + throwParseException("List parsing failed: expecting ','"); + currIndex++; + skipCommentWhitespace(); + if (isAtEnd()) + throwParseException("Input exhausted while parsing list"); + } + + if (buffer[currIndex] != ')') + { + Object plistObject = readObject(); + if (plistObject == null) + throwParseException("List parsing failed: could not read contained object."); + newList.add(plistObject); + skipCommentWhitespace(); + } + } + + if (isAtEnd()) + throwParseException("Input exhausted while parsing list"); + currIndex++; //skip past ')' + + return newList; + } + + private Map readMap() + { + HashMap newMap = new HashMap(); + + currIndex++; // skip over open brace + skipCommentWhitespace(); + + while (!isAtEnd() && buffer[currIndex] != '}') + { + Object key; + Object value; + + key = readObject(); + if (key == null || !(key instanceof String)) + throwParseException("Map parsing failed: could not parse key or key is not a String"); + + skipCommentWhitespace(); + if (isAtEnd() || buffer[currIndex] != '=') + throwParseException("Map parsing failed: expecting '='"); + currIndex++; //skip over '=' + skipCommentWhitespace(); + if (isAtEnd()) + throwParseException("Input exhausted while parsing map"); + + value = readObject(); + if (value == null) + throwParseException("Map parsing failed: could not parse value object"); + + skipCommentWhitespace(); + if (isAtEnd() || buffer[currIndex] != ';') + throwParseException("Map parsing failed: expecting ';'"); + currIndex++; //skip over ';' + skipCommentWhitespace(); + + newMap.put(key, value); + } + + if (isAtEnd()) + throwParseException("Input exhausted while parsing map"); + currIndex++; //skip past '}' + + return newMap; + } + + + public static void main(String[] args) + { + String filename = args[0]; + Object plist = PropertyListParser.propertyListFromFile(filename); + System.out.println(plist); + } +} + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java new file mode 100644 index 0000000..6d35b7b --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/QueueMap.java @@ -0,0 +1,538 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Blacksmith, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +import java.util.*; //collections + +/** +* A queue based implementation of the Map interface. This class provides for +* all the opertions of a map, but keeps the entries in the same sequence as +* originally added. The first entry placed in the map will be the first +* entry retreived during iteration: first-in, first-out (FIFO).

+* +* Keys cannot be duplicated. If an entry is made using a key that already +* exists, the value for that key will be replaced with that new value. There +* are no such restrictions on the values. The values may be null.

+* +* Some convenience methods are provided for the queue type operations. The +* first key can be retreived as well as the last key. A key can be used +* to retreive its corresponding value from the map. A value can also be used +* to retreive its key from the map, however, since there may be multiple values +* of the same equality, the first key found will be returned.

+* +* @author rglista@blacksmith.com +* @author mpowers@blacksmith.com +* @date $Date: 2006-02-18 17:41:36 -0500 (Sat, 18 Feb 2006) $ +* @revision $Revision: 899 $ +*/ +public class QueueMap implements Map +{ + List values; + List keys; + Map keyToValue; + +/** +* Creates an empty QueueMap. +*/ + public QueueMap() + { + values = new LinkedList(); + keys = new LinkedList(); + keyToValue = new HashMap(); + } + +/** +* Creates a QueueMap and places the entries from the passed in map into this map. +* The order of the entries is based on however the iterator iteratated through +* the map entries. +* @param t A map object. +*/ + public QueueMap( Map t ) + { + values = new ArrayList(); + keys = new ArrayList(); + keyToValue = new HashMap(); + + putAll( t ); + } + +/** +* Removes all the entries from this map. +*/ + public void clear() + { + values.clear(); + keys.clear(); + keyToValue.clear(); + } + +/** +* Tests to see if the key is contained in the map. If the key is present in +* the map, then TRUE is returned, otherwise FALSE is returned. The equals() +* operation is used to determine equality. +* @return True if the map contains the given key, false otherwise. +*/ + public boolean containsKey( Object key ) + { + return keyToValue.containsKey( key ); + } + +/** +* Tests to see if the value is contained in the map. If the value is present in +* the map, then TRUE is returned, otherwise FALSE is returned. The equals() +* operation is used to determine equality. The value can be null and will +* return TRUE if null is a value in the map. If TRUE is returned, then there +* may be more than one values in the map. +* @return True if the map contains the given value, false otherwise. +*/ + public boolean containsValue( Object value ) + { + return keyToValue.containsValue( value ); + } + +/** +* Returns a set view of the mappings contained in this map. Each element +* in the returned set is a Map.Entry. The returned set is NOT backed +* by the map, so changes to the map are NOT reflected in the set, and vice-versa. +* The returned set is independent of this Map and its underlying structure. +* +* @return a set view of the mappings contained in this map. +*/ + public Set entrySet() + { + Set result = new HashSet(keys.size(), 1F); + + for ( int i = 0; i < keys.size(); i++ ) + { + result.add( new KeyValuePair( keys.get(i), values.get(i) ) ); + } + + return result; + } + +/** +* Compares the specified object with this map for equality. Returns +* true if the given object is also a map and the two Maps +* represent the same mappings. More formally, two maps t1 and +* t2 represent the same mappings if +* t1.entrySet().equals(t2.entrySet()). This ensures that the +* equals method works properly across different implementations +* of the Map interface. +* +* @param o object to be compared for equality with this map. +* @return true if the specified object is equal to this map. +*/ + public boolean equals( Object o ) + { + return keyToValue.equals( o ); + } + +/** +* Returns the corresponding value for the given key. The returned value may be +* null as that is a legal value in this map. However, if the key is not +* contained in this map then null is also returned. Use the containsKey() +* method to distinguish between the two cases. +* @param key A key into the map. +* @return The value corresponding to the key (can be null), or null if the key +* is not contained in the map. +*/ + public Object get( Object key ) + { + return keyToValue.get( key ); + } + +/** +* Returns the hash code value for this map. The hash code of a map +* is defined to be the sum of the hashCodes of each entry in the map's +* entrySet view. This ensures that t1.equals(t2) implies +* that t1.hashCode()==t2.hashCode() for any two maps +* t1 and t2, as required by the general +* contract of Object.hashCode. +* +* @return the hash code value for this map. +*/ + public int hashCode() + { + return keyToValue.hashCode(); + } + +/** +* Returns true is this map contains no key-value mappings. +* @return True is this map contains no entries, false otherwise. +*/ + public boolean isEmpty() + { + return keyToValue.isEmpty(); + } + +/** +* Returns the keys used in the map. There is no order implied in the returned +* set and may be different than the the order in which they were inserted. +* @return A Set containing all the keys used in the map. +*/ + public Set keySet() + { + Set result = new HashSet(keys.size(), 1F); + + for ( int i = 0; i < keys.size(); i++ ) + { + result.add( keys.get(i) ); + } + + return result; + } + +/** +* Places the key/value entry into the map at the end position. If the key +* already exists in the map, then its value is replaced by the new given +* value. The mapping does not change order in this case. The specified +* key cannot be null. +* @param key The key to place into the map, cannot be null. +* @param value The value to associate with the key, may be null. +* @return Null if the key is new, the replaced value if the key already +* existed. (Note: If the key was null, then null is returned.) +*/ + public Object put( Object key, Object value ) + { + if ( key == null ) return null; + + if ( keys.contains( key ) ) + { + values.remove( keys.indexOf( key ) ); + values.add( keys.indexOf( key ), value ); + } + else + { + values.add( value ); + keys.add( key ); + } + + return keyToValue.put( key, value ); + } + +/** +* Places all the entries from this given map into this map. If the keys +* already exist, then there values are replaced. +* @param t A map of key/value entries. +*/ + public void putAll( Map t ) + { + if ( t == null ) + { + // Nothing to do! + return; + } + + // Place the entries from the passed in map into this map. + for ( Iterator it = t.keySet().iterator(); it.hasNext(); ) + { + Object aKey = it.next(); + put( aKey, t.get( aKey ) ); + } + } + +/** +* Remove the mapping of the key and associated value from this map. +* Note: null is a valid value in the map. +* @param key A key to remove from the map, cannot be null. +* @return The value of the removed mapping, null if the mapping did not exist. +* Null could also be returned if the associated value of the key was null. +*/ + public Object remove( Object key ) + { + if ( key == null ) return null; + + Object value = null; + + if ( containsKey( key ) ) + { + value = keyToValue.remove( key ); + int i = values.indexOf( value ); + if ( i != -1 ) + { + keys.remove( i ); + values.remove( i ); + } + } + + return value; + } + +/** +* Returns the number of key/value pairs in this map. +* @return The number of pairs. +*/ + public int size() + { + return values.size(); + } + +/** +* Returns an ordered list of keys from the map. The order is the same +* as the added order of the mapped items.
+* NOTE: The returned list is the underlying keys list used by this class. +* If modification are to be made to this list, it should be cloned first. +* @return An ordered list of keys. +*/ + public List keys() + { + return keys; + } + +/** +* Returns an ordered list of values from the map. The order is the same +* as the added order of the mapped items. +* NOTE: The returned list is the underlying keys list used by this class. +* If modification are to be made to this list, it should be cloned first. +* @return An ordered list of values. +*/ + public Collection values() + { + return values; + } + +/** +* Returns the corresponding value for the given key. The returned value may be +* null as that is a legal value in this map. However, if the key is not +* contained in this map then null is also returned. Use the containsKey() +* method to distinguish between the two cases. +* @param key A key into the map. +* @return The value corresponding to the key (can be null), or null if the key +* is not contained in the map. +*/ + public Object getValueForKey( Object key ) + { + return keyToValue.get( key ); + } + +/** +* Returns the associated key for this value. Since there may be more than one +* of the same value in the map, this returns the first key associated with this +* value. Null is returned if the value is not in the map. +* @param value A value that is contained in this map. +* @return A first key that corresponds to this value. +*/ + public Object getKeyForValue( Object value ) + { + int i = values.indexOf( value ); + if ( i != -1 ) + { + return keys.get( i ); + } + return null; + } + +/** +* Returns the first key in the map. If the map is empty, then null is +* returned. +* @return The first key in the map, null if there are no mappings. +*/ + public Object getFirstKey() + { + if ( keys.size() < 1 ) + { + return null; + } + return keys.get(0); + } + +/** +* Returns the last key in the map. If the map is empty, then null is +* returned. +* @return The last key in the map, null if there are no mappings. +*/ + public Object getLastKey() + { + if ( keys.size() < 1 ) + { + return null; + } + return keys.get( keys.size() -1 ); + } + +/** +* This class contains a key/value pair. The key must be a valid object, +* it cannot be null. The value can be a valid object or null. +*/ + static public class KeyValuePair implements Map.Entry + { + Object key; + Object value; + + /** + * Default constructor. The constructor takes the key and value as parameters. + * Since the key cannot be null, it must be specified during initialization. + * The value can be null. + * @param key The key object of this pair. The key cannot be null. + * @param value The value object of this pair. The value can be null. + */ + public KeyValuePair( Object aKey, Object aValue ) + { + key = aKey; + value = aValue; + } + + /** + * Returns the key object of this pair. + * @return The key object. + */ + public Object getKey() + { + return key; + } + + /** + * Returns the the value object of this pair. May be null. + * @return The value object. + */ + public Object getValue() + { + return value; + } + + /** + * Sets the value object of this pair. May be an object or null. + * @param aValue The value object to place into this pair. + */ + public Object setValue( Object aValue ) + { + Object result = value; + value = aValue; + return result; + } + + public boolean equals( Object o ) + { + if ( o instanceof KeyValuePair ) + { + KeyValuePair p = (KeyValuePair) o; + if ( ( key.equals( p.getKey() ) ) && ( value.equals( p.getValue() ) ) ) + { + return true; + } + } + return false; + } + } + + /** + * Returns a string reprsentation of this class. The contents of the + * map are placed in the string in its proper order. + */ + public String toString() + { + int max = size() - 1; + StringBuffer buf = new StringBuffer(); + + buf.append("{"); + for (int j = 0; j <= max; j++) + { + buf.append(keys.get(j) + "=" + values.get(j)); + if (j < max) + { + buf.append(", "); + } + } + buf.append("}"); + + return buf.toString(); + } + + // unit test + public static void main( String[] argv ) + { + QueueMap qMap; + + qMap = new QueueMap(); + for (int i = 0; i < 5; i++) + { + qMap.put(Integer.toString(i), Integer.toString(i)); + } + System.out.println("\nMap = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + qMap = new QueueMap(); + for (int i = 0; i < 5; i++) + { + qMap.put(Integer.toString(i), "A"); + } + System.out.println("\nMap = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + qMap = new QueueMap(); + for (int i = 0; i < 5; i++) + { + qMap.put(Integer.toString(i), null); + } + System.out.println("\nMap = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + Map aMap = new HashMap(); + for (int i = 0; i < 5; i++) + { + aMap.put(Integer.toString(i), Integer.toString(i)); + } + qMap = new QueueMap( aMap ); + System.out.println("\nHashMap = " + aMap); + System.out.println("Map = " + qMap); + System.out.println("Keys = " + qMap.keys()); + System.out.println("Values = " + qMap.values()); + + qMap = new QueueMap(); + qMap.put( "Test1", "String1" ); + qMap.put( "Test2", "String2" ); + qMap.put( "Test3", "String3" ); + qMap.put( "Test4", "String4" ); + qMap.put( "Test5", "String5" ); + System.out.println("\nStandard Test, Map = " + qMap); + qMap.put( "Test6", "String6" ); + qMap.put( "Test7", "String7" ); + System.out.println("Put New Test, Map = " + qMap); + qMap.put( "Test2", "New String2" ); + qMap.put( "Test6", "New String6" ); + System.out.println("Put Existing Test, Map = " + qMap); + qMap.put( "Test5", null ); + qMap.put( "Test8", null ); + System.out.println("Put Null Test, Map = " + qMap); + qMap.remove( "Test1" ); + qMap.remove( "Test3" ); + qMap.remove( "Test5" ); + qMap.remove( "Test9" ); + System.out.println("Remove Test, Map = " + qMap); + System.out.println(" Keys = " + qMap.keys()); + System.out.println(" Values = " + qMap.values()); + qMap.clear(); + qMap.put( "Test10", "String10" ); + qMap.put( "Test11", "String11" ); + qMap.put( "Test12", "String12" ); + System.out.println("Clear Test, Map = " + qMap); + + aMap = new HashMap(); + aMap.put( "Test10", "String10" ); + aMap.put( "Test11", "String11" ); + aMap.put( "Test12", "String12" ); + System.out.println("Equality Test, Equal = " + qMap.equals( aMap )); + } + +} + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java new file mode 100644 index 0000000..9133a8d --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/URLResourceReader.java @@ -0,0 +1,207 @@ +/* + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + */ + +package net.wotonomy.foundation.internal; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * This implementation of URL Resource Reader assumes 2 types + * of base urls. A base url that ends with / is considered a + * resource folder, whereas a resource that does not end with + * / is considered a zip/jar resource folder. + * + * If the resource folder happens is a zip/jar archive, the + * entries are always cached. + * For non-zip base urls, one could specify whether or not it should + * be cached. + * + * @author Harish Prabandham + */ +public class URLResourceReader { + private Hashtable resourceCache = new Hashtable(); + private boolean iszip = true; + private URL url = null; + private boolean cache = true; + + /** + * Creates a new URLResourceReader object. You can either give + * the URL of the zip/jar file or a base url where to + * look for additional resources. If the url ends with + * "/" then it is assumed to be a Base URL. + * @param The base url to look for the resources. + * @param If the base url is not a zip/jar, then true indicates + * that entries should be cached, false otherwise. + */ + public URLResourceReader(URL baseurl, boolean cache) throws IOException { + this.url = baseurl; + this.cache = cache; + this.iszip = !url.getFile().endsWith("/"); + if(this.iszip) + this.cache = true; + initialize(); + } + + /** + * equivalent to URLResourceReader(baseurl, false) + */ + public URLResourceReader(URL baseurl) throws IOException { + this(baseurl, false); + } + + /** + * Creates a new URLResourceReader object with the given + * input stream. The stream is assumed to be a zip/jar + * stream. + */ + public URLResourceReader(InputStream is) throws IOException { + init(is); + } + + private void initialize() throws IOException { + if(iszip) { + InputStream is = url.openStream(); + init(is); + is.close(); + } + } + + private byte[] readFully(InputStream is) throws IOException { + byte[] buf = new byte[1024]; + int num = 0; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + + while( (num = is.read(buf)) != -1) { + bout.write(buf, 0, num); + } + + return bout.toByteArray(); + } + + private void init(InputStream is) throws IOException { + ZipInputStream zstream = new ZipInputStream(is); + ZipEntry entry; + + while( (entry = zstream.getNextEntry()) != null) { + byte[] entryData = readFully(zstream); + if(cache) + resourceCache.put(entry.getName(), entryData); + zstream.closeEntry(); + } + + zstream.close(); + } + + /** + * Returns an Enumeration of all "known" resource names. + */ + public Enumeration getResourceNames() { + return resourceCache.keys(); + } + + /** + * Returns an array of bytes read for this resource if the + * resource exists. This method blocks until the resource + * has been fully read. If the resource does not exist, + * this method returns null. + */ + public byte[] getResource(String resource) { + // lookup the data in the cache... + byte[] data = (byte[]) resourceCache.get(resource); + if(data != null) { + return data; + } + + // if the data was to come from a zip file that we + // already read fully & cached , then it is probably + // not there. + if(iszip) { + return null; + } + + // Now the only choice left is to make a url connection. + try { + URL realURL = new URL(url.getProtocol(), url.getHost(), + url.getFile() + resource); + data = readFully(realURL.openStream()); + // add it to cache if needed... + if(cache) + resourceCache.put(resource, data); + return data; + } catch(Exception e) { + return null; + } + } + + public void close() { + resourceCache.clear(); + resourceCache = null; + } + + public String toString() { + return url.toString(); + } +} + + + + + + + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java new file mode 100644 index 0000000..d6bc797 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/ValueConverter.java @@ -0,0 +1,718 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; + +/** +* A utility class to convert objects to a desired class. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class ValueConverter +{ + /** + * Returns the specified object as converted to an instance of the + * specified class, or null if the conversion could not be performed. + */ + static public Object convertObjectToClass( Object anObject, Class aClass ) + { + if ( aClass == String.class ) + { + return getString( anObject ); + } + if ( aClass == Short.class ) + { + return getShort( anObject ); + } + if ( aClass == short.class ) + { + return getShort( anObject ); + } + if ( aClass == Integer.class ) + { + return getInteger( anObject ); + } + if ( aClass == int.class ) + { + return getInteger( anObject ); + } + if ( aClass == Long.class ) + { + return getLong( anObject ); + } + if ( aClass == long.class ) + { + return getLong( anObject ); + } + if ( aClass == Float.class ) + { + return getFloat( anObject ); + } + if ( aClass == float.class ) + { + return getFloat( anObject ); + } + if ( aClass == Double.class ) + { + return getDouble( anObject ); + } + if ( aClass == double.class ) + { + return getDouble( anObject ); + } + if ( aClass == java.util.Date.class ) + { + return getDate( anObject ); + } + if ( aClass == Boolean.class ) + { + return getBoolean( anObject ); + } + if ( aClass == boolean.class ) + { + return getBoolean( anObject ); + } + if ( aClass == Character.class ) + { + return getCharacter( anObject ); + } + if ( aClass == char.class ) + { + return getCharacter( anObject ); + } + if ( aClass == Byte.class ) + { + return getByte( anObject ); + } + if ( aClass == byte.class ) + { + return getByte( anObject ); + } + if ( Collection.class.isAssignableFrom( aClass ) ) + { + return getCollection( anObject, aClass ); + } + if ( aClass.isArray() ) + { + return getArray( anObject, aClass ); + } + + return convert( anObject, aClass ); + } + + /** + * Called by convertObjectToClass() when we need to + * convert to an unrecognized type. + * This implementation scans the constructors of the + * specified class for the best fit to the object. + * and returns a new instance with that constructor. + * Subclasses can override to directly support specific + * types. + */ + static protected Object convert( Object anObject, Class aClass ) + { + Constructor[] ctors = aClass.getConstructors(); + + Class[] types; + for ( int i = 0; i < ctors.length; i++ ) + { + types = ctors[i].getParameterTypes(); + if ( types.length == 1 ) + { + if ( types[0].equals( anObject.getClass() ) ) + { + try + { + return ctors[i].newInstance( new Object[] { anObject } ); + } + catch ( Exception exc ) + { + // fall through + } + } + } + } + + for ( int i = 0; i < ctors.length; i++ ) + { + types = ctors[i].getParameterTypes(); + if ( types.length == 1 ) + { + if ( anObject.getClass().isAssignableFrom( types[0] ) ) + { + try + { + return ctors[i].newInstance( new Object[] { anObject } ); + } + catch ( Exception exc ) + { + // fall through + } + } + } + } + + return null; + } + + /** + * Tries to convert all objects to either Numbers or objects + * that will produce a parsable toString result. + */ + static protected Object preprocess( Object anObject ) + { + if ( anObject instanceof Boolean ) + { + if ( ((Boolean)anObject).booleanValue() ) + { + return new Double( 1.0 ); + } + return new Double( 0.0 ); + } + + if ( anObject instanceof Character ) + { + return anObject.toString(); + } + + return anObject; + } + + static public short getShortValue( Object anObject ) + { + Short result = getShort( anObject ); + return ( result == null ) ? (short) 0 : result.shortValue(); + } + static public Short getShort( Object anObject ) + { + if ( anObject == null ) return new Short( (short) 0 ); + if ( "".equals( anObject ) ) return new Short( (short) 0 ); + if ( anObject instanceof Short ) return (Short) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Short( ((Number)anObject).shortValue() ); + } + + try + { + return Short.valueOf( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + static public int getIntValue( Object anObject ) + { + Integer result = getInteger( anObject ); + return ( result == null ) ? 0 : result.intValue(); + } + static public Integer getInteger( Object anObject ) + { + if ( anObject == null ) return new Integer( 0 ); + if ( "".equals( anObject ) ) return new Integer( 0 ); + if ( anObject instanceof Integer ) return (Integer) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Integer( ((Number)anObject).intValue() ); + } + + try + { + return Integer.valueOf( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + static public long getLongValue ( Object anObject ) + { + Long result = getLong( anObject ); + return ( result == null ) ? (long) 0 : result.longValue(); + } + static public Long getLong( Object anObject ) + { + if ( anObject == null ) return new Long( 0 ); + if ( "".equals( anObject ) ) return new Long( 0 ); + if ( anObject instanceof Long ) return (Long) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Long( ((Number)anObject).longValue() ); + } + + try + { + return Long.valueOf( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + static public double getDoubleValue ( Object anObject ) + { + Double result = getDouble( anObject ); + return ( result == null ) ? 0.0f : result.doubleValue(); + } + static public Double getDouble( Object anObject ) + { + if ( anObject == null ) return new Double( 0.0 ); + if ( "".equals( anObject ) ) return new Double( 0 ); + if ( anObject instanceof Double ) return (Double) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Double( ((Number)anObject).doubleValue() ); + } + + try + { + return Double.valueOf( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + static public float getFloatValue( Object anObject ) + { + Float result = getFloat( anObject ); + return ( result == null ) ? 0.0f : result.floatValue(); + } + static public Float getFloat( Object anObject ) + { + if ( anObject == null ) return new Float( 0.0 ); + if ( "".equals( anObject ) ) return new Float( 0.0 ); + if ( anObject instanceof Float ) return (Float) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Float( ((Number)anObject).floatValue() ); + } + + try + { + return Float.valueOf( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + static public char getCharValue( Object anObject ) + { + Character result = getCharacter( anObject ); + return ( result == null ) ? (char) 0 : result.charValue(); + } + static public Character getCharacter( Object anObject ) + { + if ( anObject == null ) return new Character( (char) 0 ); + if ( anObject instanceof Character ) return (Character) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Character( (char) ((Number)anObject).byteValue() ); + } + + try + { + return new Character( anObject.toString().charAt( 0 ) ); + } + catch ( Exception exc ) + { + return null; + } + } + + static public byte getByteValue( Object anObject ) + { + Byte result = getByte ( anObject ); + return ( result == null ) ? (byte) 0 : result.byteValue(); + } + static public Byte getByte( Object anObject ) + { + if ( anObject == null ) return new Byte( Byte.MIN_VALUE ); + if ( "".equals( anObject ) ) return new Byte( Byte.MIN_VALUE ); + if ( anObject instanceof Byte ) return (Byte) anObject; + + anObject = preprocess( anObject ); + + if ( anObject instanceof Number ) + { + return new Byte( ((Number)anObject).byteValue() ); + } + + try + { + return Byte.decode( anObject.toString() ); + } + catch ( Exception exc ) + { + // fall through + } + + try + { + return Byte.valueOf( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + /** + * Calls getBoolean and converts result to primitive. + */ + static public boolean getBooleanValue( Object anObject ) + { + Boolean result = getBoolean( anObject ); + return ( result == null ) ? false : result.booleanValue(); + } + + /** + * Numbers equal to zero are true; Strings equal to "yes" are true; + * Strings are then passed to the Boolean constructor. + * Other values return null. + */ + static public Boolean getBoolean( Object anObject ) + { + if ( anObject instanceof Boolean ) + { + return (Boolean) anObject; + } + if ( anObject instanceof Number ) + { + return new Boolean( ((Number)anObject).doubleValue() == 0.0 ); + } + + if ( anObject instanceof String ) + { + if ( anObject.toString().toLowerCase().equals( "yes" ) ) + { + return Boolean.TRUE; + } + return new Boolean( (String) anObject ); + } + + return null; + } + + /** + * Get an appropriate String representation for the + * object. Nulls are converted to "null". Date are + * formatted according to the current date format. + * All else uses toString. + */ + static public String getString( Object anObject ) + { + if ( anObject == null ) return "null"; + if ( anObject instanceof java.util.Date ) + { + return dateFormat.format( (java.util.Date) anObject ); + } + return anObject.toString(); + } + + /** + * Converts the object into the specified collection class. + * If unable to convert in any other way, resorts to creating + * a new collection of the specified type containing the + * specified object. + */ + static public Collection getCollection( Object anObject, Class aCollectionClass ) + { + if ( anObject == null ) return null; + if ( aCollectionClass.isAssignableFrom( anObject.getClass() ) ) + { + return (Collection) anObject; + } + + Collection converted = null; + + // convert to collection class + if ( anObject instanceof Collection ) + { + converted = (Collection) anObject; + } + else + // try to convert an array + if ( anObject.getClass().isArray() ) + { + try + { + int length = Array.getLength( anObject ); + converted = new LinkedList(); + for ( int i = 0; i < length; i++ ) + { + converted.add( Array.get( anObject, i ) ); + } + } + catch ( Exception exc ) + { + // try another approach + } + } + else + // convert map values to collection and pass through + if ( anObject instanceof Map ) + { + converted = ((Map)anObject).values(); + } + + // fall back on list containing the object + if ( converted == null ) + { + converted = new LinkedList(); + converted.add( anObject ); + } + + Collection result = null; + + if ( converted != null ) + { + try + { + // collections required to have the copy constructor. + Constructor ctor = aCollectionClass.getConstructor( + new Class[] { Collection.class } ); + result = (Collection) ctor.newInstance( new Object[] { converted } ); + } + catch ( Exception exc ) + { + try + { + result = new LinkedList(); + result.addAll( converted ); + } + catch ( Exception exc2 ) + { + // all attempts failed + result = null; + } + } + } + + return result; + } + + /** + * Convert the object to the specified array type. + */ + static public Object getArray( Object anObject, Class anArrayClass ) + { + if ( anObject == null ) return null; + + // try to convert an array + if ( anObject.getClass().isArray() ) + { + try + { + int length = Array.getLength( anObject ); + Object result = Array.newInstance( + anArrayClass.getComponentType(), length ); + for ( int i = 0; i < length; i++ ) + { + Array.set( result, i, Array.get( anObject, i ) ); + } + return result; + } + catch ( Exception exc ) + { + // try another approach + } + } + // convert map values to collection and pass through + if ( anObject instanceof Map ) + { + anObject = ((Map)anObject).values(); + } + // try to convert a collection + if ( anObject instanceof Collection ) + { + try + { + int length = ((Collection)anObject).size(); + Object result = Array.newInstance( + anArrayClass.getComponentType(), length ); + Iterator it = ((Collection)anObject).iterator(); + for ( int i = 0; i < length; i++ ) + { + Array.set( result, i, it.next() ); + } + return result; + } + catch ( Exception exc ) + { + // try another approach + } + } + // if appropriate type, put the object in a single element array + if ( anObject.getClass().equals( anArrayClass.getComponentType() ) ) + { + try + { + Object result = Array.newInstance( + anArrayClass.getComponentType(), 1 ); + Array.set( result, 0, anObject ); + return result; + } + catch ( Exception exc ) + { + // try another approach + } + } + return null; + } + + /** + * Get an appropriate Date from the given object. + */ + static public java.util.Date getDate( Object anObject ) + { + if ( anObject == null ) return new java.util.Date( 0 ); + if ( anObject instanceof java.util.Date ) + return (java.util.Date) anObject; + + if ( anObject instanceof Number ) + { + return new java.util.Date( getLongValue( anObject ) ); + } + + try + { + return dateFormat.parse( anObject.toString() ); + } + catch ( Exception exc ) + { + return null; + } + } + + static private java.text.DateFormat dateFormat = + new java.text.SimpleDateFormat(); + static public java.text.DateFormat getDateFormat() + { + return dateFormat; + } + static public void setDateFormat( java.text.DateFormat aDateFormat ) + { + if ( aDateFormat != null ) + { + dateFormat = aDateFormat; + } + } + + /** + * Returns the "inverted" value of the specified object. + * Numbers except for chars and bytes are converted to + * their negative representation. Chars and bytes are + * treated as booleans. String are converted to booleans. + * Booleans are converted to their opposite. + * All other types return null. + */ + public static Object invert( Object anObject ) + { + if ( anObject == null ) return null; + Class aClass = anObject.getClass(); + + if ( ( ( anObject instanceof Number ) + &&! ( anObject instanceof Byte ) + &&! ( anObject instanceof Character ) ) + || ( aClass == short.class ) + || ( aClass == int.class ) + || ( aClass == long.class ) + || ( aClass == float.class ) + || ( aClass == double.class ) ) + { + return convertObjectToClass( + new Double( getDoubleValue( anObject ) * -1 ), aClass ); + } + + Boolean converted = getBoolean( anObject ); + if ( converted != null ) + { + if ( converted.booleanValue() ) + { + return convertObjectToClass( + Boolean.FALSE, anObject.getClass() ); + } + else + { + return convertObjectToClass( + Boolean.TRUE, anObject.getClass() ); + } + } + + return null; + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:11:47 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2002/10/11 15:33:53 mpowers + * Now supporting "!" to invert the value of a string property. + * + * Revision 1.3 2001/07/02 16:29:08 mpowers + * XMLRPC decoder was relying on ValueConverter to convert LinkedLists into + * the appropriate type. This is now implemented in ValueConverter. + * + * Revision 1.2 2001/03/01 20:36:35 mpowers + * Better error handling and better handling of nulls. + * + * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers + * Contributing wotonomy. + * + * Revision 1.8 2000/12/20 16:25:48 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java new file mode 100644 index 0000000..e2210d0 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/internal/WotonomyException.java @@ -0,0 +1,134 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2000 Michael Powers + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation.internal; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** +* This is a simple RuntimeException that can encapsulate +* another throwable. Behaves as a normal RuntimeException +* except that it prints a stack trace of the wrapped +* throwable if one is specified. +* +* Intended to be used anytime you'd +* report an exception with System.out.println: that is, +* for debugging and non-user-visible exceptions. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class WotonomyException extends RuntimeException +{ + protected String message; + protected Throwable wrappedThrowable; + + /** + * Default constructor. + */ + public WotonomyException() + { + super(); + message = null; + wrappedThrowable = null; + } + + /** + * Standard constructor with message. + */ + public WotonomyException( String aMessage ) + { + super( aMessage ); + message = aMessage; + wrappedThrowable = null; + } + + /** + * Specifies a throwable to wrap. + */ + public WotonomyException( Throwable aThrowable ) + { + super(); + message = null; + wrappedThrowable = aThrowable; + } + + /** + * Specifies a message and a throwable to wrap. + */ + public WotonomyException( String aMessage, Throwable aThrowable ) + { + super( aMessage ); + message = aMessage; + wrappedThrowable = aThrowable; + } + + /** + * Returns the wrapped throwable. + */ + public Throwable getWrappedThrowable() + { + return wrappedThrowable; + } + + public void printStackTrace(PrintWriter s) + { + if ( message != null ) + { + s.println( "Exception: " + message ); + } + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace( s ); + return; + } + super.printStackTrace( s ); + } + + public void printStackTrace(PrintStream s) + { + if ( message != null ) + { + s.println( "Exception: " + message ); + } + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace( s ); + return; + } + super.printStackTrace( s ); + } + + public void printStackTrace() + { + if ( message != null ) + { + System.err.println( "Exception: " + message ); + } + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace(); + return; + } + super.printStackTrace(); + } + +} -- cgit v1.2.3