diff options
| author | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
|---|---|---|
| committer | Benjamin Culkin <scorpress@gmail.com> | 2024-05-19 17:56:33 -0400 |
| commit | aedc34d55462a75e329bbf342251ff6504cd117e (patch) | |
| tree | bcc8f1f2352582717b484df302aeea6696b8f000 /projects/net.wotonomy.foundation/src/main/java/net | |
Initial import from SVN
Diffstat (limited to 'projects/net.wotonomy.foundation/src/main/java/net')
50 files changed, 13192 insertions, 0 deletions
diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java new file mode 100644 index 0000000..491722c --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSArray.java @@ -0,0 +1,661 @@ +/* +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; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** +* NSArray is an unmodifiable List. +* Calling the mutator methods of the List interface (add, addAll, +* set, etc.) on an instance of NSArray will throw an Unsupported +* Operation exception: use a NSMutableArray instead. This is to +* simulate Objective-C's pattern of exposing mutator methods only +* on mutable subinterface, which is wonderful for communicating +* via interface the contract on returned collections (whether you +* may modify return values) as well as implementing array faults. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 929 $ +*/ +public class NSArray implements List, Serializable +{ + /** + * Actual list that backs this instance. + */ + List list; + + /** + * Return value when array index is not found. + */ + public static final int NotFound = -1; + + /** + * A constant representing an empty array. + */ + public static final NSArray EmptyArray = new NSArray(); + + /** + * Returns an NSArray backed by the specified List. + * This is useful to "protect" an internal representation + * that is returned by a method of return type NSArray. + */ + public static NSArray arrayBackedByList( List aList ) + { + return new NSArray( aList, null ); + } + + /** + * A constructor that uses the provided list as the backing + * list object. This is unlike ArrayList and other + * java.util.Collection types insofar as that API requires + * that the provided Collection be copied into the newly constructed + * Collection. + * + * TODO: See if this signature can be reasonably changed, as having a no-op parameter is a little counter-intuitive. + * @param aList A list that the caller wishes to become the backing list for the NSArray. + * @param ignored This parameter is entirely ignored, and is only there to distinguish the API. + */ + NSArray( List aList, Object ignored ) // differentiates + { + list = aList; + } + + /** + * Constructor with a size hint, used by NSMutableArray. + */ + NSArray( int aSize ) + { + list = new ArrayList( aSize ); + } + + /** + * Default constructor returns an empty array. + */ + public NSArray () + { + list = new ArrayList(); + } + + /** + * Produces an array containing only the specified object. + */ + public NSArray (Object anObject) + { + this(); + list.add( anObject ); + } + + /** + * Produces an array containing the specified objects. + */ + public NSArray (Object[] anArray) + { + this(); + for ( int i = 0; i < anArray.length; i++ ) + { + list.add( anArray[i] ); + } + } + + /** + * Produces an array containing the objects in the specified collection. + */ + public NSArray (Collection aCollection) + { + this(); + Iterator i = aCollection.iterator(); + while ( i.hasNext() ) list.add( i.next() ); + } + + /** + * Returns the number of items in this array. + */ + public int count () + { + return list.size(); + } + + /** + * Returns an array containing all objects in this array + * plus the specified object. + */ + public NSArray arrayByAddingObject (Object anObject) + { + NSArray result = new NSArray( this ); + result.protectedAdd( anObject ); + return result; + } + + /** + * Returns an array containing all objects in this array + * plus all objects in the specified list. + */ + public NSArray arrayByAddingObjectsFromArray (Collection aCollection) + { + NSArray result = new NSArray( this ); + result.protectedAddAll( aCollection ); + return result; + } + + /** + * Returns a string containing the string representations of + * each element in this array, with each element separated from + * each neighboring element by the specified string. + */ + public String componentsJoinedByString (String separator) + { + StringBuffer buf = new StringBuffer(); + Iterator it = list.iterator(); + if ( it.hasNext() ) + { + buf.append( it.next().toString() ); + } + while ( it.hasNext() ) + { + buf.append( separator ); + buf.append( String.valueOf(it.next()) ); + } + return buf.toString(); + } + + /** + * Returns whether an equivalent object is contained in this array. + */ + public boolean containsObject (Object anObject) + { + return list.contains( anObject ); + } + + /** + * Returns the first object in this array that is equivalent to + * an object in the specified list, or null if no objects are + * in common. + */ + public Object firstObjectCommonWithArray (Collection aCollection) + { + if ( aCollection == null ) return null; + + Object o; + Iterator it = list.iterator(); + while ( it.hasNext() ) + { + o = it.next(); + if ( aCollection.contains( o ) ) return o; + } + return null; + } + + /** + * Returns whether the specified list contains elements equivalent + * to those in this array in the same order. + */ + public boolean isEqualToArray (List aList) + { + return list.equals( aList ); + } + + /** + * Returns the last object in this array, or null if the array is empty. + */ + public Object lastObject () + { + int i; + if ( (i = list.size()) == 0 ) return null; + return list.get( i - 1 ); + } + + /** + * + */ +/* + public NSArray sortedArrayUsingSelector (NSSelector); +*/ + + /** + * Returns an array comprised of only those elements whose + * indices fall within the specified range. + */ + public NSArray subarrayWithRange (NSRange aRange) + { + //TODO: Test this logic. + NSArray result = new NSArray(); + if ( aRange == null ) return result; + + int loc = aRange.location(); + int max = aRange.maxRange(); + int count = count(); + for ( int i = loc; i <= max && i < count; i++ ) + { + result.protectedAdd( list.get( i ) ); + } + return result; + } + + /** + * Returns an enumeration over the the elements of the array. + */ + public Enumeration objectEnumerator () + { + //TODO: Test this logic. + return new Enumeration() + { + Iterator it = NSArray.this.iterator(); + public boolean hasMoreElements() + { + return it.hasNext(); + } + public Object nextElement() + { + return it.next(); + } + }; + } + + /** + * Returns an enumeration over the elements of the array in reverse order. + */ + public java.util.Enumeration reverseObjectEnumerator () + { + return new java.util.Enumeration() + { + ListIterator it = null; + public ListIterator getIterator() + { + if ( it == null ) + { + it = NSArray.this.listIterator(); + // zoom to end + while ( it.hasNext() ) it.next(); + } + return it; + } + public boolean hasMoreElements() + { + return getIterator().hasPrevious(); + } + public Object nextElement() + { + return getIterator().previous(); + } + }; + } + + /** + * Copies the elements of this array into the specified object array + * as the array's capacity permits. + */ + public void getObjects (Object[] anArray) + { + getObjects(anArray,null); + } + + /** + * Copies the elements of this array that fall within the specified range + * into the specified object array as the array's capacity permits. This + * method must not overflow, even in the face of a null range, an over or + * under-sized array, or a bad range. It may underflow and fail to + * entirely populate the array, if the array is larger than the data to + * be copied. + * + * TODO: Check whether in WebObjects the range supposed to be measured against the parameter or the NSArray itself??? -ceg + * + * @param anArray An object array to be filled by this method. + * @param range An NSRange object representing the range of data in the NSArray to be copied. + */ + public void getObjects (Object[] array, NSRange range) + { + if ( array == null ) return; + if ( range == null) range = new NSRange(0,array.length); + int limit = Math.min(Math.min(array.length,range.length()),(count()-range.location())); + for ( int i = 0; i < limit ; i++ ) { + //anArray[ i-aRange.location() ] = objectAtIndex( i ); + array[ i ] = objectAtIndex( range.location() + i ); + } + } + + /** + * Returns the index of the first object in the array equivalent + * to the specified object. Returns NotFound if the item is not found. + */ + public int indexOfObject (Object anObject) + { + int result = list.indexOf( anObject ); + if ( result == -1 ) return NotFound; // in case this changes + return result; + } + + /** + * Returns the index of the first object in the array + * within the specified range equivalent to the specified object. + * Returns NotFound if the item is not found. + */ + public int indexOfObject (Object anObject, NSRange aRange) + { + if ( ( anObject == null ) || ( aRange == null ) ) return NotFound; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + if ( anObject.equals( list.get(i) ) ) + { + return i; + } + } + return NotFound; + } + + /** + * Returns the index of the specified object if it exists + * in the array, comparing by reference. + * Returns NotFound if the item is not found. + */ + public int indexOfIdenticalObject (Object anObject) + { + int size = list.size(); + for ( int i = 0; i < size; i++ ) + { + if ( anObject == list.get(i) ) + { + return i; + } + } + return NotFound; + } + + /** + * Returns the index of the first object in the array + * within the specified range equivalent to the specified object. + */ + public int indexOfIdenticalObject (Object anObject, NSRange aRange) + { + if ( aRange == null ) return NotFound; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + if ( anObject == list.get(i) ) + { + return i; + } + } + return NotFound; + } + + /** + * Returns the object at the specified index. Throws an + * IndexOutOfRange exception if the index is out of range. + */ + public Object objectAtIndex (int anIndex) + { + return list.get( anIndex ); + } + + /** + * Returns an array consisting of strings within the specified string + * as delimited by the specified separator characters. + */ + public static NSArray componentsSeparatedByString + (String aString, String aSeparator) + { + NSArray result = new NSArray(); + if ( aString == null ) return result; + if ( aSeparator == null ) return new NSArray( aString ); + + //FIXME: The spec probably considers the whole + // string as a separator, unlike string tokenizer. + java.util.StringTokenizer tokens = + new java.util.StringTokenizer( aString, aSeparator ); + while ( tokens.hasMoreTokens() ) + { + result.protectedAdd( tokens.nextToken() ); + } + + return result; + } + + public Object clone() + { + return new NSArray( list ); + } + + public NSArray immutableClone() + { + return this; + } + + public NSMutableArray mutableClone() + { + return new NSMutableArray( this ); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_ARRAY]); + for (int i = 0; i < count(); i++) { + Object x = objectAtIndex(i); + buf.append(NSPropertyListSerialization.stringForPropertyList(x)); + if (i < count() - 1) + buf.append(", "); + } + buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_ARRAY]); + return buf.toString(); + } + + // interface List: accessors + + public boolean contains(Object o) { return list.contains(o); } + public boolean containsAll(Collection c) { return list.containsAll(c); } + public boolean equals(Object o) { return list.equals(o); } + public Object get(int index) { return list.get(index); } + public int hashCode() { + int code = 19; + code *= getClass().hashCode(); + code *= list.hashCode(); + return code; + } + public int indexOf(Object o) { return list.indexOf(o); } + public boolean isEmpty() { return list.isEmpty(); } + public int lastIndexOf(Object o) { return list.lastIndexOf(o); } + public int size() { return list.size(); } + public Object[] toArray() { return list.toArray(); } + public Object[] toArray(Object[] a) { return list.toArray(a); } + + // interface List: mutators + + + public void add(int index, Object element) + { + this.list.add(index,element); + } + + public boolean add(Object o) + { + return this.list.add(o); + } + + public boolean addAll(Collection coll) + { + return this.list.addAll(coll); + } + + public boolean addAll(int index, Collection c) + { + return this.list.addAll(index,c); + } + + public void clear() + { + this.list.clear(); + } + + public Iterator iterator() + { + // make a copy to avoid ConcurrentModificationExceptions + final Iterator i = new LinkedList( list ).iterator(); + return new Iterator() + { + public boolean hasNext() {return i.hasNext();} + public Object next() {return i.next();} + public void remove() { throw new UnsupportedOperationException(); } + }; + } + + public ListIterator listIterator() { return listIterator(0); } + + public ListIterator listIterator(final int index) + { + // make a copy to avoid ConcurrentModificationExceptions + final ListIterator i = new LinkedList( list ).listIterator(index); + return new ListIterator() + { + public boolean hasNext() {return i.hasNext();} + public Object next() {return i.next();} + public boolean hasPrevious() {return i.hasPrevious();} + public Object previous() {return i.previous();} + public int nextIndex() {return i.nextIndex();} + public int previousIndex() {return i.previousIndex();} + public void remove() { throw new UnsupportedOperationException(); } + public void set(Object o) { throw new UnsupportedOperationException(); } + public void add(Object o) { throw new UnsupportedOperationException(); } + }; + } + + public Object remove(int index) + { + return this.list.remove(index); + } + + public boolean remove(Object o) + { + return this.list.remove(o); + } + + public boolean removeAll(Collection coll) + { + return this.list.removeAll(coll); + } + + public boolean retainAll(Collection coll) + { + return this.list.retainAll(coll); + } + + public Object set(int index, Object element) + { + return this.list.set(index,element); + } + + public List subList(int fromIndex, int toIndex) + { + return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); + } + + /** + * Provided for the use of subclasses like ArrayFault. + */ + protected boolean protectedAdd( Object o ) + { + return list.add( o ); + } + + /** + * Provided for the use of subclasses like ArrayFault. + */ + protected boolean protectedAddAll( Collection coll ) + { + return list.addAll( coll ); + } +} + +/* + * $Log$ + * Revision 1.2 2006/03/10 00:52:27 cgruber + * Add tests for NSArray and fix some problems that became obvious as a result. + * + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.16 2005/07/13 14:12:44 cgruber + * Add mutableClone() and immutableClone() per. WebObjects 5.3 conformance. + * + * Revision 1.15 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.14 2003/08/05 00:48:56 chochos + * use NSPropertyListSerialization to get the opening and closing tokens for the string representation + * + * Revision 1.13 2003/08/04 20:26:10 chochos + * use NSPropertyListSerialization inside toString() + * + * Revision 1.12 2003/08/04 18:18:43 chochos + * toString() yields strings in the same format as Apple's NSArray + * + * Revision 1.11 2003/01/28 19:44:20 mpowers + * Fixed reverse enumerator. + * + * Revision 1.10 2003/01/18 23:49:55 mpowers + * Added mutableClone(). + * + * Revision 1.9 2003/01/18 23:30:42 mpowers + * WODisplayGroup now compiles. + * + * Revision 1.8 2003/01/16 22:47:30 mpowers + * Compatibility changes to support compiling woextensions source. + * (34 out of 56 classes compile!) + * + * Revision 1.7 2003/01/10 19:16:40 mpowers + * Implemented support for page caching. + * + * Revision 1.6 2002/10/24 21:15:36 mpowers + * New implementations of NSArray and subclasses. + * + * Revision 1.5 2002/10/24 18:16:30 mpowers + * Now enforcing NSArray's immutable nature. + * + * Revision 1.4 2002/03/08 19:02:54 mpowers + * Long-overdue speed optimization of indexOfIdenticalObject. + * + * Revision 1.3 2002/02/13 22:02:56 mpowers + * Fixed: bug in componentsSeparatedByString when separator is null + * (thanks to Cedrik LIME). + * + * Revision 1.2 2001/01/11 20:34:26 mpowers + * Implemented EOSortOrdering and added support in framework. + * Added header-click to sort table columns. + * + * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:37 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java new file mode 100644 index 0000000..b735404 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSBundle.java @@ -0,0 +1,418 @@ +/* + Wotonomy: OpenStep design patterns for pure Java applications. + Copyright (C) 2005 Israfil Consulting Services Corporation + Copyright (C) 2005 Christian Gruber + + 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; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; +import java.util.Properties; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; + +import net.wotonomy.foundation.internal.NetworkClassLoader; + +/** + * An implementation of NSBundle. Unlike the standard WebObjects NSBundle, this + * implementation loads bundles dynamically. This means that all bundles do not + * need to exist in the classpath at startup time. Practically, this means that + * NSBundle has a custom classloader. + * + * This behaviour is not supported in Apple's WebObjects, and should be used + * only if compatibility is not desired. It is largely intended for internal use + * within this framework, but is exposed through bundleForURL() + * + * Another difference between Wotonomy's NSBundle and Apple's implementation is + * the ability to initialize the application with a custom resource lookup + * "path". + * + * @author cgruber@israfil.net + * @author $Author: cgruber $ + * @version $Revision: 892 $ + * + */ +public class NSBundle { + + /* Class variables */ + + public static final String BundleDidLoadNotification = "NSBundleDidLoadNotification"; + + public static final String LoadedClassesNotification = "NSLoadedClassesNotification"; + + private static final NSMutableArray _allBundles = new NSMutableArray(); + + private static final NSMutableArray _allFrameworks = new NSMutableArray(); + + private static NSMutableDictionary _languageCodes = new NSMutableDictionary(); + + private static NSBundle _mainBundle = null; + + protected static NetworkClassLoader _classLoader = new NetworkClassLoader(ClassLoader.getSystemClassLoader()); + + /* Instance variables */ + + protected String name; + + protected NSMutableDictionary info = null; + + protected String path; + + protected NSMutableArray classNames = new NSMutableArray(); + + protected NSMutableArray packages = new NSMutableArray(); + + protected Properties properties; + + protected boolean isFramework = false; + + protected boolean loaded = false; + + protected Class principalClass; + + /* Constructors */ + + /** + * The default constructor, which is only public to support other framework + * functionality, and to be API compatible with Apple's WebObjects. + * Generally, framework users should use bundleForXXXX() methods. + */ + public NSBundle() { + } + + /* Methods */ + + /** + * @deprecated use mainBundle() to access the application bundle and + * frameworkBundles() to access any frameworks. + */ + public static synchronized NSArray allBundles() { + return _allBundles.immutableClone(); + } + + /** + * @deprecated use frameworkBundles() to access any frameworks. + */ + public static NSArray allFrameworks() { + return frameworkBundles(); + } + + /** + * Returns the bundle that contains the provided class, if any. Otherwise, + * it returns null. Because NSBundles have a specialized class-loader, if + * any two bundles contain duiplicates of the same class, the second will + * fail to load. TODO: Determine if class-load scoping of duplicate classes + * is appropriate. + * + * @param class1 + * @return NSBundle + */ + public static synchronized NSBundle bundleForClass(Class class1) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + /** + * @deprecated Apple's WebObjects says you should not load from arbitrary + * path. + * @param path + * @return + */ + public static synchronized NSBundle bundleWithPath(String path) { + try { + return bundleWithURL(new File(path).toURI().toURL()); + } catch (MalformedURLException e) { + NSLog.err.appendln("Bundle path is invalid: " + path); + return null; + } + } + + /** + * <strong>Note:</strong>This method is only in Wotonomy. + * + * This method returns a bundle at a given URL, registering that bundle as + * well. If the bundle has already been loaded/registered, it is simply + * returned from the cache. + * + * @param url + * @return + */ + public static synchronized NSBundle bundleWithURL(URL url) { + NSBundle result = null; + String sep = System.getProperty("file.separator"); + String protocol = url.getProtocol(); + if (protocol.equals("file")) { + File f = new File(url.getPath()); + if (!f.exists()) { + NSLog.err.appendln("Bundle not found: " + url); + return null; + } + StringBuffer filename = new StringBuffer(f.getName()); + int extensionIndex = filename.lastIndexOf("."); + if (extensionIndex == -1) { + NSLog.err + .appendln("Named URL does not point to a bundle with an extension: " + + url); + return null; + } + String basename = filename.substring(0, extensionIndex); + String extension = filename.substring(extensionIndex + 1, filename + .length()); + System.out.println("basename: " + basename); + System.out.println("extension: " + extension); + result = new NSBundle(); + result.name = basename; + result.isFramework = extension.equals("framework"); + if (f.isDirectory()) { + try { + File javadir = new File(f.getCanonicalPath() + sep + "Contents" + + sep + "Resources" + sep + "Java"); + System.out.println(javadir); + System.out.println(javadir.exists()); + File[] jars = javadir.listFiles(); + + } catch (IOException e) { } + } else { + throw new RuntimeException( + "Compressed bundle files not currently supported."); + } + throw new RuntimeException("Method not finished."); + } else { + try { + JarInputStream j = new JarInputStream(url.openStream()); + JarEntry entry1 = j.getNextJarEntry(); + + JarFile f; + throw new RuntimeException("Method not finished."); + } catch (IOException e) { + NSLog.err + .appendln("IOException loading framework jar from URL " + + url + " - message: " + + e.getLocalizedMessage()); + StringWriter stacktrace = new StringWriter(); + e.printStackTrace(new PrintWriter(stacktrace)); + NSLog.err.appendln(stacktrace); + return null; + } + } + } + + /** + * This method returns a bundle, either from cache, or if it doesn't exist + * yet, it attempts to look it up - first from the classpath, then from the + * resource path. TODO: Determine if the lookup order is the desired + * semantic. + * + * @param name + * @return + */ + public static synchronized NSBundle bundleForName(String name) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public static synchronized NSArray frameworkBundles() { + return _allFrameworks.immutableClone(); + } + + /** + * Used to set the "Main" application bundle, in which primary resources are + * loaded for GUI applications. This is mostly only relevant for + * XXApplication objects. This should therefore not be generally used by + * consumers of the framework. + * + * @param aBundle + */ + public static void setMainBundle(NSBundle aBundle) { + _mainBundle = aBundle; + } + + public static NSBundle mainBundle() { + return _mainBundle; + } + + /** + * Get the default prefix for locale. TODO: This really needs to be made + * dynamic somehow. + * + * @return + */ + protected static String defaultLocalePrefix() { + String language = (String) _languageCodes.objectForKey(Locale + .getDefault().getLanguage()); + return language + ".lproj"; + } + + protected static synchronized NSBundle findOrCreateBundleWithPath(String s) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + /** + * TODO: figure out what this does. + * + * @return + */ + public NSArray bundleClassPackageNames() { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public String bundlePath() { + return this.path; + } + + /** + * Returns a byte array for the given resource path. TODO: Lookup semantics + * in WebObjects javadocs. + * + * @param path + * @return + */ + public byte[] bytesForResourcePath(String path) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public NSArray bundleClassNames() { + return classNames.immutableClone(); + } + + public NSDictionary infoDictionary() { + return info; + } + + /** + * Returns an input stream for a given resource path. TODO: Lookup semantics + * in WebObjects javadocs. + * + * @param path + * @return + */ + public InputStream inputStreamForResourcePath(String path) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public boolean isFramework() { + return isFramework; + } + + public boolean load() { + return loaded; + } + + public String name() { + return name; + } + + /** + * @deprecated Don't use this method, use + * resourcePathForLocalizedResourceNamed() instead. + */ + + public String pathForResource(String aName, String anExtension) { + return this.resourcePathForLocalizedResourceNamed(aName, null); + } + + /** + * @deprecated Don't use this method, use + * resourcePathForLocalizedResourceNamed() instead. + */ + public String pathForResource(String aName, String anExtension, + String subDir) { + return this.resourcePathForLocalizedResourceNamed(aName, subDir); + } + + /** + * @deprecated Don't use this method, use resourcePathsForResources() + * instead. + */ + public NSArray pathsForResources(String aName, String anExtension) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public Class principalClass() { + return principalClass; + } + + public Properties properties() { + return properties; + } + + /** + * @deprecated Resources are now accessed using the bytesForResourcePath() + * and inputStreamForResourcePath() methods. + */ + public String resourcePath() { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public String resourcePathForLocalizedResourceNamed(String aName, + String subDir) { + throw new UnsupportedOperationException("Method not yet implemented."); + // TODO: Implement. + } + + public NSArray resourcePathsForDirectories(String extension, + String subdirPath) { + throw new UnsupportedOperationException("Method not yet implemented."); + } + + public NSArray resourcePathsForLocalizedResources(String extension, + String subdirPath) { + throw new UnsupportedOperationException("Method not yet implemented."); + } + + public NSArray resourcePathsForResources(String extension, String subdirPath) { + throw new UnsupportedOperationException("Method not yet implemented."); + + } + + public String toString() { + int i = 0; + if (classNames != null) + i = classNames.count(); + return "<" + getClass().getName() + " name:'" + name + "' bundlePath:'" + + path + "' packages:'" + packages + "' " + i + " classes >"; + } + + /* Static initialization code */ + + private static void _initLanguages() { + _languageCodes.setObjectForKey("de", "German"); + _languageCodes.setObjectForKey("en", "English"); + _languageCodes.setObjectForKey("eo", "Esperanto"); + _languageCodes.setObjectForKey("es", "Spanish"); + _languageCodes.setObjectForKey("fr", "French"); + _languageCodes.setObjectForKey("ja", "Japanese"); + } + + static { + NSBundle._initLanguages(); + + } + +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java new file mode 100644 index 0000000..ca32dd5 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoder.java @@ -0,0 +1,113 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSCoder.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.io.InputStream; +import java.io.OutputStream; + +/** +* A class that defines a simple encode/decode paradigm. Subclasses +* would handle the target format. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public abstract class NSCoder { + + public NSCoder() { + } + + public abstract void encodeBoolean(boolean flag); + + public abstract void encodeByte(byte byte0); + + public abstract void encodeBytes(byte abyte0[]); + + public abstract void encodeChar(char c); + + public abstract void encodeShort(short word0); + + public abstract void encodeInt(int i); + + public abstract void encodeLong(long l); + + public abstract void encodeFloat(float f); + + public abstract void encodeDouble(double d); + + public abstract void encodeObject(Object obj); + + public abstract void encodeClass(Class class1); + + public abstract void encodeObjects(Object aobj[]); + + public abstract boolean decodeBoolean(); + + public abstract byte decodeByte(); + + public abstract byte[] decodeBytes(); + + public abstract char decodeChar(); + + public abstract short decodeShort(); + + public abstract int decodeInt(); + + public abstract long decodeLong(); + + public abstract float decodeFloat(); + + public abstract double decodeDouble(); + + public abstract Object decodeObject(); + + public abstract Class decodeClass(); + + public abstract Object[] decodeObjects(); + + public void prepareForWriting(OutputStream outputstream) { + } + + public void prepareForReading(InputStream inputstream) { + } + + public void finishCoding() { + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.2 2002/06/25 19:03:02 cgruber + * Internal documentation fixes. + * + * Revision 1.1 2002/06/25 07:52:57 cgruber + * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java new file mode 100644 index 0000000..56e4124 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSCoding.java @@ -0,0 +1,371 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSCoding.java 892 2006-02-16 12:47:16Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.math.BigInteger; + +/** +* A helper interface supporting the NSCoder APIs. At present it +* very confusing to me how this even works from a structural +* perspective, but to be consistent with WebObjects APIs, this will +* have to be properly implemented. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ + +public interface NSCoding { + + /** Not yet implemented */ + public static class _BigDecimalSupport extends _BigIntegerSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _BigDecimalSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _BigIntegerSupport extends Support { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + protected static void _encodeBigInteger( + NSCoder nscoder, + BigInteger biginteger) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + protected static BigInteger _decodeBigInteger(NSCoder nscoder, int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _BigIntegerSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _DoubleSupport extends _NumberSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _DoubleSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _FloatSupport extends _NumberSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _FloatSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _LongSupport extends _NumberSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _LongSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _IntegerSupport extends _NumberSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _IntegerSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _ShortSupport extends _NumberSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _ShortSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _ByteSupport extends _NumberSupport { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _ByteSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _NumberSupport extends Support { + + /** Not yet implemented */ + public Class classForCoder(Object obj) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _NumberSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _CharacterSupport extends Support { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _CharacterSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _DateSupport extends Support { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _DateSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _StringSupport extends Support { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _StringSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Not yet implemented */ + public static class _BooleanSupport extends Support { + + /** Not yet implemented */ + public void encodeWithCoder(Object obj, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** Not yet implemented */ + public _BooleanSupport() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + /** Helper class for NSCoding. */ + public static abstract class Support { + + private static NSMutableDictionary classSupportMap = + new NSMutableDictionary(16); + + public static Support supportForClass(Class aClass) { + Support support = null; + Class realClass = aClass; + while (support == null && realClass != null) { + support = (Support) classSupportMap.objectForKey(realClass); + if (support == null) + realClass.getSuperclass(); + // Cache if we had to look to a superclass. + else if (aClass != realClass) + classSupportMap.setObjectForKey(support, aClass); + } + return support; + } + + public static void setSupportForClass(Support support, Class class1) { + classSupportMap.setObjectForKey(support, class1); + } + + /** Return the class of a given object. It boggles the mind + * as to why this is not a static, but in the original, it's + * not. <sigh> + */ + public Class classForCoder(Object obj) { + return obj.getClass(); + } + + public abstract void encodeWithCoder(Object obj, NSCoder nscoder); + + public abstract Object decodeObject(NSCoder nscoder); + + protected static void _encodeUTF8(String s, NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected static String _decodeUTF8(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + static { + setSupportForClass(new _StringSupport(), java.lang.String.class); + setSupportForClass(new _BooleanSupport(), java.lang.Boolean.class); + setSupportForClass(new _NumberSupport(), null); + setSupportForClass(new _ByteSupport(), java.lang.Byte.class); + setSupportForClass(new _ShortSupport(), java.lang.Short.class); + setSupportForClass(new _IntegerSupport(), java.lang.Integer.class); + setSupportForClass(new _LongSupport(), java.lang.Long.class); + setSupportForClass(new _FloatSupport(), java.lang.Float.class); + setSupportForClass(new _DoubleSupport(), java.lang.Double.class); + setSupportForClass( + new _BigIntegerSupport(), + java.math.BigInteger.class); + setSupportForClass( + new _BigDecimalSupport(), + java.math.BigDecimal.class); + setSupportForClass(new _DateSupport(), java.util.Date.class); + setSupportForClass( + new _CharacterSupport(), + java.lang.Character.class); + } + + public Support() { + } + } + + //CEG: I'm not sure why these are here, since NSCoding is an interface. + // It doesn't seem to even be used anywhere. + //CEG: I'm not even sure why this compiles!! + //public abstract Class classForCoder(); + + //CEG: I'm not sure why these are here, since NSCoding is an interface. + // It doesn't seem to even be used anywhere. + //CEG: I'm not even sure why this compiles!! + //public abstract void encodeWithCoder(NSCoder nscoder); + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.2 2002/06/25 19:03:02 cgruber + * Internal documentation fixes. + * + * Revision 1.1 2002/06/25 07:52:56 cgruber + * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java new file mode 100644 index 0000000..287a59b --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSComparator.java @@ -0,0 +1,104 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSComparator.java 913 2006-03-10 00:52:27Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.util.Comparator; + + +/** +* An object that compares two other objects. As a convenience, it +* also implements java.util.Comparator. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 913 $ +*/ + +public abstract class NSComparator implements Comparator { + protected static class _NSSelectorComparator extends NSComparator { + + public int compare(Object obj, Object obj1) throws ComparisonException { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public _NSSelectorComparator(NSSelector nsselector) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + private static class StandInComparator extends NSComparator { + + public StandInComparator() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int compare(Object obj, Object obj1) throws ComparisonException { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + } + + public static class ComparisonException extends ClassCastException { + + public ComparisonException(String s) { + super(s); + } + } + + + public static final NSComparator AscendingStringComparator = new StandInComparator(); + public static final NSComparator DescendingStringComparator = new StandInComparator(); + public static final NSComparator AscendingCaseInsensitiveStringComparator = new StandInComparator(); + public static final NSComparator DescendingCaseInsensitiveStringComparator = new StandInComparator(); + public static final NSComparator AscendingNumberComparator = new StandInComparator(); + public static final NSComparator DescendingNumberComparator = new StandInComparator(); + public static final NSComparator AscendingTimestampComparator = new StandInComparator(); + public static final NSComparator DescendingTimestampComparator = new StandInComparator(); + public static final int OrderedAscending = -1; + public static final int OrderedSame = 0; + public static final int OrderedDescending = 1; + + public NSComparator() { + } + + public abstract int compare(Object obj, Object obj1) throws ClassCastException; + + public static int _compareObjects(Comparable comparable, Comparable comparable1) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + +} +/* + * $Log$ + * Revision 1.2 2006/03/10 00:52:27 cgruber + * Add tests for NSArray and fix some problems that became obvious as a result. + * + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.1 2002/06/25 07:52:56 cgruber + * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java new file mode 100644 index 0000000..36c527c --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSData.java @@ -0,0 +1,263 @@ +/* +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; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +/** +* A pure java implementation of NSData, which +* is basically a wrapper on a byte array. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSData +{ + public static final NSData EmptyData = new NSData(); + + protected byte[] bytes; + + /** + * Default constructor creates a zero-data object. + */ + public NSData () + { + bytes = new byte[0]; + } + + /** + * Creates an object containing a copy of the specified bytes. + */ + public NSData (byte[] data) + { + this( data, 0, data.length ); + } + + /** + * Creates an object containing a copy of the bytes from the specified + * array within the specified range. + */ + public NSData (byte[] data, int start, int length) + { + bytes = new byte[ length ]; + for ( int i = 0; i < length; i++ ) + { + bytes[i] = data[ start+i ]; + } + } + + /** + * Creates an object containing the bytes of the specified string. + */ + public NSData (String aString) + { + this( aString.getBytes() ); + } + + /** + * Creates an object containing the contents of the specified file. + * Errors reading the file will produce an empty or partially blank array. + */ + public NSData (File aFile) + { + int len = (int) aFile.length(); + byte[] data = new byte[ len ]; + try + { + new java.io.FileInputStream( aFile ).read( data ); + } + catch ( Exception exc ) + { + // produce an empty or partially blank array + } + bytes = data; + } + + /** + * Creates an object containing the contents of the specified URL. + */ + public NSData (java.net.URL aURL) + { + throw new RuntimeException( "Not Implemented" ); + } + + /** + * Creates an object containing a copy of the contents of the + * specified NSData object. + */ + public NSData (NSData aData) + { + this( aData.bytes() ); + } + + /** + * Creates a new NSData object from the bytes in the input stream. + * The input stream is read fully and is not closed. + * @param stream The stream to read from. + * @param chunkSize The buffer size used to read from the stream. + * @throws IOException if the stream cannot be read from. + */ + public NSData(InputStream stream, int chunkSize) throws IOException { + super(); + byte[] b = new byte[chunkSize]; + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + int read = 0; + do { + read = stream.read(b); + if (read > 0) + bout.write(b, 0, read); + } while (read > 0); + bytes = bout.toByteArray(); + } + + /** + * Returns the length of the contained data. + */ + public int length () + { + return bytes.length; + } + + /** + * Returns whether the specified data is equivalent to these data. + */ + public boolean isEqualToData (NSData aData) + { + if (length() != aData.length()) + return false; + byte[] a = bytes(); + byte[] b = aData.bytes(); + + for ( int i = 0; i < a.length; i++ ) { + if ( a[i] != b[i] ) + return false; + } + return true; + } + + /** + * Return the bytes within the data that fall within the specified range. + */ + public NSData subdataWithRange (NSRange aRange) + { + int loc = aRange.location(); + byte[] src = bytes(); + byte[] data = new byte[ aRange.length() ]; + System.arraycopy(src, loc, data, 0, data.length); + return new NSData( data ); + } + + /** + * Writes the contents of this data to the specified URL. + * If atomically is true, then the data is written to a temporary + * file and then renamed to the name specified by the URL when + * the data transfer is complete. + */ + public boolean writeToURL (java.net.URL aURL, boolean atomically) + { + throw new RuntimeException( "Not Implemented" ); + } + + /** + * Convenience to return the contents of the specified file. + */ + public static NSData dataWithContentsOfMappedFile (java.io.File aFile) + { + return new NSData( aFile ); + } + + /** + * Returns a copy of the bytes starting at the specified location + * and ranging for the specified length. + */ + public byte[] bytes (int location, int length) + { + byte[] data = new byte[ length ]; + for ( int i = 0; i < length; i++ ) + { + data[i] = bytes[ location + i ]; + } + return data; + } + + /** + * Returns a copy of the bytes backing this data object. + * NOTE: This method is not in the NSData spec and is + * included for convenience only. + */ + public byte[] bytes() + { + return bytes( 0, length() ); + } + + public String toString() { + String hex = "0123456789ABCDEF"; + StringBuffer buf = new StringBuffer(); + buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_DATA]); + for (int i = 0; i < bytes.length; i++) { + byte b = bytes[i]; + buf.append(hex.charAt((b & 0xf0) >> 4)); + buf.append(hex.charAt(b & 0x0f)); + if (i % 5 == 4) + buf.append(' '); + } + buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_DATA]); + return buf.toString(); + } + + public boolean isEqual(Object obj) { + if (obj == this) + return true; + if (obj instanceof NSData) + return isEqualToData((NSData)obj); + return false; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/08/19 01:53:52 chochos + * added constructor with an InputStream + * + * Revision 1.4 2003/08/05 00:51:31 chochos + * get the enclosing tokens from NSPropertyListSerialization + * + * Revision 1.3 2003/08/04 22:45:47 chochos + * toString() prints out the bytes in hex (in property list format) + * + * Revision 1.2 2003/08/02 01:52:00 chochos + * added EmptyData + * + * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java new file mode 100644 index 0000000..d5b6f61 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDate.java @@ -0,0 +1,217 @@ +/* +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; + +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +/** +* A pure java implementation of NSDate that extends +* java.util.Date for greater java compatibility. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ +public class NSDate extends Date +{ + // NSComparisonResult compatibility + + public static final int NSOrderedAscending = -1; + public static final int NSOrderedSame = 0; + public static final int NSOrderedDescending = 1; + +// public static final double TimeIntervalSince1970; +// public static final NSDate DateFor1970; + + /** + * Default constructor represents the current date. + */ + public NSDate () + { + super(); + } + + /** + * Represents the specified number of seconds from the current date. + */ + public NSDate (double seconds) + { + super( (long) new NSDate().getTime() + + timeIntervalToMilliseconds(seconds) ); + } + + /** + * Represents the specified number of seconds from the specified date. + */ + public NSDate (double seconds, Date sinceDate) + { + super( (long) sinceDate.getTime() + + timeIntervalToMilliseconds(seconds) ); + } + + /** + * Returns the interval between this date and 1 January 2001 GMT. + */ + public double timeIntervalSinceReferenceDate () + { + GregorianCalendar referenceDate = + new GregorianCalendar( TimeZone.getTimeZone( "GMT" ) ); + referenceDate.set( 2001, 0, 0, 0, 0, 0 ); + return timeIntervalSinceDate( referenceDate.getTime() ); + } + + /** + * Returns the interval between this date and the specified date + * in seconds. + */ + public double timeIntervalSinceDate (Date aDate) + { + return millisecondsToTimeInterval( + this.getTime() - aDate.getTime() ); + } + + /** + * Returns the interval between this date and the current date + * in seconds. + */ + public double timeIntervalSinceNow () + { + return timeIntervalSinceDate( new NSDate() ); + } + + /** + * Compares this date to the specified date and returns the + * earlier date. Unspecified which is returned if both are equal. + */ + public NSDate earlierDate (NSDate aDate) + { + if ( aDate == null ) return this; + if ( after( aDate ) ) return aDate; + return this; + } + + /** + * Compares this date to the specified date and returns the + * later date. Unspecified which is returned if both are equal. + */ + public NSDate laterDate (NSDate aDate) + { + if ( aDate == null ) return this; + if ( before( aDate ) ) return aDate; + return this; + } + + /** + * Returns a negative value if the specified date is later than + * this date, a positive value if the specified date is earlier + * than this date, or zero if the dates are equal. The return + * values are compatible with type NSComparisonResult. + */ + public int compare (Date aDate) + { + if ( before( aDate ) ) return NSOrderedAscending; + if ( after( aDate ) ) return NSOrderedDescending; + return NSOrderedSame; + } + + /** + * Returns whether the this date is equal to the specified date, + * per the result of equals(). + */ + public boolean isEqualToDate (Date aDate) + { + return equals( aDate ); + } + + /** + * Returns a date that differs from this date by the specified + * number of seconds. + */ + public NSDate dateByAddingTimeInterval (double seconds) + { + return new NSDate( seconds, this ); + } + + /** + * Returns the number of seconds between now and the reference date. + */ + public static double currentTimeIntervalSinceReferenceDate () + { + return new NSDate().timeIntervalSinceReferenceDate(); + } + + /** + * Converts seconds to milliseconds. Included for compatibility. + */ + public static long timeIntervalToMilliseconds (double seconds) + { + return (long) seconds*1000; + } + + /** + * Converts milliseconds to seconds. Included for compatibility. + */ + public static double millisecondsToTimeInterval (long millis) + { + return millis/1000.0; + } + + /** + * Returns a date that is greater than all representable dates. + */ + public static NSDate distantFuture () + { + NSDate result = new NSDate(); + result.setTime( Long.MAX_VALUE ); + return result; + } + + /** + * Returns a date that is less than all representable dates. + */ + public static NSDate distantPast () + { + NSDate result = new NSDate(); + result.setTime( Long.MIN_VALUE ); + return result; + } + +// inherited from java.util.Date +// public java.lang.String toString (); +// public boolean equals (java.lang.Object); +// public int hashCode (); + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:47:28 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java new file mode 100644 index 0000000..235a7bb --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDictionary.java @@ -0,0 +1,332 @@ +/* +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; + +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** +* A pure java implementation of NSDictionary that +* implements Map for greater java interoperability. +* +* @author michael@mpowers.net +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSDictionary extends HashMap implements NSKeyValueCoding +{ + public static final NSDictionary EmptyDictionary = new NSDictionary(); + + /** + * Default constructor produces an empty dictionary. + */ + public NSDictionary () + { + super(); + } + + /** + * Constructor produces an empty dictionary with an initial capacity. + */ + public NSDictionary (int initialCapacity) + { + super(initialCapacity); + } + + /** + * Produces a dictionary that contains one key referencing one value. + */ + public NSDictionary (Object key, Object value) + { + super(); + put( key, value ); + } + + /** + * Produces a dictionary containing the specified keys and values. + * An IllegalArgumentException is thrown if the arrays are not + * of the same length. + */ + public NSDictionary (Object[] objects, Object[] keys) + { + super(); + if ( keys.length != objects.length ) + { + throw new IllegalArgumentException( "Array lengths do not match." ); + } + + for ( int i = 0; i < keys.length; i++ ) + { + put( keys[i], objects[i] ); + } + } + + /** + * Produces a dictionary that is a copy of the specified map (or dictionary). + */ + public NSDictionary (Map aMap) + { + super( aMap ); + } + + /** + * Returns a count of the key-value pairs in this dictionary. + */ + public int count () + { + return size(); + } + + /** + * Returns an NSArray containing all keys in this dictionary. + */ + public NSArray allKeys () + { + return new NSArray( keySet() ); + + } + + /** + * Returns an NSArray containing all keys that reference the + * specified value. + */ + public NSArray allKeysForObject (Object value) + { + NSMutableArray result = new NSMutableArray(); + Map.Entry entry; + Iterator it = entrySet().iterator(); + + while ( it.hasNext() ) + { + entry = (Map.Entry) it.next(); + + // handle null values + if ( ( value == null ) && ( entry.getValue() == null ) + || ( value.equals( entry.getValue() ) ) ) + { + // if match, add to result set + result.addObject( entry.getKey() ); + } + } + + return result; + } + + /** + * Returns an NSArray containing all values in this dictionary. + */ + public NSArray allValues () + { + return new NSArray( values() ); + } + + /** + * Returns whether the specified dictionary has the same or + * equivalent key-value pairs as this dictionary. + */ + public boolean isEqualToDictionary (NSDictionary aDictionary) + { + return equals( aDictionary ); + } + + /** + * Returns an array of objects for the specified array of keys. + * If a key isn't found, the marker parameter will be placed + * in the corresponding index(es) in the returned array. + */ + public NSArray objectsForKeys (NSArray anArray, Object aMarker) + { + NSMutableArray result = new NSMutableArray(); + if ( anArray == null ) return result; + + Object value; + Enumeration enumeration = anArray.objectEnumerator(); + while ( enumeration.hasMoreElements() ) + { + value = objectForKey( enumeration.nextElement() ); + if ( value == null ) + { + value = aMarker; + } + result.addObject( value ); + } + return result; + } + + /** + * Returns an enumeration over the keys in this dictionary. + */ + public java.util.Enumeration keyEnumerator () + { + return new java.util.Enumeration() + { + Iterator it = NSDictionary.this.keySet().iterator(); + public boolean hasMoreElements() + { + return it.hasNext(); + } + public Object nextElement() + { + return it.next(); + } + }; + } + + /** + * Returns an enumeration over the values in this dictionary. + */ + public java.util.Enumeration objectEnumerator () + { + return new java.util.Enumeration() + { + Iterator it = NSDictionary.this.values().iterator(); + public boolean hasMoreElements() + { + return it.hasNext(); + } + public Object nextElement() + { + return it.next(); + } + }; + } + + /** + * Returns the value for the specified key, or null + * if the key is not found. + */ + public Object objectForKey (Object aKey) + { + return get( aKey ); + } + + // interface NSKeyValueCoding + + public Object valueForKey (String aKey) + { // System.out.println( "valueForKey: " + aKey + "->" + this ); + Object result = objectForKey( aKey ); + if ( result == null ) + result = NSKeyValueCodingSupport.valueForKey( this, aKey ); + return result; + } + + public void takeValueForKey (Object aValue, String aKey) + { // System.out.println( "takeValueForKey: " + aKey + " : " + aValue + "->" + this ); + put( aKey, aValue ); //FIXME: technically cheating since this is a read-only class + } + + public Object storedValueForKey (String aKey) + { + Object result = objectForKey( aKey ); + if ( result == null ) + result = NSKeyValueCodingSupport.storedValueForKey( this, aKey ); + return result; + } + + public void takeStoredValueForKey (Object aValue, String aKey) + { + put( aKey, aValue ); //FIXME: technically cheating since this is a read-only class + } + + public Object handleQueryWithUnboundKey (String aKey) + { + return NSKeyValueCodingSupport.handleQueryWithUnboundKey( this, aKey ); + } + + public void handleTakeValueForUnboundKey (Object aValue, String aKey) + { + NSKeyValueCodingSupport.handleTakeValueForUnboundKey( this, aValue, aKey ); + } + + public void unableToSetNullForKey (String aKey) + { + NSKeyValueCodingSupport.unableToSetNullForKey( this, aKey ); + } + + public Object validateTakeValueForKeyPath (Object aValue, String aKey) + { + throw new RuntimeException( "Not implemented yet." ); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + Enumeration enumeration = keyEnumerator(); + boolean quote = false; + buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_DICTIONARY]); + while (enumeration.hasMoreElements()) { + if (buf.length() == 1) + buf.append(' '); + Object k = enumeration.nextElement(); + buf.append(NSPropertyListSerialization.stringForPropertyList(k)); + buf.append(" = "); + k = objectForKey(k); + buf.append(NSPropertyListSerialization.stringForPropertyList(k)); + buf.append("; "); + } + buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_DICTIONARY]); + return buf.toString(); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.9 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.8 2003/08/05 00:50:14 chochos + * use NSPropertyListSerialization to get the tokens to enclose the string description + * + * Revision 1.7 2003/08/04 20:26:10 chochos + * use NSPropertyListSerialization inside toString() + * + * Revision 1.6 2003/08/04 18:49:38 chochos + * NSDictionary(Object[], Object[]) was taking the parameters in the wrong order; for compatibility with Apple's NSDictionary, objects comes first, then keys. + * + * Revision 1.5 2003/08/04 18:26:19 chochos + * fixed opening '{' + * + * Revision 1.4 2003/01/28 22:11:30 mpowers + * Now implements NSKeyValueCoding. + * + * Revision 1.3 2002/06/30 17:58:06 mpowers + * Add a capacity constructor and static empty dictionary: thanks cgruber. + * + * Revision 1.2 2001/02/23 23:43:41 mpowers + * Removed ill-advised this. + * + * Revision 1.1.1.1 2000/12/21 15:47:31 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java new file mode 100644 index 0000000..46df7d2 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSDisposable.java @@ -0,0 +1,35 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSDisposable.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +/** +* Interface for objects that respond to dispose(); +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface NSDisposable { + + public abstract void dispose(); + +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java new file mode 100644 index 0000000..1db0ad2 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSForwardException.java @@ -0,0 +1,157 @@ +/* +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; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** +* Serves to wrap an exception inside of a RuntimeException, +* which is not required to be declared in a throws statement. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class NSForwardException extends RuntimeException +{ + protected String message; + protected Throwable wrappedThrowable; + + /** + * Default constructor. + */ + public NSForwardException() + { + super(); + message = null; + wrappedThrowable = null; + } + + /** + * Standard constructor with message. + */ + public NSForwardException( String aMessage ) + { + super( aMessage ); + message = aMessage; + wrappedThrowable = null; + } + + /** + * Specifies a throwable to wrap. + */ + public NSForwardException( Throwable aThrowable ) + { + super(); + message = null; + wrappedThrowable = aThrowable; + } + + /** + * Specifies a message and a throwable to wrap. + */ + public NSForwardException( Throwable aThrowable, String aMessage ) + { + super( aMessage ); + message = aMessage; + wrappedThrowable = aThrowable; + } + + /** + * Returns the wrapped throwable. + */ + public Throwable originalException() + { + return wrappedThrowable; + } + + public void printStackTrace(PrintWriter s) + { + if ( message != null ) + { + s.println( toString() ); + } + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace( s ); + return; + } + super.printStackTrace( s ); + } + + public void printStackTrace(PrintStream s) + { + if ( message != null ) + { + s.println( toString() ); + } + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace( s ); + return; + } + super.printStackTrace( s ); + } + + public void printStackTrace() + { + if ( message != null ) + { + System.err.println( toString() ); + } + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace(); + return; + } + super.printStackTrace(); + } + + public String stackTrace() + { + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter( writer ); + if ( wrappedThrowable != null ) + { + wrappedThrowable.printStackTrace( printWriter ); + } + else + { + super.printStackTrace( printWriter ); + } + printWriter.flush(); + printWriter.close(); + return writer.toString(); + } + + public String toString() + { + String result = message; + if ( result == null ) result = ""; + if ( wrappedThrowable != null ) + { + result = wrappedThrowable.toString() + " : " + result; + } + return result; + } + +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java new file mode 100644 index 0000000..5792303 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCoding.java @@ -0,0 +1,431 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.IntrospectorException; +import net.wotonomy.foundation.internal.MissingPropertyException; +import net.wotonomy.foundation.internal.NullPrimitiveException; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* NSKeyValueCoding defines an interface for classes that +* need to have more control over the wotonomy's property +* introspection facilities. <br><br> +* +* On an object that implements this interface, wotonomy +* will call these methods, and otherwise use the static +* methods on NSKeyValueCodingSupport. <br><br> +* +* NSKeyValueCodingSupport implements the default behaviors +* for each of these methods, so classes implementing this +* interface can call those methods to acheive the same +* behavior. <br><br> +* +* valueForKey and takeValueForKey are called in response +* to user actions, like viewing an object or updating its +* value in a user interface. These should call the public +* getter and setter methods on the object itself and the +* operations should be subject to validation. <br><br> +* +* storedValueForKey and takeStoredValueForKey are called +* in response to wotonomy actions, like snapshotting, +* faulting, commits, and reverts. These operations should +* bypass the public methods and directly modify the internal +* state of the object without validation. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface NSKeyValueCoding +{ + + public static final Null NullValue = new Null(); + + /** + * Returns the value for the specified property. + * If the property does not exist, this method should + * call handleQueryWithUnboundKey. + */ + Object valueForKey( String aKey ); + + /** + * Sets the property to the specified value. + * If the property does not exist, this method should + * call handleTakeValueForUnboundKey. + * If the property is of a type that cannot allow + * null (e.g. primitive types) and aValue is null, + * this method should call unableToSetNullForKey. + */ + void takeValueForKey( Object aValue, String aKey ); + + /** + * Returns the value for the private field that + * corresponds to the specified property. + */ + Object storedValueForKey( String aKey ); + + /** + * Sets the the private field that corresponds to the + * specified property to the specified value. + */ + void takeStoredValueForKey( Object aValue, String aKey ); + + /** + * Called by valueForKey when the specified key is + * not found on this object. Implementing classes + * should handle the specified value or otherwise + * throw an exception. + */ + Object handleQueryWithUnboundKey( String aKey ); + + /** + * Called by takeValueForKey when the specified key + * is not found on this object. Implementing classes + * should handle the specified value or otherwise + * throw an exception. + */ + void handleTakeValueForUnboundKey( Object aValue, String aKey ); + + /** + * Called by takeValueForKey when the type of the + * specified key is not allowed to be null, as is + * the case with primitive types. Implementing + * classes should handle this case appropriately + * or otherwise throw an exception. + */ + void unableToSetNullForKey( String aKey ); + + /** + * Static utility methods that + * call the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + public class Utility + { + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public Object valueForKey( + Object anObject, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + return ((NSKeyValueCoding)anObject).valueForKey( aKey ); + } + return DefaultImplementation.valueForKey( anObject, aKey ); + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public void takeValueForKey( + Object anObject, Object aValue, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).takeValueForKey( aValue, aKey ); + } + DefaultImplementation.takeValueForKey( anObject, aValue, aKey ); + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public Object storedValueForKey( + Object anObject, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + return ((NSKeyValueCoding)anObject).storedValueForKey( aKey ); + } + return DefaultImplementation.storedValueForKey( anObject, aKey ); + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public void takeStoredValueForKey( + Object anObject, Object aValue, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).takeStoredValueForKey( aValue, aKey ); + } + DefaultImplementation.takeStoredValueForKey( anObject, aValue, aKey ); + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public Object handleQueryWithUnboundKey( + Object anObject, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + return ((NSKeyValueCoding)anObject).handleQueryWithUnboundKey( aKey ); + } + return DefaultImplementation.handleQueryWithUnboundKey( anObject, aKey ); + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public void handleTakeValueForUnboundKey( + Object anObject, Object aValue, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).handleTakeValueForUnboundKey( aValue, aKey ); + } + DefaultImplementation.handleTakeValueForUnboundKey( anObject, aValue, aKey ); + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCoding, otherwise calls the method + * on DefaultImplementation. + */ + static public void unableToSetNullForKey( + Object anObject, String aKey ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).unableToSetNullForKey( aKey ); + } + DefaultImplementation.unableToSetNullForKey( anObject, aKey ); + } + } + + public class DefaultImplementation + { + /** + * Returns the value for the specified property key + * on the specified object. <br><br> + * + * If the property does not exist, this method calls + * handleQueryWithUnboundKey on the object if it + * implements NSKeyValueCoding, otherwise calls + * handleQueryWithUnboundKey on this class. <br><br> + */ + static public Object valueForKey( + Object anObject, String aKey ) + { + if ( anObject == null ) return null; + //TODO: may need to handle "." nesting here so + // that handleQueryWithUnboundKey gets called for + // for the nested object, not the parent object + + //Correction: need to handle key paths in + // KeyValueCodingAdditionsSupport. + + try + { + return Introspector.get( anObject, aKey ); + } + catch ( IntrospectorException exc ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + return ((NSKeyValueCoding)anObject).handleQueryWithUnboundKey( aKey ); + } + return handleQueryWithUnboundKey( anObject, aKey ); + } + } + + /** + * Sets the property to the specified value on + * the specified object. + * + * If the property does not exist, this method calls + * handleTakeValueForUnboundKey on the object if it + * implements NSKeyValueCoding, otherwise calls + * handleTakeValueForUnboundKey on this class. + * + * If the property is of a type that cannot allow + * null (e.g. primitive types) and aValue is null, + * this method should call unableToSetNullForKey + * on the object if it implements NSKeyValueCoding, + * otherwise calls unableToSetNullForKey on this class. + */ + static public void takeValueForKey( + Object anObject, Object aValue, String aKey ) + { + if ( anObject == null ) return; + //TODO: may need to handle "." nesting here so + // that handleTakeValueForUnboundKey gets called for + // for the nested object, not the parent object + try + { + Introspector.set( anObject, aKey, aValue ); + } + catch ( NullPrimitiveException exc ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).unableToSetNullForKey( aKey ); + } + else + { + unableToSetNullForKey( anObject, aKey ); + } + } + catch ( MissingPropertyException exc ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).handleTakeValueForUnboundKey( + aValue, aKey ); + } + else + { + handleTakeValueForUnboundKey( anObject, aValue, aKey ); + } + } + + } + + /** + * Returns the value for the private field that + * corresponds to the specified property on + * the specified object. + * + * This implementation currently calls valueForKey, + * because java security currently prevents us from + * accessing the fields of another object. + */ + static public Object storedValueForKey( + Object anObject, String aKey ) + { + //TODO: this currently just calls valueForKey + return valueForKey( anObject, aKey ); + } + + /** + * Sets the the private field that corresponds to the + * specified property to the specified value on the + * specified object. + * + * This implementation currently calls takeValueForKey, + * because java security currently prevents us from + * accessing the fields of another object. + */ + static public void takeStoredValueForKey( + Object anObject, Object aValue, String aKey ) + { + //TODO: this currently just calls takeValueForKey + takeValueForKey( anObject, aValue, aKey ); + } + + /** + * Called by valueForKey when the specified key is + * not found on the specified object, if that object + * does not implement NSKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public Object handleQueryWithUnboundKey( + Object anObject, String aKey ) + { + throw new WotonomyException( + "Key not found for object: " + + aKey + " : " + anObject ); + } + + /** + * Called by takeValueForKey when the specified key + * is not found on the specified object, if that object + * does not implement NSKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public void handleTakeValueForUnboundKey( + Object anObject, Object aValue, String aKey ) + { + throw new WotonomyException( + "Key not found for object while setting value: " + + aKey + " : " + anObject + " : " + aValue ); + } + + /** + * Called by takeValueForKey when the type of the + * specified key is not allowed to be null, as is + * the case with primitive types, if the specified + * object does not implement NSKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public void unableToSetNullForKey( + Object anObject, String aKey ) + { + throw new WotonomyException( + "Tried to key on object to null: " + + aKey + " : " + anObject ); + } + } + + public class Null { + public Null() { + super(); + } + + public String toString() { + return "<null>"; + } + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2003/08/07 02:43:56 chochos + * added NullValue, which points to a Null instance. + * + * Revision 1.2 2003/01/18 23:30:42 mpowers + * WODisplayGroup now compiles. + * + * Revision 1.1 2003/01/17 14:40:49 mpowers + * Adding files to fix build. + * + * Revision 1.2 2001/03/28 16:12:30 mpowers + * Documented interface. + * + * Revision 1.1 2001/03/27 23:25:05 mpowers + * Contributing interface, no docs yet. + * + * + */ + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java new file mode 100644 index 0000000..785facd --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingAdditions.java @@ -0,0 +1,213 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import java.util.List; +import java.util.Map; + +/** +* NSKeyValueCodingAdditions defines an interface for classes +* that need to have more control over the wotonomy's bulk +* property copying and cloning facilities. <br><br> +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public interface NSKeyValueCodingAdditions extends NSKeyValueCoding +{ + /** + * Returns the value for the specified key path, which is + * a series of keys delimited by ".", for example: + * "createTime.year.length". + */ + Object valueForKeyPath( String aKeyPath ); + + /** + * Sets the value for the specified key path, which is + * a series of keys delimited by ".", for example: + * "createTime.year.length". + * The value is set for the last object referenced by + * the key path. + */ + void takeValueForKeyPath( Object aValue, String aKeyPath ); + + /** + * Returns a Map of the specified keys to their values, + * each of which might be obtained by calling valueForKey. + */ + NSDictionary valuesForKeys( List aKeyList ); + + /** + * Takes the keys from the specified map as properties + * and applies the corresponding values, each of which + * might be set by calling takeValueForKey. + */ + void takeValuesFromDictionary( Map aMap ); + + + /** + * Static utility methods that + * call the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public class Utility + { + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public static void takeValuesFromDictionary( + Object object, Map dictionary) + { + if (object instanceof NSKeyValueCodingAdditions) { + ((NSKeyValueCodingAdditions)object).takeValuesFromDictionary(dictionary); + } else { + DefaultImplementation.takeValuesFromDictionary(object, dictionary); + } + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public static void takeValueForKeyPath( + Object object, Object aValue, String aKeyPath) + { + if (object instanceof NSKeyValueCodingAdditions) { + ((NSKeyValueCodingAdditions)object).takeValueForKeyPath(aValue, aKeyPath); + } else { + DefaultImplementation.takeValueForKeyPath(object, aValue, aKeyPath); + } + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public static NSDictionary valuesForKeys( + Object object, List keys) + { + if (object instanceof NSKeyValueCodingAdditions) { + return ((NSKeyValueCodingAdditions)object).valuesForKeys(keys); + } else { + return DefaultImplementation.valuesForKeys(object, keys); + } + } + + /** + * Calls the appropriate method if the object implements + * NSKeyValueCodingAdditions, otherwise calls the method + * on DefaultImplementation. + */ + public static Object valueForKeyPath( + Object object, String aKeyPath) + { + if (object instanceof NSKeyValueCodingAdditions) { + return ((NSKeyValueCodingAdditions)object).valueForKeyPath(aKeyPath); + } else { + return DefaultImplementation.valueForKeyPath(object, aKeyPath); + } + } + } + + /** + * Provides a reflection-based implementation for classes that + * don't implement NSKeyValueCodingAdditions. + */ + public class DefaultImplementation + { + /** + * Provides a reflection-based implementation for classes that + * don't implement NSKeyValueCodingAdditions. + */ + public static void takeValuesFromDictionary( + Object object, Map dictionary) + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Provides a reflection-based implementation for classes that + * don't implement NSKeyValueCodingAdditions. + */ + public static void takeValueForKeyPath( + Object object, Object aValue, String aKeyPath) + { + // currently, NSKeyValueCoding.takeValueForKey accepts paths + NSKeyValueCoding.DefaultImplementation.takeValueForKey( object, aValue, aKeyPath ); + } + + /** + * Provides a reflection-based implementation for classes that + * don't implement NSKeyValueCodingAdditions. + */ + public static NSDictionary valuesForKeys( + Object object, List keys) + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Provides a reflection-based implementation for classes that + * don't implement NSKeyValueCodingAdditions. + */ + public static Object valueForKeyPath( + Object object, String aKeyPath) + { + // currently, NSKeyValueCoding.valueForKey accepts paths + return NSKeyValueCoding.DefaultImplementation.valueForKey( object, aKeyPath ); + } + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/01/18 23:30:42 mpowers + * WODisplayGroup now compiles. + * + * Revision 1.1 2003/01/17 14:40:49 mpowers + * Adding files to fix build. + * + * Revision 1.3 2001/12/10 15:25:11 mpowers + * Now properly extending NSKeyValueCoding. + * + * Revision 1.2 2001/04/28 14:12:23 mpowers + * Refactored cloning/copying into KeyValueCodingUtilities. + * + * Revision 1.1 2001/03/29 03:29:49 mpowers + * Now using KeyValueCoding and Support instead of Introspector. + * + * Revision 1.2 2001/03/28 16:12:30 mpowers + * Documented interface. + * + * Revision 1.1 2001/03/27 23:25:05 mpowers + * Contributing interface, no docs yet. + * + * + */ + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java new file mode 100644 index 0000000..a947896 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSKeyValueCodingSupport.java @@ -0,0 +1,228 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import net.wotonomy.foundation.internal.Introspector; +import net.wotonomy.foundation.internal.IntrospectorException; +import net.wotonomy.foundation.internal.MissingPropertyException; +import net.wotonomy.foundation.internal.NullPrimitiveException; +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* NSKeyValueCodingSupport defines default behavior for +* classes implementing NSKeyValueSupport. <br><br> +* +* On an object that does not implement NSKeyValueCoding, +* wotonomy will call the methods on this class directly. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ +public class NSKeyValueCodingSupport +{ + /** + * Returns the value for the specified property key + * on the specified object. <br><br> + * + * If the property does not exist, this method calls + * handleQueryWithUnboundKey on the object if it + * implements NSKeyValueCoding, otherwise calls + * handleQueryWithUnboundKey on this class. <br><br> + */ + static public Object valueForKey( + Object anObject, String aKey ) + { + //TODO: may need to handle "." nesting here so + // that handleQueryWithUnboundKey gets called for + // for the nested object, not the parent object + + //Correction: need to handle key paths in + // KeyValueCodingAdditionsSupport. + + try + { + return Introspector.get( anObject, aKey ); + } + catch ( IntrospectorException exc ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + return ((NSKeyValueCoding)anObject).handleQueryWithUnboundKey( aKey ); + } + return handleQueryWithUnboundKey( anObject, aKey ); + } + } + + /** + * Sets the property to the specified value on + * the specified object. + * + * If the property does not exist, this method calls + * handleTakeValueForUnboundKey on the object if it + * implements NSKeyValueCoding, otherwise calls + * handleTakeValueForUnboundKey on this class. + * + * If the property is of a type that cannot allow + * null (e.g. primitive types) and aValue is null, + * this method should call unableToSetNullForKey + * on the object if it implements NSKeyValueCoding, + * otherwise calls unableToSetNullForKey on this class. + */ + static public void takeValueForKey( + Object anObject, Object aValue, String aKey ) + { + //TODO: may need to handle "." nesting here so + // that handleTakeValueForUnboundKey gets called for + // for the nested object, not the parent object + + try + { + Introspector.set( anObject, aKey, aValue ); + } + catch ( NullPrimitiveException exc ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).unableToSetNullForKey( aKey ); + } + else + { + unableToSetNullForKey( anObject, aKey ); + } + } + catch ( MissingPropertyException exc ) + { + if ( anObject instanceof NSKeyValueCoding ) + { + ((NSKeyValueCoding)anObject).handleTakeValueForUnboundKey( + aValue, aKey ); + } + else + { + handleTakeValueForUnboundKey( anObject, aValue, aKey ); + } + } + + } + + /** + * Returns the value for the private field that + * corresponds to the specified property on + * the specified object. + * + * This implementation currently calls valueForKey, + * because java security currently prevents us from + * accessing the fields of another object. + */ + static public Object storedValueForKey( + Object anObject, String aKey ) + { + //TODO: this currently just calls valueForKey + return valueForKey( anObject, aKey ); + } + + /** + * Sets the the private field that corresponds to the + * specified property to the specified value on the + * specified object. + * + * This implementation currently calls takeValueForKey, + * because java security currently prevents us from + * accessing the fields of another object. + */ + static public void takeStoredValueForKey( + Object anObject, Object aValue, String aKey ) + { + //TODO: this currently just calls takeValueForKey + takeValueForKey( anObject, aValue, aKey ); + } + + /** + * Called by valueForKey when the specified key is + * not found on the specified object, if that object + * does not implement NSKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public Object handleQueryWithUnboundKey( + Object anObject, String aKey ) + { + throw new WotonomyException( + "Key not found for object: " + + aKey + " : " + anObject ); + } + + /** + * Called by takeValueForKey when the specified key + * is not found on the specified object, if that object + * does not implement NSKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public void handleTakeValueForUnboundKey( + Object anObject, Object aValue, String aKey ) + { + throw new WotonomyException( + "Key not found for object while setting value: " + + aKey + " : " + anObject + " : " + aValue ); + } + + /** + * Called by takeValueForKey when the type of the + * specified key is not allowed to be null, as is + * the case with primitive types, if the specified + * object does not implement NSKeyValueCoding. + * + * This implementation throws a WotonomyException. + */ + static public void unableToSetNullForKey( + Object anObject, String aKey ) + { + throw new WotonomyException( + "Tried to key on object to null: " + + aKey + " : " + anObject ); + } + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/01/17 14:40:50 mpowers + * Adding files to fix build. + * + * Revision 1.4 2001/05/18 21:04:33 mpowers + * Reimplemented EditingContext.initializeObject. + * + * Revision 1.3 2001/04/27 00:28:29 mpowers + * Fixed a return value. + * + * Revision 1.2 2001/04/03 20:36:01 mpowers + * Fixed refaulting/reverting/invalidating to be self-consistent. + * + * Revision 1.1 2001/03/28 17:49:33 mpowers + * Implemented NSKeyValueCodingSupport. + * + * + */ + + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java new file mode 100644 index 0000000..e6a5147 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLock.java @@ -0,0 +1,108 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSLock.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import EDU.oswego.cs.dl.util.concurrent.Mutex; + +/** +* A simple mutually-exclusive lock. Currently an API-compliance +* subclass of an external Mutex class, conforming to the API and +* behavior of com.webobjects.foundation.NSLock. This class implements +* the NSLocking protocol (interface), and is implemented using +* Doug Lea's concurrent programming package. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +* +*/ + +public class NSLock extends Mutex implements NSLocking { + + + public NSLock() { + } + + public synchronized void lock() { + try { + acquire(); + } catch (InterruptedException interruptedexception) { + notify(); + } + } + + public synchronized void unlock() { + release(); + } + + public synchronized boolean tryLock() { + return tryLock(0); + } + + public synchronized boolean tryLock(long l) { + try { + return attempt(l); + } catch (InterruptedException interruptedexception) { + notify(); + return false; + } + } + + public boolean tryLock(NSTimestamp nstimestamp) { + return tryLock(nstimestamp.getTime() - System.currentTimeMillis()); + } + + public String toString() { + return getClass().getName() + " <" + (inuse_ ? "Locked" : "Unlocked") + ">"; + } + + public synchronized boolean isLocked() { + return inuse_; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.4 2002/06/25 19:03:02 cgruber + * Internal documentation fixes. + * + * Revision 1.3 2002/06/25 18:52:56 cgruber + * Fix javadocs that resulted from bad cut-and-paste of the + * boilerplate. + * + * Revision 1.2 2002/06/25 17:45:52 cgruber + * Add implementation of NSLock using Doug Lea's concurrent + * programming APIs. + * + * Revision 1.1 2002/06/25 07:52:57 cgruber + * Add quite a few abstract classes, interfaces, and classes. All + * API consistent with WebObjects, but with no implementation, nor + * any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java new file mode 100644 index 0000000..f8c8ff4 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLocking.java @@ -0,0 +1,59 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSLocking.java 892 2006-02-16 12:47:16Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +/** +* Defines a simple locking protocol. Very course-grain locking. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ + +public interface NSLocking { + + public static final long OneSecond = 1000L; + public static final long OneMinute = 60000L; + public static final long OneHour = 0x36ee80L; + public static final long OneDay = 0x5265c00L; + public static final long OneWeek = 0x240c8400L; + public static final long OneYear = 0x758f0dfc0L; + public static final long OneCentury = 0x2debe176700L; + + public abstract void lock(); + + public abstract void unlock(); + +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.2 2002/06/21 22:11:19 cgruber + * Add a log trail + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java new file mode 100644 index 0000000..52e4090 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSLog.java @@ -0,0 +1,489 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.util.Date; + +/** +* NSLog is foundation's built-in logging facility: IMPLEMENTED, BUT NOT TESTED. +* By default, all groups are enabled, and debug level is DebugLevelOff. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSLog +{ + public static long DebugGroupApplicationGeneration = 1L << 3; + public static long DebugGroupArchiving = 1L << 6; + public static long DebugGroupAssociations = 1L << 19; + public static long DebugGroupComponentBindings = 1L << 9; + public static long DebugGroupControllers = 1L << 20; + public static long DebugGroupComponents = 1L << 26; + public static long DebugGroupDatabaseAccess = 1L << 16; + public static long DebugGroupDeployment = 1L << 22; + public static long DebugGroupEnterpriseObjects = 1L << 1; + public static long DebugGroupFormatting = 1L << 10; + public static long DebugGroupIO = 1L << 13; + public static long DebugGroupJSPServlets = 1L << 27; + public static long DebugGroupKeyValueCoding = 1L << 8; + public static long DebugGroupModel = 1L << 15; + public static long DebugGroupMultithreading = 1L << 4; + public static long DebugGroupParsing = 1L << 23; + public static long DebugGroupQualifiers = 1L << 11; + public static long DebugGroupReflection = 1L << 24; + public static long DebugGroupRequestHandling = 1L << 25; + public static long DebugGroupResources = 1L << 5; + public static long DebugGroupRules = 1L << 21; + public static long DebugGroupSQLGeneration = 1L << 17; + public static long DebugGroupTiming = 1L << 14; + public static long DebugGroupUserInterface = 1L << 18; + public static long DebugGroupValidation = 1L << 7; + public static long DebugGroupWebObjects = 1L << 2; + public static long DebugGroupWebServices = 1L << 2; + + public static int DebugLevelOff = 0; + public static int DebugLevelCritical = 1; + public static int DebugLevelInformational = 2; + public static int DebugLevelDetailed = 3; + + /** + * The logger to which debug statements should be + * conditionally written. By default, these messages + * appear on the standard error stream. + */ + public static Logger debug; + + /** + * The logger to which error messages should be written, + * which may not always be user-visible. By default, + * these messages appear on the standard error stream. + */ + public static Logger err; + + /** + * The logger to which user-visible messages should be written. + * By default, these messages appear on the standard output stream. + */ + public static Logger out; + + private static long allowedGroups; + private static int allowedLevel; + + static + { + debug = new PrintStreamLogger( System.err ); + err = new PrintStreamLogger( System.err ); + out = new PrintStreamLogger( System.out ); + + //TODO: need to initialize the debug level and groups based + // on the value of the NSDebugLevel and NSDebugGroup properties + allowedGroups = Long.MAX_VALUE; + allowedLevel = 0; + } + + /** + * Adds the specified group masks to those allowed for logging. + */ + public static void allowDebugLoggingForGroups(long aDebugGroups) + { + allowedGroups = allowedGroups | aDebugGroups; + } + + /** + * Returns the current logging debug level. + */ + public static int allowedDebugLevel() + { + return allowedLevel; + } + + /** + * Returns whether logging is allowed for the specified groups masks. + */ + public static boolean debugLoggingAllowedForGroups(long aDebugGroups) + { + return ( allowedGroups == ( allowedGroups | aDebugGroups ) ); + } + + /** + * Returns whether logging is allowed for the specified level. + */ + public static boolean debugLoggingAllowedForLevel(int aDebugLevel) + { + return ( allowedLevel >= aDebugLevel ); + } + + /** + * Returns whether logging allowed for the specified groups masks + * at the specified level. Convenience method. + */ + public static boolean debugLoggingAllowedForLevelAndGroups(int aDebugLevel, + long aDebugGroups) + { + return ( ( allowedLevel >= aDebugLevel ) + && ( allowedGroups == ( allowedGroups | aDebugGroups ) ) ); + } + + /** + * Convenience to obtain a java PrintStream for the specified file path. + * Returns null if the stream could not be created. + */ + public static PrintStream printStreamForPath(String aPath) + { + try + { + return new PrintStream( new FileOutputStream( aPath ) ); + } + catch ( Throwable t ) + { + } + return null; + } + + /** + * Removes the specified group masks from those allowed for logging. + */ + public static void refuseDebugLoggingForGroups(long aDebugGroups) + { + allowedGroups = ( allowedGroups | aDebugGroups ) ^ aDebugGroups; + } + + /** + * Sets the allowed groups to only those specified by the mask. + */ + public static void setAllowedDebugGroups(long aDebugGroups) + { + allowedGroups = aDebugGroups; + } + + /** + * Sets the current debug level. + */ + public static void setAllowedDebugLevel(int aDebugLevel) + { + allowedLevel = aDebugLevel; + } + + /** + * Sets the current debug logger. + * Does nothing if logger is null. + */ + public static void setDebug(NSLog.Logger logger) + { + if ( logger != null ) + { + debug = logger; + } + } + + /** + * Sets the current error logger. + * Does nothing if logger is null. + */ + public static void setErr(NSLog.Logger logger) + { + if ( logger != null ) + { + err = logger; + } + } + + /** + * Sets the current output logger. + * Does nothing if logger is null. + */ + public static void setOut(NSLog.Logger logger) + { + if ( logger != null ) + { + out = logger; + } + } + + /** + * Convenience to write the throwable's stack trace + * to a string. + */ + public static String throwableAsString(Throwable t) + { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream( os ); + t.printStackTrace( ps ); + return os.toString(); + } + + /** + * The abstract superclass of all Logger implementations. + */ + static abstract public class Logger + { + private boolean enabled; + private boolean verbose; + + /** + * Default constructor sets enabled + * and verbose to true. + */ + public Logger() + { + enabled = true; + verbose = true; + } + + /** + * Convenience to append a Boolean. + */ + public void appendln(boolean aValue) + { + appendln( new Boolean( aValue ) ); + } + + /** + * Convenience to append a Byte. + */ + public void appendln(byte aValue) + { + appendln( new Byte( aValue ) ); + } + + /** + * Convenience to write a String + * comprised of the byte array using + * the default encoding. + */ + public void appendln(byte[] aValue) + { + appendln( new String( aValue ) ); + } + + /** + * Convenience to append a Character. + */ + public void appendln(char aValue) + { + appendln( new Character( aValue ) ); + } + + /** + * Convenience to append a String + * comprised of the character array. + */ + public void appendln(char[] aValue) + { + appendln( new String( aValue ) ); + } + + /** + * Convenience to append a Double. + */ + public void appendln(double aValue) + { + appendln( new Double( aValue ) ); + } + + /** + * Convenience to append a Float. + */ + public void appendln(float aValue) + { + appendln( new Float( aValue ) ); + } + + /** + * Convenience to append a Integer. + */ + public void appendln(int aValue) + { + appendln( new Integer( aValue ) ); + } + + /** + * Convenience to append a Long. + */ + public void appendln(long aValue) + { + appendln( new Long( aValue ) ); + } + + /** + * Convenience to append a Short + */ + public void appendln(short aValue) + { + appendln( new Short( aValue ) ); + } + + /** + * Convenience to append a Throwable. + */ + public void appendln(Throwable aValue) + { + appendln( NSLog.throwableAsString( aValue ) ); + } + + /** + * Writes the object to the log. + */ + public abstract void appendln(Object aValue); + + /** + * Appends a line to the log. + */ + public abstract void appendln(); + + /** + * Flushes any buffered output to the log. + */ + public abstract void flush(); + + /** + * Returns whether the logger is enabled, + * the meaning of which is defined by the + * implementing class. + */ + public boolean isEnabled() + { + return enabled; + } + + /** + * Returns whether the logger is verbose, + * the meaning of which is defined by the + * implementing class. + */ + public boolean isVerbose() + { + return verbose; + } + + /** + * Sets whether the logger is enabled, + * the meaning of which is defined by the + * implementing class. + */ + public void setIsEnabled(boolean aBool) + { + enabled = aBool; + } + + /** + * Sets whether the logger is verbose, + * the meaning of which is defined by the + * implementing class. + */ + public void setIsVerbose(boolean aBool) + { + verbose = aBool; + } + } + + /** + * The default implementation of Logger that writes to a Java + * PrintStream. If not enabled, no output is written. + * If verbose, output is in format: "[time] <thread name> message". + */ + static public class PrintStreamLogger extends Logger + { + private PrintStream thePrintStream; + + /** + * Constructor takes a PrintStream. + */ + public PrintStreamLogger(PrintStream ps) + { + thePrintStream = ps; + } + + /** + * Sends a newline to the print stream. + */ + public void appendln() + { + if ( isEnabled() ) + { + thePrintStream.println(); + } + } + + /** + * Writes the throwable to the print stream. + */ + public void appendln(Throwable aValue) + { + appendln( NSLog.throwableAsString( aValue ) ); + } + + /** + * Writes aValue.toString to the print stream. + */ + public void appendln(Object aValue) + { + if ( isEnabled() ) + { + if ( isVerbose() ) + { + thePrintStream.print( '[' + new Date().toString() + "] <" + + Thread.currentThread().getName() + "> " ); + } + if ( aValue == null ) aValue = "null"; + thePrintStream.println( aValue.toString() ); + } + } + + /** + * Flushes the print stream. + */ + public void flush() + { + thePrintStream.flush(); + } + + /** + * Returns the current print stream. + */ + public PrintStream printStream() + { + return thePrintStream; + } + + /** + * Replaces the current print stream. + */ + public void setPrintStream(PrintStream aStream) + { + thePrintStream = aStream; + } + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/01/31 22:33:00 mpowers + * Contributing NSLog. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java new file mode 100644 index 0000000..abed576 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMultiReaderLock.java @@ -0,0 +1,159 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSMultiReaderLock.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import EDU.oswego.cs.dl.util.concurrent.ReentrantWriterPreferenceReadWriteLock; + +/** +* A Read-Write lock that allows unlimited number of calling threads to +* acquire read locks, but only one thread to acquire a write lock. It +* is also reentrant, allowing each thread to re-acquire it's lock +* recursively. For that reason it is somewhat slower, as there is a +* hash lookup when attempting to acquire and release reader locks. Of +* course a writer lock is quite a bit slower than a reader lock. A +* write lock is mutally exclusive with read locks, though a thread +* that has obtained a read-lock may be promoted to a write lock and +* vice versa when conditions permit. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSMultiReaderLock extends ReentrantWriterPreferenceReadWriteLock implements NSLocking { + + NSMutableDictionary _readerSuspended = new NSMutableDictionary(); + + public NSMultiReaderLock() { + } + + public void lockForReading() { + try { + readerLock_.acquire(); + } catch (InterruptedException interruptedexception) { + // Null behavior, as notify() is already called + // by acquire(); + // We may want to log here. + } + } + + public void unlockForReading() { + readerLock_.release(); + } + + public void lock() { + lockForWriting(); + } + + public void lockForWriting() { + try { + writerLock_.acquire(); + } catch (InterruptedException interruptedexception) { + // Null behavior, as notify() is already called + // by acquire(); + // We may want to log here. + } + } + + public void unlock() { + unlockForWriting(); + } + + public void unlockForWriting() { + writerLock_.release(); + } + + /** @see com.webobjects.foundation.NSMultiReaderLock#suspendReaderLock() */ + public void suspendReaderLocks() { + Thread thisThread = Thread.currentThread(); + Integer suspendedReaders = (Integer)_readerSuspended.get(thisThread); + if (suspendedReaders != null && suspendedReaders.intValue() > 0) return; + // logic is to override startRead / endRead and ensure that the suspension + // isn't improperly stopped. + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @see com.webobjects.foundation.NSMultiReaderLock#retrieveReaderLock() */ + public void retrieveReaderLocks() { + Thread thisThread = Thread.currentThread(); + Integer suspendedReaders = (Integer)_readerSuspended.get(thisThread); + if (suspendedReaders != null && suspendedReaders.intValue() > 0) return; + // logic is to override startRead / endRead and ensure that the suspension + // isn't improperly stopped. + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean tryLockForWriting() { + try { + return writerLock_.attempt(0); + } catch (InterruptedException interruptedexception) { + // notify() is already called by attempt(); + // We may want to log here. + return false; + } + } + + public boolean tryLockForReading() { + try { + return readerLock_.attempt(0); + } catch (InterruptedException interruptedexception) { + // notify() is already called by attempt(); + // We may want to log here. + return false; + } + } + + public String toString() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected String _padString(long l, int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected String _padString(String s, int i, boolean flag) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.2 2002/06/26 00:40:22 cgruber + * Add implementation, using ReentrantWriterPreferenceReadWriteLock + * as a base. + * + * suspendReaderLocks and retreiveReaderLocks is the one + * that's likeliest to be a pain. + * + * Revision 1.1 2002/06/25 07:52:57 cgruber + * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java new file mode 100644 index 0000000..f705000 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableArray.java @@ -0,0 +1,429 @@ +/* +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; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/** +* NSMutableArray extends NSArray to allow modification. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSMutableArray extends NSArray +{ + /** + * Returns an NSArray backed by the specified List. + * This is useful to "protect" an internal representation + * that is returned by a method of return type NSArray. + */ + public static NSMutableArray mutableArrayBackedByList( List aList ) + { + return new NSMutableArray( aList, null ); + } + + NSMutableArray( List aList, Object ignored ) // differentiates + { + super( aList, ignored ); + } + + /** + * Default constructor returns an empty array. + */ + public NSMutableArray () + { + super(); +//System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); + } + + /** + * Constructor with a size hint. + */ + public NSMutableArray ( int aSize ) + { + super(); +//System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); + } + + /** + * Produces an array containing only the specified object. + */ + public NSMutableArray (Object anObject) + { + super( anObject ); +//System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); + } + + /** + * Produces an array containing the specified objects. + */ + public NSMutableArray (Object[] anArray) + { + super( anArray ); +//System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); + } + + /** + * Produces an array containing the objects in the specified collection. + */ + public NSMutableArray (Collection aCollection) + { + super( aCollection ); +//System.out.println( "NSMutableArray: " + net.wotonomy.ui.swing.util.StackTraceInspector.getMyCaller() ); + } + + /** + * Removes the last object from the array. + */ + public void removeLastObject () + { + list.remove( count() - 1 ); + } + + /** + * Removes the object at the specified index. + */ + public void removeObjectAtIndex (int index) + { + list.remove( index ); + } + + /** + * Adds all objects in the specified collection. + */ + public void addObjectsFromArray (Collection aCollection) + { + list.addAll( aCollection ); + } + + /** + * Removes all objects from the array. + */ + public void removeAllObjects () + { + list.clear(); + } + + /** + * Removes all objects equivalent to the specified object + * within the range of specified indices. + */ + public void removeObject (Object anObject, NSRange aRange) + { + if ( ( anObject == null ) || ( aRange == null ) ) return; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + if ( anObject.equals( list.get( i ) ) ) + { + list.remove( i ); + i = i - 1; + max = max - 1; + } + } + } + + /** + * Removes all instances of the specified object within the + * range of specified indices, comparing by reference. + */ + public void removeIdenticalObject (Object anObject, NSRange aRange) + { + if ( ( anObject == null ) || ( aRange == null ) ) return; + + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + if ( anObject == list.get( i ) ) + { + list.remove( i ); + i = i - 1; + max = max - 1; + } + } + } + + /** + * Removes all objects in the specified collection from the array. + */ + public void removeObjectsInArray (Collection aCollection) + { + list.removeAll( aCollection ); + } + + /** + * Removes all objects in the indices within the specified range + * from the array. + */ + public void removeObjectsInRange (NSRange aRange) + { + if ( aRange == null ) return; + + for ( int i = 0; i < aRange.length(); i++ ) + { + list.remove( aRange.location() ); + } + } + + /** + * Replaces objects in the current range with objects from + * the specified range of the specified array. If currentRange + * is larger than otherRange, the extra objects are removed. + * If otherRange is larger than currentRange, the extra objects + * are added. + */ + public void replaceObjectsInRange (NSRange currentRange, + List otherArray, NSRange otherRange) + { + if ( ( currentRange == null ) || ( otherArray == null ) || + ( otherRange == null ) ) return; + + // transform otherRange if out of bounds for array + if ( otherRange.maxRange() > otherArray.size() ) + { + // TODO: Test this logic. + int loc = Math.min( otherRange.location(), otherArray.size() - 1 ); + otherRange = new NSRange( loc, otherArray.size() - loc ); + } + + Object o; + List subList = list.subList( + currentRange.location(), currentRange.maxRange() ); + int otherIndex = otherRange.location(); + // TODO: Test this logic. + for ( int i = 0; i < subList.size(); i++ ) + { + if ( otherIndex < otherRange.maxRange() ) + { // set object + subList.set( i, otherArray.get( otherIndex ) ); + } + else + { // remove extra elements from currentRange + subList.remove( i ); + i--; + } + otherIndex++; + } + // TODO: Test this logic. + for ( int i = otherIndex; i < otherRange.maxRange(); i++ ) + { + list.add( otherArray.get( i ) ); + } + } + + /** + * Clears the current array and then populates it with the + * contents of the specified collection. + */ + public void setArray (Collection aCollection) + { + list.clear(); + list.addAll( aCollection ); + } + + /** + * Sorts this array using the values from the specified selector. + */ + public void sortUsingSelector (NSSelector aSelector) + { + //TODO: implement + throw new UnsupportedOperationException( "Not implemented yet." ); + } + + /** + * Removes all objects equivalent to the specified object. + */ + public void removeObject (Object anObject) + { + list.remove( anObject ); + } + + /** + * Removes all occurences of the specified object, + * comparing by reference. + */ + public void removeIdenticalObject (Object anObject) + { + Iterator it = list.iterator(); + while ( it.hasNext() ) + { + if ( it.next() == anObject ) + { + it.remove(); + } + } + } + + /** + * Inserts the specified object into this array at the + * specified index. + */ + public void insertObjectAtIndex (Object anObject, int anIndex) + { + list.add( anIndex, anObject ); + } + + /** + * Replaces the object at the specified index with the + * specified object. + */ + public void replaceObjectAtIndex (int anIndex, Object anObject) + { + list.set( anIndex, anObject ); + } + + /** + * Adds the specified object to the end of this array. + */ + public void addObject (Object anObject) + { + list.add( anObject ); + } + + public Object clone() + { + return new NSMutableArray( list ); + } + + public NSArray immutableClone() { + return new NSArray(this); + } + + public NSMutableArray mutableClone() { + return new NSMutableArray(this); + } + + // interface List: mutators + + public void add(int index, Object element) + { + list.add( index, element ); + } + + public boolean add(Object o) + { + return list.add(o); + } + + public boolean addAll(Collection coll) + { + return list.addAll(coll); + } + + public boolean addAll(int index, Collection c) + { + return list.addAll( index, c ); + } + + public void clear() + { + list.clear(); + } + + public Iterator iterator() + { + return list.iterator(); + } + + public ListIterator listIterator() + { + return list.listIterator(); + } + + public ListIterator listIterator(int index) + { + return list.listIterator(); + } + + public Object remove(int index) + { + return list.remove( index ); + } + + public boolean remove(Object o) + { + return list.remove(o); + } + + public boolean removeAll(Collection coll) + { + return list.removeAll(coll); + } + + public boolean retainAll(Collection coll) + { + return list.retainAll(coll); + } + + public Object set(int index, Object element) + { + return list.set( index, element ); + } + + public List subList(int fromIndex, int toIndex) + { + return list.subList( fromIndex, toIndex ); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.8 2005/07/13 14:12:44 cgruber + * Add mutableClone() and immutableClone() per. WebObjects 5.3 conformance. + * + * Revision 1.7 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.6 2003/01/16 22:47:30 mpowers + * Compatibility changes to support compiling woextensions source. + * (34 out of 56 classes compile!) + * + * Revision 1.5 2003/01/10 19:16:40 mpowers + * Implemented support for page caching. + * + * Revision 1.4 2002/10/24 21:15:36 mpowers + * New implementations of NSArray and subclasses. + * + * Revision 1.3 2002/10/24 18:16:30 mpowers + * Now enforcing NSArray's immutable nature. + * + * Revision 1.2 2001/01/11 20:34:26 mpowers + * Implemented EOSortOrdering and added support in framework. + * Added header-click to sort table columns. + * + * Revision 1.1.1.1 2000/12/21 15:47:31 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java new file mode 100644 index 0000000..c677ea7 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableData.java @@ -0,0 +1,167 @@ +/* +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; + +/** +* A pure java implementation of NSMutableData, which +* is basically an editable wrapper for a byte array. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ +public class NSMutableData + extends NSData +{ + /** + * Default constructor creates a zero-data object. + */ + public NSMutableData () + { + super(); + } + + /** + * Creates an object containing the contents of the specified URL. + */ + public NSMutableData (java.net.URL aURL) + { + super( aURL ); + } + + /** + * Creates an object containing a copy of the contents of the + * specified NSData object. + */ + public NSMutableData (NSData aData) + { + super( aData ); + } + + /** + * Creates an object containing the specified number of bytes + * initialized to all zeroes. + */ + public NSMutableData (int size) + { + super( new byte[size] ); // inits to zeroes + } + + + /** + * Sets the length of the data to the specified length. + * If shorter, the data is truncated. If longer, the extra + * bytes are initialized to zeroes. + */ + public void setLength (int length) + { + byte[] data = new byte[ length ]; // inits to zeroes + int limit = length; + if (limit > bytes.length) + limit = bytes.length; + for ( int i = 0; i < limit; i++ ) + { + data[i] = this.bytes[ i ]; + } + this.bytes = data; + } + + /** + * Appends the specified data to the end of this data. + */ + public void appendData (NSData aData) + { + int len = aData.length(); + byte[] data = new byte[ bytes.length + len ]; + + int i; + for ( i = 0; i < bytes.length; i++ ) + { + data[i] = bytes[i]; + } + + byte[] src = aData.bytes( 0, len ); + for ( int j = 0; j < len; j++ ) + { + data[i+j] = src[j]; + } + + bytes = data; + } + + public void appendByte(byte b) { + setLength(bytes.length + 1); + bytes[bytes.length-1] = b; + } + + public void appendBytes(byte[] b) { + int origLen = bytes.length; + setLength(origLen + b.length); + for (int i = 0; i < b.length; i++) + bytes[i + origLen] = b[i]; + } + + /** + * Increases the size of the byte array by the specified amount. + */ + public void increaseLengthBy (int increment) + { + setLength( length() + increment ); + } + + /** + * Sets the bytes in the array within the specified range to zero. + */ + public void resetBytesInRange (NSRange aRange) + { + int loc = aRange.location(); + int max = aRange.maxRange(); + for ( int i = loc; i < max; i++ ) + { + bytes[i] = 0; + } + } + + /** + * Copies the data in the specified object to this object, + * completely replacing the previous contents. + */ + public void setData (NSData aData) + { + bytes = aData.bytes( 0, aData.length() ); + } +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:57:13 chochos + * appendByte(), appendBytes() + * + * Revision 1.1.1.1 2000/12/21 15:47:34 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java new file mode 100644 index 0000000..0b291a7 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableDictionary.java @@ -0,0 +1,166 @@ +/* +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; + +import java.util.Enumeration; +import java.util.Map; + +/** +* A pure java implementation of NSMutableDictionary that +* implements Map for greater java interoperability. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSMutableDictionary + extends NSDictionary +{ + /** + * Default constructor produces an empty dictionary. + */ + public NSMutableDictionary () + { + super(); + } + + /** + * Default constructor produces an empty dictionary. + */ + public NSMutableDictionary (int initialSize) + { + super(initialSize); + } + + /** + * Produces a dictionary that contains one key referencing one value. + */ + public NSMutableDictionary (Object key, Object value) + { + super( key, value ); + } + + /** + * Produces a dictionary containing the specified keys and values. + * An IllegalArgumentException is thrown if the arrays are not + * of the same length. + */ + public NSMutableDictionary (Object[] keys, Object[] values) + { + super( keys, values ); + } + + /** + * Produces a dictionary that is a copy of the specified map (or dictionary). + */ + public NSMutableDictionary (Map aMap) + { + super( aMap ); + } + + /** + * Removes the key-value pair for the specified key. + */ + public void removeObjectForKey (Object aKey) + { + remove( aKey ); + } + + /** + * Copies all mappings from the specified dictionary to this dictionary, + * replacing any mappings this map had for any keys in the specified map. + */ + public void addEntriesFromDictionary (Map aMap) + { + putAll( aMap ); + } + + /** + * Removes all mappings from this dictionary. + */ + public void removeAllObjects () + { + clear(); + } + + /** + * Removes all keys in the specified array from this dictionary. + */ + public void removeObjectsForKeys (NSArray anArray) + { + Enumeration enumeration = anArray.objectEnumerator(); + while ( enumeration.hasMoreElements() ) + { + removeObjectForKey( enumeration.nextElement() ); + } + } + + /** + * Clears all mappings in this dictionary and then adds all entries + * in the specified dictionary. + */ + public void setDictionary (Map aMap) + { + removeAllObjects(); + addEntriesFromDictionary( aMap ); + } + + /** + * Sets the value for the specified key. If the key currently + * exists to the dictionary, the old value is replaced with the + * specified value. An IllegalArgumentException is thrown if + * either the key or value is null. + */ + public void setObjectForKey (Object aValue, Object aKey) + { + if ( ( aKey == null ) || ( aValue == null ) ) + { + throw new IllegalArgumentException( + "Cannot use null objects with an NSMutableDictionary." ); + } + put( aKey, aValue ); + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.4 2005/05/11 15:21:53 cgruber + * Change enum to enumeration, since enum is now a keyword as of Java 5.0 + * + * A few other comments in the code. + * + * Revision 1.3 2002/06/30 17:16:26 mpowers + * Added new constructor taking an int: thanks cgruber. + * + * + * Revision 1.2 2001/02/23 23:43:41 mpowers + * Removed ill-advised this. + * + * Revision 1.1.1.1 2000/12/21 15:47:34 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java new file mode 100644 index 0000000..a0bcda1 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSMutableRange.java @@ -0,0 +1,116 @@ +/* +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; + +/** +* A pure java implementation of NSMutableRange. +* An NSMutableRange is a modifiable NSRange. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSMutableRange extends NSRange +{ + /** + * Default constructor produces an empty range. + */ + public NSMutableRange () + { + super(); + } + + /** + * Produces a range that has the same location and length as + * the specified range. + */ + public NSMutableRange (NSRange aRange) + { + super( aRange ); + } + + /** + * Produces a range with the specified location and length. + */ + public NSMutableRange (int location, int length) + { + super( location, length ); + } + + /** + * Sets the location of this range. + */ + public void setLocation (int location) + { + loc = location; + } + + /** + * Sets the length of this range. + */ + public void setLength (int length) + { + len = length; + } + + /** + * Modifies this range to be the union of this + * range and the specified range. + */ + public void unionRange (NSRange aRange) + { + NSRange range = rangeByUnioningRange( aRange ); + setLocation( range.location() ); + setLength( range.length() ); + } + + /** + * Modifies this range to be the intersection of this + * range and the specified range. + */ + public void intersectRange (NSRange aRange) + { + NSRange range = rangeByIntersectingRange( aRange ); + setLocation( range.location() ); + setLength( range.length() ); + } + + /** + * Returns a copy of this range. + */ + public Object clone () + { + return new NSMutableRange( location(), length() ); + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:47:36 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java new file mode 100644 index 0000000..f288d3f --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotification.java @@ -0,0 +1,164 @@ +/* +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; + +import java.util.Map; + +/** +* An NSNotification is a generic message that can be +* dispatched by the NSNotificationCenter. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSNotification +{ + public static boolean showStack = false; + + protected String name; + protected Object object; + protected Map userInfo; + + // for debugging only + private Throwable stackTrace; + + /** + * Default constructor creates a new notification + * with no name, object, or info dictionary. + */ + public NSNotification () + { + this( null, null, null ); + } + + /** + * Constructor specifying name and object. + */ + public NSNotification ( String aName, Object anObject ) + { + this( aName, anObject, null ); + } + + /** + * Constructor specifying name, object, and a Map + * containing application specific information. + */ + public NSNotification ( + String aName, Object anObject, Map aUserInfo ) + { + name = aName; + object = anObject; + if ( showStack ) stackTrace = new RuntimeException(); + userInfo = aUserInfo; + } + + /** + * Returns the name of this notification. + */ + public String name () + { + return name; + } + + /** + * Returns the object of this notification. + */ + public Object object () + { + return object; + } + + /** + * Returns an NSDictionary that is a copy of + * the map containing application specific + * information relating to this notification, + * or null if no such data exists. + */ + public NSDictionary userInfo () + { + if ( userInfo == null ) return null; + return new NSDictionary( userInfo ); + } + + /** + * Returns a Map containing application specific + * information relating to this notification, + * or null if no such data exists. + * Note: this method is not in the spec. + */ + public Map userInfoMap () + { + return userInfo; + } + + /** + * Returns the stack trace when this notification was generated, + * or null if showStack is false, which is the default. + * NOTE: This method is not part of the specification. + */ + public Throwable stackTrace() + { + return stackTrace; + } + + /** + * Returns a human-readable string representation. + */ + public String toString() + { + return "[ " + name() + " : " + object() + " : " + userInfo() + " ]"; + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.7 2002/10/24 18:16:04 mpowers + * No longer generating stack trace by default. + * + * Revision 1.6 2002/06/21 22:02:47 mpowers + * Oops: Fixed NPE. + * + * Revision 1.5 2002/06/21 21:50:41 mpowers + * Added a method to get the map directly from the notification. + * Changed the internal representation to a map not a dictionary. + * We had been creating a new dictionary with each creation. + * This also allows people to modify the contents of the userInfo. + * + * Revision 1.4 2001/04/09 21:41:49 mpowers + * Better debugging. + * + * Revision 1.3 2001/02/21 18:31:07 mpowers + * Finished and tested implementation of NSNotificationCenter. + * + * Revision 1.2 2001/02/20 23:57:03 mpowers + * Implemented NSNotificationCenter. + * + * Revision 1.1.1.1 2000/12/21 15:47:36 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java new file mode 100644 index 0000000..aaf8261 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationCenter.java @@ -0,0 +1,671 @@ +/* +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; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import net.wotonomy.foundation.internal.WotonomyException; + +/** +* NSNotificationCenter broadcasts NSNotifications to +* registered observers. Observers can register for all +* notifications of a specific type, or all notifications +* about a specific object, or both. Observers specify +* the method that will be called when they are notified. +* A global notification center can be accessed with +* defaultCenter(), but other centers can be created and +* used independently of the default center. <br><br> +* +* This implementation uses weak references for observers +* and observables. The advantage to this approach is +* that you do not need to explicitly unregister observers +* or observables; they will be unregistered when they are +* garbage-collected. Note that you will need to retain +* a reference to any objects you register or they may +* become unregistered if no other object references them. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSNotificationCenter +{ + /** + * Null marker class simplifies equals() logic + * for CompoundKey class below. + */ + public static final Object NullMarker = new Object(); + + private static NSNotificationCenter defaultCenter = null; + + /** + * A Map of (name,object) pairs to a List + * of (observer,selector) pairs. + */ + private Hashtable observers; // thread-safe + + /** + * Default constructor creates a new notification center. + */ + public NSNotificationCenter() + { + observers = new Hashtable(); + } + + /** + * Returns the system default center, creating one + * if it has not yet been created. + */ + static public NSNotificationCenter defaultCenter() + { + if ( defaultCenter == null ) + { + defaultCenter = new NSNotificationCenter(); + } + return defaultCenter; + } + + /** + * Addes the specified observer to the notification queue for + * notifications with the specified name or the specified + * object or both. + * @param anObserver The observer that wishes to be notified. + * @param aSelector The selector that will be invoked. + * Must have exactly one argument, to which a notification + * will be passed. + * @param notificationName The name of the notifications for + * which the observer will be notified. If null, will notify + * only based on matching anObject. + * @param anObject The object of the notifications for which + * the observer will be notified. If null, will notify + * only based on matching notificationName. + */ + public void addObserver( + Object anObserver, NSSelector aSelector, + String notificationName, Object anObject ) + { + // remove freed objects + processKeyQueue(); + + Object name = notificationName; + if ( name == null ) + { + name = NullMarker; + } + if ( anObject == null ) + { + anObject = NullMarker; + } + Object key = new CompoundKey( name, anObject ); + Object value = new CompoundValue( anObserver, aSelector ); + List list = (List) observers.get( key ); + if ( list == null ) + { + // create new list with value and put it in map + list = new Vector(); // thread-safe + list.add( value ); + observers.put( new CompoundKey( + name, anObject, keyQueue ), list ); + } + else + { + // add only if not already in list + if ( ! list.contains( value ) ) + { + list.add( value ); + } + } + } + + /** + * Posts the specified notification. Notifies all registered + * observers that match either the notification name or + * the notification object, or both. + * @param aNotification The notification that will be passed + * to the observers selector. + */ + public void postNotification( + NSNotification aNotification ) + { + List mergedList = new LinkedList(); + Object key, observerList; + + Object name = aNotification.name(); + Object object = aNotification.object(); + + if ( name != null ) + { + if ( object != null ) + { // both are specified + observerList = observers.get( new CompoundKey( name, object ) ); + if ( observerList != null ) + { + mergedList.addAll( (List) observerList ); + } + observerList = observers.get( new CompoundKey( name, NullMarker ) ); + if ( observerList != null ) + { + mergedList.addAll( (List) observerList ); + } + observerList = observers.get( new CompoundKey( NullMarker, object ) ); + if ( observerList != null ) + { + mergedList.addAll( (List) observerList ); + } + } + else + { // object is null + observerList = observers.get( new CompoundKey( name, NullMarker ) ); + if ( observerList != null ) + { + mergedList.addAll( (List) observerList ); + } + } + } + else + if ( object != null ) + { // name is null + observerList = observers.get( new CompoundKey( NullMarker, object ) ); + if ( observerList != null ) + { + mergedList.addAll( (List) observerList ); + } + } + + key = new CompoundKey( + NullMarker, NullMarker ); + observerList = observers.get( key ); + if ( observerList != null ) + { + mergedList.addAll( (List) observerList ); + } + + CompoundValue value; + Iterator it = mergedList.iterator(); + while ( it.hasNext() ) + { + value = (CompoundValue) it.next(); + if ( value.get() == null ) + { + it.remove(); + } + else + { + try + { + value.selector().invoke( + value.get(), + new Object[] { aNotification } ); + } + catch ( Exception exc ) + { + WotonomyException w = new WotonomyException( + "Error notifying object: " + value.get() + " : " + aNotification, exc ); +// throw w; + w.printStackTrace(); +postNotification( "Error notifying object", this, new NSDictionary( "exception", w ) ); + } + } + } + + } + + /** + * Posts a notification created from the specified name + * and object. Calls postNotification( NSNotification ). + * @param notificationName a String key to distinguish + * this notification. + * @param anObject any object, by convention this is + * the originator of the notification. + */ + public void postNotification( + String notificationName, Object anObject ) + { + postNotification( new NSNotification( + notificationName, anObject ) ); + } + + /** + * Posts a notification created from the specified name, + * object, and info. Calls postNotification( NSNotification ). + * @param notificationName a String key to distinguish + * this notification. + * @param anObject any object, by convention this is + * the originator of the notification. + * @param userInfo a Map containing information specific + * to the originator of the notification and that may + * be of interest to a knowledgable observer. + */ + public void postNotification( + String notificationName, Object anObject, Map userInfo ) + { + postNotification( new NSNotification( + notificationName, anObject, userInfo ) ); + } + + /** + * Unregisters the specified observer from all notification + * queues for which it is registered. + * @param anObserver The observer to be unregistered. + */ + public void removeObserver( + Object anObserver ) + { + // remove freed objects + processKeyQueue(); + + Iterator it = new LinkedList( observers.keySet() ).iterator(); + while ( it.hasNext() ) + { + removeObserver( anObserver, it.next() ); + } + } + + /** + * Unregisters the specified observer from all notifications + * queues associated with the specified name or object or both. + * @param anObserver The observer to be unregistered, if null + * will unregister all observers for the specified notification + * name and object. + * @param notificationName The name of the notification for which + * the observer will be unregistered, if null will unregister + * the specified observer for all notifications with the + * specified object. + * @param anObject The object for the notification for which + * the observer will be unregistered, if null will unregister + * the specified observer for all objects with the specified + * notification. + */ + public void removeObserver( + Object anObserver, String notificationName, Object anObject ) + { + // remove freed objects + processKeyQueue(); + + // get key matches + List keys = matchingKeys( notificationName, anObject ); + + // remove specified observer from each matching key + Iterator it = keys.iterator(); + while ( it.hasNext() ) + { + removeObserver( anObserver, it.next() ); + } + } + + /** + * Returns all keys that match the specified name and object, + * but in this case null parameters are considered wildcards. + * Pass NullMarkers if you want to explicitly match nulls. + */ + private List matchingKeys( String name, Object object ) + { + List result = new LinkedList(); + + boolean willAdd; + CompoundKey key; + Iterator it = observers.keySet().iterator(); + while ( it.hasNext() ) + { + key = (CompoundKey) it.next(); + willAdd = false; + if ( ( name == null ) || ( name == key.name() ) ) + { + if ( ( object == null ) || ( object == key.get() ) ) + { + willAdd = true; + } + } + if ( willAdd ) + { + result.add( key ); + } + } + return result; + } + + /** + * Removes the specified observer from the list referenced + * by the specified key in the observer map. + */ + private void removeObserver( + Object anObserver, Object key ) + { + // if observer null, remove all observers for key + if ( anObserver == null ) + { + observers.remove( key ); + return; + } + + List list = (List) observers.get( key ); + if ( list == null ) return; + + // remove specified observer from list + Object observer; + Iterator it = list.iterator(); + while ( it.hasNext() ) + { + observer = ((CompoundValue)it.next()).get(); + if ( ( observer == null ) || ( anObserver == observer ) ) + { + // remove if match or freed object + it.remove(); + + // do not return; process entire list + } + } + if ( list.size() == 0 ) + { + observers.remove( key ); + } + } + + /* Reference queues for cleared WeakKeys */ + private ReferenceQueue keyQueue = new ReferenceQueue(); + + /** + * Removes any keys whose object has been garbage collected. + * (Garbage collected values are removed as they are encountered.) + */ + private void processKeyQueue() + { + CompoundKey ck; + while ((ck = (CompoundKey)keyQueue.poll()) != null) + { + //System.out.println( "EOObserverCenter.processQueue: removing object" ); + observers.remove(ck); + } + } + + /** + * Key combining a name with an object. + * The object is weakly referenced, and keys + * are deallocated by reference queue. + * equals() compares by reference. + */ + private static class CompoundKey extends WeakReference + { + private Object name; + private int hashCode; + + /** + * Creates compound key. + * Neither name nor object may be null. + * Use NullMarker to represent null + * in either name or object. + */ + public CompoundKey ( + Object aName, Object anObject ) + { + super( anObject ); + name = aName; + hashCode = aName.hashCode() + anObject.hashCode(); + } + + /** + * Creates compound key with queue. + * Neither name nor object may be null. + * Use NullMarker to represent null + * in either name or object. + */ + public CompoundKey ( + Object aName, Object anObject, ReferenceQueue aQueue ) + { + super( anObject, aQueue ); + name = aName; + hashCode = aName.hashCode() + anObject.hashCode(); + } + + public Object name() + { + return name; + } + + public int hashCode() + { + return hashCode; + } + + public boolean equals( Object anObject ) + { + if ( this == anObject ) return true; + // assumes only used with other compound keys + CompoundKey key = (CompoundKey) anObject; + if ( name == key.name || ( name != null && name.equals( key.name ) ) ) + { + Object object = get(); + if ( object != null ) + { + // compares by reference + if ( object == ( key.get() ) ) + { + return true; + } + } + } + return false; + } + + public String toString() + { + return "[CompoundKey:"+name()+":"+get()+"]"; + } + } + + /** + * Value combining an object with a selector. + * The object is weakly referenced, and null + * values are not allowed. + */ + private static class CompoundValue extends WeakReference + { + private NSSelector selector; + private int hashCode; + + public CompoundValue( Object anObject, NSSelector aSelector ) + { + super( anObject ); + hashCode = anObject.hashCode(); + selector = aSelector; + } + + public NSSelector selector() + { + return selector; + } + + public int hashCode() + { + return hashCode; + } + + public boolean equals( Object anObject ) + { + if ( this == anObject ) return true; + // assumes only used with other compound values + CompoundValue value = (CompoundValue) anObject; + if ( selector == value.selector || + ( selector != null && selector.equals( value.selector ) ) ) + { + Object object = get(); + if ( object != null ) + { + if ( object == value.get() ) + { + return true; + } + } + } + return false; + } + + public String toString() + { + return "[CompoundValue:"+get()+":"+selector().name()+"]"; + } + } +/* + public static void main( String[] argv ) + { + Object aSource = "aSource"; + Object bSource = "bSource"; + + Object oneTest = new OneTest(); + Object twoTest = new TwoTest(); + NSSelector notifyMeOnce = + new NSSelector( "notifyMeOnce", + new Class[] { NSNotification.class } ); + NSSelector notifyMeTwice = + new NSSelector( "notifyMeTwice", + new Class[] { NSNotification.class } ); + + NSNotificationCenter.defaultCenter().addObserver( + oneTest, notifyMeOnce, "aMessage", null ); + + NSNotificationCenter.defaultCenter().addObserver( + oneTest, notifyMeOnce, null, aSource ); + + NSNotificationCenter.defaultCenter().addObserver( + twoTest, notifyMeOnce, "aMessage", aSource ); + + NSNotificationCenter.defaultCenter().addObserver( + twoTest, notifyMeTwice, null, null ); + + NSNotificationCenter.defaultCenter().postNotification( + "aMessage", aSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "aMessage", bSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "bMessage", aSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "bMessage", bSource ); + System.out.println( "---" ); + + NSNotificationCenter.defaultCenter().removeObserver( + oneTest, null, aSource ); + + NSNotificationCenter.defaultCenter().postNotification( + "aMessage", aSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "aMessage", bSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "bMessage", aSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "bMessage", bSource ); + System.out.println( "---" ); + + NSNotificationCenter.defaultCenter().removeObserver( + null ); + + NSNotificationCenter.defaultCenter().postNotification( + "aMessage", aSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "aMessage", bSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "bMessage", aSource ); + System.out.println(); + NSNotificationCenter.defaultCenter().postNotification( + "bMessage", bSource ); + System.out.println( "---" ); + } + + static private class OneTest + { + public void notifyMeOnce( NSNotification aNotification ) + { + System.out.println( "OneTest.notifyMeOnce: " + aNotification ); + } + } + + static private class TwoTest + { + public void notifyMeOnce( NSNotification aNotification ) + { + System.out.println( "TwoTest.notifyMeOnce: " + aNotification ); + } + public void notifyMeTwice( NSNotification aNotification ) + { + System.out.println( "TwoTest.notifyMeTwice: " + aNotification ); + } + } +*/ +} + + + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.11 2003/06/03 14:51:15 mpowers + * Added commented-out println for debugging. + * + * Revision 1.10 2003/03/27 21:46:00 mpowers + * Better handling for null parameters on subscribe. + * Better handling for null parameters on post. + * + * Revision 1.9 2003/01/28 19:44:38 mpowers + * Now comparing strings by value not reference. + * + * Revision 1.8 2001/06/29 16:14:23 mpowers + * Fixed a javac compiler error that jikes allowed: shoe's on the other foot! + * + * Revision 1.7 2001/06/07 22:09:03 mpowers + * Exceptions during a notification are no longer being thrown + * so we can assure that all notifications get handled. + * Instead, we're printing stack traces... + * + * Revision 1.6 2001/04/09 21:41:50 mpowers + * Better debugging. + * + * Revision 1.5 2001/03/15 21:09:06 mpowers + * Fixed notifications with null objects. + * + * Revision 1.4 2001/02/21 21:18:34 mpowers + * Clarified need to retain references. + * + * Revision 1.3 2001/02/21 18:31:07 mpowers + * Finished and tested implementation of NSNotificationCenter. + * + * Revision 1.1.1.1 2000/12/21 15:47:39 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java new file mode 100644 index 0000000..7350a39 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNotificationQueue.java @@ -0,0 +1,345 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** +* NSNotificationQueue coalesces notifications to be +* posted to the NSNotificationCenter and can post them +* asynchronously. While calling postNotification on +* the notification center does not return until all +* receivers have been notified, calling enqueueNotification +* can return immediately. Use this class when you want +* to coalesce notifications or notify asynchronously, or +* both, which is the typical case. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSNotificationQueue +{ + private static NSNotificationQueue defaultQueue = null; + + /** + * Posting style specifying that the notification should + * be posted on the next available event loop. + */ + public static final int PostASAP = 4; + + /** + * Posting style specifying that the notification should + * be posted on the next available idle loop. + */ + public static final int PostWhenIdle = 8; + + /** + * Posting style specifying that the notification should + * be posted immediately. The enqueue method will not + * return until all receivers have been notified. + */ + public static final int PostNow = 16; + + /** + * Used to indicate that this notification should not + * be coalesced with other notifications. + * Ignored if combined with other coalesce flags. + */ + public static final int NotificationNoCoalescing = 0; + + /** + * Used to indicate that this notification should + * be coalesced with other notifications with the + * same name. + * May be combined with NotificationCoalescingOnSender. + */ + public static final int NotificationCoalescingOnName = 1; + + /** + * Used to indicate that this notification should + * be coalesced with other notifications with the + * same object argument (which is typically the sender). + * May be combined with NotificationCoalescingOnName. + */ + public static final int NotificationCoalescingOnSender = 2; + + /** + * The ASAP queue, which should probably use a LinkedList. + */ + private List queue; + + /** + * The idle queue, which should probably use a LinkedList. + */ + private List idleQueue; + + /** + * The notification center we will be using. + */ + private NSNotificationCenter center; + + /** + * Our private ASAP notifier. + */ + private Notifier notifier; + + /** + * Our private idle notifier. + */ + private Notifier idleNotifier; + + /** + * Default constructor creates a new notification queue + * that uses the default notification center. + */ + public NSNotificationQueue() + { + this( NSNotificationCenter.defaultCenter() ); + } + + /** + * Creates a new notification queue that uses the + * specified notification center. + */ + public NSNotificationQueue( + NSNotificationCenter aCenter ) + { + queue = new LinkedList(); + idleQueue = new LinkedList(); + center = aCenter; + notifier = new Notifier( this, queue ); + idleNotifier = new Notifier( this, idleQueue ); + } + + /** + * Returns the system default queue, creating one + * if it has not yet been created. The system default + * queue uses the system default notification center. + */ + static public NSNotificationQueue defaultQueue() + { + if ( defaultQueue == null ) + { + defaultQueue = new NSNotificationQueue(); + } + return defaultQueue; + } + + /** + * Removes notifications from the queue that match + * the specified notification, considering the + * specified coalesce mask. + */ + public void dequeueMatchingNotifications( + NSNotification aNotification, + int aCoalesceMask) + { + if ( aCoalesceMask == NotificationNoCoalescing ) return; + dequeueFromQueue( aNotification, aCoalesceMask, queue ); + dequeueFromQueue( aNotification, aCoalesceMask, idleQueue ); + } + + private void dequeueFromQueue( + NSNotification aNotification, + int aCoalesceMask, + List aQueue ) + { + synchronized ( aQueue ) + { + int flag; + NSNotification notification; + Object name = aNotification.name(); + Object object = aNotification.object(); + Iterator it = aQueue.iterator(); + while ( it.hasNext() ) + { + flag = 0; + notification = (NSNotification) it.next(); + // if NotificationCoalescingOnName + if ( ( aCoalesceMask == 1 ) || ( aCoalesceMask == 3 ) ) + { + if ( name == null ) + { + if ( notification.name() != null ) + { + flag += NotificationCoalescingOnName; + } + } + else + { + // compare by value + if ( name.equals( notification.name() ) ) + { + flag += NotificationCoalescingOnName; + } + } + } + // if NotificationCoalescingOnSender + if ( aCoalesceMask >= 2 ) + { + // compare by reference + if ( object == notification.object() ) + { + flag += NotificationCoalescingOnSender; + } + } + + if ( flag == aCoalesceMask ) + { + it.remove(); + } + } + } + } + + /** + * Adds the notification to the queue to be run at + * the time specified by the posting style. The + * notification will be coalesced with other notifications + * that match the same name and object (sender) argument. + */ + public void enqueueNotification( + NSNotification aNotification, + int aPostingStyle) + { + enqueueNotificationWithCoalesceMaskForModes( + aNotification, aPostingStyle, + NotificationCoalescingOnName + NotificationCoalescingOnSender, + null ); + } + + /** + * Adds the notification to the queue to be run at + * the time specified by the posting style and coelesced + * as the specified mask indicates. + * aModeList is currently ignored and may be null. + */ + public void enqueueNotificationWithCoalesceMaskForModes( + NSNotification aNotification, + int aPostingStyle, + int aCoalesceMask, + List aModeList) + { + dequeueMatchingNotifications( aNotification, aCoalesceMask ); + + if ( aPostingStyle == PostNow ) + { + center.postNotification( aNotification ); + return; + } + + if ( aPostingStyle == PostASAP ) + { + synchronized ( queue ) + { + queue.add( aNotification ); + if ( ! notifier.willRun ) + { + // asap runs at the very first run loop ordering, plus one just in case + NSRunLoop.invokeLaterWithOrder( notifier, 1 ); + notifier.willRun = true; + } + } + return; + } + + if ( aPostingStyle == PostWhenIdle ) + { + synchronized ( idleQueue ) + { + idleQueue.add( aNotification ); + if ( ! idleNotifier.willRun ) + { + // when idle runs at the very last run loop ordering, minus one just in case + NSRunLoop.invokeLaterWithOrder( idleNotifier, Integer.MAX_VALUE - 1 ); + idleNotifier.willRun = true; + } + } + return; + } + + } + + private class Notifier implements Runnable + { + public boolean willRun; + + NSNotificationQueue parent; + List queue; + + public Notifier( + NSNotificationQueue aParent, List aQueue ) + { + willRun = false; + parent = aParent; + queue = aQueue; + } + + public void run() + { + Iterator it = new LinkedList( queue ).iterator(); + synchronized ( queue ) + { + queue.clear(); + } + willRun = false; + + // queue must already be cleared and willRun reset + // because this loop might queue more notifications + while ( it.hasNext() ) + { + parent.center.postNotification( + (NSNotification) it.next() ); + } + } + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.6 2003/08/11 18:18:08 chochos + * improved encoding of strings, removed warnings + * + * Revision 1.5 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.4 2001/11/01 15:49:26 mpowers + * With NSRunLoop, we can now correctly implement PostASAP and PostWhenIdle. + * + * Revision 1.3 2001/06/25 14:47:24 mpowers + * Fixed serious error where some notifications were not being posted at all. + * A simple change to Notifier class fixed the problem. Thanks to glista. + * + * Revision 1.2 2001/02/26 15:53:22 mpowers + * Fine-tuning notification firing. + * Child display groups now update properly after parent save or invalidate. + * + * Revision 1.1 2001/02/24 17:03:22 mpowers + * Implemented the notification queue, and changed editing context to use it. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java new file mode 100644 index 0000000..db1f216 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNull.java @@ -0,0 +1,104 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import java.io.Serializable; + +/** +* NSNull is used to represent null in Collections classes +* because List and Map do not specify whether null values +* are allowed and because NSArray and NSDictionary explicitly +* do not allow null values. <br><br> +* +* Use of the static singleton method nullValue() is required +* by this implementation because Java cannot return a singleton +* instance from a constructor. Even then, more than one instance +* may exist in the application due to object serialization. +* Be sure to compare with equals(). +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ +public class NSNull implements Serializable +{ + private static final NSNull instance = new NSNull(); + + /** + * Create a new instance of NSNull. + */ + private NSNull () + { + } + + /** + * Returns the static instance of nullValue. + * Note that serialization may mean that more than + * one instance of NSNull exists, so be sure to + * compare with equals(). + */ + public static NSNull nullValue () + { + return instance; + } + + /** + * Returns a human-readable string representation. + */ + public String toString() + { + return "[null]"; + } + + /** + * Hashcode of all instances is zero. + */ + public int hashCode() + { + return 0; + } + + /** + * Implemented to return true for any instance of NSNull. + */ + public boolean equals( Object anObject ) + { + return ( anObject instanceof NSNull ); + } +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.3 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.2 2001/03/01 20:36:09 mpowers + * Implemented equals, hashcode, and serializable. + * + * Revision 1.1 2001/02/26 22:41:51 mpowers + * Implemented null placeholder classes. + * Duplicator now uses NSNull. + * No longer catching base exception class. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java new file mode 100644 index 0000000..064ee3c --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSNumberFormatter.java @@ -0,0 +1,57 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2003 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org + +$Id: NSNumberFormatter.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.text.DecimalFormat; + +/** +* A Format that accepts C-style number formatting syntax. +* Not currently implemented, included for compile compatibility. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class NSNumberFormatter extends DecimalFormat +{ + public NSNumberFormatter() + { + super(); + } + + public NSNumberFormatter(String aPattern) + { + super( aPattern ); + } +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/01/17 14:40:50 mpowers + * Adding files to fix build. + * + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java new file mode 100644 index 0000000..7f8bcc9 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSPropertyListSerialization.java @@ -0,0 +1,275 @@ + +package net.wotonomy.foundation; + +public class NSPropertyListSerialization { + + public static final int PLIST_ARRAY = 0; + public static final int PLIST_DICTIONARY = 1; + public static final int PLIST_DATA = 2; + public static final int PLIST_STRING = 3; + + public static final char[] TOKEN_BEGIN = new char[]{ + '(', '{', '<', '"' + }; + public static final char[] TOKEN_END = new char[]{ + ')', '}', '>', '"' + }; + public static final char[] QUOTING_CHARS = new char[]{ + ':', '/', '-', '.', '\\' + }; + + private NSPropertyListSerialization() { + super(); + } + + /** Creates a NSArray object from a string representation. + @s The string representation of a NSArray object. */ + public static NSArray arrayForString(String s) { + s = s.trim(); + if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_ARRAY] && s.charAt(s.length()-1) == TOKEN_END[PLIST_ARRAY])) + return null; + NSMutableArray arr = new NSMutableArray(); + int pos = 1; + int valbegin = -1; + while (pos < s.length()) { + char c = s.charAt(pos); + int tokenCount = 0; + int what = 0; + for (int i = 0 ; i < TOKEN_BEGIN.length; i++) { + if (c == TOKEN_BEGIN[i]) { + tokenCount = 1; + what = i; + break; + } + } + if (tokenCount > 0) { + //mark it + int quote = pos; + //find the closing token + do { + pos++; + try { + c = s.charAt(pos); + } catch (StringIndexOutOfBoundsException ex) { + throw new IllegalArgumentException("Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); + } + if (c == '"' && what == PLIST_STRING) { + if (pos > 0 && s.charAt(pos-1) != '\\') { + tokenCount--; + } + } else if (c == TOKEN_BEGIN[what]) + tokenCount++; + else if (c == TOKEN_END[what]) + tokenCount--; + } while (tokenCount > 0); + arr.addObject(propertyListFromString(s.substring(quote, pos+1))); + valbegin = -1; + //advance to the next position + do { + pos++; + c = s.charAt(pos); + } while (Character.isWhitespace(c)); + } + if (c == ',' || c ==')') { + if (valbegin > 0) { + arr.addObject(s.substring(valbegin, pos).trim()); + valbegin = -1; + } + } else if (!Character.isWhitespace(c)) { + if (valbegin < 0) { + valbegin = pos; + } + } + pos++; + } + return arr; + } + + /** Creates a NSDictionary instance from a string representation. + @s The string representation of a NSDictionary. */ + public static NSDictionary dictionaryForString(String s) { + s = s.trim(); + if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DICTIONARY] && s.charAt(s.length()-1) == TOKEN_END[PLIST_DICTIONARY])) + return null; + NSMutableDictionary d = new NSMutableDictionary(); + int pos = 1; + boolean parsing = true; + Object key = null; + int valbegin = -1; + while (pos < s.length()) { + //look for an opening token + char c = s.charAt(pos); + int tokenCount = 0; + int what = 0; + for (int i = 0 ; i < TOKEN_BEGIN.length; i++) { + if (c == TOKEN_BEGIN[i]) { + tokenCount = 1; + what = i; + break; + } + } + if (tokenCount > 0) { + //mark it + int quote = pos; + //find the closing token + do { + pos++; + try { + c = s.charAt(pos); + } catch (StringIndexOutOfBoundsException ex) { + throw new IllegalArgumentException("Could not parse property list; unclosed token '" + TOKEN_BEGIN[what] + "'"); + } + if (c == '"' && what == PLIST_STRING) { + if (pos > 0 && s.charAt(pos-1) != '\\') { + tokenCount--; + } + } else if (c == TOKEN_BEGIN[what]) + tokenCount++; + else if (c == TOKEN_END[what]) + tokenCount--; + } while (tokenCount > 0); + if (key == null) { + key = propertyListFromString(s.substring(quote, pos+1)); + } else { + d.setObjectForKey(propertyListFromString(s.substring(quote, pos+1)), key); + key = null; + } + valbegin = -1; + //advance to the next position + do { + pos++; + c = s.charAt(pos); + } while (Character.isWhitespace(c)); + } + if (c == ';' || c == '=' || c == '}') { + if (valbegin > 0) { + if (key == null) { + key = s.substring(valbegin, pos).trim(); + } else { + d.setObjectForKey(s.substring(valbegin, pos).trim(), key); + key = null; + } + valbegin = -1; + } + } else if (!Character.isWhitespace(c)) { + if (valbegin < 0) { + valbegin = pos; + } + } + pos++; + } + return d; + } + + public static boolean booleanForString(String s) { + return s.trim().toLowerCase().equals("true"); + } + + /** Creates a NSData instance from a string representation. + @s The string representation of a NSData object. */ + public static NSData dataFromPropertyList(String s) { + String hex = "0123456789ABCDEF"; + s = s.trim(); + if (!(s.charAt(0) == TOKEN_BEGIN[PLIST_DATA] && s.charAt(s.length()-1) == TOKEN_END[PLIST_DATA])) + return null; + int pos = 1; + java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(); + while (pos < s.length()-1) { + char c1 = s.charAt(pos); + while (c1 == ' ') { + pos++; + if (pos == s.length()-1) + return new NSData(bout.toByteArray()); + c1 = s.charAt(pos); + } + if (hex.indexOf(c1) < 0) + throw new IllegalArgumentException("The string does not represent a NSData object (" + s + ", pos " + pos + ")"); + pos++; + char c2 = s.charAt(pos); + if (hex.indexOf(c2) < 0) + throw new IllegalArgumentException("The string does not represent a NSData object (" + s + ")"); + int x = (hex.indexOf(c1) << 4) | hex.indexOf(c2); + bout.write(x); + pos++; + } + return new NSData(bout.toByteArray()); + } + + public static int intForString(String s) { + return Integer.parseInt(s); + } + + /** Returns the string representation of a property list. + @plist The property list. It can be a String, NSData, NSArray, NSDictionary. */ + public static String stringForPropertyList(Object plist) { + if (plist == null) + return ""; + if (plist instanceof NSArray || plist instanceof NSDictionary || plist instanceof NSData) + return plist.toString(); + String x = plist.toString(); + boolean quote = false; + for (int i = 0; i < x.length(); i++) { + char c = x.charAt(i); + for (int z = 0; z < TOKEN_BEGIN.length; z++) { + if (c == TOKEN_BEGIN[z] || c == TOKEN_END[z]) + quote = true; + } + if (!quote) { + for (int z = 0; z < QUOTING_CHARS.length; z++) { + if (c == QUOTING_CHARS[z]) + quote = true; + } + } + if (!quote && Character.isWhitespace(c)) { + quote = true; + i = x.length(); + } + } + if (quote) + return "\"" + x + "\""; + return x; + } + + /** Returns an property list created from a string representation. + @s The string with a representation of a property list. + @returns A property list object; either a NSData, NSArray, NSDictionary, or a String. */ + public static Object propertyListFromString(String s) { + s = s.trim(); + int type = -1; + for (int i = 0; i < TOKEN_BEGIN.length; i++) { + if (TOKEN_BEGIN[i] == s.charAt(0)) { + if (TOKEN_END[i] == s.charAt(s.length()-1)) + type = i; + } + } + switch (type) { + case PLIST_DATA: + return dataFromPropertyList(s); + case PLIST_ARRAY: + return arrayForString(s); + case PLIST_DICTIONARY: + return dictionaryForString(s); + case PLIST_STRING: + if (s.equals("\"\"")) + return ""; + return s.substring(1, s.length()-1); + } + return s; + } + + /* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.5 2003/08/11 18:18:08 chochos + * improved encoding of strings, removed warnings + * + * Revision 1.4 2003/08/11 17:33:45 chochos + * Implemented detection of escaped quotes (\"). improved quoting strings when converting property lists to strings. + * + * Revision 1.3 2003/08/04 23:50:55 chochos + * propertyListForString() now works. dictionaryForString and arrayForString can parse complex structures with nested arrays and dictionaries. + * + */ +}
\ No newline at end of file diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java new file mode 100644 index 0000000..2de52f5 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRange.java @@ -0,0 +1,351 @@ +/* +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; + +/** +* A pure java implementation of NSRange. +* An NSRange represents a range of numbers +* having a starting location and spanning a +* length. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 920 $ +*/ +public class NSRange implements Cloneable +{ + /** + * An empty range. + */ + public static final NSRange ZeroRange = new NSRange(); + + protected int loc; + protected int len; + + /** + * Default constructor produces an empty range. + */ + public NSRange () + { + this( 0, 0 ); + } + + /** + * Produces a range with the specified location and length. + */ + public NSRange (int location, int length) + { + loc = location; + len = length; + } + + /** + * Produces a range that has the same location and length as + * the specified range. + */ + public NSRange (NSRange aRange) + { + this( aRange.location(), aRange.length() ); + } + + /** + * Returns the location of this range. + */ + public int location () + { + return loc; + } + + /** + * Returns the length of this range. + */ + public int length () + { + return len; + } + + /** + * Returns the maximum extent of the range. This number is + * one more than the last position in the range. + */ + public int maxRange () + { + return location() + length() -1; + } + + /** + * Returns whether this is an empty range, therefore + * whether the length is zero. + */ + public boolean isEmpty () + { + return ( length() == 0 ); + } + + /** + * Returns whether the specified location is contained + * within this range. + */ + public boolean locationInRange (int location) + { + if ( location < location() ) return false; + if ( location >= maxRange() ) return false; + return true; + } + + /** + * Returns whether the specified range is equal to this range. + */ + public boolean isEqualToRange (NSRange aRange) + { + if ( aRange == null ) return false; + return ( ( aRange.location() == location() ) + && ( aRange.length() == length() ) ); + } + + /** + * Returns whether the specified object is equal to this range. + */ + public boolean equals (Object anObject) + { + if ( anObject instanceof NSRange ) + return isEqualToRange( (NSRange) anObject ); + return false; + } + + /** + * Returns a hashCode. + */ + public int hashCode () + { + // TODO: Test this logic. + return ( location() << 2 ) & length(); // bitwise ops never my forte + } + + /** + * Returns a string representation of this range. + */ + public String toString () + { + return "[NSRange: location = " + location() + + "; length = " + length() + "]"; + } + + /** + * Returns the union of this range and the specified range, if any. + * Gaps are filled, so the result is the smallest starting position + * and the largest ending position. + */ + public NSRange rangeByUnioningRange (NSRange aRange) + { + if ( aRange == null ) return this; + + // TODO: Test this logic. + int resultLoc = Math.min( this.location(), aRange.location() ); + int resultLen = Math.max( this.location() + this.length(), + aRange.location() + aRange.length() ) - resultLoc; + return new NSRange( resultLoc, resultLen ); + } + + /** + * Returns the intersection of this range and the specified range, + * if any. If no intersection, returns an empty range. + */ + public NSRange rangeByIntersectingRange (NSRange aRange) + { + // TODO: Test this logic. + if ( ! intersectsRange( aRange ) ) return ZeroRange; + int start = Math.max( this.location(), aRange.location() ); + int end = Math.min( this.location() + this.length(), + aRange.location() + aRange.length() ); + return new NSRange( start, end - start ); + } + + /** + * Returns whether the specified range overlaps + * at any point with this range. + */ + public boolean intersectsRange (NSRange aRange) + { + // TODO: Test this logic. + if ( aRange == null ) return false; + if ( ( this.location() >= aRange.location() ) + && ( this.location() < aRange.location() + aRange.length() ) ) + return true; + if ( ( aRange.location() >= this.location() ) + && ( aRange.location() < this.location() + this.length() ) ) + return true; + return false; + } + + /** + * Returns whether this range is completely + * contained within the specified range. + */ + public boolean isSubrangeOfRange (NSRange aRange) + { + // TODO: Test this logic. + if ( aRange == null ) return false; + if ( ( this.location() >= aRange.location() ) + && ( this.maxRange() <= aRange.maxRange() ) ) + return true; + return false; + } + + /** + * Eliminates any intersections between this range and the specified + * range. This produces two ranges, either of which may be empty. + * These two ranges are returned by modifying the supplied second + * and third parameters. + */ + public void subtractRange (NSRange aRange, + NSMutableRange firstResult, NSMutableRange secondResult) + { + if ( aRange == null ) return; + + // TODO: Test this logic. + // no intersection: return this and aRange without calculation + if ( ! intersectsRange( aRange ) ) + { + if ( firstResult != null ) + { + firstResult.setLocation( this.location() ); + firstResult.setLength( this.length() ); + } + if ( secondResult != null ) + { + secondResult.setLocation( aRange.location() ); + secondResult.setLength( aRange.location() ); + } + return; + } + + // TODO: Test this logic. + // this range is completely contained by other range + if ( isSubrangeOfRange( aRange ) ) + { + if ( firstResult != null ) + { + firstResult.setLocation( aRange.location() ); + firstResult.setLength( this.location() - aRange.location() ); + } + if ( secondResult != null ) + { + secondResult.setLocation( this.maxRange() ); + secondResult.setLength( + aRange.maxRange() - this.maxRange() - 1 ); // test this + } + return; + } + + // TODO: Test this logic. + // other range is completely contained by this range + if ( aRange.isSubrangeOfRange( this ) ) + { + if ( firstResult != null ) + { + firstResult.setLocation( this.location() ); + firstResult.setLength( aRange.location() - this.location() ); + } + if ( secondResult != null ) + { + secondResult.setLocation( aRange.maxRange() ); + secondResult.setLength( + this.maxRange() - aRange.maxRange() - 1 ); // test this + } + return; + } + + // TODO: Test this logic. + // ranges intersect: remove only the intersection + + NSRange firstRange, secondRange; + if ( this.location() <= aRange.location() ) + { + firstRange = this; + secondRange = aRange; + } + else + { + firstRange = aRange; + secondRange = this; + } + + if ( firstResult != null ) + { + firstResult.setLocation( firstRange.location() ); + firstResult.setLength( + secondRange.location() - firstRange.location() ); + } + if ( secondResult != null ) + { + secondResult.setLocation( firstRange.maxRange() ); + secondResult.setLength( + secondRange.maxRange() - aRange.maxRange() - 1 ); // test this + } + return; + + } + + /** + * Returns a copy of this range. + */ + public Object clone () + { + return new NSRange( location(), length() ); + } + + /** + * Parses a range from a string of the form "{x,y}" where + * x is the location and y is the length. If not parsable, + * an IllegalArgumentException is thrown. + */ + public static NSRange fromString (String aString) + { + // TODO: Test this logic. + try + { + java.util.StringTokenizer tokens = + new java.util.StringTokenizer( aString, "{,}" ); + int loc = Integer.parseInt( tokens.nextToken() ); + int len = Integer.parseInt( tokens.nextToken() ); + return new NSRange( loc, len ); + } + catch ( Exception exc ) + { + throw new IllegalArgumentException( exc.toString() ); + } + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:47:42 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:38 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java new file mode 100644 index 0000000..65f58db --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRecursiveLock.java @@ -0,0 +1,148 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSRecursiveLock.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import EDU.oswego.cs.dl.util.concurrent.ReentrantLock; + +/** +* A lock class that allows a thread to re-acquire it's lock +* recursively. Currently an API-compliance wrapper around Doug Lea's +* ReentrantLock, conforming to the API and behavior of +* com.webobjects.foundation.NSRecursiveLock. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class NSRecursiveLock extends ReentrantLock implements NSLocking { + + public NSRecursiveLock() { + } + /** Acquire the lock, catching the thrown exception to mirror the + * behavior of com.webobjects.foundation.NSRecursiveLock. Note that + * ReentrantLock.acquire() performs a notify() when it's interrupted. + * + * @see edu.oswego.cs.dl.util.concurrent.ReentrantLock#acquire() + */ + public void lock() { + try { + acquire(); + } catch (InterruptedException interruptedexception) { + // Null behavior, as notify() is already called + // by acquire(); + // We may want to log here. + } + } + + /** Pass the buck to tryLock(long), passing zero time as the parameter. + * + * @see #tryLock(long) + */ + public boolean tryLock() { + return tryLock(1); + } + + /** Attempt to acquire the lock, catching the thrown exception to mirror + * the behavior of com.webobjects.foundation.NSRecursiveLock. Note that + * ReentrantLock.attempt(*) performs a notify() when it's interrupted. + * Fail gracefully after the given milliseconds + * + * @param (long) + * @see edu.oswego.cs.dl.util.concurrent.ReentrantLock#acquire() + */ + public boolean tryLock(long milliseconds) { + try { + return attempt(milliseconds); + } catch (InterruptedException interruptedexception) { + // notify() is already called by attempt(); + // We may want to log here. + return false; + } + } + /** + * Attempt to acquire a lock until the timestamp is reached. Add + * 1 to the recursion count if the calling thread already owns the + * lock. Otherwise block until free or until the given timestamp + * is reached. + * + * @see Timestamp + * @see ReentrantLock.attempt(long); + */ + public boolean tryLock(NSTimestamp nstimestamp) { + return tryLock(nstimestamp.getTime() - System.currentTimeMillis()); + } + + /** Unlock the current lock precisely once. + */ + public synchronized void unlock() { + unlock(1); + } + + /** Unlock the current lock count times. + */ + public synchronized void unlock(long count) { + if (owner_ != null && Thread.currentThread() != owner_) + throw new IllegalStateException("Illegal Lock usage: unlocking thread not owner."); + if (owner_ == null || holds_ == 0L) + throw new IllegalStateException("Illegal Lock usage: unlock() called without a lock()."); + release(count); + } + + public synchronized long recursionCount() { + return holds(); + } + + public String toString() { + long holds = holds(); + boolean oneHold = (holds == 1); + boolean noHolds = (holds < 1 || owner_ == null); + return getClass().getName() + " <" + + ((noHolds) ? "Unlocked" : ( "Locked " + holds + " time" + (oneHold ? "" : "s") + " by " + owner_ ) ) + ">"; + } + +} +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.5 2002/06/25 19:02:19 cgruber + * I'm a dumbass. + * + * Revision 1.4 2002/06/25 18:52:56 cgruber + * Fix javadocs that resulted from bad cut-and-paste of the boilerplate. + * + * Revision 1.3 2002/06/25 18:45:27 cgruber + * Add some javadocs. + * + * Revision 1.2 2002/06/25 18:06:48 cgruber + * Add implementation of NSRecursiveLock using Doug Lea's concurrent programming APIs. + * Specifically inherit from ReentrantLock, which magically does the exact job we want! + * + * Revision 1.1 2002/06/25 07:52:56 cgruber + * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java new file mode 100644 index 0000000..2d122aa --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSRunLoop.java @@ -0,0 +1,522 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2001 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org +*/ + +package net.wotonomy.foundation; + +import java.awt.AWTEvent; +import java.awt.EventQueue; +import java.awt.Toolkit; +import java.awt.event.InvocationEvent; +import java.util.EmptyStackException; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** +* NSRunLoop is provided specifically for EODelayedObserverQueue +* and EOEditingContext, which assume the existence of a +* prioritized event queue that Java does not provide. <br><br> +* +* This extends java.awt.EventQueue and does not conform to the +* NSRunLoop specifications. The only supported methods are +* NSRunLoop.currentRunLoop, performSelectorWithOrder, and +* cancelSelectorWithOrder. Note that in Swing there is only +* one AWT thread and one event queue; newly created threads +* will not get their own run loop as in OpenStep.<br><br> +* +* That said, this event queue is servicable as a replacement +* for the default event queue and will provide prioritized +* execution of selectors before and after normal AWT events. +* <br><br> +* +* Each run loop dispatches the lowest order event from +* the queue. When queued events have the same ordering, +* they are dispatched as first-in, first-out (FIFO). Because +* all AWT events have the same ordering (AWTEventsRunLoopOrdering), +* they are processed FIFO, just like the default event queue. <br><br> +* +* Note that because EventQueue is not well-factored for +* subclassing, pushing a new event queue onto the stack +* on top of this one will only copy the existing AWT events +* to the new queue. For this reason, pushing new event +* queues onto the stack is not supported and will throw +* an exception. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSRunLoop extends EventQueue +{ + /** + * This is the ordering at which the conventional AWT event + * queue will be executed. Selectors with this ordering + * or less will be executed before AWT events, and + * selectors with ordering greater than this ordering will be + * be executed after AWT events. + */ + public static final int AWTEventsRunLoopOrdering = 500000; + + /** + * The singleton instance. + */ + protected static NSRunLoop instance; + + private LinkedList earlyQueue; + private LinkedList lateQueue; + + /** + * Needed because JDK1.4 made our lives more difficult. + */ + private static Toolkit toolkit; + + /** + * Because SunToolkit.flushPendingEvents was changed + * to a static method in 1.4, you can't compile the library + * in such a way that it works with both 1.3 and 1.4. + * So we have to rely on dynamic method invocation, + * which is slower, but we try to make it as fast as + * humanly possible. + */ + private static NSSelector flushPendingEvents; + + /** + * Create a new instance of NSRunLoop. + */ + protected NSRunLoop () + { + earlyQueue = new LinkedList(); + lateQueue = new LinkedList(); + } + + /** + * Returns the singleton instance of NSRunLoop. + * This returns the same instance no matter what + * thread calls it, which is different from OpenStep. + * NSRunLoop is limited to a singleton instance + * because there is no way of obtaining the stack + * of event queues from EventQueue because it is + * private state. + */ + public synchronized static NSRunLoop currentRunLoop () + { + if ( instance == null ) + { + // create and initialize + flushPendingEvents = new NSSelector( "flushPendingEvents" ); + toolkit = Toolkit.getDefaultToolkit(); + instance = new NSRunLoop(); + + toolkit.getSystemEventQueue().push( instance ); + } + + return instance; + } + + /** + * Post a 1.1-style event to the EventQueue. If there is an + * existing event on the queue with the same ID and event source, + * the source Component's coalesceEvents method will be called. + * + * @param theEvent an instance of java.awt.AWTEvent, or a + * subclass of it. + */ + public void postEvent(AWTEvent theEvent) + { + if ( theEvent instanceof OrderedInvocationEvent ) + { + OrderedInvocationEvent event = (OrderedInvocationEvent) theEvent; + if ( event.getOrdering() > AWTEventsRunLoopOrdering ) + { + insertEventIntoQueue( event, lateQueue ); + } + else + { + insertEventIntoQueue( event, earlyQueue ); + } + } + else + { + super.postEvent( theEvent ); + } + } + + private synchronized void insertEventIntoQueue( OrderedInvocationEvent e, LinkedList q ) + { + OrderedInvocationEvent o; + int ordering = e.getOrdering(); + ListIterator iterator = + q.listIterator(); + + // iterate forwards until we find a priority + // greater than our priority, + // then insert ourself before that element. + while ( iterator.hasNext() ) + { + o = (OrderedInvocationEvent) iterator.next(); + if ( o.getOrdering() > ordering ) + { + // back up one + iterator.previous(); + break; + } + } + // add after the current element + iterator.add( e ); + } + + /** + * Useful method, but not in the spec. + * Dispatches the next AWT event in the queue. + * Returns whether a selector or an event was executed: + * if the event queue is empty, returns false. + */ + public boolean dispatchNextEvent() + { + // check for empty queue to avoid blocking + if ( peekEvent() == null ) + { + return false; + } + + // queue not empty: dispatch the next event + try + { + dispatchEvent( getNextEvent() ); + } + catch ( InterruptedException exc ) + { + System.out.println( "NSRunLoop: error while dispatching event: " ); + exc.printStackTrace(); + } + return true; + } + + /** + * Useful method, but not in the spec. + * Dispatches all events in the queue before returning. + */ + public void dispatchAllEvents() + { + while ( dispatchNextEvent() ); + } + + /** + * Remove an event from the EventQueue and return it. + * This override will dispatch all selectors up to 5000, + * and then check if there are AWT events on the queue. + * If the queue is empty, all remaining selectors + * are dispatched. Then, this method calls the + * super class' implementation. + * @return the next AWTEvent + * @exception InterruptedException + * if another thread has interrupted this thread. + */ + public AWTEvent getNextEvent() throws InterruptedException + { + //NOTE: it's currently unclear to me whether we should + // be operating as a run loop or as a priority queue. + // I'm opting for priority queue now, but that means that + // selectors that requeue themselves could hang the application. + // In the future, we could fake a run loop by putting a marker + // event on the AWT queue to mark the boundary between loops. + + AWTEvent result; + + while ( true ) + { + //NOTE: as of java 1.4, we have to flush pending events + // using this cheesy undocumented method on suntoolkit. + // Unsurprisingly, java.awt.EventQueue got worse, not better. + // See notes above about our use of an NSSelector. + try + { + flushPendingEvents.invoke( toolkit ); + } + catch ( Throwable t ) + { + System.out.println( "NSRunLoop.getNextEvent: " + Thread.currentThread() ); + System.err.println( "Unexpected error while flushing pending events: " ); + t.printStackTrace(); + }; + + synchronized( this ) + { + result = popNextEarlyEvent(); + if ( result != null ) + { +//System.out.println( "getNextEvent: early : " + result ); + return result; + } + } + + if ( ( result = peekEvent() ) != null ) + { +//System.out.println( "getNextEvent: AWT : " + result ); + return super.getNextEvent(); + } + + synchronized( this ) + { + result = popNextLateEvent(); + if ( result != null ) + { +//System.out.println( "getNextEvent: late : " + result ); + return result; + } + + // yield +//System.out.println( "getNextEvent: wait" ); + wait(); +//System.out.println( "getNextEvent: notified" ); + } + } + } + + private AWTEvent popNextEarlyEvent() + { + if ( earlyQueue == null ) return null; // shouldn't be necessary, but is + if ( earlyQueue.isEmpty() ) return null; + return (AWTEvent) earlyQueue.removeFirst(); + } + + private AWTEvent popNextLateEvent() + { + if ( lateQueue == null ) return null; // shouldn't be necessary, but is + if ( lateQueue.isEmpty() ) return null; + return (AWTEvent) lateQueue.removeFirst(); + } + + /** + * This implementation calls super and then throws an + * UnsupportedOperationException. Catch that exception + * and ignore it if you know what you are doing. + */ + public synchronized void push(EventQueue newEventQueue) + { + super.push( newEventQueue ); + throw new UnsupportedOperationException( + "NSRunLoop may not function properly with push()" ); + } + + /** + * This implementation calls super and then throws an + * UnsupportedOperationException. Catch that exception + * and ignore it if you know what you are doing. + */ + protected void pop() throws EmptyStackException + { + super.pop(); + throw new UnsupportedOperationException( + "NSRunLoop may not function properly with pop()" ); + } + + /** + * Schedules the specified selector with the specified target and parameter + * to be invoked on the next event loop with the specified ordering. + * The selector must be able to be invoked on the target and the target method + * must accept the parameter. aModeList is currently ignored. + */ + public void performSelectorWithOrder( + NSSelector aSelector, Object aTarget, Object aParameter, int anOrdering, List aModeList ) + { + postEvent( new OrderedInvocationEvent( aSelector, aTarget, aParameter, anOrdering, aModeList ) ); + } + + /** + * Cancels the next scheduled invocation of the specified selector, target, and parameter. + * If no such invocation is scheduled, does nothing. + */ + public synchronized void cancelPerformSelectorWithOrder( + NSSelector aSelector, Object aTarget, Object aParameter ) + { + ListIterator i; + i = earlyQueue.listIterator(); + while ( i.hasNext() ) + { + if ( ((OrderedInvocationEvent)i.next()).compareTo( + aSelector, aTarget, aParameter ) ) + { + i.remove(); + return; + } + } + i = lateQueue.listIterator(); + while ( i.hasNext() ) + { + if ( ((OrderedInvocationEvent)i.next()).compareTo( + aSelector, aTarget, aParameter ) ) + { + i.remove(); + return; + } + } + } + + /** + * Causes runnable to have its run() method on the next + * event loop with the specified priority ordering. + */ + public static void invokeLaterWithOrder(Runnable aRunnable, int anOrdering) { + currentRunLoop().postEvent( + new OrderedInvocationEvent( Toolkit.getDefaultToolkit(), aRunnable, anOrdering ) ); + } + + /** + * An invocation event that can specify a priority for execution. + * The prioritization only works if the current event queue is an + * NSRunLoop; otherwise, performs as a normal invocation event. + */ + private static class OrderedInvocationEvent extends InvocationEvent + { + int ordering; + NSSelector selector = null; + Object target = null; + Object parameter = null; + + /** + * Constructs an InvocationEvent with the specified source which will + * execute the runnable's run() method when dispatched at the specified ordering. + */ + public OrderedInvocationEvent(Object source, + Runnable runnable, int anOrdering) + { + super( source, runnable ); + ordering = anOrdering; + } + + /** + * Constructs an InvocationEvent with the specified source which will + * execute the runnable's run() method when dispatched at the specified ordering. + * If notifier is non-null, notifyAll() will be called on it immediately after run() returns. + */ + public OrderedInvocationEvent(Object source, + Runnable runnable, + Object notifier, + boolean catchExceptions, int anOrdering) + { + super( source, runnable, notifier, catchExceptions ); + ordering = anOrdering; + } + + OrderedInvocationEvent( + final NSSelector aSelector, + final Object aTarget, + final Object aParameter, + int anOrdering, List aModeList) + { + this( Toolkit.getDefaultToolkit(), new Runnable() + { + public void run() + { + try + { + aSelector.invoke( aTarget, aParameter ); + } + catch ( Exception exc ) + { + System.out.println( "NSRunLoop: error invoking selector: " ); + exc.printStackTrace(); + } + } + }, anOrdering ); + + selector = aSelector; + target = aTarget; + parameter = aParameter; + } + + /** + * Called by cancelPerformSelectorWithOrder. + * Compares against the specified arguments. + */ + boolean compareTo( NSSelector aSelector, Object aTarget, Object aParameter ) + { + return ( + compareByValue( selector, aSelector ) && + compareByValue( target, aTarget ) && + compareByValue( parameter, aParameter ) ); + } + + private boolean compareByValue( Object first, Object second ) + { + if ( first == second ) return true; + if ( first == null ) return second.equals( first ); + return first.equals( second ); + + } + + /** + * Returns the ordering for this event in the run loop. + */ + public int getOrdering() + { + return ordering; + } + + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.13 2003/06/06 20:48:19 mpowers + * Fixed race condition when run loop is started from main thread. + * That was causing the dispatch thread to call getNextEvent before the + * static fields had been initialized. + * + * Revision 1.12 2003/06/03 14:52:11 mpowers + * Super constructor was calling getNextEvent before selector was created. + * + * Revision 1.10 2002/05/28 21:59:19 mpowers + * We now can compile against 1.3 and 1.4, as well as run against both too. + * + * Revision 1.9 2002/04/09 18:10:45 mpowers + * Fixes for 1.4. Commented out until we start building on 1.4. + * + * Revision 1.8 2002/02/13 21:20:15 mpowers + * Updated comments. + * + * Revision 1.7 2001/11/01 15:48:49 mpowers + * Additional debug code. + * + * Revision 1.6 2001/10/30 22:14:35 mpowers + * Constructor is now protected, not private. + * + * Revision 1.5 2001/10/29 20:41:49 mpowers + * Improved docs, better support for potential subclassing, invokeLater. + * + * Revision 1.4 2001/10/26 18:46:30 mpowers + * Now running AWT events with the appropriate ordering. + * Added invokeLaterWithOrder for java compatibility. + * + * Revision 1.3 2001/10/26 14:39:46 mpowers + * Completed implementation. + * + * Revision 1.2 2001/10/25 22:20:21 mpowers + * Got to check in an interim version - this will briefly break the build. + * + * Revision 1.1 2001/10/24 19:30:38 mpowers + * Initial check-in: incomplete implementation. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java new file mode 100644 index 0000000..965606b --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSelector.java @@ -0,0 +1,391 @@ +/* +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; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Comparator; + +import net.wotonomy.foundation.internal.PropertyComparator; + +/** +* A pure java implementation of NSSelector. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ +public class NSSelector implements Comparator, Serializable +{ + protected NSMutableDictionary methodMap; // map of classes to methods + protected String methodName; + protected Class[] parameterTypes; + + /** + * A marker to indicate object not found. + */ + protected static final String NOT_FOUND = "NOT_FOUND"; + + /** + * Saves creating a new class array for parameterless method invocation. + */ + protected static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; + + /** + * Saves creating a new object array for parameterless method invocation. + */ + protected static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; + + /** + * Constructor specifying a method name and an array of parameter types. + */ + public NSSelector (String aMethodName, Class[] aParameterTypeArray) + { + methodName = aMethodName; + parameterTypes = aParameterTypeArray; + methodMap = new NSMutableDictionary(); + } + + /** + * Constructor specifying a method name with no parameters. + */ + public NSSelector (String aMethodName) + { + this( aMethodName, EMPTY_CLASS_ARRAY ); + } + + /** + * Constructor for custom subclasses that implement specific operators + * and that do not use dynamic method invocation. + */ + protected NSSelector() + { + } + + /** + * Returns the name of the method. + */ + public String name () + { + return methodName; + } + + /** + * Returns the array of parameter types. + */ + public Class[] parameterTypes () + { + return parameterTypes; + } + + /** + * A String description of this selector. + */ + public String toString () + { + StringBuffer result = new StringBuffer(); + result.append( "[" + getClass().getName() + ": name = " + name() + ", parameter types = [" ); + if ( parameterTypes != null ) + { + if ( parameterTypes.length > 0 ) + { + result.append( parameterTypes[0].toString() ); + } + for ( int i = 1; i < parameterTypes.length; i++ ) + { + result.append( ", " ); + result.append( parameterTypes[i].toString() ); + } + } + result.append( "] ]" ); + return result.toString(); + } + + /** + * Returns the appropriate method for the specified class. + */ + public Method methodOnClass (Class aClass) + throws NoSuchMethodException + { + Object result = methodMap.objectForKey( aClass ); + + if ( result == null ) + { + result = getMethodForClass( aClass ); + if ( result == null ) + { + result = NOT_FOUND; + } + methodMap.setObjectForKey( result, aClass ); + } + + if ( result == NOT_FOUND ) + { + throw new NoSuchMethodException(); + } + return (Method) result; + } + + /** + * Returns the appropriate method, or null if not found. + */ + private Method getMethodForClass( Class aClass ) + { + Method[] methods = aClass.getMethods(); + for ( int i = 0; i < methods.length; i++ ) + { + if ( methods[i].getName().equals( name() ) ) + { + Class[] params = methods[i].getParameterTypes(); + if ( params.length == parameterTypes.length ) + { + boolean pass = true; + for ( int j = 0; j < params.length; j++ ) + { + if ( ! params[j].isAssignableFrom( parameterTypes[j] ) ) + { + pass = false; + } + } + if ( pass ) return methods[i]; + } + } + } + return null; + } + + /** + * Convenience to get a method for an object. + */ + public Method methodOnObject (Object anObject) + throws NoSuchMethodException + { + Method m = methodOnClass( anObject.getClass() ); + if ( m == null ) throw new NoSuchMethodException( name() ); + return m; + } + + /** + * Returns whether the class implements the method for this selector. + */ + public boolean implementedByClass (Class aClass) + { + try + { + methodOnClass( aClass ); + return true; + } + catch ( NoSuchMethodException exc ) + { + } + return false; + } + + /** + * Returns whether the object's class implements the method + * for this selector. + */ + public boolean implementedByObject (Object anObject) + { + try + { + methodOnObject( anObject ); + return true; + } + catch ( NoSuchMethodException exc ) + { + } + return false; + } + + /** + * Invokes this selector's method on the specified object + * using the specified parameters. + */ + public Object invoke (Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return methodOnObject( anObject ).invoke( anObject, parameters ); + } + + /** + * Invokes this selector's method on the specified object + * with no parameters. + */ + public Object invoke (Object anObject) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return invoke( anObject, EMPTY_OBJECT_ARRAY ); + } + + /** + * Invokes this selector's method on the specified object + * with the specified parameter. + */ + public Object invoke (Object anObject, Object aParameter) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return invoke( anObject, new Object[] { aParameter } ); + } + + /** + * Invokes this selector's method on the specified object + * using the specified two parameters. + */ + public Object invoke (Object anObject, Object p1, Object p2) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return invoke( anObject, new Object[] { p1, p2 } ); + } + + /** + * Invokes the method with the specified signature on the specified + * object using the specified parameters. + */ + public static Object invoke + (String methodName, Class[] parameterTypes, Object anObject, Object[] parameters) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return new NSSelector( methodName, parameterTypes ).invoke( anObject, parameters ); + } + + /** + * Invokes the method with the specified signature on the specified object + * with no parameters. + */ + public static Object invoke + (String methodName, Object anObject) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return NSSelector.invoke( + methodName, EMPTY_CLASS_ARRAY, anObject, EMPTY_OBJECT_ARRAY ); + } + + /** + * Invokes the method with the specified signature on the specified + * object using the specified parameter. + */ + public static Object invoke + (String methodName, Class[] parameterTypes, + Object anObject, Object aParameter) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return NSSelector.invoke( + methodName, parameterTypes, anObject, new Object[] { aParameter } ); + } + + /** + * Invokes the method with the specified signature on the specified + * object using the specified two parameters. + */ + public static Object invoke + (String methodName, Class[] parameterTypes, + Object anObject, Object p1, Object p2) + throws IllegalAccessException, IllegalArgumentException, + InvocationTargetException, NoSuchMethodException + { + return NSSelector.invoke( + methodName, parameterTypes, anObject, new Object[] { p1, p2 } ); + } + + // interface Comparator + + private Comparator comparator; + + /** + * Constructor specifying a method name and a comparator. + * This is not in the spec. + */ + public NSSelector (String aMethodName, Comparator aComparator) + { + this( aMethodName, EMPTY_CLASS_ARRAY ); + comparator = aComparator; + } + + /** + * Returns the Comparator used for this selector. + * This is not in the spec. + */ + public Comparator comparator() + { + if ( comparator == null ) + { + comparator = new PropertyComparator( methodName ); + } + return comparator; + } + + public int compare(Object o1, Object o2) + { + if ( comparator == null ) + { + comparator = new PropertyComparator( methodName ); + } + return comparator.compare( o1, o2 ); + } + + public boolean equals(Object obj) + { + return ( obj == this ); + } +} + +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.9 2003/02/12 19:34:35 mpowers + * Added accessor for comparator. + * + * Revision 1.8 2003/02/07 20:23:41 mpowers + * Provided backwards compatibility for comparators. + * + * Revision 1.7 2003/01/22 23:02:25 mpowers + * Fixed a null pointer error in NSSelector.toString. + * + * Revision 1.6 2003/01/18 23:46:58 mpowers + * EOSortOrdering is now correctly using NSSelectors. + * + * Revision 1.4 2001/10/31 15:24:45 mpowers + * Implicit constructor is now protected. + * + * Revision 1.3 2001/02/07 19:25:51 mpowers + * Fixed: method matching uses isAssignableFrom rather than ==. + * + * Revision 1.2 2001/01/08 23:30:16 mpowers + * Fixed major bug - selectors were not supposed to share a method map. + * + * Revision 1.1.1.1 2000/12/21 15:47:45 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:39 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java new file mode 100644 index 0000000..5318fe8 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSSet.java @@ -0,0 +1,212 @@ +/* +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; + +import java.util.Collection; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.Vector; + +/** +* A pure java implementation of NSSet that +* implements Set for greater java interoperability. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ +public class NSSet extends HashSet +{ + /** + * Default constructor. + */ + public NSSet () + { + super(); + } + + /** + * Constructs a NSSet containing the objects + * in the specified collection. + */ + public NSSet ( Collection aCollection ) + { + super( aCollection ); + } + + /** + * Constructs a NSSet containing only + * the specified object. + */ + public NSSet ( Object anObject ) + { + super(); + add( anObject ); + } + + /** + * Constructs a NSSet containing the objects + * in the specified array. + */ + public NSSet ( Object[] anObjectArray ) + { + super(); + for ( int i = 0; i < anObjectArray.length; i++ ) + { + add( anObjectArray[i] ); + } + } + + /** + * Returns an NSArray containing all objects in the set. + */ + public NSArray allObjects () + { + return new NSArray( this ); + } + + /** + * + */ + public Object anyObject () + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns whether this set contains the + * specified object. + */ + public boolean containsObject ( Object anObject ) + { + return contains( anObject ); + } + + /** + * Returns the number of elements in this set. + */ + public int count () + { + return size(); + } + + /** + * Returns whether this set has one or more + * elements in common with the specified set. + */ + public boolean intersectsSet ( Set aSet ) + { + Iterator it = aSet.iterator(); + while ( it.hasNext() ) + { + if ( this.containsObject( it.next() ) ) + { + return true; + } + } + return false; + + } + + /** + * Returns whether this set contains the + * same object as the specified set. + */ + public boolean isEqualToSet ( Set aSet ) + { + return equals( aSet ); + } + + /** + * Returns whether this set is a subset + * of the specified set. + */ + public boolean isSubsetOfSet ( Set aSet ) + { + return aSet.containsAll( this ); + } + + /** + * + */ + public Object member ( Object anObject ) + { + throw new RuntimeException( "Not implemented yet." ); + } + + /** + * Returns an enumerator over the objects + * in this set. + */ + public Enumeration objectEnumerator () + { + return new Vector( this ).elements(); + } + + /** + * Returns a set that is the intersection + * of this set and the specified set. + */ + public NSSet setByIntersectingSet ( Set aSet ) + { + NSSet result = new NSSet( this ); + result.retainAll( aSet ); + return result; + } + + /** + * Returns a set that contains all elements + * in this set that are not in the specified set. + */ + public NSSet setBySubtractingSet ( Set aSet ) + { + NSSet result = new NSSet( this ); + result.removeAll( aSet ); + return result; + } + + /** + * Returns a set that is the union + * of this set and the specified set. + */ + public NSSet setByUnioningSet ( Set aSet ) + { + NSSet result = new NSSet( this ); + result.addAll( aSet ); + return result; + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1.1.1 2000/12/21 15:47:45 mpowers + * Contributing wotonomy. + * + * Revision 1.3 2000/12/20 16:25:39 michael + * Added log to all files. + * + * + */ + diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java new file mode 100644 index 0000000..171e756 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimeZone.java @@ -0,0 +1,272 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSTimeZone.java 892 2006-02-16 12:47:16Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +/** +* A channel to the database, representing a communication +* stream within a context of an adaptor. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ + +public class NSTimeZone extends TimeZone + implements Cloneable, Serializable, NSCoding { + protected static class __NSTZPeriodComparator extends NSComparator { + + protected boolean _ascending = false; + + public int compare(Object obj, Object obj1) throws NSComparator.ComparisonException { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public __NSTZPeriodComparator() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public __NSTZPeriodComparator(boolean flag) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + protected static class __NSTZPeriod { + + protected String _abbreviation = null; + protected int _isdst = 0; + protected int _offset = 0; + protected double _startTime = 0; + + protected boolean before(__NSTZPeriod _p_nstzperiod) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected boolean equals(__NSTZPeriod _p_nstzperiod) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected __NSTZPeriod() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + } + + + public static final String SystemTimeZoneDidChangeNotification = "NSSystemTimeZoneDidChangeNotification"; + protected NSData _data = null; + protected transient int _hashCode = 0; + protected transient boolean _initialized = false; + protected transient TimeZone _jdkTimeZone = null; + protected String _name = null; + protected transient int _rawOffset = 0; + protected transient NSMutableArray _timeZonePeriods = null; + protected transient int _timeZonePeriodsCount = 0; + protected transient boolean _useDaylightTime = false; + + public NSTimeZone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected NSTimeZone(String s, NSData nsdata) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static NSDictionary abbreviationDictionary() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public Class classForCoder() { + return getClass(); + } + + public Object clone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void encodeWithCoder(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized NSTimeZone defaultTimeZone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static String[] getAvailableIDs() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static TimeZone getDefault() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static NSArray knownTimeZoneNames() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static NSTimeZone localTimeZone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized void resetSystemTimeZone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized void setDefault(TimeZone timezone) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized void setDefaultTimeZone(NSTimeZone nstimezone) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void setID(String s) { + throw new IllegalStateException(getClass().getName() + " is immutable."); + } + + public void setRawOffset(int i) { + throw new IllegalStateException(getClass().getName() + " is immutable."); + } + + public static synchronized NSTimeZone systemTimeZone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized NSTimeZone timeZoneForSecondsFromGMT(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized NSTimeZone timeZoneWithName(String s, boolean flag) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static synchronized NSTimeZone timeZoneWithNameAndData(String s, NSData nsdata) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public static NSTimeZone _nstimeZoneWithTimeZone(TimeZone timezone) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String abbreviation() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String abbreviationForTimestamp(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSData data() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean equals(Object obj) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String getDisplayName(boolean flag, int i, Locale locale) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String getID() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int getOffset(int i, int j, int k, int l, int i1, int j1) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int getRawOffset() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public synchronized int hashCode() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean hasSameRules(TimeZone timezone) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean inDaylightTime(Date date) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean isDaylightSavingTime() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean isDaylightSavingTimeForTimestamp(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean isEqualToTimeZone(NSTimeZone nstimezone) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String name() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int secondsFromGMT() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int secondsFromGMTForTimestamp(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String toString() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public boolean useDaylightTime() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + protected Object readResolve() throws ObjectStreamException { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + +} +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.1 2002/06/25 07:52:57 cgruber + * Add quite a few abstract classes, interfaces, and classes. All API consistent with WebObjects, but with no implementation, nor any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java new file mode 100644 index 0000000..bd246e0 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestamp.java @@ -0,0 +1,285 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2002 Israfil consulting Services 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 + +$Id: NSTimestamp.java 892 2006-02-16 12:47:16Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.sql.Timestamp; +import java.util.Date; +import java.util.TimeZone; + +/** +* A channel to the database, representing a communication +* stream within a context of an adaptor. +* +* @author cgruber@israfil.net +* @author $Author: cgruber $ +* @version $Revision: 892 $ +*/ + +public class NSTimestamp extends Timestamp + implements NSCoding { + + public static class IntRef { + + public int value = 0; + + public String toString() { + return getClass().getName() + " < value = " + value + " >"; + } + + public IntRef() { + } + } + + + public static final NSTimestamp DistantPast = new NSTimestamp(0xffffc77f2e9b6800L); + public static final NSTimestamp DistantFuture = new NSTimestamp(0x7fffffffffffffffL); + public static long currentTimeIntervalSinceReferenceDate() { + return System.currentTimeMillis() / 1000L; + } + + public static NSTimestamp distantFuture() { + return DistantFuture; + } + + public static NSTimestamp distantPast() { + return DistantPast; + } + + public static long millisecondsToTimeInterval(long l) { + return l / 1000L; + } + + public static long timeIntervalToMilliseconds(long l) { + return l * 1000L; + } + + public Class classForCoder() { + return getClass(); + } + + public static Object decodeObject(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void encodeWithCoder(NSCoder nscoder) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp() { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(long l) { + super(l); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(long l, int i) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(long l, NSTimestamp nstimestamp) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(long l, TimeZone timezone) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(long l, int i, TimeZone timezone) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(int i, int j, int k, int l, int i1, int j1, TimeZone timezone) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(Date date) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp(Timestamp timestamp) { + super(0); + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp timestampByAddingGregorianUnits(int i, int j, int k, int l, int i1, int j1) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp timestampByAddingTimeInterval(long l) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public long dayOfCommonEra() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int dayOfMonth() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int dayOfWeek() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int dayOfYear() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int hourOfDay() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int microsecondOfSecond() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int minuteOfHour() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int monthOfYear() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int secondOfMinute() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int yearOfCommonEra() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void gregorianUnitsSinceTimestamp(IntRef intref, IntRef intref1, IntRef intref2, IntRef intref3, IntRef intref4, IntRef intref5, NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public long timeIntervalSinceTimestamp(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public long timeIntervalSinceNow() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public long timeIntervalSinceReferenceDate() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public int compare(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp earlierTimestamp(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimestamp laterTimestamp(NSTimestamp nstimestamp) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public String toString() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public NSTimeZone timeZone() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public long _getTimeInMillis() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void setNanos(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @deprecated This method deprecated in parent java.util.Date */ + public void setDate(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @deprecated This method deprecated in parent java.util.Date */ + public void setHours(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @deprecated This method deprecated in parent java.util.Date */ + public void setMinutes(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @deprecated This method deprecated in parent java.util.Date */ + public void setMonth(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @deprecated This method deprecated in parent java.util.Date */ + public void setSeconds(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public void setTime(long l) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + public long getTime() { + throw new UnsupportedOperationException("Not Yet Implemented"); + } + + /** @deprecated This method deprecated in parent java.util.Date */ + public void setYear(int i) { + throw new UnsupportedOperationException("Not Yet Implemented"); + } +} +/* + * $Log$ + * Revision 1.1 2006/02/16 12:47:16 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2003/08/06 23:07:52 chochos + * general code cleanup (mostly, removing unused imports) + * + * Revision 1.1 2002/07/14 21:56:16 mpowers + * Contributions from cgruber. + * + * Revision 1.3 2002/06/25 19:06:13 cgruber + * Comment fix. + * + * Revision 1.2 2002/06/25 19:05:27 cgruber + * Add deprecation statements to remove warnings + * about java.util.Date's deprecated APIs. + * + * Revision 1.1 2002/06/25 07:52:56 cgruber + * Add quite a few abstract classes, interfaces, and classes. All + * API consistent with WebObjects, but with no implementation, nor + * any private or package access members from the original. + * + */ diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java new file mode 100644 index 0000000..ecc67ca --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/NSTimestampFormatter.java @@ -0,0 +1,65 @@ +/* +Wotonomy: OpenStep design patterns for pure Java applications. +Copyright (C) 2003 Intersect Software Corporation + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see http://www.gnu.org + +$Id: NSTimestampFormatter.java 893 2006-02-16 13:22:23Z cgruber $ + +*/ + +package net.wotonomy.foundation; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; + +/** +* A Format that accepts C-style date formatting syntax. +* Not currently implemented, included for compile compatibility. +* +* @author michael@mpowers.net +* @author $Author: cgruber $ +* @version $Revision: 893 $ +*/ + +public class NSTimestampFormatter extends SimpleDateFormat +{ + public NSTimestampFormatter() + { + super(); + } + + public NSTimestampFormatter(String aPattern) + { + super( aPattern ); + } + + public NSTimestampFormatter(String aPattern, + DateFormatSymbols symbols) + { + super( aPattern, symbols ); + } + +} + +/* + * $Log$ + * Revision 1.2 2006/02/16 13:15:00 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.1 2003/01/17 14:40:51 mpowers + * Adding files to fix build. + * + * + */ 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.<br><br> +* +* 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.<br><br> +* +* 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. <br><br> +* +* 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.<br><br> + * + * 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. <p> + * + * 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. <p> + * + * 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. <p> + * + * 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. + * + * <pre> + * { + * key1 = value1; + * key2 = value2; + * etc... + * } + * </pre> + * + * A List is specified by a comma-separated list of values surrounded by parentheses, like: + * <pre> + * ( value1, value2, value3, etc... ) + * </pre> + * + * 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. <p> + * + * All whitespace between elements is ignored, and both //-style and + * /*-style comments are allowed to appear anywhere between elements. <p> + * + * If there are any syntax errors encountered while parsing, + * RuntimeExceptions are thrown with the line number and column of the + * problem. <p> + * + * Currenty, HashMaps and ArrayLists are the actual Map and List classes + * used when creating the property list. <p> + * + * Examples: <p><blockquote> + <pre> + // 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"; + } + ); + } + </pre> + </blockquote> + * 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). <BR><BR> +* +* 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. <BR><BR> +* +* 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. <BR><BR> +* +* @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 <tt>Map.Entry</tt>. 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 +* <tt>true</tt> if the given object is also a map and the two Maps +* represent the same mappings. More formally, two maps <tt>t1</tt> and +* <tt>t2</tt> represent the same mappings if +* <tt>t1.entrySet().equals(t2.entrySet())</tt>. This ensures that the +* <tt>equals</tt> method works properly across different implementations +* of the <tt>Map</tt> interface. +* +* @param o object to be compared for equality with this map. +* @return <tt>true</tt> 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 <tt>t1.equals(t2)</tt> implies +* that <tt>t1.hashCode()==t2.hashCode()</tt> for any two maps +* <tt>t1</tt> and <tt>t2</tt>, 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.<br> +* 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(); + } + +} diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/package.html b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/package.html new file mode 100644 index 0000000..c52ca95 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/package.html @@ -0,0 +1,18 @@ +<body> +<p> +A set of collections classes that are needed +by the ui and web packages. These correspond +to the foundation classes in the OpenStep +specification. +</p> +<p> +These classes for the most part extend their +java counterparts, adding methods to be API +compatible with the OpenStep specification. +In this way, they retain compatibility with +the Java Collections APIs. +</p> +<p> +This package has no external dependencies. +</p> +</body> diff --git a/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java new file mode 100644 index 0000000..64ac4b2 --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLDecoder.java @@ -0,0 +1,68 @@ +/* +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.xml; + +import java.io.InputStream; +import java.net.URL; + +/** +* Defines an interface for classes that can de-serialize +* java objects from an XML representation. +*/ +public interface XMLDecoder +{ + /** + * Decodes an object from the specified input stream. + * @param anInputStream The input stream from which to read. + * The stream will be read fully. + * @param aDescription A description to accompany error messages + * for the stream, typically a file name. + * @param aURL A URL against which relative references within the + * XML will be resolved. + * @return The object that was constructed from the XML content, + * or null if no object could be constructed. + */ + public Object decode( + InputStream anInputStream, String aDescription, URL aURL ); +} + +/* + * $Log$ + * Revision 1.1 2006/02/18 22:21:10 cgruber + * Add in simple xml interfaces from net.wotonomy.xml project. + * Add cobertura.ser to .cvsignore. God I wish sourceforge would move on this subversion thing... + * + * Revision 1.1 2006/02/16 13:22:22 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2001/02/06 14:34:23 mpowers + * Forgot to rename the package declarations. + * + * Revision 1.1 2001/02/06 14:31:19 mpowers + * Moving XML utilities from util to xml package. + * + * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers + * Contributing wotonomy. + * + * Revision 1.2 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/xml/XMLEncoder.java b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLEncoder.java new file mode 100644 index 0000000..e543bca --- /dev/null +++ b/projects/net.wotonomy.foundation/src/main/java/net/wotonomy/foundation/xml/XMLEncoder.java @@ -0,0 +1,61 @@ +/* +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.xml; + +import java.io.OutputStream; + +/** +* Defines an interface for classes that can serialize +* an arbitrary java object into XML format. +*/ +public interface XMLEncoder +{ + /** + * Encodes an object to the specified output stream as XML. + * @param anObject The object to be serialized to XML format. + * @param anOutputStream The output stream to which the object + * will be written. The stream will not be flushed nor closed. + */ + public void encode( Object anObject, OutputStream anOutputStream ); +} + +/* + * $Log$ + * Revision 1.1 2006/02/18 22:21:10 cgruber + * Add in simple xml interfaces from net.wotonomy.xml project. + * Add cobertura.ser to .cvsignore. God I wish sourceforge would move on this subversion thing... + * + * Revision 1.1 2006/02/16 13:22:22 cgruber + * Check in all sources in eclipse-friendly maven-enabled packages. + * + * Revision 1.2 2001/02/06 14:34:23 mpowers + * Forgot to rename the package declarations. + * + * Revision 1.1 2001/02/06 14:31:19 mpowers + * Moving XML utilities from util to xml package. + * + * Revision 1.1.1.1 2000/12/21 15:52:33 mpowers + * Contributing wotonomy. + * + * Revision 1.2 2000/12/20 16:25:48 michael + * Added log to all files. + * + * + */ + |
