Tweaked release to be immediate instead of on autorelease pool.
5 // Created by Jens Alfke on 5/13/08.
6 // Copyright 2008 Jens Alfke. All rights reserved.
9 #import "BLIPProperties.h"
14 /** Common strings are abbreviated as single-byte strings in the packed form.
15 The ascii value of the single character minus one is the index into this table. */
16 static const char* kAbbreviations[] = {
19 "application/octet-stream",
20 "text/plain; charset=UTF-8",
27 #define kNAbbreviations ((sizeof(kAbbreviations)/sizeof(const char*))) // cannot exceed 31!
31 @interface BLIPPackedProperties : BLIPProperties
35 const char **_strings;
43 // The base class just represents an immutable empty collection.
44 @implementation BLIPProperties
47 + (BLIPProperties*) propertiesWithEncodedData: (NSData*)data usedLength: (ssize_t*)usedLength
49 size_t available = data.length;
50 if( available < sizeof(UInt16) ) {
51 // Not enough available to read length:
57 const char *bytes = data.bytes;
58 size_t length = NSSwapBigShortToHost( *(UInt16*)bytes ) + sizeof(UInt16);
59 if( length > available ) {
60 // Properties not complete yet.
65 // Complete -- try to create an object:
66 BLIPProperties *props;
67 if( length > sizeof(UInt16) )
68 props = [[[BLIPPackedProperties alloc] initWithBytes: bytes length: length] autorelease];
70 props = [BLIPProperties properties];
72 *usedLength = props ?(ssize_t)length :-1;
77 - (id) copyWithZone: (NSZone*)zone
82 - (id) mutableCopyWithZone: (NSZone*)zone
84 return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
87 - (BOOL) isEqual: (id)other
89 return [other isKindOfClass: [BLIPProperties class]]
90 && [self.allProperties isEqual: [other allProperties]];
93 - (NSString*) valueOfProperty: (NSString*)prop {return nil;}
94 - (NSDictionary*) allProperties {return [NSDictionary dictionary];}
95 - (NSUInteger) count {return 0;}
96 - (NSUInteger) dataLength {return sizeof(UInt16);}
98 - (NSData*) encodedData
101 return [NSData dataWithBytes: &len length: sizeof(len)];
105 + (BLIPProperties*) properties
107 static BLIPProperties *sEmptyInstance;
108 if( ! sEmptyInstance )
109 sEmptyInstance = [[self alloc] init];
110 return sEmptyInstance;
118 /** Internal immutable subclass that keeps its contents in the packed data representation. */
119 @implementation BLIPPackedProperties
122 - (id) initWithBytes: (const char*)bytes length: (size_t)length
126 // Copy data, then skip the length field:
127 _data = [[NSData alloc] initWithBytes: bytes length: length];
128 bytes = (const char*)_data.bytes + sizeof(UInt16);
129 length -= sizeof(UInt16);
131 if( bytes[length-1]!='\0' )
134 // The data consists of consecutive NUL-terminated strings, alternating key/value:
136 const char *end = bytes+length;
137 for( const char *str=bytes; str < end; str += strlen(str)+1, _nStrings++ ) {
138 if( _nStrings >= capacity ) {
139 capacity = capacity ?(2*capacity) :4;
140 _strings = realloc(_strings, capacity*sizeof(const char**));
142 UInt8 first = (UInt8)str[0];
143 if( first>'\0' && first<' ' && str[1]=='\0' ) {
144 // Single-control-character property string is an abbreviation:
145 if( first > kNAbbreviations )
147 _strings[_nStrings] = kAbbreviations[first-1];
149 _strings[_nStrings] = str;
152 // It's illegal for the data to end with a non-NUL or for there to be an odd number of strings:
153 if( (_nStrings & 1) )
159 Warn(@"BLIPProperties: invalid data");
169 if( _strings ) free(_strings);
174 - (id) copyWithZone: (NSZone*)zone
176 return [self retain];
179 - (id) mutableCopyWithZone: (NSZone*)zone
181 return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
185 - (NSString*) valueOfProperty: (NSString*)prop
187 const char *propStr = [prop UTF8String];
189 // Search in reverse order so that later values will take precedence over earlier ones.
190 for( int i=_nStrings-2; i>=0; i-=2 ) {
191 if( strcmp(propStr, _strings[i]) == 0 )
192 return [NSString stringWithUTF8String: _strings[i+1]];
198 - (NSDictionary*) allProperties
200 NSMutableDictionary *props = [NSMutableDictionary dictionaryWithCapacity: _nStrings/2];
201 // Add values in forward order so that later ones will overwrite (take precedence over)
202 // earlier ones, which matches the behavior of -valueOfProperty.
203 // (However, note that unlike -valueOfProperty, this dictionary is case-sensitive!)
204 for( int i=0; i<_nStrings; i+=2 ) {
205 NSString *key = [[NSString alloc] initWithUTF8String: _strings[i]];
206 NSString *value = [[NSString alloc] initWithUTF8String: _strings[i+1]];
208 [props setObject: value forKey: key];
216 - (NSUInteger) count {return _nStrings/2;}
217 - (NSData*) encodedData {return _data;}
218 - (NSUInteger) dataLength {return _data.length;}
225 /** Mutable subclass that stores its properties in an NSMutableDictionary. */
226 @implementation BLIPMutableProperties
229 + (BLIPProperties*) properties
231 return [[[self alloc] initWithDictionary: nil] autorelease];
238 _properties = [[NSMutableDictionary alloc] init];
243 - (id) initWithDictionary: (NSDictionary*)dict
247 _properties = dict ?[dict mutableCopy] :[[NSMutableDictionary alloc] init];
252 - (id) initWithProperties: (BLIPProperties*)properties
254 return [self initWithDictionary: [properties allProperties]];
259 [_properties release];
263 - (id) copyWithZone: (NSZone*)zone
266 BLIPProperties *copy = [BLIPProperties propertiesWithEncodedData: self.encodedData usedLength: &usedLength];
268 return [copy retain];
272 - (NSString*) valueOfProperty: (NSString*)prop
274 return [_properties objectForKey: prop];
277 - (NSDictionary*) allProperties
282 - (NSUInteger) count {return _properties.count;}
285 static void appendStr( NSMutableData *data, NSString *str ) {
286 const char *utf8 = [str UTF8String];
287 size_t size = strlen(utf8)+1;
288 for( unsigned i=0; i<kNAbbreviations; i++ )
289 if( memcmp(utf8,kAbbreviations[i],size)==0 ) {
290 const UInt8 abbrev[2] = {i+1,0};
291 [data appendBytes: &abbrev length: 2];
294 [data appendBytes: utf8 length: size];
297 - (NSData*) encodedData
299 NSMutableData *data = [NSMutableData dataWithCapacity: 16*_properties.count];
300 [data setLength: sizeof(UInt16)]; // leave room for length
301 for( NSString *name in _properties ) {
302 appendStr(data,name);
303 appendStr(data,[_properties objectForKey: name]);
306 NSUInteger length = data.length - sizeof(UInt16);
307 if( length > 0xFFFF )
309 *(UInt16*)[data mutableBytes] = NSSwapHostShortToBig((UInt16)length);
314 - (void) setValue: (NSString*)value ofProperty: (NSString*)prop
316 Assert(prop.length>0);
318 [_properties setObject: value forKey: prop];
320 [_properties removeObjectForKey: prop];
324 - (void) setAllProperties: (NSDictionary*)properties
326 if( properties.count ) {
327 for( id key in properties ) {
328 Assert([key isKindOfClass: [NSString class]]);
329 Assert([key length] > 0);
330 Assert([[properties objectForKey: key] isKindOfClass: [NSString class]]);
332 [_properties setDictionary: properties];
334 [_properties removeAllObjects];
343 TestCase(BLIPProperties) {
344 BLIPProperties *props;
346 props = [BLIPProperties properties];
348 CAssertEq(props.count,0U);
349 Log(@"Empty properties:\n%@", props.allProperties);
350 NSData *data = props.encodedData;
351 Log(@"As data: %@", data);
352 CAssertEqual(data,[NSMutableData dataWithLength: 2]);
354 BLIPMutableProperties *mprops = [props mutableCopy];
355 Log(@"Mutable copy:\n%@", mprops.allProperties);
356 data = mprops.encodedData;
357 Log(@"As data: %@", data);
358 CAssertEqual(data,[NSMutableData dataWithLength: 2]);
361 props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
362 CAssertEq(used,(ssize_t)data.length);
363 CAssertEqual(props,mprops);
365 [mprops setValue: @"Jens" ofProperty: @"First-Name"];
366 [mprops setValue: @"Alfke" ofProperty: @"Last-Name"];
367 [mprops setValue: @"" ofProperty: @"Empty-String"];
368 [mprops setValue: @"Z" ofProperty: @"A"];
369 Log(@"With properties:\n%@", mprops.allProperties);
370 data = mprops.encodedData;
371 Log(@"As data: %@", data);
373 for( unsigned len=0; len<data.length; len++ ) {
374 props = [BLIPProperties propertiesWithEncodedData: [data subdataWithRange: NSMakeRange(0,len)]
376 CAssertEq(props,nil);
379 props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
380 CAssertEq(used,(ssize_t)data.length);
381 Log(@"Read back in:\n%@",props.allProperties);
382 CAssertEqual(props,mprops);
384 NSDictionary *all = mprops.allProperties;
385 for( NSString *prop in all )
386 CAssertEqual([props valueOfProperty: prop],[all objectForKey: prop]);
393 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
395 Redistribution and use in source and binary forms, with or without modification, are permitted
396 provided that the following conditions are met:
398 * Redistributions of source code must retain the above copyright notice, this list of conditions
399 and the following disclaimer.
400 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
401 and the following disclaimer in the documentation and/or other materials provided with the
404 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
405 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
406 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
407 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
408 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
409 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
410 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
411 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.