Commits

Jens Alfke committed e1cb94c

- Added DictObject.
- Fixed deprecation warning in Base64.m.
- Fixed a termination bug in MYTask, and made it log the elapsed time in debug builds with MYTaskVerbose logging on.

  • Participants
  • Parent commits e833e9e

Comments (0)

Files changed (5)

     // 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];
+    NSString * base64String = [[[NSString alloc] initWithBytes: base64Pointer 
+                                                       length: base64Length
+                                                     encoding: NSASCIIStringEncoding]
+                               autorelease];
     
     // Clean up and go home
     BIO_free_all(mem);

File DictObject.h

+//
+//  DictObject.h
+//  MYUtilities
+//
+//  Created by Jens Alfke on 8/6/09.
+//  Copyright 2009 Jens Alfke. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+/** A generic object whose properties are stored in an NSDictionary.
+    You can subclass this and declare properties in the subclass without needing to implement them
+    or make instance variables; simply note them as '@dynamic' in the @implementation.
+    The property values will automatically be stored in the object's dictionary. */
+@interface DictObject : NSObject <NSCopying, NSMutableCopying>
+{
+  @private
+    NSMutableDictionary *_dict;
+    BOOL _mutable;
+}
+
+/** Creates an immutable instance with the given property values. */
+- (id) initWithDictionary: (NSDictionary*)dict;
+
+/** Creates a mutable instance with the given property values (none, if dict is nil.) */
+- (id) initMutableWithDictionary: (NSDictionary*)dict;
+
+/** The object's property dictionary. */
+@property (readonly) NSDictionary* dictionary;
+
+/** The object's property dictionary in mutable form.
+    Calling this will raise an exception if the object is immutable. */
+@property (readonly) NSMutableDictionary* mutableDictionary;
+
+/** Makes the object immutable from now on. 
+    Any further attempt to set a property value will result in a runtime exception. */
+- (void) makeImmutable;
+
+@end

File DictObject.m

+//
+//  DictObject.m
+//  MYUtilities
+//
+//  Created by Jens Alfke on 8/6/09.
+//  Copyright 2009 Jens Alfke. All rights reserved.
+//
+
+#import "DictObject.h"
+#import <objc/runtime.h>
+
+
+@implementation DictObject
+
+- (id) initWithDictionary: (NSDictionary*)dict {
+    Assert(dict);
+    self = [super init];
+    if (self != nil) {
+        _dict = [dict copy];
+    }
+    return self;
+}
+
+- (id) initMutableWithDictionary: (NSDictionary*)dict {
+    Assert(dict);
+    self = [super init];
+    if (self != nil) {
+        _dict = dict ?[dict mutableCopy] :$mdict();
+        _mutable = YES;
+    }
+    return self;
+}
+
+- (void) dealloc
+{
+    [_dict release];
+    [super dealloc];
+}
+
+- (id) copyWithZone: (NSZone*)zone {
+    if (_mutable)
+        return [[[self class] allocWithZone: zone] initWithDictionary: _dict];
+    else
+        return [self retain];
+}
+
+- (id) mutableCopyWithZone: (NSZone*)zone {
+    return [[[self class] allocWithZone: zone] initMutableWithDictionary: _dict];
+}
+
+@synthesize dictionary=_dict;
+
+- (NSMutableDictionary*) mutableDictionary {
+    Assert(_mutable, @"Attempt to access mutable dictionary of immutable instance %@", self);
+    return _dict;
+}
+
+- (NSString*) description {
+    return $sprintf(@"%@%s%@", [self class], (_mutable ?"*" :""), _dict.description);
+}
+
+- (BOOL) isEqual: (id)other {
+    return [other isKindOfClass: [DictObject class]] && [_dict isEqual: ((DictObject*)other)->_dict];
+}
+
+- (NSUInteger) hash {
+    return [_dict hash];
+}
+
+- (void) makeImmutable {
+    _mutable = NO;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// The rest of this class implementation is adapted from Apple's sample code:
+// http://developer.apple.com/samplecode/DynamicProperties
+
+
+// to speed this code up, should create a map from SEL to NSString mapping selectors to their keys.
+
+// converts a getter selector to an NSString, equivalent to NSStringFromSelector().
+NS_INLINE NSString *getterKey(SEL sel) {
+    return [NSString stringWithUTF8String:sel_getName(sel)];
+}
+
+// converts a setter selector, of the form "set<Key>:" to an NSString of the form @"<key>".
+NS_INLINE NSString *setterKey(SEL sel) {
+    const char* name = sel_getName(sel) + 3; // skip past 'set'
+    int length = strlen(name);
+    char buffer[1 + length];
+    strcpy(buffer, name);
+    buffer[0] = tolower(buffer[0]);
+    buffer[length - 1] = '\0';
+    return [NSString stringWithUTF8String:buffer];
+}
+
+// Generic accessor methods for property types id, double, and NSRect.
+
+static void setIdProperty(DictObject *self, SEL name, id value) {
+    CAssert(self->_mutable, @"Attempt to call %s on an immutable %@", sel_getName(name),[self class]);
+    NSString *key = setterKey(name);
+    if (value )
+        [self->_dict setObject:value forKey: key];
+    else
+        [self->_dict removeObjectForKey: key];
+}
+
+static id getIdProperty(DictObject *self, SEL name) {
+    return [self->_dict objectForKey:getterKey(name)];
+}
+
+static void setIntProperty(DictObject *self, SEL name, int value) {
+    CAssert(self->_mutable, @"Attempt to call %s on an immutable %@", sel_getName(name),[self class]);
+    [self->_dict setObject:[NSNumber numberWithInt:value] forKey:setterKey(name)];
+}
+
+static int getIntProperty(DictObject *self, SEL name) {
+    return [[self->_dict objectForKey:getterKey(name)] intValue];
+}
+
+static void setDoubleProperty(DictObject *self, SEL name, double value) {
+    CAssert(self->_mutable, @"Attempt to call %s on an immutable %@", sel_getName(name),[self class]);
+    [self->_dict setObject:[NSNumber numberWithDouble:value] forKey:setterKey(name)];
+}
+
+static double getDoubleProperty(DictObject *self, SEL name) {
+    id number = [self->_dict objectForKey:getterKey(name)];
+    return number ?[number doubleValue] :0.0;
+}
+
+
+static const char* getPropertyType(objc_property_t property, BOOL *outIsSettable) {
+    *outIsSettable = YES;
+    const char *result = "@";
+    
+    // Copy property attributes into a writeable buffer:
+    const char *attributes = property_getAttributes(property);
+    char buffer[1 + strlen(attributes)];
+    strcpy(buffer, attributes);
+    
+    // Scan the comma-delimited sections of the string:
+    char *state = buffer, *attribute;
+    while ((attribute = strsep(&state, ",")) != NULL) {
+        switch (attribute[0]) {
+            case 'T':       // Property type in @encode format
+                result = (const char *)[[NSData dataWithBytes: (attribute + 1) 
+                                                       length: strlen(attribute)] bytes];
+                break;
+            case 'R':       // Read-only indicator
+                *outIsSettable = NO;
+                break;
+        }
+    }
+    return result;
+}
+
+static BOOL getPropertyInfo(Class cls, 
+                            NSString *propertyName, 
+                            BOOL setter,
+                            Class *propertyClass,
+                            const char* *propertyType) {
+    const char *name = [propertyName UTF8String];
+    while (cls != NULL && cls != [DictObject class]) {
+        objc_property_t property = class_getProperty(cls, name);
+        if (property) {
+            *propertyClass = cls;
+            BOOL isSettable;
+            *propertyType = getPropertyType(property, &isSettable);
+            if (setter && !isSettable)
+                return NO;
+            return YES;
+        }
+        cls = class_getSuperclass(cls);
+    }
+    return NO;
+}
+
++ (BOOL)resolveInstanceMethod:(SEL)sel {
+    const char *name = sel_getName(sel);
+    Class propertyClass;
+    const char *propertyType;
+    IMP accessor = NULL;
+    const char *signature = NULL;
+    
+    // TODO:  handle more property types. This code handles id, int, and double.
+    if (strncmp("set", name, 3) == 0) {
+        // choose an appropriately typed generic setter function.
+        if (getPropertyInfo(self, setterKey(sel), YES, &propertyClass, &propertyType)) {
+            switch (propertyType[0]) {
+                case _C_ID:
+                    accessor = (IMP)setIdProperty;
+                    signature = "v@:@"; 
+                    break;
+                case _C_INT:
+                    accessor = (IMP)setIntProperty;
+                    signature = "v@:i"; 
+                    break;
+                case _C_DBL:
+                    accessor = (IMP)setDoubleProperty;
+                    signature = "v@:d"; 
+                    break;
+                default:
+                    Warn(@"Unsupported value type '%s' for setter %s of DictObject subclass %@", 
+                         propertyType,name,self);
+                    break;
+            }
+        }
+    } else {
+        // choose an appropriately typed getter function.
+        if (getPropertyInfo(self, getterKey(sel), NO, &propertyClass, &propertyType)) {
+            switch (propertyType[0]) {
+                case _C_ID:
+                    accessor = (IMP)getIdProperty;
+                    signature = "@@:"; 
+                    break;
+                case _C_INT:
+                    accessor = (IMP)getIntProperty;
+                    signature = "i@:"; 
+                    break;
+                case _C_DBL:
+                    accessor = (IMP)getDoubleProperty;
+                    signature = "d@:"; 
+                    break;
+                default:
+                    Warn(@"Unsupported value type '%s' for getter %s of DictObject subclass %@", 
+                         propertyType, name,self);
+                    break;
+            }
+        }
+    }
+    if (accessor && signature) {
+        Log(@"Creating dynamic accessor method -[%@ %s]", self, name);
+        class_addMethod(propertyClass, sel, accessor, signature);
+        return YES;
+    }
+    return NO;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+@end
+
+
+
+
+#if DEBUG
+
+@interface TestDictObject : DictObject
+@property (readwrite,copy) NSString *stringy;
+@property (readonly) int intey;
+@property (readwrite) double doubley;
+@end
+
+@implementation TestDictObject
+@dynamic stringy, intey, doubley;
+@end
+
+
+TestCase(DictObject) {
+    TestDictObject *test = [[TestDictObject alloc] initWithDictionary: $dict()];
+    Log(@"Testing empty object: %@", test);
+    CAssertEqual(test.dictionary, $dict());
+    CAssertEqual(test.stringy, nil);
+    CAssertEq(test.intey, 0);
+    CAssertEq(test.doubley, 0.0);
+    [test release];
+    
+    NSDictionary *dict = $dict( {@"stringy", @"String value"},
+                                {@"intey", $object(-6789)} );
+    test = [[TestDictObject alloc] initMutableWithDictionary: dict];
+    Log(@"Testing immutable object: %@", test);
+    CAssertEqual(test.dictionary, dict);
+    Log(@"test.stringy = %@", test.stringy);
+    CAssertEqual(test.stringy, @"String value");
+    Log(@"test.intey = %i", test.intey);
+    CAssertEq(test.intey, -6789);
+    Log(@"test.doubley = %g", test.doubley);
+    CAssertEq(test.doubley, 0.0);
+    
+    test.stringy = nil;
+    CAssertEqual(test.stringy, nil);
+    test.doubley = 123.456;
+    CAssertEq(test.doubley, 123.456);
+    
+    CAssert(![test respondsToSelector: @selector(setIntey:)]);
+    CAssert(![test respondsToSelector: @selector(size)]);
+    
+    [test makeImmutable];
+    @try{
+        test.doubley = 1.0;
+        CAssert(NO, @"setting doubley should have failed");
+    }@catch( NSException *x ) {
+    }
+}
+
+#endif DEBUG
     NSString *_output;
     NSMutableArray *_modes;
     BOOL _isRunning, _taskRunning;
+#if DEBUG
+    CFAbsoluteTime _startTime;
+#endif
 }
 
 - (id) initWithCommand: (NSString*)subcommand, ... NS_REQUIRES_NIL_TERMINATION;
                                                  name: NSTaskDidTerminateNotification
                                                object: _task];
     
+#if DEBUG
+    _startTime = CFAbsoluteTimeGetCurrent();
+#endif
+    
     @try{
         [_task launch];
     }@catch( id x ) {
 
 - (BOOL) _shouldFinishUp
 {
-    return !_task.isRunning && (_ignoreOutput || (!_outHandle && !_errHandle));
+    return _isRunning && !_task.isRunning && (_ignoreOutput || (!_outHandle && !_errHandle));
 }
 
 
 - (void) _finishUp
 {
     [NSObject cancelPreviousPerformRequestsWithTarget: self selector: @selector(_finishUp) object: nil];
+    if (!_isRunning)
+        return;
+    
     _resultCode = _task.terminationStatus;
     [self _close];
 
-    LogTo(MYTaskVerbose, @"Finished!");
-
+#if DEBUG
+    if (_startTime > 0.0) {
+        CFAbsoluteTime elapsed = CFAbsoluteTimeGetCurrent() - _startTime;
+        _startTime = 0.0;
+        LogTo(MYTask, @"    Finished! (status=%i) -- %.3f sec", _resultCode, elapsed);
+    } else {
+        Warn(@"_finishUp called twice");
+    }
+#else
+    LogTo(MYTaskVerbose, @"Finished! (status=%i)", _resultCode);
+#endif
+    
     if( _resultCode != 0 ) {
         // Handle errors:
         NSString *errStr = nil;