adium 3106:f75c7bfd396b: Restructure AHHyperlinkScanner for bett...

commits at adium.im commits at adium.im
Fri Feb 5 00:29:43 UTC 2010


details:	http://hg.adium.im/adium/rev/f75c7bfd396b
revision:	3106:f75c7bfd396b
author:		Stephen Holt <sholt at adium.im>
date:		Thu Feb 04 18:54:56 2010 -0500

Restructure AHHyperlinkScanner for better synchronization to nextURI.
Subject: adium 3107:ad12d0d854b9: Make the current scan location a property. Less code is good. Atomicy is good.

details:	http://hg.adium.im/adium/rev/ad12d0d854b9
revision:	3107:ad12d0d854b9
author:		Stephen Holt <sholt at adium.im>
date:		Thu Feb 04 19:28:56 2010 -0500

Make the current scan location a property. Less code is good. Atomicy is good.
Subject: adium 3108:7754de9d8f18: Create the linkified string once and cache it, also make it a r/o property.

details:	http://hg.adium.im/adium/rev/7754de9d8f18
revision:	3108:7754de9d8f18
author:		Stephen Holt <sholt at adium.im>
date:		Thu Feb 04 19:28:58 2010 -0500

Create the linkified string once and cache it, also make it a r/o property.

It's probably rare that the linkified string will be requested more than once, but I'd rather not re-calculate it if it is.

diffs (488 lines):

diff -r e864e707fb51 -r 7754de9d8f18 Frameworks/AutoHyperlinks Framework/Source/AHHyperlinkScanner.h
--- a/Frameworks/AutoHyperlinks Framework/Source/AHHyperlinkScanner.h	Wed Feb 03 22:06:53 2010 -0500
+++ b/Frameworks/AutoHyperlinks Framework/Source/AHHyperlinkScanner.h	Thu Feb 04 19:28:58 2010 -0500
@@ -44,14 +44,17 @@
 
 @interface AHHyperlinkScanner : NSObject <NSFastEnumeration>
 {
-	NSDictionary				*m_urlSchemes;
-	NSString					*m_scanString;
-	NSAttributedString			*m_scanAttrString;
-	BOOL						 m_strictChecking;
-	unsigned long				 m_scanLocation;
-	unsigned long				 m_scanStringLength;
+	NSDictionary        *m_urlSchemes;
+	NSString            *m_scanString;
+	NSAttributedString  *m_scanAttrString;
+	NSAttributedString  *m_linkifiedString;
+	BOOL                 m_strictChecking;
+	unsigned long        m_scanLocation;
+	unsigned long        m_scanStringLength;
 }
 
+ at property (assign) unsigned long scanLocation;
+ at property (readonly) NSAttributedString *linkifiedString;
 
 /*!
  * @brief Allocs and inits a new lax AHHyperlinkScanner with the given NSString
@@ -146,7 +149,4 @@
  */
 - (NSAttributedString *)linkifiedString;
 
-- (unsigned long)scanLocation;
-- (void)setScanLocation:(unsigned int)location;
-
 @end
diff -r e864e707fb51 -r 7754de9d8f18 Frameworks/AutoHyperlinks Framework/Source/AHHyperlinkScanner.m
--- a/Frameworks/AutoHyperlinks Framework/Source/AHHyperlinkScanner.m	Wed Feb 03 22:06:53 2010 -0500
+++ b/Frameworks/AutoHyperlinks Framework/Source/AHHyperlinkScanner.m	Thu Feb 04 19:28:58 2010 -0500
@@ -28,12 +28,15 @@
 #import "AHHyperlinkScanner.h"
 #import "AHLinkLexer.h"
 #import "AHMarkedHyperlink.h"
+#import <libkern/OSAtomic.h>
 
 #define	DEFAULT_URL_SCHEME	@"http://"
 #define ENC_INDEX_KEY @"encIndex"
 #define ENC_CHAR_KEY @"encChar"
 
 @interface AHHyperlinkScanner (PRIVATE)
+- (AHMarkedHyperlink *)nextURIFromLocation:(unsigned long *)_scanLocation;
+- (NSAttributedString *)_createLinkifiedString;
 - (NSRange)_longestBalancedEnclosureInRange:(NSRange)inRange;
 - (BOOL)_scanString:(NSString *)inString upToCharactersFromSet:(NSCharacterSet *)inCharSet intoRange:(NSRange *)outRangeRef fromIndex:(unsigned long *)idx;
 - (BOOL)_scanString:(NSString *)inString charactersFromSet:(NSCharacterSet *)inCharSet intoRange:(NSRange *)outRangeRef fromIndex:(unsigned long *)idx;
@@ -50,7 +53,10 @@
 	static NSCharacterSet	*enclosureSet = nil;
 	static NSArray			*enclosureStopArray = nil;
 	static NSArray			*encKeys = nil;
-	
+
+ at synthesize scanLocation = m_scanLocation;
+ at dynamic linkifiedString;
+
 #pragma mark Class Methods
 + (id)hyperlinkScannerWithString:(NSString *)inString
 {
@@ -125,7 +131,14 @@
 }
 
 #pragma mark Init/Dealloc
-
+- (id)init
+{
+	if((self = [super init])){
+		self.scanLocation = 0;
+		m_linkifiedString = nil;
+	}
+	return self;
+}
 
 - (id)initWithString:(NSString *)inString usingStrictChecking:(BOOL)flag
 {
@@ -136,7 +149,6 @@
 			@"ftp://", @"ftp",
 			nil];
 		m_strictChecking = flag;
-		m_scanLocation = 0;
 		m_scanStringLength = [m_scanString length];
 	}
 	return self;
@@ -151,7 +163,6 @@
 			@"ftp://", @"ftp",
 			nil];
 		m_strictChecking = flag;
-		m_scanLocation = 0;
 		m_scanStringLength = [m_scanString length];
 	}
 	return self;
@@ -159,6 +170,8 @@
 
 - (void)dealloc
 {
+	self.scanLocation = 0;
+	[m_linkifiedString release];
 	[m_scanString release];
 	[m_urlSchemes release];
 	if(m_scanAttrString) [m_scanAttrString release];
@@ -174,84 +187,84 @@
 
 + (BOOL)isStringValidURI:(NSString *)inString usingStrict:(BOOL)useStrictChecking fromIndex:(unsigned long *)index withStatus:(AH_URI_VERIFICATION_STATUS *)validStatus
 {
-    AH_BUFFER_STATE	 buf;  // buffer for flex to scan from
+	AH_BUFFER_STATE	 buf; // buffer for flex to scan from
 	yyscan_t		 scanner; // pointer to the flex scanner opaque type
 	const char		*inStringEnc;
-    unsigned long	 encodedLength;
-
+	unsigned long	 encodedLength;
+	
 	if(!validStatus){
 		AH_URI_VERIFICATION_STATUS newStatus = AH_URL_INVALID;
 		validStatus = &newStatus;
 	}
 	
 	*validStatus = AH_URL_INVALID; // assume the URL is invalid
-
+	
 	// Find the fastest 8-bit wide encoding possible for the c string
 	NSStringEncoding stringEnc = [inString fastestEncoding];
 	if([@" " lengthOfBytesUsingEncoding:stringEnc] > 1U)
 		stringEnc = NSUTF8StringEncoding;
-
+	
 	if (!(inStringEnc = [inString cStringUsingEncoding:stringEnc])) {
 		return NO;
 	}
 	
 	
 	encodedLength = strlen(inStringEnc); // length of the string in utf-8
-    
+	
 	// initialize the buffer (flex automatically switches to the buffer in this function)
 	AHlex_init(&scanner);
-    buf = AH_scan_string(inStringEnc, scanner);
-
-    // call flex to parse the input
-    *validStatus = AHlex(scanner);
+	buf = AH_scan_string(inStringEnc, scanner);
+	
+	// call flex to parse the input
+	*validStatus = AHlex(scanner);
 	if(index) *index += AHget_leng(scanner);
 	
-    // condition for valid URI's
-    if(*validStatus == AH_URL_VALID || *validStatus == AH_MAILTO_VALID || *validStatus == AH_FILE_VALID){
-        AH_delete_buffer(buf, scanner); //remove the buffer from flex.
-        buf = NULL; //null the buffer pointer for safty's sake.
-        
-        // check that the whole string was matched by flex.
-        // this prevents silly things like "blah...com" from being seen as links
-        if(AHget_leng(scanner) == encodedLength){
+	// condition for valid URI's
+	if(*validStatus == AH_URL_VALID || *validStatus == AH_MAILTO_VALID || *validStatus == AH_FILE_VALID){
+		AH_delete_buffer(buf, scanner); //remove the buffer from flex.
+		buf = NULL; //null the buffer pointer for safty's sake.
+		
+		// check that the whole string was matched by flex.
+		// this prevents silly things like "blah...com" from being seen as links
+		if(AHget_leng(scanner) == encodedLength){
 			AHlex_destroy(scanner);
-            return YES;
-        }
+			return YES;
+		}
     // condition for degenerate URL's (A.K.A. URI's sans specifiers), requres strict checking to be NO.
-    }else if((*validStatus == AH_URL_DEGENERATE || *validStatus == AH_MAILTO_DEGENERATE) && !useStrictChecking){
-        AH_delete_buffer(buf, scanner);
-        buf = NULL;
-        if(AHget_leng(scanner) == encodedLength){
+	}else if((*validStatus == AH_URL_DEGENERATE || *validStatus == AH_MAILTO_DEGENERATE) && !useStrictChecking){
+		AH_delete_buffer(buf, scanner);
+		buf = NULL;
+		if(AHget_leng(scanner) == encodedLength){
 			AHlex_destroy(scanner);
-            return YES;
-        }
+			return YES;
+		}
     // if it ain't vaild, and it ain't degenerate, then it's invalid.
-    }else{
-        AH_delete_buffer(buf, scanner);
-        buf = NULL;
+	}else{
+		AH_delete_buffer(buf, scanner);
+		buf = NULL;
 		AHlex_destroy(scanner);
-        return NO;
-    }
-    // default case, if the range checking above fails.
+		return NO;
+	}
+	// default case, if the range checking above fails.
 	AHlex_destroy(scanner);
-    return NO;
+	return NO;
 }
 
 #pragma mark Accessors
 
-- (AHMarkedHyperlink *)nextURI
+- (AHMarkedHyperlink *)nextURIFromLocation:(unsigned long * const)_scanLocation
 {
-	NSRange	scannedRange;
-	unsigned long scannedLocation = m_scanLocation;
+	NSRange	scannedRange = NSMakeRange(0, 0);
+	unsigned long scannedLocation = *_scanLocation;
 	
-    // scan upto the next whitespace char so that we don't unnecessarity confuse flex
-    // otherwise we end up validating urls that look like this "http://www.adium.im/ <--cool"
+	// scan upto the next whitespace char so that we don't unnecessarity confuse flex
+	// otherwise we end up validating urls that look like this "http://www.adium.im/ <--cool"
 	[self _scanString:m_scanString charactersFromSet:startSet intoRange:nil fromIndex:&scannedLocation];
-
+	
 	// main scanning loop
 	while([self _scanString:m_scanString upToCharactersFromSet:skipSet intoRange:&scannedRange fromIndex:&scannedLocation]) {
 		BOOL foundUnpairedEnclosureCharacter = NO;
-
+		
 		// Check for and filter enclosures.  We can't add (, [, etc. to the skipSet as they may be in a URI
 		if([enclosureSet characterIsMember:[m_scanString characterAtIndex:scannedRange.location]]){
 			unsigned long encIdx = [enclosureStartArray indexOfObject:[m_scanString substringWithRange:NSMakeRange(scannedRange.location, 1)]];
@@ -266,7 +279,7 @@
 			}
 		}
 		if(!scannedRange.length) break;
-				
+		
 		// Find balanced enclosure chars
 		NSRange longestEnclosure = [self _longestBalancedEnclosureInRange:scannedRange];
 		while (scannedRange.length > 2 && [endSet characterIsMember:[m_scanString characterAtIndex:(scannedRange.location + scannedRange.length - 1)]]) {
@@ -276,102 +289,105 @@
 			}else break;
 		}
 		
-        // if we have a valid URL then save the scanned string, and make a SHMarkedHyperlink out of it.
-        // this way, we can preserve things like the matched string (to be converted to a NSURL),
-        // parent string, its validation status (valid, file, degenerate, etc), and its range in the parent string
+		// if we have a valid URL then save the scanned string, and make a SHMarkedHyperlink out of it.
+		// this way, we can preserve things like the matched string (to be converted to a NSURL),
+		// parent string, its validation status (valid, file, degenerate, etc), and its range in the parent string
 		AH_URI_VERIFICATION_STATUS	 validStatus;
 		NSString					*_scanString = nil;
 		if(3 < scannedRange.length) _scanString = [m_scanString substringWithRange:scannedRange];
-
-        if((3 < scannedRange.length) && [[self class] isStringValidURI:_scanString usingStrict:m_strictChecking fromIndex:&m_scanLocation withStatus:&validStatus]){
-            AHMarkedHyperlink	*markedLink;
+		
+		if((3 < scannedRange.length) && [[self class] isStringValidURI:_scanString usingStrict:m_strictChecking fromIndex:_scanLocation withStatus:&validStatus]){
+			AHMarkedHyperlink	*markedLink;
 			
-            //insert typical specifiers if the URL is degenerate
-            switch(validStatus){
-                case AH_URL_DEGENERATE:
-                {
-                    NSString *scheme = DEFAULT_URL_SCHEME;
-					unsigned long i = 0;
-
-                    NSRange  firstComponent;
-					[self		  _scanString:_scanString
-						upToCharactersFromSet:hostnameComponentSeparatorSet
-									intoRange:&firstComponent
-									fromIndex:&i];
-
-                    if(NSNotFound != firstComponent.location) {
-                    	NSString *hostnameScheme = [m_urlSchemes objectForKey:[_scanString substringWithRange:firstComponent]];
-                    	if(hostnameScheme) scheme = hostnameScheme;
-                    }
-
-                    _scanString = [scheme stringByAppendingString:_scanString];
-
-                    break;
-                }
-
-                case AH_MAILTO_DEGENERATE:
+			//insert typical specifiers if the URL is degenerate
+			switch(validStatus){
+				case AH_URL_DEGENERATE:
+			 {
+				NSString *scheme = DEFAULT_URL_SCHEME;
+				unsigned long i = 0;
+				
+				NSRange  firstComponent;
+				[self		  _scanString:_scanString
+			 upToCharactersFromSet:hostnameComponentSeparatorSet
+									 intoRange:&firstComponent
+									 fromIndex:&i];
+				
+				if(NSNotFound != firstComponent.location) {
+					NSString *hostnameScheme = [m_urlSchemes objectForKey:[_scanString substringWithRange:firstComponent]];
+					if(hostnameScheme) scheme = hostnameScheme;
+				}
+				
+				_scanString = [scheme stringByAppendingString:_scanString];
+				
+				break;
+			 }
+					
+				case AH_MAILTO_DEGENERATE:
 					_scanString = [@"mailto:" stringByAppendingString:_scanString];
-                    break;
-                default:
-                    break;
-            }
-            
-            //make a marked link
-            markedLink = [AHMarkedHyperlink hyperlinkWithString:_scanString
-											  withValidationStatus:validStatus
-													  parentString:m_scanString
-														  andRange:scannedRange];
-            return [markedLink URL]? markedLink : nil;
-        }
-
+					break;
+				default:
+					break;
+			}
+			
+			//make a marked link
+			markedLink = [AHMarkedHyperlink hyperlinkWithString:_scanString
+																		 withValidationStatus:validStatus
+																						 parentString:m_scanString
+																								 andRange:scannedRange];
+			return [markedLink URL]? markedLink : nil;
+		}
+		
 		//step location after scanning a string
 		if (foundUnpairedEnclosureCharacter){
-			m_scanLocation++;
+			(*_scanLocation)++;
 		}else{
 			NSRange startRange = [m_scanString rangeOfCharacterFromSet:puncSet options:NSLiteralSearch range:scannedRange];
 			if (startRange.location != NSNotFound)
-				m_scanLocation = startRange.location + startRange.length;
+				*_scanLocation = startRange.location + startRange.length;
 			else
-				m_scanLocation += scannedRange.length;
+				*_scanLocation += scannedRange.length;
 		}
-			
-		scannedLocation = m_scanLocation;
-    }
+		
+		scannedLocation = *_scanLocation;
+	}
 	
-    // if we're here, then NSScanner hit the end of the string
-    // set AHStringOffset to the string length here so we avoid potential infinite looping with many trailing spaces.
-    m_scanLocation = m_scanStringLength;
-    return nil;
+	// if we're here, then NSScanner hit the end of the string
+	// set AHStringOffset to the string length here so we avoid potential infinite looping with many trailing spaces.
+	*_scanLocation = m_scanStringLength;
+	return nil;
+}
+
+- (AHMarkedHyperlink *)nextURI
+{
+	@synchronized(self) {
+		return [self nextURIFromLocation:&m_scanLocation];
+	}
 }
 
 -(NSArray *)allURIs
 {
-    NSMutableArray		*rangeArray = [NSMutableArray array];
-    AHMarkedHyperlink	*markedLink;
-	unsigned long		 _holdOffset = m_scanLocation; // store location for later restoration;
-	m_scanLocation = 0; //set the offset to 0.
-    
-    //build an array of marked links.
-	while((markedLink = [self nextURI])){
+	NSMutableArray    *rangeArray = [NSMutableArray array];
+	AHMarkedHyperlink *markedLink;
+	unsigned long      offset = 0;
+	
+	//build an array of marked links.
+	while((markedLink = [self nextURIFromLocation:&offset])){
 		[rangeArray addObject:markedLink];
 	}
-    m_scanLocation = _holdOffset; // reset scanLocation
 	return rangeArray;
 }
 
--(NSAttributedString *)linkifiedString
+-(NSAttributedString *)_createLinkifiedString
 {
-	NSMutableAttributedString	*linkifiedString;
+	NSMutableAttributedString	*_linkifiedString;
 	AHMarkedHyperlink			*markedLink;
 	BOOL						_didFindLinks = NO;
-	unsigned long				_holdOffset = m_scanLocation; // store location for later restoration;
+	unsigned long _scanLocationCache = self.scanLocation;
 	
-	m_scanLocation = 0;
-
 	if(m_scanAttrString) {
-		linkifiedString = [[m_scanAttrString mutableCopy] autorelease];
+		_linkifiedString = [m_scanAttrString mutableCopy];
 	} else {
-		linkifiedString = [[[NSMutableAttributedString alloc] initWithString:m_scanString] autorelease];
+		_linkifiedString = [[NSMutableAttributedString alloc] initWithString:m_scanString];
 	}
 
 	//for each SHMarkedHyperlink, add the proper URL to the proper range in the string.
@@ -379,26 +395,27 @@
 		NSURL *markedLinkURL;
 		_didFindLinks = YES;
 		if((markedLinkURL = [markedLink URL])) {
-			[linkifiedString addAttribute:NSLinkAttributeName
+			[_linkifiedString addAttribute:NSLinkAttributeName
 									value:markedLinkURL
 									range:[markedLink range]];
 		}
 	}
-	
-	m_scanLocation = _holdOffset; // reset scanLocation
-		
-	return _didFindLinks? linkifiedString :
-						  m_scanAttrString ? [[m_scanAttrString retain] autorelease] : [[[NSMutableAttributedString alloc] initWithString:m_scanString] autorelease];
+			
+	self.scanLocation = _scanLocationCache;
+	return _didFindLinks? _linkifiedString :
+						  m_scanAttrString ? m_scanAttrString : [[NSMutableAttributedString alloc] initWithString:m_scanString];
 }
 
--(unsigned long)scanLocation
+-(NSAttributedString *)linkifiedString
 {
-	return m_scanLocation;
-}
-
-- (void)setScanLocation:(unsigned int)location
-{
-	m_scanLocation = location;
+	if(!m_linkifiedString){
+		NSAttributedString *newLinkifiedString = [self _createLinkifiedString];
+		// compare the old object to nil, and swap in the new value if they match.
+		// if the old object (m_linkifiedString) already has a value, release the duplicated new object
+		if(!OSAtomicCompareAndSwapPtrBarrier(nil, newLinkifiedString, (void *)&m_linkifiedString))
+			[newLinkifiedString release];
+	}
+	return [[[NSAttributedString alloc] initWithAttributedString:m_linkifiedString] autorelease];
 }
 
 #pragma mark NSFastEnumeration
@@ -430,15 +447,15 @@
 	
 	while(encScanLocation < inRange.length + inRange.location) {
 		[self _scanString:m_scanString upToCharactersFromSet:enclosureSet intoRange:nil fromIndex:&encScanLocation];
-			
+		
 		if(encScanLocation >= (inRange.location + inRange.length)) break;
-			
+		
 		matchChar = [m_scanString substringWithRange:NSMakeRange(encScanLocation, 1)];
-			
+		
 		if([enclosureStartArray containsObject:matchChar]) {
 			encDict = [NSDictionary	dictionaryWithObjects:[NSArray arrayWithObjects:[NSNumber numberWithUnsignedLong:encScanLocation], matchChar, nil]
-												forKeys:encKeys];
-			if(!enclosureStack) enclosureStack = [NSMutableArray arrayWithCapacity:1];
+																						forKeys:encKeys];
+			if(!enclosureStack) enclosureStack = [NSMutableArray array];
 			[enclosureStack addObject:encDict];
 		}else if([enclosureStopArray containsObject:matchChar]) {
 			NSEnumerator *encEnumerator = [enclosureStack objectEnumerator];
@@ -447,8 +464,8 @@
 				unsigned long encStartIndex = [enclosureStartArray indexOfObjectIdenticalTo:[encDict objectForKey:ENC_CHAR_KEY]];
 				if([enclosureStopArray indexOfObjectIdenticalTo:matchChar] == encStartIndex) {
 					NSRange encRange = NSMakeRange(encTagIndex, encScanLocation - encTagIndex + 1);
-					if(!enclosureStack) enclosureStack = [NSMutableArray arrayWithCapacity:1];
-					if(!enclosureArray) enclosureArray = [NSMutableArray arrayWithCapacity:1];
+					if(!enclosureStack) enclosureStack = [NSMutableArray array];
+					if(!enclosureArray) enclosureArray = [NSMutableArray array];
 					[enclosureStack removeObject:encDict];
 					[enclosureArray addObject:NSStringFromRange(encRange)];
 					break;




More information about the commits mailing list