BLIP/BLIPProperties.m
author Jens Alfke <jens@mooseyard.com>
Sat May 24 17:25:06 2008 -0700 (2008-05-24)
changeset 2 9fdd8dba529c
parent 1 8267d5c429c4
child 8 6f539dd9921c
permissions -rw-r--r--
* Added more documentation.
* Minor API changes.
jens@0
     1
//
jens@0
     2
//  BLIPProperties.m
jens@0
     3
//  MYNetwork
jens@0
     4
//
jens@0
     5
//  Created by Jens Alfke on 5/13/08.
jens@0
     6
//  Copyright 2008 Jens Alfke. All rights reserved.
jens@0
     7
//
jens@0
     8
jens@0
     9
#import "BLIPProperties.h"
jens@1
    10
#import "Logging.h"
jens@1
    11
#import "Test.h"
jens@0
    12
jens@0
    13
jens@0
    14
/** Common strings are abbreviated as single-byte strings in the packed form.
jens@0
    15
    The ascii value of the single character minus one is the index into this table. */
jens@0
    16
static const char* kAbbreviations[] = {
jens@0
    17
    "Content-Type",
jens@0
    18
    "Profile",
jens@0
    19
    "Channel"
jens@0
    20
    "Error-Code"
jens@0
    21
    "Error-Domain",
jens@0
    22
    "application/octet-stream",
jens@2
    23
    "text/plain; charset=UTF-8",
jens@0
    24
    "text/xml",
jens@0
    25
    "text/yaml",
jens@0
    26
    "application/x-cloudy-signed+yaml",
jens@0
    27
};
jens@0
    28
#define kNAbbreviations ((sizeof(kAbbreviations)/sizeof(const char*)))  // cannot exceed 31!
jens@0
    29
jens@0
    30
jens@0
    31
jens@0
    32
@interface BLIPPackedProperties : BLIPProperties
jens@0
    33
{
jens@0
    34
    NSData *_data;
jens@0
    35
    int _count;
jens@0
    36
    const char **_strings;
jens@0
    37
    int _nStrings;
jens@0
    38
}
jens@0
    39
jens@0
    40
@end
jens@0
    41
jens@0
    42
jens@0
    43
jens@0
    44
// The base class just represents an immutable empty collection.
jens@0
    45
@implementation BLIPProperties
jens@0
    46
jens@0
    47
jens@0
    48
+ (BLIPProperties*) propertiesWithEncodedData: (NSData*)data usedLength: (ssize_t*)usedLength
jens@0
    49
{
jens@0
    50
    size_t available = data.length;
jens@0
    51
    if( available < sizeof(UInt16) ) {
jens@0
    52
        // Not enough available to read length:
jens@0
    53
        *usedLength = 0;
jens@0
    54
        return nil;
jens@0
    55
    }
jens@0
    56
    
jens@0
    57
    // Read the length:
jens@0
    58
    const char *bytes = data.bytes;
jens@0
    59
    size_t length = EndianU16_BtoN( *(UInt16*)bytes ) + sizeof(UInt16);
jens@0
    60
    if( length > available ) {
jens@0
    61
        // Properties not complete yet.
jens@0
    62
        *usedLength = 0;
jens@0
    63
        return nil;
jens@0
    64
    }
jens@0
    65
    
jens@0
    66
    // Complete -- try to create an object:
jens@0
    67
    BLIPProperties *props;
jens@0
    68
    if( length > sizeof(UInt16) )
jens@0
    69
        props = [[[BLIPPackedProperties alloc] initWithBytes: bytes length: length] autorelease];
jens@0
    70
    else
jens@0
    71
        props = [BLIPProperties properties];
jens@0
    72
    
jens@0
    73
    *usedLength = props ?length :-1;
jens@0
    74
    return props;
jens@0
    75
}
jens@0
    76
jens@0
    77
jens@0
    78
- (id) copyWithZone: (NSZone*)zone
jens@0
    79
{
jens@0
    80
    return [self retain];
jens@0
    81
}
jens@0
    82
jens@0
    83
- (id) mutableCopyWithZone: (NSZone*)zone
jens@0
    84
{
jens@0
    85
    return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
jens@0
    86
}
jens@0
    87
jens@0
    88
- (BOOL) isEqual: (id)other
jens@0
    89
{
jens@0
    90
    return [other isKindOfClass: [BLIPProperties class]]
jens@0
    91
        && [self.allProperties isEqual: [other allProperties]];
jens@0
    92
}
jens@0
    93
jens@0
    94
- (NSString*) valueOfProperty: (NSString*)prop  {return nil;}
jens@0
    95
- (NSDictionary*) allProperties                 {return [NSDictionary dictionary];}
jens@0
    96
- (NSUInteger) count                            {return 0;}
jens@0
    97
- (NSUInteger) dataLength                       {return sizeof(UInt16);}
jens@0
    98
jens@0
    99
- (NSData*) encodedData
jens@0
   100
{
jens@0
   101
    UInt16 len = 0;
jens@0
   102
    return [NSData dataWithBytes: &len length: sizeof(len)];
jens@0
   103
}
jens@0
   104
jens@0
   105
jens@0
   106
+ (BLIPProperties*) properties
jens@0
   107
{
jens@0
   108
    static BLIPProperties *sEmptyInstance;
jens@0
   109
    if( ! sEmptyInstance )
jens@0
   110
        sEmptyInstance = [[self alloc] init];
jens@0
   111
    return sEmptyInstance;
jens@0
   112
}
jens@0
   113
jens@0
   114
jens@0
   115
@end
jens@0
   116
jens@0
   117
jens@0
   118
jens@0
   119
/** Internal immutable subclass that keeps its contents in the packed data representation. */
jens@0
   120
@implementation BLIPPackedProperties
jens@0
   121
jens@0
   122
jens@0
   123
- (id) initWithBytes: (const char*)bytes length: (size_t)length
jens@0
   124
{
jens@0
   125
    self = [super init];
jens@0
   126
    if (self != nil) {
jens@0
   127
        // Copy data, then skip the length field:
jens@0
   128
        _data = [[NSData alloc] initWithBytes: bytes length: length];
jens@0
   129
        bytes = (const char*)_data.bytes + sizeof(UInt16);
jens@0
   130
        length -= sizeof(UInt16);
jens@0
   131
        
jens@0
   132
        if( bytes[length-1]!='\0' )
jens@0
   133
            goto fail;
jens@0
   134
jens@0
   135
        // The data consists of consecutive NUL-terminated strings, alternating key/value:
jens@0
   136
        unsigned capacity = 0;
jens@0
   137
        const char *end = bytes+length;
jens@0
   138
        for( const char *str=bytes; str < end; str += strlen(str)+1, _nStrings++ ) {
jens@0
   139
            if( _nStrings >= capacity ) {
jens@0
   140
                capacity = capacity ?(2*capacity) :4;
jens@0
   141
                _strings = realloc(_strings, capacity*sizeof(const char**));
jens@0
   142
            }
jens@0
   143
            UInt8 first = (UInt8)str[0];
jens@0
   144
            if( first>'\0' && first<' ' && str[1]=='\0' ) {
jens@0
   145
                // Single-control-character property string is an abbreviation:
jens@0
   146
                if( first > kNAbbreviations )
jens@0
   147
                    goto fail;
jens@0
   148
                _strings[_nStrings] = kAbbreviations[first-1];
jens@0
   149
            } else
jens@0
   150
                _strings[_nStrings] = str;
jens@0
   151
        }
jens@0
   152
        
jens@0
   153
        // It's illegal for the data to end with a non-NUL or for there to be an odd number of strings:
jens@0
   154
        if( (_nStrings & 1) )
jens@0
   155
            goto fail;
jens@0
   156
        
jens@0
   157
        return self;
jens@0
   158
            
jens@0
   159
    fail:
jens@0
   160
        Warn(@"BLIPProperties: invalid data");
jens@0
   161
        [self release];
jens@0
   162
        return nil;
jens@0
   163
    }
jens@0
   164
    return self;
jens@0
   165
}
jens@0
   166
jens@0
   167
jens@0
   168
- (void) dealloc
jens@0
   169
{
jens@0
   170
    if( _strings ) free(_strings);
jens@0
   171
    [_data release];
jens@0
   172
    [super dealloc];
jens@0
   173
}
jens@0
   174
jens@0
   175
- (id) copyWithZone: (NSZone*)zone
jens@0
   176
{
jens@0
   177
    return [self retain];
jens@0
   178
}
jens@0
   179
jens@0
   180
- (id) mutableCopyWithZone: (NSZone*)zone
jens@0
   181
{
jens@0
   182
    return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
jens@0
   183
}
jens@0
   184
jens@0
   185
jens@0
   186
- (NSString*) valueOfProperty: (NSString*)prop
jens@0
   187
{
jens@0
   188
    const char *propStr = [prop UTF8String];
jens@0
   189
    Assert(propStr);
jens@0
   190
    // Search in reverse order so that later values will take precedence over earlier ones.
jens@0
   191
    for( int i=_nStrings-2; i>=0; i-=2 ) {
jens@0
   192
        if( strcmp(propStr, _strings[i]) == 0 )
jens@0
   193
            return [NSString stringWithUTF8String: _strings[i+1]];
jens@0
   194
    }
jens@0
   195
    return nil;
jens@0
   196
}
jens@0
   197
jens@0
   198
jens@0
   199
- (NSDictionary*) allProperties
jens@0
   200
{
jens@0
   201
    NSMutableDictionary *props = [NSMutableDictionary dictionaryWithCapacity: _nStrings/2];
jens@0
   202
    // Add values in forward order so that later ones will overwrite (take precedence over)
jens@0
   203
    // earlier ones, which matches the behavior of -valueOfProperty.
jens@0
   204
    // (However, note that unlike -valueOfProperty, this dictionary is case-sensitive!)
jens@0
   205
    for( int i=0; i<_nStrings; i+=2 ) {
jens@0
   206
        NSString *key = [[NSString alloc] initWithUTF8String: _strings[i]];
jens@0
   207
        NSString *value = [[NSString alloc] initWithUTF8String: _strings[i+1]];
jens@0
   208
        if( key && value )
jens@0
   209
            [props setObject: value forKey: key];
jens@0
   210
        [key release];
jens@0
   211
        [value release];
jens@0
   212
    }
jens@0
   213
    return props;
jens@0
   214
}
jens@0
   215
jens@0
   216
jens@0
   217
- (NSUInteger) count        {return _nStrings/2;}
jens@0
   218
- (NSData*) encodedData            {return _data;}
jens@0
   219
- (NSUInteger) dataLength   {return _data.length;}
jens@0
   220
jens@0
   221
jens@0
   222
@end
jens@0
   223
jens@0
   224
jens@0
   225
jens@0
   226
/** Mutable subclass that stores its properties in an NSMutableDictionary. */
jens@0
   227
@implementation BLIPMutableProperties
jens@0
   228
jens@0
   229
jens@0
   230
+ (BLIPProperties*) properties
jens@0
   231
{
jens@0
   232
    return [[self alloc] initWithDictionary: nil];
jens@0
   233
}
jens@0
   234
jens@0
   235
- (id) init
jens@0
   236
{
jens@0
   237
    self = [super init];
jens@0
   238
    if (self != nil) {
jens@0
   239
        _properties = [[NSMutableDictionary alloc] init];
jens@0
   240
    }
jens@0
   241
    return self;
jens@0
   242
}
jens@0
   243
jens@0
   244
- (id) initWithDictionary: (NSDictionary*)dict
jens@0
   245
{
jens@0
   246
    self = [super init];
jens@0
   247
    if (self != nil) {
jens@0
   248
        _properties = dict ?[dict mutableCopy] :[[NSMutableDictionary alloc] init];
jens@0
   249
    }
jens@0
   250
    return self;
jens@0
   251
}
jens@0
   252
jens@0
   253
- (id) initWithProperties: (BLIPProperties*)properties
jens@0
   254
{
jens@0
   255
    return [self initWithDictionary: [properties allProperties]];
jens@0
   256
}
jens@0
   257
jens@0
   258
- (void) dealloc
jens@0
   259
{
jens@0
   260
    [_properties release];
jens@0
   261
    [super dealloc];
jens@0
   262
}
jens@0
   263
jens@0
   264
- (id) copyWithZone: (NSZone*)zone
jens@0
   265
{
jens@0
   266
    ssize_t usedLength;
jens@0
   267
    BLIPProperties *copy = [BLIPProperties propertiesWithEncodedData: self.encodedData usedLength: &usedLength];
jens@0
   268
    Assert(copy);
jens@0
   269
    return [copy retain];
jens@0
   270
}
jens@0
   271
jens@0
   272
jens@0
   273
- (NSString*) valueOfProperty: (NSString*)prop
jens@0
   274
{
jens@0
   275
    return [_properties objectForKey: prop];
jens@0
   276
}
jens@0
   277
jens@0
   278
- (NSDictionary*) allProperties
jens@0
   279
{
jens@0
   280
    return _properties;
jens@0
   281
}
jens@0
   282
jens@0
   283
- (NSUInteger) count        {return _properties.count;}
jens@0
   284
jens@0
   285
jens@0
   286
static void appendStr( NSMutableData *data, NSString *str ) {
jens@0
   287
    const char *utf8 = [str UTF8String];
jens@0
   288
    size_t size = strlen(utf8)+1;
jens@0
   289
    for( int i=0; i<kNAbbreviations; i++ )
jens@0
   290
        if( memcmp(utf8,kAbbreviations[i],size)==0 ) {
jens@0
   291
            const UInt8 abbrev[2] = {i+1,0};
jens@0
   292
            [data appendBytes: &abbrev length: 2];
jens@0
   293
            return;
jens@0
   294
        }
jens@0
   295
    [data appendBytes: utf8 length: size];
jens@0
   296
}
jens@0
   297
jens@0
   298
- (NSData*) encodedData
jens@0
   299
{
jens@0
   300
    NSMutableData *data = [NSMutableData dataWithCapacity: 16*_properties.count];
jens@0
   301
    [data setLength: sizeof(UInt16)]; // leave room for length
jens@0
   302
    for( NSString *name in _properties ) {
jens@0
   303
        appendStr(data,name);
jens@0
   304
        appendStr(data,[_properties objectForKey: name]);
jens@0
   305
    }
jens@0
   306
    
jens@0
   307
    NSUInteger length = data.length - sizeof(UInt16);
jens@0
   308
    if( length > 0xFFFF )
jens@0
   309
        return nil;
jens@0
   310
    *(UInt16*)[data mutableBytes] = EndianU16_NtoB((UInt16)length);
jens@0
   311
    return data;
jens@0
   312
}
jens@0
   313
jens@0
   314
    
jens@0
   315
- (void) setValue: (NSString*)value ofProperty: (NSString*)prop
jens@0
   316
{
jens@0
   317
    Assert(prop.length>0);
jens@0
   318
    if( value )
jens@0
   319
        [_properties setObject: value forKey: prop];
jens@0
   320
    else
jens@0
   321
        [_properties removeObjectForKey: prop];
jens@0
   322
}
jens@0
   323
jens@0
   324
jens@0
   325
- (void) setAllProperties: (NSDictionary*)properties
jens@0
   326
{
jens@0
   327
    if( properties.count ) {
jens@0
   328
        for( id key in properties ) {
jens@0
   329
            Assert([key isKindOfClass: [NSString class]]);
jens@0
   330
            Assert([key length] > 0);
jens@0
   331
            Assert([[properties objectForKey: key] isKindOfClass: [NSString class]]);
jens@0
   332
        }
jens@0
   333
        [_properties setDictionary: properties];
jens@0
   334
    } else
jens@0
   335
        [_properties removeAllObjects];
jens@0
   336
}
jens@0
   337
jens@0
   338
jens@0
   339
@end
jens@0
   340
jens@0
   341
jens@0
   342
jens@0
   343
jens@0
   344
TestCase(BLIPProperties) {
jens@0
   345
    BLIPProperties *props;
jens@0
   346
    
jens@0
   347
    props = [BLIPProperties properties];
jens@0
   348
    CAssert(props);
jens@0
   349
    CAssertEq(props.count,0);
jens@0
   350
    Log(@"Empty properties:\n%@", props.allProperties);
jens@0
   351
    NSData *data = props.encodedData;
jens@0
   352
    Log(@"As data: %@", data);
jens@0
   353
    CAssertEqual(data,[NSMutableData dataWithLength: 2]);
jens@0
   354
    
jens@0
   355
    BLIPMutableProperties *mprops = [props mutableCopy];
jens@0
   356
    Log(@"Mutable copy:\n%@", mprops.allProperties);
jens@0
   357
    data = mprops.encodedData;
jens@0
   358
    Log(@"As data: %@", data);
jens@0
   359
    CAssertEqual(data,[NSMutableData dataWithLength: 2]);
jens@0
   360
    
jens@0
   361
    ssize_t used;
jens@0
   362
    props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
jens@0
   363
    CAssertEq(used,data.length);
jens@0
   364
    CAssertEqual(props,mprops);
jens@0
   365
    
jens@0
   366
    [mprops setValue: @"Jens" ofProperty: @"First-Name"];
jens@0
   367
    [mprops setValue: @"Alfke" ofProperty: @"Last-Name"];
jens@0
   368
    [mprops setValue: @"" ofProperty: @"Empty-String"];
jens@0
   369
    [mprops setValue: @"Z" ofProperty: @"A"];
jens@0
   370
    Log(@"With properties:\n%@", mprops.allProperties);
jens@0
   371
    data = mprops.encodedData;
jens@0
   372
    Log(@"As data: %@", data);
jens@0
   373
    
jens@0
   374
    for( unsigned len=0; len<data.length; len++ ) {
jens@0
   375
        props = [BLIPProperties propertiesWithEncodedData: [data subdataWithRange: NSMakeRange(0,len)]
jens@0
   376
                                                                usedLength: &used];
jens@0
   377
        CAssertEq(props,nil);
jens@0
   378
        CAssertEq(used,0);
jens@0
   379
    }
jens@0
   380
    props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
jens@0
   381
    CAssertEq(used,data.length);
jens@0
   382
    Log(@"Read back in:\n%@",props.allProperties);
jens@0
   383
    CAssertEqual(props,mprops);
jens@0
   384
    
jens@0
   385
    NSDictionary *all = mprops.allProperties;
jens@0
   386
    for( NSString *prop in all )
jens@0
   387
        CAssertEqual([props valueOfProperty: prop],[all objectForKey: prop]);
jens@0
   388
}
jens@0
   389
jens@0
   390
jens@0
   391
/*
jens@0
   392
 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
jens@0
   393
 
jens@0
   394
 Redistribution and use in source and binary forms, with or without modification, are permitted
jens@0
   395
 provided that the following conditions are met:
jens@0
   396
 
jens@0
   397
 * Redistributions of source code must retain the above copyright notice, this list of conditions
jens@0
   398
 and the following disclaimer.
jens@0
   399
 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
jens@0
   400
 and the following disclaimer in the documentation and/or other materials provided with the
jens@0
   401
 distribution.
jens@0
   402
 
jens@0
   403
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
jens@0
   404
 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
jens@0
   405
 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
jens@0
   406
 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
jens@0
   407
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
jens@0
   408
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
jens@0
   409
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
jens@0
   410
 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
jens@0
   411
 */