// // SKVersionNumber.h // Skim // // Created by Christiaan Hofman on 2/15/07. /* This software is Copyright (c) 2007-2018 Christiaan Hofman. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of Christiaan Hofman nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "SKVersionNumber.h" #define VERSION_LONG @"version" #define VERSION_SHORT @"v" #define ALPHA_LONG @"alpha" #define ALPHA_SHORT @"a" #define BETA_LONG @"beta" #define BETA_SHORT @"b" #define DEVELOPMENT_LONG @"development" #define DEVELOPMENT_SHORT @"d" #define FINAL_LONG @"final" #define FINAL_SHORT @"f" #define RELEASE_CANDIDATE_LONG @"release candidate" #define RELEASE_CANDIDATE_SHORT @"rc" #define SEPARATOR @"." #define DASH @"-" #define EMPTY @"" @implementation SKVersionNumber @synthesize originalVersionString, cleanVersionString, componentCount, releaseType; + (NSComparisonResult)compareVersionString:(NSString *)versionString toVersionString:(NSString *)otherVersionString; { SKVersionNumber *versionNumber = [[self alloc] initWithVersionString:versionString]; SKVersionNumber *otherVersionNumber = [[self alloc] initWithVersionString:otherVersionString]; NSComparisonResult result = [versionNumber compare:otherVersionNumber]; // [versionNumber release]; // [otherVersionNumber release]; return result; } // Initializes the receiver from a string representation of a version number. The input string may have an optional leading 'v' or 'V' followed by a sequence of positive integers separated by '.'s. Any trailing component of the input string that doesn't match this pattern is ignored. If no portion of this string matches the pattern, nil is returned. - (id)initWithVersionString:(NSString *)versionString; { self = [super init]; if (self) { // Input might be from a NSBundle info dictionary that could be misconfigured, so check at runtime too if (versionString == nil || [versionString isKindOfClass:[NSString class]] == NO) { // [self release]; return nil; } originalVersionString = [versionString copy]; releaseType = SKReleaseVersionType; NSMutableString *mutableVersionString = [[NSMutableString alloc] init]; NSScanner *scanner = [[NSScanner alloc] initWithString:versionString]; NSString *sep = EMPTY; [scanner setCharactersToBeSkipped:[NSCharacterSet whitespaceCharacterSet]]; // ignore a leading "version" or "v", possibly followed by "-" if ([scanner scanString:VERSION_LONG intoString:NULL] || [scanner scanString:VERSION_SHORT intoString:NULL]) [scanner scanString:DASH intoString:NULL]; while ([scanner isAtEnd] == NO && sep != nil) { NSInteger component; if ([scanner scanInteger:&component] && component >= 0) { [mutableVersionString appendFormat:@"%@%ld", sep, (long)component]; componentCount++; components = (NSInteger *)NSZoneRealloc(NSDefaultMallocZone(), components, sizeof(NSInteger) * componentCount); components[componentCount - 1] = component; if ([scanner isAtEnd] == NO) { sep = nil; if ([scanner scanString:SEPARATOR intoString:NULL] || [scanner scanString:DASH intoString:NULL] || [scanner scanString:VERSION_LONG intoString:NULL] || [scanner scanString:VERSION_SHORT intoString:NULL]) { sep = SEPARATOR; } if (releaseType == SKReleaseVersionType) { if ([scanner scanString:ALPHA_LONG intoString:NULL] || [scanner scanString:ALPHA_SHORT intoString:NULL]) { releaseType = SKAlphaVersionType; [mutableVersionString appendString:ALPHA_SHORT]; } else if ([scanner scanString:BETA_LONG intoString:NULL] || [scanner scanString:BETA_SHORT intoString:NULL]) { releaseType = SKBetaVersionType; [mutableVersionString appendString:BETA_SHORT]; } else if ([scanner scanString:DEVELOPMENT_LONG intoString:NULL] || [scanner scanString:DEVELOPMENT_SHORT intoString:NULL]) { releaseType = SKDevelopmentVersionType; [mutableVersionString appendString:DEVELOPMENT_SHORT]; } else if ([scanner scanString:FINAL_LONG intoString:NULL] || [scanner scanString:FINAL_SHORT intoString:NULL]) { releaseType = SKReleaseCandidateVersionType; [mutableVersionString appendString:FINAL_SHORT]; } else if ([scanner scanString:RELEASE_CANDIDATE_LONG intoString:NULL] || [scanner scanString:RELEASE_CANDIDATE_SHORT intoString:NULL]) { releaseType = SKReleaseCandidateVersionType; [mutableVersionString appendString:RELEASE_CANDIDATE_SHORT]; } if (releaseType != SKReleaseVersionType) { // we scanned an "a", "b", "d", "f", or "rc" componentCount++; components = (NSInteger *)NSZoneRealloc(NSDefaultMallocZone(), components, sizeof(NSInteger) * componentCount); components[componentCount - 1] = releaseType; sep = EMPTY; // ignore a "." or "-" if ([scanner scanString:SEPARATOR intoString:NULL] == NO) [scanner scanString:DASH intoString:NULL]; } } } } else sep = nil; } if ([mutableVersionString isEqualToString:originalVersionString]) // cleanVersionString = [originalVersionString retain]; cleanVersionString = originalVersionString; else cleanVersionString = [mutableVersionString copy]; // [mutableVersionString release]; // [scanner release]; if (componentCount == 0) { // Failed to parse anything and we don't allow empty version strings. For now, we'll not assert on this, since people might want to use this to detect if a string begins with a valid version number. // [self release]; return nil; } } return self; } - (void)dealloc; { // SKDESTROY(originalVersionString); // SKDESTROY(cleanVersionString); // SKZONEDESTROY(components); // [super dealloc]; } - (NSString *)description { return [NSString stringWithFormat:@"<%@: %@>", [self class], [self originalVersionString]]; } #pragma mark API - (NSInteger)componentAtIndex:(NSUInteger)componentIndex; { // This treats the version as a infinite sequence ending in "...0.0.0.0", making comparison easier if (componentIndex < componentCount) return components[componentIndex]; return 0; } #pragma mark NSCopying //- (id)copyWithZone:(NSZone *)zone; //{ // if (NSShouldRetainWithZone(self, zone)) // return [self retain]; // else // return [[[self class] allocWithZone:zone] initWithVersionString:originalVersionString]; //} #pragma mark Comparison - (NSUInteger)hash; { return [cleanVersionString hash]; } - (BOOL)isEqual:(id)otherObject; { if ([otherObject isMemberOfClass:[self class]] == NO) return NO; return [self compare:(SKVersionNumber *)otherObject] == NSOrderedSame; } - (NSComparisonResult)compare:(SKVersionNumber *)otherVersion; { if (otherVersion == nil) return NSOrderedAscending; NSUInteger idx = 0, otherIdx = 0, otherCount = [otherVersion componentCount]; while (idx < componentCount || otherIdx < otherCount) { NSInteger component = [self componentAtIndex:idx]; NSInteger otherComponent = [otherVersion componentAtIndex:otherIdx]; // insert zeros before matching possible a/d/b/rc components, e.g. to get 1b1 > 1.0a1 if (component < 0 && otherComponent >= 0 && otherIdx < otherCount) { component = 0; otherIdx++; } else if (component >= 0 && otherComponent < 0 && idx < componentCount) { otherComponent = 0; idx++; } else { idx++; otherIdx++; } if (component < otherComponent) return NSOrderedAscending; else if (component > otherComponent) return NSOrderedDescending; } return NSOrderedSame; } @end