* Added more documentation.
* Minor API changes.
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[] = {
22 "application/octet-stream",
23 "text/plain; charset=UTF-8",
26 "application/x-cloudy-signed+yaml",
28 #define kNAbbreviations ((sizeof(kAbbreviations)/sizeof(const char*))) // cannot exceed 31!
32 @interface BLIPPackedProperties : BLIPProperties
36 const char **_strings;
44 // The base class just represents an immutable empty collection.
45 @implementation BLIPProperties
48 + (BLIPProperties*) propertiesWithEncodedData: (NSData*)data usedLength: (ssize_t*)usedLength
50 size_t available = data.length;
51 if( available < sizeof(UInt16) ) {
52 // Not enough available to read length:
58 const char *bytes = data.bytes;
59 size_t length = EndianU16_BtoN( *(UInt16*)bytes ) + sizeof(UInt16);
60 if( length > available ) {
61 // Properties not complete yet.
66 // Complete -- try to create an object:
67 BLIPProperties *props;
68 if( length > sizeof(UInt16) )
69 props = [[[BLIPPackedProperties alloc] initWithBytes: bytes length: length] autorelease];
71 props = [BLIPProperties properties];
73 *usedLength = props ?length :-1;
78 - (id) copyWithZone: (NSZone*)zone
83 - (id) mutableCopyWithZone: (NSZone*)zone
85 return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
88 - (BOOL) isEqual: (id)other
90 return [other isKindOfClass: [BLIPProperties class]]
91 && [self.allProperties isEqual: [other allProperties]];
94 - (NSString*) valueOfProperty: (NSString*)prop {return nil;}
95 - (NSDictionary*) allProperties {return [NSDictionary dictionary];}
96 - (NSUInteger) count {return 0;}
97 - (NSUInteger) dataLength {return sizeof(UInt16);}
99 - (NSData*) encodedData
102 return [NSData dataWithBytes: &len length: sizeof(len)];
106 + (BLIPProperties*) properties
108 static BLIPProperties *sEmptyInstance;
109 if( ! sEmptyInstance )
110 sEmptyInstance = [[self alloc] init];
111 return sEmptyInstance;
119 /** Internal immutable subclass that keeps its contents in the packed data representation. */
120 @implementation BLIPPackedProperties
123 - (id) initWithBytes: (const char*)bytes length: (size_t)length
127 // Copy data, then skip the length field:
128 _data = [[NSData alloc] initWithBytes: bytes length: length];
129 bytes = (const char*)_data.bytes + sizeof(UInt16);
130 length -= sizeof(UInt16);
132 if( bytes[length-1]!='\0' )
135 // The data consists of consecutive NUL-terminated strings, alternating key/value:
136 unsigned capacity = 0;
137 const char *end = bytes+length;
138 for( const char *str=bytes; str < end; str += strlen(str)+1, _nStrings++ ) {
139 if( _nStrings >= capacity ) {
140 capacity = capacity ?(2*capacity) :4;
141 _strings = realloc(_strings, capacity*sizeof(const char**));
143 UInt8 first = (UInt8)str[0];
144 if( first>'\0' && first<' ' && str[1]=='\0' ) {
145 // Single-control-character property string is an abbreviation:
146 if( first > kNAbbreviations )
148 _strings[_nStrings] = kAbbreviations[first-1];
150 _strings[_nStrings] = str;
153 // It's illegal for the data to end with a non-NUL or for there to be an odd number of strings:
154 if( (_nStrings & 1) )
160 Warn(@"BLIPProperties: invalid data");
170 if( _strings ) free(_strings);
175 - (id) copyWithZone: (NSZone*)zone
177 return [self retain];
180 - (id) mutableCopyWithZone: (NSZone*)zone
182 return [[BLIPMutableProperties allocWithZone: zone] initWithDictionary: self.allProperties];
186 - (NSString*) valueOfProperty: (NSString*)prop
188 const char *propStr = [prop UTF8String];
190 // Search in reverse order so that later values will take precedence over earlier ones.
191 for( int i=_nStrings-2; i>=0; i-=2 ) {
192 if( strcmp(propStr, _strings[i]) == 0 )
193 return [NSString stringWithUTF8String: _strings[i+1]];
199 - (NSDictionary*) allProperties
201 NSMutableDictionary *props = [NSMutableDictionary dictionaryWithCapacity: _nStrings/2];
202 // Add values in forward order so that later ones will overwrite (take precedence over)
203 // earlier ones, which matches the behavior of -valueOfProperty.
204 // (However, note that unlike -valueOfProperty, this dictionary is case-sensitive!)
205 for( int i=0; i<_nStrings; i+=2 ) {
206 NSString *key = [[NSString alloc] initWithUTF8String: _strings[i]];
207 NSString *value = [[NSString alloc] initWithUTF8String: _strings[i+1]];
209 [props setObject: value forKey: key];
217 - (NSUInteger) count {return _nStrings/2;}
218 - (NSData*) encodedData {return _data;}
219 - (NSUInteger) dataLength {return _data.length;}
226 /** Mutable subclass that stores its properties in an NSMutableDictionary. */
227 @implementation BLIPMutableProperties
230 + (BLIPProperties*) properties
232 return [[self alloc] initWithDictionary: nil];
239 _properties = [[NSMutableDictionary alloc] init];
244 - (id) initWithDictionary: (NSDictionary*)dict
248 _properties = dict ?[dict mutableCopy] :[[NSMutableDictionary alloc] init];
253 - (id) initWithProperties: (BLIPProperties*)properties
255 return [self initWithDictionary: [properties allProperties]];
260 [_properties release];
264 - (id) copyWithZone: (NSZone*)zone
267 BLIPProperties *copy = [BLIPProperties propertiesWithEncodedData: self.encodedData usedLength: &usedLength];
269 return [copy retain];
273 - (NSString*) valueOfProperty: (NSString*)prop
275 return [_properties objectForKey: prop];
278 - (NSDictionary*) allProperties
283 - (NSUInteger) count {return _properties.count;}
286 static void appendStr( NSMutableData *data, NSString *str ) {
287 const char *utf8 = [str UTF8String];
288 size_t size = strlen(utf8)+1;
289 for( int i=0; i<kNAbbreviations; i++ )
290 if( memcmp(utf8,kAbbreviations[i],size)==0 ) {
291 const UInt8 abbrev[2] = {i+1,0};
292 [data appendBytes: &abbrev length: 2];
295 [data appendBytes: utf8 length: size];
298 - (NSData*) encodedData
300 NSMutableData *data = [NSMutableData dataWithCapacity: 16*_properties.count];
301 [data setLength: sizeof(UInt16)]; // leave room for length
302 for( NSString *name in _properties ) {
303 appendStr(data,name);
304 appendStr(data,[_properties objectForKey: name]);
307 NSUInteger length = data.length - sizeof(UInt16);
308 if( length > 0xFFFF )
310 *(UInt16*)[data mutableBytes] = EndianU16_NtoB((UInt16)length);
315 - (void) setValue: (NSString*)value ofProperty: (NSString*)prop
317 Assert(prop.length>0);
319 [_properties setObject: value forKey: prop];
321 [_properties removeObjectForKey: prop];
325 - (void) setAllProperties: (NSDictionary*)properties
327 if( properties.count ) {
328 for( id key in properties ) {
329 Assert([key isKindOfClass: [NSString class]]);
330 Assert([key length] > 0);
331 Assert([[properties objectForKey: key] isKindOfClass: [NSString class]]);
333 [_properties setDictionary: properties];
335 [_properties removeAllObjects];
344 TestCase(BLIPProperties) {
345 BLIPProperties *props;
347 props = [BLIPProperties properties];
349 CAssertEq(props.count,0);
350 Log(@"Empty properties:\n%@", props.allProperties);
351 NSData *data = props.encodedData;
352 Log(@"As data: %@", data);
353 CAssertEqual(data,[NSMutableData dataWithLength: 2]);
355 BLIPMutableProperties *mprops = [props mutableCopy];
356 Log(@"Mutable copy:\n%@", mprops.allProperties);
357 data = mprops.encodedData;
358 Log(@"As data: %@", data);
359 CAssertEqual(data,[NSMutableData dataWithLength: 2]);
362 props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
363 CAssertEq(used,data.length);
364 CAssertEqual(props,mprops);
366 [mprops setValue: @"Jens" ofProperty: @"First-Name"];
367 [mprops setValue: @"Alfke" ofProperty: @"Last-Name"];
368 [mprops setValue: @"" ofProperty: @"Empty-String"];
369 [mprops setValue: @"Z" ofProperty: @"A"];
370 Log(@"With properties:\n%@", mprops.allProperties);
371 data = mprops.encodedData;
372 Log(@"As data: %@", data);
374 for( unsigned len=0; len<data.length; len++ ) {
375 props = [BLIPProperties propertiesWithEncodedData: [data subdataWithRange: NSMakeRange(0,len)]
377 CAssertEq(props,nil);
380 props = [BLIPProperties propertiesWithEncodedData: data usedLength: &used];
381 CAssertEq(used,data.length);
382 Log(@"Read back in:\n%@",props.allProperties);
383 CAssertEqual(props,mprops);
385 NSDictionary *all = mprops.allProperties;
386 for( NSString *prop in all )
387 CAssertEqual([props valueOfProperty: prop],[all objectForKey: prop]);
392 Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
394 Redistribution and use in source and binary forms, with or without modification, are permitted
395 provided that the following conditions are met:
397 * Redistributions of source code must retain the above copyright notice, this list of conditions
398 and the following disclaimer.
399 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
400 and the following disclaimer in the documentation and/or other materials provided with the
403 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
404 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
405 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
406 BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
407 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
408 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
409 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
410 THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.