Commits

Jens Alfke committed d84d25d

Initial checkin.

Comments (0)

Files changed (38)

+syntax: glob
+.DS_Store
+build
+.svn
+*.pbxuser
+*.perspectivev3
+*.mpkg
+*.framework
+//
+//  Base64.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/27/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface NSData (Base64)
+
+- (NSString *)base64String;
+- (NSString *)base64StringWithNewlines:(BOOL)encodeWithNewlines;
+
+- (NSData *)decodeBase64;
+- (NSData *)decodeBase64WithNewLines:(BOOL)encodedWithNewlines;
+
+- (NSString *)hexString;
+- (NSString *)hexDump;
+
+@end
+//
+//  Base64.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/27/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+//  Adapted from SSCrypto.m by Ed Silva;
+//  Copyright (c) 2003-2006 Septicus Software. All rights reserved.
+//
+
+#import "Base64.h"
+#import <openssl/bio.h>
+#import <openssl/evp.h>
+
+
+@implementation NSData (Base64)
+
+
+/**
+ * Encodes the current data in base64, and creates and returns an NSString from the result.
+ * This is the same as piping data through "... | openssl enc -base64" on the command line.
+ *
+ * Code courtesy of DaveDribin (http://www.dribin.org/dave/)
+ * Taken from http://www.cocoadev.com/index.pl?BaseSixtyFour
+ **/
+- (NSString *)base64String
+{
+    return [self base64StringWithNewlines: YES];
+}
+
+/**
+ * Encodes the current data in base64, and creates and returns an NSString from the result.
+ * This is the same as piping data through "... | openssl enc -base64" on the command line.
+ *
+ * Code courtesy of DaveDribin (http://www.dribin.org/dave/)
+ * Taken from http://www.cocoadev.com/index.pl?BaseSixtyFour
+ **/
+- (NSString *)base64StringWithNewlines:(BOOL)encodeWithNewlines
+{
+    // Create a memory buffer which will contain the Base64 encoded string
+    BIO * mem = BIO_new(BIO_s_mem());
+    
+    // Push on a Base64 filter so that writing to the buffer encodes the data
+    BIO * b64 = BIO_new(BIO_f_base64());
+    if (!encodeWithNewlines)
+        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+    mem = BIO_push(b64, mem);
+    
+    // Encode all the data
+    BIO_write(mem, [self bytes], [self length]);
+    BIO_flush(mem);
+    
+    // Create a new string from the data in the memory buffer
+    char * base64Pointer;
+    long base64Length = BIO_get_mem_data(mem, &base64Pointer);
+    NSString * base64String = [NSString stringWithCString:base64Pointer length:base64Length];
+    
+    // Clean up and go home
+    BIO_free_all(mem);
+    return base64String;
+}
+
+- (NSData *)decodeBase64
+{
+    return [self decodeBase64WithNewLines:YES];
+}
+
+- (NSData *)decodeBase64WithNewLines:(BOOL)encodedWithNewlines
+{
+    // Create a memory buffer containing Base64 encoded string data
+    BIO * mem = BIO_new_mem_buf((void *) [self bytes], [self length]);
+    
+    // Push a Base64 filter so that reading from the buffer decodes it
+    BIO * b64 = BIO_new(BIO_f_base64());
+    if (!encodedWithNewlines)
+        BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+    mem = BIO_push(b64, mem);
+    
+    // Decode into an NSMutableData
+    NSMutableData * data = [NSMutableData data];
+    char inbuf[512];
+    int inlen;
+    while ((inlen = BIO_read(mem, inbuf, sizeof(inbuf))) > 0)
+        [data appendBytes: inbuf length: inlen];
+    
+    // Clean up and go home
+    BIO_free_all(mem);
+    return data;
+}
+
+
+- (NSString *)hexString
+{
+    const UInt8 *bytes = self.bytes;
+    NSUInteger length = self.length;
+    char out[2*length+1];
+    char *dst = &out[0];
+    for( int i=0; i<length; i+=1 )
+        dst += sprintf(dst,"%02X",*(bytes++));
+    return [[[NSString alloc] initWithBytes: out length: 2*length encoding: NSASCIIStringEncoding]
+            autorelease];
+}
+
+- (NSString *)hexDump
+{
+    NSMutableString *ret=[NSMutableString stringWithCapacity:[self length]*2];
+    /* dumps size bytes of *data to string. Looks like:
+     * [0000] 75 6E 6B 6E 6F 77 6E 20
+     *                  30 FF 00 00 00 00 39 00 unknown 0.....9.
+     * (in a single line of course)
+     */
+    unsigned int size= [self length];
+    const unsigned char *p = [self bytes];
+    unsigned char c;
+    int n;
+    char bytestr[4] = {0};
+    char addrstr[10] = {0};
+    char hexstr[ 16*3 + 5] = {0};
+    char charstr[16*1 + 5] = {0};
+    for(n=1;n<=size;n++) {
+        if (n%16 == 1) {
+            /* store address for this line */
+            snprintf(addrstr, sizeof(addrstr), "%.4x",
+                     ((unsigned int)p-(unsigned int)self) );
+        }
+        
+        c = *p;
+        if (isalnum(c) == 0) {
+            c = '.';
+        }
+        
+        /* store hex str (for left side) */
+        snprintf(bytestr, sizeof(bytestr), "%02X ", *p);
+        strncat(hexstr, bytestr, sizeof(hexstr)-strlen(hexstr)-1);
+        
+        /* store char str (for right side) */
+        snprintf(bytestr, sizeof(bytestr), "%c", c);
+        strncat(charstr, bytestr, sizeof(charstr)-strlen(charstr)-1);
+        
+        if(n%16 == 0) {
+            /* line completed */
+            //printf("[%4.4s]   %-50.50s  %s\n", addrstr, hexstr, charstr);
+            [ret appendString:[NSString stringWithFormat:@"[%4.4s]   %-50.50s  %s\n",
+                               addrstr, hexstr, charstr]];
+            hexstr[0] = 0;
+            charstr[0] = 0;
+        } else if(n%8 == 0) {
+            /* half line: add whitespaces */
+            strncat(hexstr, "  ", sizeof(hexstr)-strlen(hexstr)-1);
+            strncat(charstr, " ", sizeof(charstr)-strlen(charstr)-1);
+        }
+        p++; /* next byte */
+    }
+    
+    if (strlen(hexstr) > 0) {
+        /* print rest of buffer if not empty */
+        //printf("[%4.4s]   %-50.50s  %s\n", addrstr, hexstr, charstr);
+        [ret appendString:[NSString stringWithFormat:@"[%4.4s]   %-50.50s  %s\n",
+                           addrstr, hexstr, charstr]];
+    }
+    return ret;
+}
+
+@end
+//
+//  CollectionUtils.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+// Collection creation conveniences:
+
+#define $array(OBJS...)     ({id objs[]={OBJS}; \
+                              [NSArray arrayWithObjects: objs count: sizeof(objs)/sizeof(id)];})
+#define $marray(OBJS...)    ({id objs[]={OBJS}; \
+                              [NSMutableArray arrayWithObjects: objs count: sizeof(objs)/sizeof(id)];})
+
+#define $dict(PAIRS...)     ({struct _dictpair pairs[]={PAIRS}; \
+                              _dictof(pairs,sizeof(pairs)/sizeof(struct _dictpair));})
+#define $mdict(PAIRS...)    ({struct _dictpair pairs[]={PAIRS}; \
+                              _mdictof(pairs,sizeof(pairs)/sizeof(struct _dictpair));})
+
+#define $object(VAL)        ({__typeof(VAL) v=(VAL); _box(&v,@encode(__typeof(v)));})
+
+
+// Object conveniences:
+
+BOOL $equal(id obj1, id obj2);      // Like -isEqual: but works even if either/both are nil
+
+#define $sprintf(FORMAT, ARGS... )  [NSString stringWithFormat: (FORMAT), ARGS]
+
+#define $cast(CLASSNAME,OBJ)        (CLASSNAME*)(_cast([CLASSNAME class],(OBJ)))
+#define $castNotNil(CLASSNAME,OBJ)  (CLASSNAME*)(_castNotNil([CLASSNAME class],(OBJ)))
+#define $castIf(CLASSNAME,OBJ)      (CLASSNAME*)(_castIf([CLASSNAME class],(OBJ)))
+#define $castArrayOf(ITEMCLASSNAME,OBJ) _castArrayOf([ITEMCLASSNAME class],(OBJ)))
+
+void setObj( id *var, id value );
+BOOL ifSetObj( id *var, id value );
+void setString( NSString **var, NSString *value );
+BOOL ifSetString( NSString **var, NSString *value );
+
+
+@interface NSArray (MYUtils)
+- (BOOL) my_containsObjectIdenticalTo: (id)object;
+@end
+
+
+#pragma mark -
+#pragma mark FOREACH:
+    
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+#define foreach(VAR,ARR) for(VAR in ARR)
+
+#else
+struct foreachstate {NSArray *array; unsigned n, i;};
+static inline struct foreachstate _initforeach( NSArray *arr ) {
+    struct foreachstate s;
+    s.array = arr;
+    s.n = [arr count];
+    s.i = 0;
+    return s;
+}
+#define foreach(VAR,ARR) for( struct foreachstate _s = _initforeach((ARR)); \
+                                   _s.i<_s.n && ((VAR)=[_s.array objectAtIndex: _s.i], YES); \
+                                   _s.i++ )
+#endif
+
+
+// Internals (don't use directly)
+struct _dictpair { id key; id value; };
+NSDictionary* _dictof(const struct _dictpair*, size_t count);
+NSMutableDictionary* _mdictof(const struct _dictpair*, size_t count);
+NSValue* _box(const void *value, const char *encoding);
+id _cast(Class,id);
+id _castNotNil(Class,id);
+id _castIf(Class,id);
+NSArray* _castArrayOf(Class,NSArray*);
+//
+//  CollectionUtils.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "CollectionUtils.h"
+#import "Test.h"
+
+
+NSDictionary* _dictof(const struct _dictpair* pairs, size_t count)
+{
+    CAssert(count<10000);
+    id objects[count], keys[count];
+    size_t n = 0;
+    for( size_t i=0; i<count; i++,pairs++ ) {
+        if( pairs->value ) {
+            objects[n] = pairs->value;
+            keys[n] = pairs->key;
+            n++;
+        }
+    }
+    return [NSDictionary dictionaryWithObjects: objects forKeys: keys count: n];
+}
+
+
+NSMutableDictionary* _mdictof(const struct _dictpair* pairs, size_t count)
+{
+    CAssert(count<10000);
+    id objects[count], keys[count];
+    size_t n = 0;
+    for( size_t i=0; i<count; i++,pairs++ ) {
+        if( pairs->value ) {
+            objects[n] = pairs->value;
+            keys[n] = pairs->key;
+            n++;
+        }
+    }
+    return [NSMutableDictionary dictionaryWithObjects: objects forKeys: keys count: n];
+}
+
+
+BOOL $equal(id obj1, id obj2)      // Like -isEqual: but works even if either/both are nil
+{
+    if( obj1 )
+        return obj2 && [obj1 isEqual: obj2];
+    else
+        return obj2==nil;
+}
+
+
+NSValue* _box(const void *value, const char *encoding)
+{
+    // file:///Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.DeveloperTools.docset/Contents/Resources/Documents/documentation/DeveloperTools/gcc-4.0.1/gcc/Type-encoding.html
+    char e = encoding[0];
+    if( e=='r' )                // ignore 'const' modifier
+        e = encoding[1];
+    switch( e ) {
+        case 'c':   return [NSNumber numberWithChar: *(char*)value];
+        case 'C':   return [NSNumber numberWithUnsignedChar: *(char*)value];
+        case 's':   return [NSNumber numberWithShort: *(short*)value];
+        case 'S':   return [NSNumber numberWithUnsignedShort: *(unsigned short*)value];
+        case 'i':   return [NSNumber numberWithInt: *(int*)value];
+        case 'I':   return [NSNumber numberWithUnsignedInt: *(unsigned int*)value];
+        case 'l':   return [NSNumber numberWithLong: *(long*)value];
+        case 'L':   return [NSNumber numberWithUnsignedLong: *(unsigned long*)value];
+        case 'q':   return [NSNumber numberWithLongLong: *(long long*)value];
+        case 'Q':   return [NSNumber numberWithUnsignedLongLong: *(unsigned long long*)value];
+        case 'f':   return [NSNumber numberWithFloat: *(float*)value];
+        case 'd':   return [NSNumber numberWithDouble: *(double*)value];
+        case '*':   return [NSString stringWithUTF8String: *(char**)value];
+        case '@':   return *(id*)value;
+        default:    return [NSValue value: value withObjCType: encoding];
+    }
+}
+
+
+id _cast( Class requiredClass, id object )
+{
+    if( object && ! [object isKindOfClass: requiredClass] )
+        [NSException raise: NSInvalidArgumentException format: @"%@ required, but got %@ %p",
+         requiredClass,[object class],object];
+    return object;
+}
+
+id _castNotNil( Class requiredClass, id object )
+{
+    if( ! [object isKindOfClass: requiredClass] )
+        [NSException raise: NSInvalidArgumentException format: @"%@ required, but got %@ %p",
+         requiredClass,[object class],object];
+    return object;
+}
+
+id _castIf( Class requiredClass, id object )
+{
+    if( object && ! [object isKindOfClass: requiredClass] )
+        object = nil;
+    return object;
+}
+
+NSArray* _castArrayOf(Class itemClass, NSArray *a)
+{
+    id item;
+    foreach( item, $cast(NSArray,a) )
+        _cast(itemClass,item);
+    return a;
+}
+
+
+void setObj( id *var, id value )
+{
+    if( value != *var ) {
+        [*var release];
+        *var = [value retain];
+    }
+}
+
+BOOL ifSetObj( id *var, id value )
+{
+    if( value != *var && ![value isEqual: *var] ) {
+        [*var release];
+        *var = [value retain];
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
+
+void setString( NSString **var, NSString *value )
+{
+    if( value != *var ) {
+        [*var release];
+        *var = [value copy];
+    }
+}
+
+
+BOOL ifSetString( NSString **var, NSString *value )
+{
+    if( value != *var && ![value isEqualToString: *var] ) {
+        [*var release];
+        *var = [value copy];
+        return YES;
+    } else {
+        return NO;
+    }
+}
+
+
+@implementation NSArray (MYUtils)
+
+- (BOOL) my_containsObjectIdenticalTo: (id)object
+{
+    return [self indexOfObjectIdenticalTo: object] != NSNotFound;
+}
+
+@end
+
+
+#import "Test.h"
+
+TestCase(CollectionUtils) {
+    NSArray *a = $array(@"foo",@"bar",@"baz");
+    //Log(@"a = %@",a);
+    NSArray *aa = [NSArray arrayWithObjects: @"foo",@"bar",@"baz",nil];
+    CAssertEqual(a,aa);
+    
+    const char *cstr = "a C string";
+    id o = $object(cstr);
+    //Log(@"o = %@",o);
+    CAssertEqual(o,@"a C string");
+    
+    NSDictionary *d = $dict({@"int",    $object(1)},
+                            {@"double", $object(-1.1)},
+                            {@"char",   $object('x')},
+                            {@"ulong",  $object(1234567UL)},
+                            {@"longlong",$object(987654321LL)},
+                            {@"cstr",   $object(cstr)});
+    //Log(@"d = %@",d);
+    NSDictionary *dd = [NSDictionary dictionaryWithObjectsAndKeys:
+                        [NSNumber numberWithInt: 1],                    @"int",
+                        [NSNumber numberWithDouble: -1.1],              @"double",
+                        [NSNumber numberWithChar: 'x'],                 @"char",
+                        [NSNumber numberWithUnsignedLong: 1234567UL],   @"ulong",
+                        [NSNumber numberWithDouble: 987654321LL],       @"longlong",
+                        @"a C string",                                  @"cstr",
+                        nil];
+    CAssertEqual(d,dd);
+}

ConcurrentOperation.h

+//
+//  ConcurrentOperation.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 2/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+@interface ConcurrentOperation : NSOperation 
+{
+    BOOL _isExecuting, _isFinished;
+}
+
+- (void) finish;
+
+@end

ConcurrentOperation.m

+//
+//  ConcurrentOperation.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 2/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "ConcurrentOperation.h"
+
+// See file:///Developer/Documentation/DocSets/com.apple.ADC_Reference_Library.CoreReference.docset/Contents/Resources/Documents/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html#//apple_ref/doc/uid/TP40004591-RH2-DontLinkElementID_4
+
+
+@implementation ConcurrentOperation
+
+
+- (BOOL) isConcurrent {return YES;}
+
+- (void) main
+{
+    Assert(NO,@"Shouldn't call -main method of ConcurrentOperation");
+}
+
+- (BOOL) isExecuting
+{
+    return _isExecuting;
+}
+
+- (BOOL) isFinished
+{
+    return _isFinished;
+}
+
+- (void) start
+{
+    // don't call super!
+    Log(@"Starting %@",self);
+    [self willChangeValueForKey: @"isExecuting"];
+    _isExecuting = YES;
+    [self didChangeValueForKey: @"isExecuting"];
+}
+
+- (void) finish
+{
+    Log(@"Finished %@",self);
+    [self willChangeValueForKey: @"isExecuting"];
+    [self willChangeValueForKey: @"isFinished"];
+    _isExecuting = NO;
+    _isFinished = YES;
+    [self didChangeValueForKey: @"isFinished"];
+    [self didChangeValueForKey: @"isExecuting"];
+}
+
+
+- (void) cancel
+{
+    Log(@"Canceling %@",self);
+    [super cancel];
+    if( _isExecuting ) {
+        [self willChangeValueForKey: @"isExecuting"];
+        _isExecuting = NO;
+        [self didChangeValueForKey: @"isExecuting"];
+    }
+}
+
+
+@end
+//
+//  FileAlias.h
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+/** A wrapper around an AliasHandle: a persistent reference to a file, which works
+    even if the file is moved or renamed, or its volume unmounted. */
+
+@interface FileAlias : NSObject <NSCoding>
+{
+    AliasHandle _alias;
+}
+
+- (id) initWithFilePath: (NSString*)path
+                  error: (NSError**)error;
+
+- (id) initWithFilePath: (NSString*)path 
+         relativeToPath: (NSString*)fromPath
+                  error: (NSError**)error;
+
+- (NSString*) filePath: (NSError**)error;
+- (NSString*) filePathRelativeToPath: (NSString*)fromPath error: (NSError**)error;
+
+- (NSArray*) findMatchesRelativeToPath: (NSString*)fromPath 
+                             withRules: (unsigned)rules      // rules = kARMSearch etc.
+                                 error: (NSError**)error;
+- (NSArray*) findMatches: (NSError**)error;
+
+- (NSString*) originalPath;
+- (NSString*) originalFilename;
+- (NSString*) originalVolumeName;
+- (void) dump;
+
+@end
+//
+//  FileAlias.m
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "FileAlias.h"
+#import "FileUtils.h"
+
+
+@implementation FileAlias
+
+
+- (id) initWithFilePath: (NSString*)targetPath 
+         relativeToPath: (NSString*)fromPath
+                  error: (NSError**)error
+{
+    NSParameterAssert(targetPath);
+    self = [super init];
+    if( self ) {
+        OSStatus err;
+        FSRef fromRef, targetRef;
+        err = PathToFSRef(targetPath, &targetRef);
+        if( ! err ) {
+            if( fromPath ) {
+                err = PathToFSRef(fromPath,&fromRef);
+                if( ! err )
+                    err = FSNewAlias(&fromRef,&targetRef,&_alias);
+            } else {
+                err = FSNewAlias(NULL,&targetRef,&_alias);
+            }
+        }
+        
+        if( ! CheckOSErr(err,error) ) {
+            Warn(@"FileAlias init failed with OSStatus %i",err);
+            [self release];
+            return nil;
+        }
+    }
+    return self;
+}
+
+- (id) initWithFilePath: (NSString*)path
+                  error: (NSError**)error
+{
+    return [self initWithFilePath: path relativeToPath: nil error: error];
+}
+
+
+- (void) dealloc
+{
+    if( _alias )
+        DisposeHandle((Handle)_alias);
+    [super dealloc];
+}
+
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+    NSParameterAssert([coder allowsKeyedCoding]);
+    NSKeyedArchiver *arch = (NSKeyedArchiver*)coder;
+
+    [arch encodeBytes: (const uint8_t *) *_alias 
+               length: GetHandleSize((Handle)_alias)
+               forKey: @"aliasHandle"];
+}
+
+
+- (id)initWithCoder:(NSCoder *)decoder
+{
+    NSParameterAssert([decoder allowsKeyedCoding]);
+    NSKeyedUnarchiver *arch = (NSKeyedUnarchiver*)decoder;
+
+    self = [super init];
+    if( self ) {
+        Handle handle;
+        unsigned length;
+        const void *bytes = [arch decodeBytesForKey:@"aliasHandle" returnedLength: &length];
+        if( bytes )
+            PtrToHand(bytes,&handle,length);
+        if( ! handle ) {
+            [self release];
+            return nil;
+        }
+        _alias = (AliasHandle) handle;
+    }
+    return self;
+}
+
+
+- (NSString*) description
+{
+    return [NSString stringWithFormat: @"%@['%@']", [self class],[self originalFilename]];
+}
+
+
+- (NSString*) originalPath
+{
+    CFStringRef path = NULL;
+    OSStatus err = FSCopyAliasInfo(_alias,NULL,NULL,&path,NULL,NULL);
+    if( err )
+        return nil;
+    else
+        return [(id)path autorelease];
+}
+
+- (NSString*) originalFilename
+{
+    HFSUniStr255 targetName;
+    OSStatus err = FSCopyAliasInfo(_alias,&targetName,NULL,NULL,NULL,NULL);
+    if( err )
+        return nil;
+    else
+        return [(id)FSCreateStringFromHFSUniStr(NULL,&targetName) autorelease];
+}
+
+- (NSString*) originalVolumeName
+{
+    HFSUniStr255 volName;
+    OSStatus err = FSCopyAliasInfo(_alias,NULL,&volName,NULL,NULL,NULL);
+    if( err )
+        return nil;
+    else
+        return [(id)FSCreateStringFromHFSUniStr(NULL,&volName) autorelease];
+}
+
+- (void) dump
+{
+    HFSUniStr255 targetName,volName;
+    CFStringRef path;
+    FSAliasInfoBitmap whichInfo = 0;
+    FSAliasInfo info;
+    OSStatus err = FSCopyAliasInfo(_alias,&targetName,&volName,&path,&whichInfo,&info);
+    if( err ) {
+        NSLog(@"FSCopyAliasInfo returned error %i",err);
+        return;
+    }
+    NSString *str = (id)FSCreateStringFromHFSUniStr(NULL,&targetName);
+    NSLog(@"Target name = '%@'",str);
+    [str release];
+    str = (id)FSCreateStringFromHFSUniStr(NULL,&volName);
+    NSLog(@"Volume name = '%@'",str);
+    [str release];
+    NSLog(@"Path        = %@",path);
+    if( path ) CFRelease(path);
+    NSLog(@"Info bitmap = %08X", whichInfo);
+}    
+
+
+#pragma mark -
+#pragma mark RESOLVING:
+
+
+- (NSString*) filePathRelativeToPath: (NSString*)fromPath error: (NSError**)error
+{
+    FSRef fromRef, targetRef, *fromRefPtr;
+    if( fromPath ) {
+        if( ! CheckOSErr( PathToFSRef(fromPath,&fromRef), error ) )
+            return NO;
+        fromRefPtr = &fromRef;
+    } else {
+        fromRefPtr = NULL;
+    }
+    
+    Boolean wasChanged;
+    NSString *targetPath;
+    if( CheckOSErr( FSResolveAlias(fromRefPtr,_alias,&targetRef,&wasChanged), error)
+            && CheckOSErr( FSRefToPath(&targetRef,&targetPath), error ) )
+        return targetPath;
+    else {
+        NSLog(@"%@: Couldn't resolve alias!",self);
+        [self dump];
+        return nil;
+    }
+}
+
+- (NSString*) filePath: (NSError**)error
+{
+    return [self filePathRelativeToPath: nil error: error];
+}
+
+
+- (NSArray*) findMatchesRelativeToPath: (NSString*)fromPath 
+                             withRules: (unsigned)rules
+                                 error: (NSError**)error
+{
+    FSRef fromRef, *fromRefPtr;
+    if( fromPath ) {
+        if( ! CheckOSErr( PathToFSRef(fromPath,&fromRef), error ) )
+            return nil;
+        fromRefPtr = &fromRef;
+    } else {
+        fromRefPtr = NULL;
+    }
+    
+    Boolean wasChanged;
+    short count = 10;
+    FSRef matches[count];
+    
+#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+    if( ! FSMatchAliasBulk(fromRefPtr, rules, _alias, &count, matches, &wasChanged, NULL, NULL), error ) {
+        NSLog(@"%@: FSMatchAliasBulk failed!",self);
+        return nil;
+    }
+#else
+    if( ! CheckOSErr( FSMatchAlias(fromRefPtr,rules,_alias,&count,matches,&wasChanged,NULL,NULL), error) ) {
+        NSLog(@"%@: FSMatchAlias failed!",self);
+        return nil;
+    }
+#endif
+    
+    NSMutableArray *paths = [NSMutableArray arrayWithCapacity: count];
+    for( short i=0; i<count; i++ ) {
+        NSString *path;
+        if( FSRefToPath(&matches[i],&path) == noErr )
+            [paths addObject: path];
+    }
+    return paths;
+}
+
+
+- (NSArray*) findMatches: (NSError**)error
+{
+    return [self findMatchesRelativeToPath: nil
+                                 withRules: kARMMultVols | kARMSearch | kARMSearchMore | kARMTryFileIDFirst
+                                     error: error];
+}
+
+
+@end
+//
+//  FileUtils.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/14/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+FOUNDATION_EXPORT OSStatus PathToFSRef( NSString *path, FSRef *outFSRef );
+FOUNDATION_EXPORT OSStatus FSRefToPath( const FSRef *fsRef, NSString **outPath );
+
+FOUNDATION_EXPORT BOOL CheckOSErr( OSStatus err, NSError **error );
+
+FOUNDATION_EXPORT NSString* AppSupportDirectory(void);
+//
+//  FileUtils.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/14/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "FileUtils.h"
+
+
+OSStatus PathToFSRef( NSString *path, FSRef *fsRef )
+{
+    NSCParameterAssert(path);
+    return FSPathMakeRef((const UInt8 *)[path UTF8String],fsRef,NULL);
+}
+
+OSStatus FSRefToPath( const FSRef *fsRef, NSString **outPath )
+{
+    NSURL *url = (id) CFURLCreateFromFSRef(NULL,fsRef);
+    if( ! url )
+        return paramErr;
+    *outPath = [url path];
+    [url release];
+    return noErr;
+}
+
+
+BOOL CheckOSErr( OSStatus err, NSError **error )
+{
+    if( err ) {
+        if( error )
+            *error = [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: nil];
+        return NO;
+    } else {
+        return YES;
+    }
+}
+
+
+NSString* AppSupportDirectory()
+{
+    static NSString *sPath;
+    if( ! sPath ) {
+        NSString *dir = [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
+                                                             NSUserDomainMask, YES)
+                         objectAtIndex: 0];
+        dir = [dir stringByAppendingPathComponent: [[NSBundle mainBundle] bundleIdentifier]];
+        if( ! [[NSFileManager defaultManager] fileExistsAtPath: dir]
+                && ! [[NSFileManager defaultManager] createDirectoryAtPath: dir attributes: nil] )
+            [NSException raise: NSGenericException format: @"Unable to create app support dir %@",dir];
+        sPath = [dir copy];
+    }
+    return sPath;
+}
+//
+//  GraphicsUtils.h
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface NSImage (MYUtilities)
+- (NSImage*) my_shrunkToFitIn: (NSSize) maxSize;
+- (NSSize) my_sizeOfLargestRep;
+- (NSData*) my_JPEGData;
+//- (NSData*) my)_PICTData;
+@end
+
+
+@interface NSBezierPath (MYUtilities)
++ (NSBezierPath*) my_bezierPathWithRoundRect: (NSRect)rect radius: (float)radius;
+@end
+//
+//  GraphicsUtils.m
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "GraphicsUtils.h"
+
+
+@implementation NSImage (MYUtilities)
+
+
+- (NSSize) my_sizeOfLargestRep
+{
+    NSArray *reps = [self representations];
+    NSSize max = {0,0};
+    int i;
+    for( i=[reps count]-1; i>=0; i-- ) {
+        NSImageRep *rep = [reps objectAtIndex: i];
+        NSSize size = [rep size];
+        if( size.width > max.width || size.height > max.height ) {
+            max = size;
+        }
+    }
+    return max;
+}
+
+
+- (NSImage*) my_shrunkToFitIn: (NSSize) maxSize
+{
+    NSSize size = self.size;
+    float scale = MIN( size.width/maxSize.width, size.height/maxSize.height );
+    if( scale >= 1.0 )
+        return self;
+    
+    NSSize newSize = {roundf(size.width*scale), roundf(size.height*scale)};
+    NSImage *newImage = [[NSImage alloc] initWithSize: newSize];
+    [newImage lockFocus];
+    NSGraphicsContext *context = [NSGraphicsContext currentContext];
+    [context saveGraphicsState];
+    [context setImageInterpolation: NSImageInterpolationHigh];
+    [self drawInRect: NSMakeRect(0,0,newSize.width,newSize.height)
+            fromRect: NSMakeRect(0,0,size.width,size.height)
+           operation: NSCompositeCopy fraction: 1.0f];
+    [context restoreGraphicsState];
+    [newImage unlockFocus];
+    return [newImage autorelease];
+}
+
+
+- (NSData*) my_JPEGData
+{
+    NSImage *tiffImage = [[NSImage alloc] initWithData:[self TIFFRepresentation]];
+    NSBitmapImageRep *rep = [[tiffImage representations] objectAtIndex:0];
+    NSDictionary *props = [NSDictionary dictionaryWithObjectsAndKeys:
+        [NSNumber numberWithFloat: 0.75f], NSImageCompressionFactor, nil];
+    NSData *jpeg = [rep representationUsingType: NSJPEGFileType properties: props];
+    [tiffImage release];
+    return jpeg;
+}
+
+
+#if 0
+
+// Adapted from Apple's CocoaCreateMovie sample
+// <http://developer.apple.com/samplecode/Sample_Code/QuickTime/Basics/CocoaCreateMovie/MyController.m.htm>
+static void CopyNSImageRepToGWorld(NSBitmapImageRep *imageRepresentation, GWorldPtr gWorldPtr)
+{
+    PixMapHandle  pixMapHandle;
+    unsigned char*   pixBaseAddr;
+
+    // Lock the pixels
+    pixMapHandle = GetGWorldPixMap(gWorldPtr);
+    LockPixels (pixMapHandle);
+    pixBaseAddr = (unsigned char*) GetPixBaseAddr(pixMapHandle);
+
+    const unsigned char* bitMapDataPtr = [imageRepresentation bitmapData];
+
+    if ((bitMapDataPtr != nil) && (pixBaseAddr != nil))
+    {
+        int i,j;
+        int pixmapRowBytes = GetPixRowBytes(pixMapHandle);
+        NSSize imageSize = [imageRepresentation size];
+        for (i=0; i< imageSize.height; i++)
+        {
+            const unsigned char *src = bitMapDataPtr + i * [imageRepresentation bytesPerRow];
+            unsigned char *dst = pixBaseAddr + i * pixmapRowBytes;
+            for (j = 0; j < imageSize.width; j++)
+            {
+                *dst++ = 0;  // X - our src is 24-bit only
+                *dst++ = *src++; // Red component
+                *dst++ = *src++; // Green component
+                *dst++ = *src++; // Blue component
+            }
+        }
+    }
+    UnlockPixels(pixMapHandle);
+}
+
+
+- (NSData*) my_PICTData
+{
+    // Locate the bitmap image rep:
+    NSBitmapImageRep *rep;
+    NSEnumerator *e = [[self representations] objectEnumerator];
+    while( (rep=[e nextObject]) != nil ) {
+        if( [rep isKindOfClass: [NSBitmapImageRep class]] )
+            break;
+    }
+    if( ! rep ) {
+        Warn(@"No bitmap image rep in image");
+        return nil;
+    }
+
+    // Copy the image data into a GWorld:
+    Rect bounds;
+    SetRect(&bounds, 0,0,[rep pixelsWide],[rep pixelsHigh]);    
+    GWorldPtr gworld;
+    OSStatus err = NewGWorld(&gworld, 32, &bounds, NULL, NULL, 0);
+    if( err ) {
+        Warn(@"NewGWorld failed with err %i",err);
+        return nil;
+    }
+    CopyNSImageRepToGWorld(rep,gworld);
+
+    // Draw the GWorld into a PicHandle:
+    CGrafPtr oldPort;
+    GDHandle oldDevice;
+    GetGWorld(&oldPort,&oldDevice);
+    SetGWorld(gworld,NULL);
+    ClipRect(&bounds);
+    PicHandle pic = OpenPicture(&bounds);
+    CopyBits(GetPortBitMapForCopyBits(gworld),
+             GetPortBitMapForCopyBits(gworld),
+             &bounds,&bounds,srcCopy,NULL);
+    ClosePicture();
+    err = QDError();
+    SetGWorld(oldPort,oldDevice);
+    DisposeGWorld(gworld);
+    
+    if( err ) {
+        Warn(@"Couldn't convert to PICT: error %i",err);
+        return nil;
+    }
+
+    // Copy the PicHandle into an NSData:
+    HLock((Handle)pic);
+    //Test to put PICT on clipboard:
+    /*ScrapRef scrap;
+    GetCurrentScrap(&scrap);
+    ClearScrap(&scrap);
+    PutScrapFlavor(scrap,'PICT',0,GetHandleSize((Handle)pic),*pic);*/
+    NSData *data = [NSData dataWithBytes: *pic length: GetHandleSize((Handle)pic)];
+    DisposeHandle((Handle)pic);
+    Log(@"Converted image to %i bytes of PICT data",[data length]);
+    return data;
+}
+#endif
+
+
+@end
+
+
+
+
+@implementation NSBezierPath (MYUtilities)
+
++ (NSBezierPath*) my_bezierPathWithRoundRect: (NSRect)rect radius: (float)radius
+{
+    radius = MIN(radius, floorf(rect.size.width/2));
+    float x0 = NSMinX(rect), y0 = NSMinY(rect),
+          x1 = NSMaxX(rect), y1 = NSMaxY(rect);
+    NSBezierPath *path = [NSBezierPath bezierPath];
+    
+    [path moveToPoint: NSMakePoint(x0+radius,y0)];
+    
+    [path appendBezierPathWithArcFromPoint: NSMakePoint(x1,y0)
+                                   toPoint: NSMakePoint(x1,y1) radius: radius];
+    [path appendBezierPathWithArcFromPoint: NSMakePoint(x1,y1)
+                                   toPoint: NSMakePoint(x0,y1) radius: radius];
+    [path appendBezierPathWithArcFromPoint: NSMakePoint(x0,y1)
+                                   toPoint: NSMakePoint(x0,y0) radius: radius];
+    [path appendBezierPathWithArcFromPoint: NSMakePoint(x0,y0)
+                                   toPoint: NSMakePoint(x1,y0) radius: radius];
+    [path closePath];
+    return path;
+}
+
+@end
+//
+//  IChatUtils.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 3/3/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+@class SBApplication;
+
+
+@interface IChatUtils : NSObject 
+
++ (SBApplication*) app;
++ (BOOL) isRunning;
++ (void) activate;
++ (NSString*) activeChatPartner;
++ (BOOL) sendMessage: (NSString*)msg;
+
+@end
+//
+//  IChatUtils.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 3/3/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "IChatUtils.h"
+#import "iChatBridge.h"
+
+@implementation IChatUtils
+
+
+static iChatApplication *sIChatApp;
+
++ (void) initialize
+{
+    if( ! sIChatApp ) {
+        sIChatApp = [SBApplication applicationWithBundleIdentifier: @"com.apple.iChat"];
+        sIChatApp.timeout = 5*60; // in ticks
+    }
+}
+
+
++ (SBApplication*) app  {return sIChatApp;}
++ (BOOL) isRunning      {return sIChatApp.isRunning;}
++ (void) activate       {[sIChatApp activate];}
+
+
++ (iChatTextChat*) activeChat
+{
+    if( ! [sIChatApp isRunning] )
+        return nil;
+    SBElementArray *chats = sIChatApp.textChats;
+    if( chats.count==0 )
+        return nil;
+    iChatTextChat *chat = [chats objectAtIndex: 0];
+    if( ! chat.active )
+        return nil;
+    return chat;
+}    
+
++ (NSString*) activeChatPartner
+{
+    iChatTextChat *chat = [self activeChat];
+    if( ! chat )
+        return nil;
+    NSMutableArray *names = $marray();
+    for( iChatBuddy *b in [chat participants] )
+        [names addObject: (b.fullName ?: b.name)];
+    return [names componentsJoinedByString: @", "];
+}
+
++ (BOOL) sendMessage: (NSString*)msg
+{
+    iChatTextChat *chat = [self activeChat];
+    if( ! chat )
+        return NO;
+    [sIChatApp send: msg to: chat];
+    return YES;
+}
+
+
+@end
+//
+//  KVUtils.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 2/25/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+enum {
+    MYKeyValueObservingOptionOnce = 1<<31,
+    MYKeyValueObservingOptionDelayed = 1<<30
+};
+
+
+
+@interface Observance : NSObject
+{
+    id _target;
+    id _observed;
+    NSString *_keyPath;
+    NSKeyValueObservingOptions _options;
+    SEL _action;
+}
+
+- (id) initWithTarget: (id)target 
+               action: (SEL)action
+             observed: (id)observed
+              keyPath: (NSString*)keyPath 
+              options: (NSKeyValueObservingOptions)options;
+
+- (void) stopObserving;
+
+@property (readonly) id observed;
+@property (readonly) NSString* keyPath;
+
+@end
+
+
+
+@interface Observer : NSObject
+{
+    id _target;
+    NSMutableArray *_observances;
+}
+
+- (id) initWithTarget: (id)target;
+
+@property (readonly) id target;
+
+- (void) observe: (id)observed 
+         keyPath: (NSString*)keyPath 
+         options: (NSKeyValueObservingOptions)options
+          action: (SEL)action;
+
+- (void) observe: (id)observed 
+         keyPath: (NSString*)keyPath 
+          action: (SEL)action;
+
+- (void) stopObserving: (id)observedOrNil keyPath: (NSString*)keyPathOrNil;
+
+- (void) stopObserving;
+
+@end
+//
+//  KVUtils.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 2/25/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "KVUtils.h"
+
+
+@implementation Observance
+
+- (id) initWithTarget: (id)target 
+               action: (SEL)action
+             observed: (id)observed
+              keyPath: (NSString*)keyPath 
+              options: (NSKeyValueObservingOptions)options
+{
+    self = [super init];
+    if (self != nil) {
+        _target = target;
+        _action = action;
+        _observed = observed;
+        _keyPath = [keyPath copy];
+        _options = options;
+        
+        options &= ~(MYKeyValueObservingOptionOnce | MYKeyValueObservingOptionDelayed);
+        
+        [_observed addObserver: self forKeyPath: _keyPath options: options context: _action];
+    }
+    return self;
+}
+
+- (void) dealloc
+{
+    [_observed removeObserver: self forKeyPath: _keyPath];
+    [_keyPath release];
+    [super dealloc];
+}
+
+
+@synthesize observed=_observed, keyPath=_keyPath;
+
+
+- (void) stopObserving
+{
+    [_observed removeObserver: self forKeyPath: _keyPath];
+    _observed = nil;
+    _target = nil;
+    _action = NULL;
+}
+
+
+- (void) _callTargetWithChange: (NSDictionary*)change
+{
+    @try{
+        [_target performSelector: _action withObject: _observed withObject: change];
+    }@catch( NSException *x ) {
+        Warn(@"Uncaught exception in -[%@<%p> %s] while observing change of key-path %@ in %@<%p>: %@",
+             _target,_target, _action, _keyPath, _observed,_observed, x);
+    }
+}
+
+
+- (void)observeValueForKeyPath:(NSString *)keyPath 
+                      ofObject:(id)object 
+                        change:(NSDictionary *)change 
+                       context:(void *)context
+{
+    if( object == _observed ) {
+        if( _options & MYKeyValueObservingOptionDelayed )
+            [self performSelector: @selector(_callTargetWithChange:) withObject: change
+                       afterDelay: 0.0];
+        else
+            [self _callTargetWithChange: change];
+        if( _options & MYKeyValueObservingOptionOnce )
+            [self stopObserving];
+    }
+}
+
+
+@end
+
+
+
+
+@implementation Observer
+
+
+- (id) init
+{
+    return [self initWithTarget: self];
+}
+
+
+
+- (id) initWithTarget: (id)target
+{
+    Assert(target);
+    self = [super init];
+    if (self != nil) {
+        _target = target;
+        _observances = [[NSMutableArray alloc] init];
+    }
+    return self;
+}
+
+
+- (void) dealloc
+{
+    [self stopObserving];
+    [_observances release];
+    [super dealloc];
+}
+
+
+@synthesize target=_target;
+
+
+- (void) observe: (id)observed 
+         keyPath: (NSString*)keyPath 
+         options: (NSKeyValueObservingOptions)options
+          action: (SEL)action
+{
+    Observance *o = [[Observance alloc] initWithTarget: _target
+                                                action: action
+                                              observed: observed
+                                               keyPath: keyPath
+                                               options: options];
+    [_observances addObject: o];
+    [o release];
+}
+
+
+- (void) observe: (id)observed 
+         keyPath: (NSString*)keyPath 
+          action: (SEL)action
+{
+    [self observe: observed keyPath: keyPath options: 0 action: action];
+}
+
+
+- (void) stopObserving
+{
+    [_observances makeObjectsPerformSelector: @selector(stopObserving)];
+    [_observances removeAllObjects];
+}
+
+
+- (void) stopObserving: (id)observed keyPath: (NSString*)keyPath
+{
+    // observed or keyPath may be nil
+    for( int i=_observances.count-1; i>=0; i-- ) {
+        Observance *o = [_observances objectAtIndex: i];
+        if( (observed==nil || observed==o.observed) && (keyPath==nil || [keyPath isEqual: o.keyPath]) ) {
+            [o stopObserving];
+            [_observances removeObjectAtIndex: i];
+        }
+    }
+}
+
+@end
+//
+//  Logging.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+NSString* LOC( NSString *key );     // Localized string lookup
+
+
+#define Log(FMT,ARGS...) do{if(__builtin_expect(gShouldLog,0)) _Log(FMT,##ARGS);}while(0)
+#define Warn Warn
+
+void AlwaysLog( NSString *msg, ... ) __attribute__((format(__NSString__, 1, 2)));
+
+
+extern int gShouldLog;
+void _Log( NSString *msg, ... ) __attribute__((format(__NSString__, 1, 2)));
+void Warn( NSString *msg, ... ) __attribute__((format(__NSString__, 1, 2)));
+//
+//  Logging.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "Logging.h"
+#import <unistd.h>
+#include <fcntl.h>
+#include <sys/param.h>
+
+
+NSString* LOC( NSString *key )     // Localized string lookup
+{
+    NSString *value = [[NSBundle mainBundle] localizedStringForKey:key value:nil table:nil];
+    if( value == key ) {
+        Warn(@"No localized string for '%@' in Localizable.strings!",key);
+        value = [key uppercaseString];
+    }
+    return value;
+}
+
+
+int gShouldLog = -1;
+
+
+static BOOL isConsole( int fd )
+{
+    if( isatty(fd) )
+        return YES;
+    char path[MAXPATHLEN];
+    if( fcntl(fd, F_GETPATH, path) == -1 )
+        return NO;
+    return YES;
+}
+
+
+static void _Logv( NSString *msg, va_list args )
+{
+    if( isConsole(STDERR_FILENO) ) {
+        NSAutoreleasePool *pool = [NSAutoreleasePool new];
+        static NSDateFormatter *sTimestampFormat;
+        if( ! sTimestampFormat ) {
+            sTimestampFormat = [[NSDateFormatter alloc] init];
+            sTimestampFormat.dateFormat = @"HH:mm:ss.SSS";
+        }
+        NSDate *now = [[NSDate alloc] init];
+        NSString *timestamp = [sTimestampFormat stringFromDate: now];
+        [now release];
+        msg = [[NSString alloc] initWithFormat: msg arguments: args];
+        NSString *finalMsg = [[NSString alloc] initWithFormat: @"%@| %@\n", timestamp,msg];
+        fputs([finalMsg UTF8String], stderr);
+        [finalMsg release];
+        [msg release];
+        [pool drain];
+    } else
+        NSLogv(msg,args);
+}
+
+
+void AlwaysLog( NSString *msg, ... )
+{
+    va_list args;
+    va_start(args,msg);
+    _Logv(msg,args);
+    va_end(args);
+}
+
+
+void _Log( NSString *msg, ... )
+{
+    if( gShouldLog == -1 )
+        gShouldLog = [[NSUserDefaults standardUserDefaults] boolForKey: @"Log"];
+    
+    if( gShouldLog ) {
+        va_list args;
+        va_start(args,msg);
+        _Logv(msg,args);
+        va_end(args);
+    }
+}
+
+
+void Warn( NSString *msg, ... )
+{
+    msg = [@"WARNING*** " stringByAppendingString: msg];
+    
+    va_list args;
+    va_start(args,msg);
+    _Logv(msg,args);
+    va_end(args);
+}
+
+
+//
+//  Target.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 2/11/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+#define $target(RCVR,METHOD)    _mktarget((RCVR),@selector(METHOD))
+
+id $calltarget( NSInvocation *target, id sender );
+
+
+NSInvocation* _mktarget( id rcvr, SEL action );
+//
+//  Target.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 2/11/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "Target.h"
+
+
+NSInvocation* _mktarget( id rcvr, SEL action )
+{
+    NSMethodSignature *sig = [rcvr methodSignatureForSelector: action];
+    CAssert(sig,@"%@<%p> does not respond to %@",[rcvr class],rcvr,NSStringFromSelector(action));
+    CAssert(sig.numberOfArguments==3,
+           @"-[%@ %@] can't be used as a target because it takes >1 param",
+           [rcvr class],NSStringFromSelector(action));
+    CAssert(0==strcmp([sig getArgumentTypeAtIndex: 2],"@"),
+           @"-[%@ %@] can't be used as a target because it takes a non-object param",
+           [rcvr class],NSStringFromSelector(action));
+    NSInvocation *inv = [NSInvocation invocationWithMethodSignature: sig];
+    inv.target = rcvr;
+    inv.selector = action;
+    return inv;
+}
+
+
+id $calltarget( NSInvocation *target, id param )
+{
+    if( target && target.target ) {
+        [target setArgument: &param atIndex: 2];
+        [target invoke];
+        
+        NSMethodSignature *sig = target.methodSignature;
+        NSUInteger returnLength = sig.methodReturnLength;
+        if( returnLength==0 )
+            return nil; // void
+        else {
+            const char *returnType = sig.methodReturnType;
+            if( returnType[0]=='@' ) {
+                id returnObject;
+                [target getReturnValue: &returnObject];
+                return returnObject;
+            } else {
+                UInt8 returnBuffer[returnLength];
+                [target getReturnValue: &returnBuffer];
+                return [NSValue valueWithBytes: &returnBuffer objCType: returnType];
+            }
+        }
+    } else
+        return nil;
+}
+//
+//  Test.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "CollectionUtils.h"
+
+
+/** Call this first thing in main() to run tests.
+    This function is a no-op if the DEBUG macro is not defined (i.e. in a release build).
+    At runtime, to cause a particular test "X" to run, add a command-line argument "Test_X".
+    To run all tests, add the argument "Test_All".
+    To run only tests without starting the main program, add the argument "Test_Only". */
+#if DEBUG
+void RunTestCases( int argc, const char **argv );
+#else
+#define RunTestCases(ARGC,ARGV)
+#endif
+
+/** The TestCase() macro declares a test case.
+    Its argument is a name for the test case (without quotes), and it's followed with a block
+    of code implementing the test.
+    The code should raise an exception if anything fails.
+    The CAssert, CAssertEqual and CAssertEq macros, below, are the most useful way to do this.
+    A test case can register a dependency on another test case by calling RequireTestCase().
+    Example:
+        TestCase(MyLib) {
+            RequireTestCase("LibIDependOn");
+            CAssertEq( myFunction(), 12345 );
+        }
+    Test cases are disabled if the DEBUG macro is not defined (i.e. in a release build). */
+#if DEBUG
+#define TestCase(NAME)      void Test_##NAME(void); \
+                            struct TestCaseLink linkToTest##NAME = {&Test_##NAME,#NAME}; \
+                            __attribute__((constructor)) static void registerTestCase##NAME() \
+                                {linkToTest##NAME.next = gAllTestCases; gAllTestCases=&linkToTest##NAME; } \
+                            void Test_##NAME(void)
+#else
+#define TestCase(NAME)      __attribute__((unused)) static void Test_##NAME(void)
+#endif
+
+/** Can call this in a test case to indicate a prerequisite.
+    The prerequisite test will be run first, and if it fails, the current test case will be skipped. */
+#define RequireTestCase(NAME)   _RequireTestCase(#NAME)
+void _RequireTestCase( const char *name );
+
+
+
+/** General-purpose assertions, replacing NSAssert etc.. You can use these outside test cases. */
+
+#define Assert(COND,MSG...)    do{ if( __builtin_expect(!(COND),NO) ) \
+                                    _AssertFailed(self,(const char*)_cmd, __FILE__, __LINE__,\
+                                                  #COND,##MSG,NULL); }while(0)
+#define CAssert(COND,MSG...)    do{ if( __builtin_expect(!(COND),NO) ) \
+                                    _AssertFailed(nil, __PRETTY_FUNCTION__, __FILE__, __LINE__,\
+                                                  #COND,##MSG,NULL); }while(0)
+
+#define AssertEqual(VAL,EXPECTED)   do{ id _val = VAL, _expected = EXPECTED;\
+                                        Assert(_val==_expected || [_val isEqual: _expected], @"Unexpected value for %s: %@ (expected %@)", #VAL,_val,_expected); \
+                                    }while(0)
+#define CAssertEqual(VAL,EXPECTED)  do{ id _val = (VAL), _expected = (EXPECTED);\
+                                        CAssert(_val==_expected || [_val isEqual: _expected], @"Unexpected value for %s: %@ (expected %@)", #VAL,_val,_expected); \
+                                    }while(0)
+
+// AssertEq is for scalars (int, float...)
+#define AssertEq(VAL,EXPECTED)  do{ typeof(VAL) _val = VAL; typeof(EXPECTED) _expected = EXPECTED;\
+                                    Assert(_val==_expected, @"Unexpected value for %s: %@ (expected %@)", #VAL,$object(_val),$object(_expected)); \
+                                }while(0)
+#define CAssertEq(VAL,EXPECTED) do{ typeof(VAL) _val = VAL; typeof(EXPECTED) _expected = EXPECTED;\
+                                    CAssert(_val==_expected, @"Unexpected value for %s: %@ (expected %@)", #VAL,$object(_val),$object(_expected)); \
+                                }while(0)
+
+
+#pragma mark -
+#pragma mark EXCEPTION UTILITIES:
+
+@interface NSException (MooseyardUtil)
+/** Returns a textual, human-readable backtrace of the point where the exception was thrown. */
+- (NSString*) my_callStack;
+@end
+
+
+
+// Nasty internals ...
+#if DEBUG
+void _RunTestCase( void (*testptr)(), const char *name );
+
+struct TestCaseLink {void (*testptr)(); const char *name; BOOL passed; struct TestCaseLink *next;};
+extern struct TestCaseLink *gAllTestCases;
+#endif DEBUG
+void _AssertFailed( id rcvr, const char *selOrFn, const char *sourceFile, int sourceLine,
+                   const char *condString, NSString *message, ... ) __attribute__((noreturn));
+//
+//  Test.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 1/5/08.
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "Test.h"
+#import <unistd.h>
+
+#if DEBUG
+
+struct TestCaseLink *gAllTestCases;
+static int sPassed, sFailed;
+
+static BOOL RunTestCase( struct TestCaseLink *test )
+{
+    if( test->testptr ) {
+        NSAutoreleasePool *pool = [NSAutoreleasePool new];
+        Log(@"=== Testing %s ...",test->name);
+        @try{
+            test->testptr();
+            Log(@"√√√ %s passed\n\n",test->name);
+            test->passed = YES;
+            sPassed++;
+        }@catch( NSException *x ) {
+            if( [x.name isEqualToString: @"TestCaseSkipped"] )
+                Log(@"... skipping test %s since %@\n\n", test->name, x.reason);
+            else {
+                Log(@"XXX FAILED test case '%s' due to:\nException: %@\n%@\n\n", 
+                      test->name,x,x.my_callStack);
+                sFailed++;
+            }
+        }@finally{
+            [pool drain];
+            test->testptr = NULL;       // prevents test from being run again
+        }
+    }
+    return test->passed;
+}
+
+
+static BOOL RunTestCaseNamed( const char *name )
+{
+    for( struct TestCaseLink *test = gAllTestCases; test; test=test->next )
+        if( strcmp(name,test->name)==0 ) {
+            return RunTestCase(test);
+        }
+    Log(@"... WARNING: Could not find test case named '%s'\n\n",name);
+    return NO;
+}
+
+
+void _RequireTestCase( const char *name )
+{
+    if( ! RunTestCaseNamed(name) ) {
+        [NSException raise: @"TestCaseSkipped" 
+                    format: @"prerequisite %s failed", name];
+    }
+}
+
+
+void RunTestCases( int argc, const char **argv )
+{
+    gShouldLog = YES;
+    sPassed = sFailed = 0;
+    BOOL stopAfterTests = NO;
+    for( int i=1; i<argc; i++ ) {
+        const char *arg = argv[i];
+        if( strncmp(arg,"Test_",5)==0 ) {
+            arg += 5;
+            if( strcmp(arg,"Only")==0 )
+                stopAfterTests = YES;
+            else if( strcmp(arg,"All") == 0 ) {
+                for( struct TestCaseLink *link = gAllTestCases; link; link=link->next )
+                    RunTestCase(link);
+            } else {
+                RunTestCaseNamed(arg);
+            }
+        }
+    }
+    if( sPassed>0 || sFailed>0 || stopAfterTests ) {
+        if( sFailed==0 )
+            Log(@"√√√√√√ ALL %i TESTS PASSED √√√√√√", sPassed);
+        else {
+            Log(@"****** %i TESTS FAILED, %i PASSED ******", sFailed,sPassed);
+            exit(1);
+        }
+        if( stopAfterTests ) {
+            Log(@"Stopping after tests ('Test_Only' arg detected)");
+            exit(0);
+        }
+    }
+}
+
+
+#endif DEBUG
+
+
+#pragma mark -
+#pragma mark ASSERTION FAILURE HANDLER:
+
+
+void _AssertFailed( id rcvr, const char *selOrFn, const char *sourceFile, int sourceLine,
+                    const char *condString, NSString *message, ... )
+{
+    if( message ) {
+        va_list args;
+        va_start(args,message);
+        message = [[[NSString alloc] initWithFormat: message arguments: args] autorelease];
+        va_end(args);
+    } else
+        message = [NSString stringWithUTF8String: condString];
+    
+    if( rcvr )
+        [[NSAssertionHandler currentHandler] handleFailureInMethod: (SEL)selOrFn
+                                                            object: rcvr 
+                                                              file: [NSString stringWithUTF8String: sourceFile]
+                                                        lineNumber: sourceLine 
+                                                       description: @"%@", message];
+    else
+        [[NSAssertionHandler currentHandler] handleFailureInFunction: [NSString stringWithUTF8String:selOrFn]
+                                                                file: [NSString stringWithUTF8String: sourceFile]
+                                                          lineNumber: sourceLine 
+                                                         description: @"%@", message];
+    abort(); // unreachable, but appeases compiler
+}
+
+
+#pragma mark -
+#pragma mark EXCEPTION BACKTRACES:
+
+
+@implementation NSException (MooseyardUtil)
+
+
+- (NSArray*) my_callStackReturnAddresses
+{
+    // On 10.5 or later, can get the backtrace:
+    if( [self respondsToSelector: @selector(callStackReturnAddresses)] )
+        return [self valueForKey: @"callStackReturnAddresses"];
+    else
+        return nil;
+}
+
+- (NSArray*) my_callStackReturnAddressesSkipping: (unsigned)skip limit: (unsigned)limit
+{
+    NSArray *addresses = [self my_callStackReturnAddresses];
+    if( addresses ) {
+        unsigned n = [addresses count];
+        skip = MIN(skip,n);
+        limit = MIN(limit,n-skip);
+        addresses = [addresses subarrayWithRange: NSMakeRange(skip,limit)];
+    }
+    return addresses;
+}
+
+
+- (NSString*) my_callStack
+{
+    NSArray *addresses = [self my_callStackReturnAddressesSkipping: 2 limit: 15];
+    if (!addresses)
+        return nil;
+    
+    // We pipe the hex return addresses through the 'atos' tool to get symbolic names:
+    // Adapted from <http://paste.lisp.org/display/47196>:
+    NSMutableString *cmd = [NSMutableString stringWithFormat: @"/usr/bin/atos -p %d", getpid()];
+    NSValue *addr;
+    foreach(addr,addresses) {
+        [cmd appendFormat: @" %p", [addr pointerValue]];
+    }
+    FILE *file = popen( [cmd UTF8String], "r" );
+    if( ! file )
+        return nil;
+    
+    NSMutableData *output = [NSMutableData data];
+    char buffer[512];
+    size_t length;
+    while ((length = fread( buffer, 1, sizeof( buffer ), file ) ))
+        [output appendBytes: buffer length: length];
+    pclose( file );
+    NSString *outStr = [[[NSString alloc] initWithData: output encoding: NSUTF8StringEncoding] autorelease];
+    
+    NSMutableString *result = [NSMutableString string];
+    NSString *line;
+    foreach( line, [outStr componentsSeparatedByString: @"\n"] ) {
+        // Skip  frames that are part of the exception/assertion handling itself:
+        if( [line hasPrefix: @"-[NSAssertionHandler"] || [line hasPrefix: @"+[NSException"] 
+                || [line hasPrefix: @"-[NSException"] || [line hasPrefix: @"_AssertFailed"] )
+            continue;
+        if( result.length )
+            [result appendString: @"\n"];
+        [result appendString: @"\t"];
+        [result appendString: line];
+        // Don't show the "__start" frame below "main":
+        if( [line hasPrefix: @"main "] )
+            break;
+    }
+    return result;
+}
+
+@end

TimeIntervalFormatter.h

+//
+//  TimeIntervalFormatter.h
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface TimeIntervalFormatter : NSFormatter
+{
+    BOOL _showsMinutes, _showsFractionalSeconds;
+}
+
+- (void) setShowsMinutes: (BOOL)showsMinutes;
+- (void) setShowsFractionalSeconds: (BOOL)showsFractionalSeconds;
+
+@end

TimeIntervalFormatter.m

+//
+//  TimeIntervalFormatter.m
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "TimeIntervalFormatter.h"
+
+
+@implementation TimeIntervalFormatter
+
+
+- (void) awakeFromNib
+{
+    _showsMinutes = YES;
+}
+
+- (void) setShowsMinutes: (BOOL)show                {_showsMinutes = show;}
+- (void) setShowsFractionalSeconds: (BOOL)show      {_showsFractionalSeconds = show;}
+
+
+- (NSString*) stringForObjectValue: (id)object
+{
+    if (![object isKindOfClass:[NSNumber class]])
+        return nil;
+    NSTimeInterval time = [object doubleValue];
+    NSString *sign;
+    if( time==0.0 )
+        return nil;
+    else if( time < 0.0 ) {
+        sign = @"-";
+        time = -time;
+    } else
+        sign = @"";
+    if( ! _showsFractionalSeconds )
+        time = floor(time);
+    int minutes = (int)floor(time / 60.0);
+    if( _showsMinutes || minutes>0 ) {
+        double seconds = time - 60.0*minutes;
+        return [NSString stringWithFormat: (_showsFractionalSeconds ?@"%@%d:%06.3lf" :@"%@%d:%02.0lf"),
+                                           sign,minutes,seconds];
+    } else {
+        return [NSString stringWithFormat: (_showsFractionalSeconds ?@"%@%.3lf" :@"%@%.0lf"),
+                                           sign,time];
+    }
+}
+
+
+- (BOOL)getObjectValue:(id *)anObject
+             forString:(NSString *)string 
+      errorDescription:(NSString **)error
+{
+    NSScanner *scanner = [NSScanner scannerWithString: string];
+    [scanner setCharactersToBeSkipped: [NSCharacterSet whitespaceCharacterSet]];
+    double seconds;
+    if( [scanner isAtEnd] ) {
+        seconds = 0.0;
+    } else {
+        if( ! [scanner scanDouble: &seconds] || seconds<0.0 ) goto error;
+        if( [scanner scanString: @":" intoString: NULL] ) {
+            double minutes = seconds;
+            if( ! [scanner scanDouble: &seconds] || seconds<0.0 ) goto error;
+            seconds += 60*minutes;
+        }
+        if( ! [scanner isAtEnd] ) goto error;
+    }
+    *anObject = [NSNumber numberWithDouble: seconds];
+    return YES;
+    
+error:
+    *anObject = nil;
+    if( error )
+        *error = @"Not a valid time interval";
+    return NO;
+}
+
+
+- (BOOL)isPartialStringValid:(NSString **)partialStringPtr 
+       proposedSelectedRange:(NSRangePointer)proposedSelRangePtr
+              originalString:(NSString *)origString 
+       originalSelectedRange:(NSRange)origSelRange 
+            errorDescription:(NSString **)error
+{
+    static NSCharacterSet *sIllegalChars;
+    if( ! sIllegalChars )
+        sIllegalChars = [[[NSCharacterSet characterSetWithCharactersInString: @"0123456789.:"] 
+                                invertedSet] retain];
+    return [*partialStringPtr rangeOfCharacterFromSet: sIllegalChars].length == 0;
+}
+
+
+@end
+//
+//  ValueArray.h
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface ValueArray : NSObject <NSCoding>
+{
+    unsigned _count;
+    size_t _valueSize;
+}
+
++ (ValueArray*) valueArrayWithCount: (unsigned)count valueSize: (size_t)valueSize;
+
+- (unsigned) count;
+- (size_t) valueSize;
+- (const void*) valueAtIndex: (unsigned)i;
+- (void) getValue: (void*)value atIndex: (unsigned)i;
+- (void) setValue: (const void*)value atIndex: (unsigned)i;
+
+@end
+
+
+#define DeclareValueArrayOf(CAPTYPE,TYPE) \
+            @interface CAPTYPE##Array : ValueArray \
+            + (CAPTYPE##Array*) TYPE##ArrayWithCount: (unsigned)count; \
+            - (TYPE) TYPE##AtIndex: (unsigned)index; \
+            - (void) set##CAPTYPE: (TYPE)value atIndex: (unsigned)index; \
+            @end
+
+#define ImplementValueArrayOf(CAPTYPE,TYPE) \
+            @implementation CAPTYPE##Array \
+            + (CAPTYPE##Array*) TYPE##Array##WithCount: (unsigned)count \
+                {return (id)[super valueArrayWithCount: count valueSize: sizeof(TYPE)];} \
+            - (TYPE) TYPE##AtIndex: (unsigned)i; \
+                {NSParameterAssert(i<_count); return ((const TYPE*)object_getIndexedIvars(self))[i];}\
+            - (void) set##CAPTYPE: (TYPE)value atIndex: (unsigned)i \
+                {NSParameterAssert(i<_count); ((TYPE*)object_getIndexedIvars(self))[i] = value;}\
+            @end
+
+
+// Declares IntArray class
+DeclareValueArrayOf(Int,int)
+
+DeclareValueArrayOf(Double,double)
+//
+//  ValueArray.m
+//  MYUtilities
+//
+//  Copyright 2008 Jens Alfke. All rights reserved.
+//
+
+#import "ValueArray.h"
+
+
+@implementation ValueArray
+
+
+- (id) initWithCount: (unsigned)count valueSize: (size_t)valueSize
+{
+    self = [super init];
+    if( self ) {
+        _count = count;
+        _valueSize = valueSize;
+    }
+    return self;
+}
+
++ (ValueArray*) valueArrayWithCount: (unsigned)count valueSize: (size_t)valueSize
+{
+    return [[(ValueArray*)NSAllocateObject(self,count*valueSize,nil)
+                            initWithCount: count valueSize: valueSize] 
+                                autorelease];
+}
+
+- (unsigned) count      {return _count;}
+- (size_t) valueSize    {return _valueSize;}
+
+- (const void*) valueAtIndex: (unsigned)i
+{
+    NSParameterAssert(i<_count);
+    return (const char*)object_getIndexedIvars(self) + i*_valueSize;
+}
+
+- (void) getValue: (void*)value atIndex: (unsigned)i
+{
+    NSParameterAssert(i<_count);
+    NSParameterAssert(value!=NULL);
+    memcpy(value, object_getIndexedIvars(self) + i*_valueSize, _valueSize);
+}
+
+- (void) setValue: (const void*)value atIndex: (unsigned)i
+{
+    NSParameterAssert(i<_count);
+    NSParameterAssert(value!=NULL);
+    memcpy(object_getIndexedIvars(self) + i*_valueSize, value, _valueSize);
+}
+
+
+
+- (id)initWithCoder:(NSCoder *)decoder
+{
+    NSParameterAssert([decoder allowsKeyedCoding]);
+    NSKeyedUnarchiver *arch = (NSKeyedUnarchiver*)decoder;
+    unsigned count = [arch decodeIntForKey: @"count"];