/* ** Criterion.m ** ** Copyright (c) 2004 ** ** Author: Yen-Ju Chen ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "Criterion.h" #include "RegEx.h" #include "GNUstep.h" #include "Constants.h" #include "ContentIndexer.h" static NSString *CriterionMatchAllKey = @"CriterionMatchAllKey"; static NSString *CriterionAllCriterionsKey = @"CriterionaAllCriterionsKey"; @interface NSString (SubjectComparison) - (NSComparisonResult) sortSubject: (NSString *) another; @end @implementation NSString (SubjectComparison) - (NSComparisonResult) sortSubject: (NSString *) another { BOOL selfHasPrefix = [self hasPrefix: @"-"]; BOOL anotherHasPrefix = [another hasPrefix: @"-"]; if (selfHasPrefix == anotherHasPrefix) { return [self compare: another]; } else if (selfHasPrefix) /* and another don't have prefix */ { return NSOrderedDescending; } else { return NSOrderedAscending; } } @end @implementation Criterion - (void) setCriterion: (Criterion *) another { NSArray *allSubjects; unsigned int i, count; id subject; CriterionType type; [self loadPropertyList: [another propertyList]]; /* set available subject */ allSubjects = [another subjectsToMatch]; count = [allSubjects count]; for(i = 0; i < count; i++) { subject = [allSubjects objectAtIndex: i]; type = [another typeOfSubject: subject]; [self addAvailableSubject: subject forType: type]; } } /* add available subject */ - (void) removeAllAvailableSubjects { [anyStringSet removeAllObjects]; [stringSet removeAllObjects]; [dateSet removeAllObjects]; [numberSet removeAllObjects]; [selectionSet removeAllObjects]; [contentSet removeAllObjects]; } - (void) addAvailableSubject: (NSString *) subject forType: (CriterionType) type { switch(type) { case CriterionAnyStringType: [anyStringSet addObject: AUTORELEASE([subject copy])]; break; case CriterionStringType: [stringSet addObject: AUTORELEASE([subject copy])]; break; case CriterionDateType: [dateSet addObject: AUTORELEASE([subject copy])]; break; case CriterionNumberType: [numberSet addObject: AUTORELEASE([subject copy])]; break; case CriterionSelectionType: [selectionSet addObject: AUTORELEASE([subject copy])]; break; case CriterionContentType: [contentSet addObject: AUTORELEASE([subject copy])]; break; } } - (void) setAvailableSelections: (NSArray *) array forSubject: (NSString *) subject { if (array == nil) [selections removeObjectForKey: subject]; else [selections setObject: AUTORELEASE([array copy]) forKey: AUTORELEASE([subject copy])]; } /* read criterion */ - (unsigned int) numberOfCriterionsInView: (CriterionView *) view { return [criterions count]; } - (NSArray *) availableSubjectsOfCriterionsInView: (CriterionView *) view { NSArray *array1, *array2; NSMutableSet *allSet = [[NSMutableSet alloc] init]; [allSet unionSet: anyStringSet]; [allSet unionSet: stringSet]; [allSet unionSet: dateSet]; [allSet unionSet: numberSet]; [allSet unionSet: selectionSet]; [allSet unionSet: contentSet]; array1 = [allSet allObjects]; AUTORELEASE(allSet); array2 = [array1 sortedArrayUsingSelector:@selector(sortSubject:)]; return array2; } - (NSString *) criterionView: (CriterionView *) view subjectAtIndex: (unsigned int) index { if (index < [criterions count]) { return AUTORELEASE([[[criterions objectAtIndex: index] objectAtIndex: 0] copy]); } } - (CriterionType) criterionView: (CriterionView *) view typeOfSubject: (NSString *) subject { return [self typeOfSubject: subject]; } - (CriterionType) typeOfSubject: (NSString *) subject { if ([anyStringSet containsObject: subject]) return CriterionAnyStringType; else if ([stringSet containsObject: subject]) return CriterionStringType; else if ([dateSet containsObject: subject]) return CriterionDateType; else if ([numberSet containsObject: subject]) return CriterionNumberType; else if ([selectionSet containsObject: subject]) return CriterionSelectionType; else if ([contentSet containsObject: subject]) return CriterionContentType; else { //NSLog(@"Internal Error: unknown criterion type"); return CriterionAnyStringType; } } - (NSArray *) criterionView: (CriterionView *) view availableSelectionsForSubject: (NSString *) subject { NSArray *array = [selections objectForKey: subject]; return AUTORELEASE([array copy]); } - (CriterionVerb) criterionView: (CriterionView *) view verbAtIndex: (unsigned int) index { if (index < [criterions count]) { return [[[criterions objectAtIndex: index] objectAtIndex: 1] intValue]; } } - (id) criterionView: (CriterionView *) view objectAtIndex: (unsigned int) index { if (index < [criterions count]) { return AUTORELEASE([[[criterions objectAtIndex: index] objectAtIndex: 2] copy]); } } /* accessories */ - (void) addCriterionWithSubject: (NSString *) subject verb: (CriterionVerb) verb object: (id) object { if ((subject == nil) || (object == nil) || (verb < 1) || (verb >= CriterionVerbCount)) { NSLog(@"Invalide criterion: Cannot add criterion"); return; } else { //NSLog(@"Criterion add criterion"); NSArray *c = [NSArray arrayWithObjects: AUTORELEASE([subject copy]), [NSNumber numberWithInt: verb], AUTORELEASE([object copy]), nil]; [criterions addObject: c]; } } - (void) insertCriterionWithSubject: (NSString *) subject verb: (CriterionVerb) verb object: (id) object atIndex: (unsigned int) index { if ((subject == nil) || (object == nil) || (verb < 1) || (verb >= CriterionVerbCount) || (index == NSNotFound)) { NSLog(@"Invalide criterion: Cannot add criterion"); return; } else if (index >= [criterions count]) { [self addCriterionWithSubject: subject verb: verb object: object]; } else { //NSLog(@"Criterion insert in %d", index); NSArray *c = [NSArray arrayWithObjects: AUTORELEASE([subject copy]), [NSNumber numberWithInt: verb], AUTORELEASE([object copy]), nil]; [criterions insertObject: c atIndex: index]; } } - (void) removeCriterionAtIndex: (unsigned int) index { if ((index == NSNotFound) || (index >= [criterions count])) { NSLog(@"Invalide criterion index: Cannot remove criterion"); return; } else { [criterions removeObjectAtIndex: index]; } } - (void) setCriterionWithSubject: (NSString *) subject verb: (CriterionVerb) verb object: (id) object atIndex: (unsigned int) index { if ((index == NSNotFound) || (index >= [criterions count]) || (subject == nil) || (object == nil) || (verb < 1) || (verb >= CriterionVerbCount)) { NSLog(@"Invalide criterion: Cannot set criterion"); return; } else { NSArray *c = [NSArray arrayWithObjects: AUTORELEASE([subject copy]), [NSNumber numberWithInt: verb], AUTORELEASE([object copy]), nil]; [criterions replaceObjectAtIndex: index withObject: c]; //NSLog(@"Set %@ %d %@ index %d", subject, verb, object, index); } } - (id) propertyList { return [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool: matchAll], CriterionMatchAllKey, AUTORELEASE([criterions copy]), CriterionAllCriterionsKey, nil]; } - (void) loadPropertyList: (id) propertyList { if ([propertyList isKindOfClass: [NSDictionary class]] == NO) { NSLog(@"Internal Error: Property list for criterions is not valide"); } NSDictionary *dict = propertyList; NSArray *array; matchAll = [[dict objectForKey: CriterionMatchAllKey] boolValue]; array = [dict objectForKey: CriterionAllCriterionsKey]; [criterions setArray: AUTORELEASE([array copy])]; } - (unsigned int) count { [criterions count]; } /* match criterion */ - (void) setMatchAll: (BOOL) match { matchAll = match; } - (BOOL) matchAll { return matchAll; } - (NSArray *) subjectsToMatch { NSMutableArray *array = [[NSMutableArray alloc] init]; NSString *subject; unsigned int i, count = [criterions count]; for(i = 0; i < count; i++) { subject = [[criterions objectAtIndex: i] objectAtIndex: 0]; [array addObject: AUTORELEASE([subject copy])]; } return AUTORELEASE(array); } /* ignore subject */ - (BOOL) _matchCriterionAtIndex: (unsigned int) index withObject: (id) o { CriterionVerb verb = [[[criterions objectAtIndex: index] objectAtIndex: 1] intValue]; id first, second; id object = [[criterions objectAtIndex: index] objectAtIndex: 2]; unsigned int length; NSRange range; NSComparisonResult comparisonResult; /* Cache */ Class stringClass, dateClass, numberClass, arrayClass; stringClass = [NSString class]; dateClass = [NSDate class]; numberClass = [NSNumber class]; arrayClass = [NSArray class]; if (([object isKindOfClass: stringClass]) && ([o isKindOfClass: stringClass])) { length = [(NSString *)o length]; range = [o rangeOfString: object options: NSCaseInsensitiveSearch]; switch (verb) { case CriterionContainsVerb: if (range.location != NSNotFound) { //NSLog(@"If %@ contains %@", o, object); return YES; } break; case CriterionDoesNotContainVerb: if (range.location == NSNotFound) { //NSLog(@"If %@ does not contains %@", o, object); return YES; } break; case CriterionIsVerb: if ((range.location == 0) && (range.length == length)) { //NSLog(@"If %@ is %@", o, object); return YES; } break; case CriterionIsNotVerb: if ((range.location == NSNotFound) || (range.location != 0) || (range.length != length)) { //NSLog(@"If %@ is not %@", o, object); return YES; } break; case CriterionStartsWithVerb: if (range.location == 0) { //NSLog(@"If %@ starts with %@", o, object); return YES; } break; case CriterionEndsWithVerb: if (NSMaxRange(range) == length) { //NSLog(@"If %@ ends with %@", o, object); return YES; } break; case CriterionMatchRegularExpressionVerb: { range = [RegExParser rangeOfString: object inString: o]; if (range.location != NSNotFound) { //NSLog(@"If %@ match regex %@", o, object); return YES; } break; } } } else if (([object isKindOfClass: stringClass]) && ([o isKindOfClass: arrayClass])) { switch (verb) { case CriterionIsVerb: if ([o containsObject: object]) return YES; break; case CriterionIsNotVerb: if ([o containsObject: object] == NO) return YES; break; } } else if (([object isKindOfClass: dateClass]) && ([o isKindOfClass: dateClass])) { comparisonResult = [(NSDate *)o compare: object]; switch (verb) { case CriterionIsVerb: if (comparisonResult == NSOrderedSame) { //NSLog(@"If %@ is %@", o, object); return YES; } break; case CriterionIsNotVerb: if (comparisonResult != NSOrderedSame) { //NSLog(@"If %@ is not %@", o, object); return YES; } break; case CriterionIsAfterVerb: if (comparisonResult == NSOrderedDescending) { //NSLog(@"If %@ is after %@", o, object); return YES; } break; case CriterionIsBeforeVerb: if (comparisonResult == NSOrderedAscending) { //NSLog(@"If %@ is before %@", o, object); return YES; } break; } } else if (([object isKindOfClass: arrayClass]) && ([o isKindOfClass: dateClass])) { switch (verb) { case CriterionIsInTheLastVerb: case CriterionIsNotInTheLastVerb: { first = [object objectAtIndex: 0]; second = [object lastObject]; if (([first isKindOfClass: numberClass]) && ([second isKindOfClass: numberClass])) { NSCalendarDate *before, *current = [NSCalendarDate date]; CriterionDateUnit unit = [second intValue]; int count = [first intValue]; NSTimeInterval interval; NSTimeInterval since = [current timeIntervalSinceDate: o]; switch (unit) { case CriterionSecondUnit: interval = count*1; break; case CriterionMinuteUnit: interval = count*60; break; case CriterionHourUnit: interval = count*60*60; break; case CriterionDayUnit: interval = count*60*60*24; break; case CriterionWeekUnit: interval = count*60*60*24*7; break; case CriterionMonthUnit: before = [current dateByAddingYears: 0 months: -count days: 0 hours: 0 minutes: 0 seconds: 0]; interval = [current timeIntervalSinceDate: before]; break; case CriterionYearUnit: before = [current dateByAddingYears: -count months: 0 days: 0 hours: 0 minutes: 0 seconds: 0]; interval = [current timeIntervalSinceDate: before]; break; } if (since < interval) { if (verb == CriterionIsInTheLastVerb) { //NSLog(@"%@ is in the last %@", o, object); return YES; } } else { if (verb == CriterionIsNotInTheLastVerb) { //NSLog(@"%@ is not in the last %@", o, object); return YES; } } } break; } case CriterionIsInTheRangeVerb: first = [object objectAtIndex: 0]; second = [object lastObject]; if (([first isKindOfClass: dateClass]) && ([second isKindOfClass: dateClass])) { comparisonResult = [(NSDate *)first compare: second]; if (([(NSDate *)o compare: first] == NSOrderedDescending) && ([(NSDate *)o compare: second] == NSOrderedAscending) && (comparisonResult == NSOrderedAscending)) { //NSLog(@"%@ is in the rage from %@ to %@", o, first, second); return YES; } else if (([(NSDate *)o compare: first] == NSOrderedAscending) && ([(NSDate *)o compare: second] == NSOrderedDescending) && (comparisonResult == NSOrderedDescending)) { //NSLog(@"%@ is in the rage from %@ to %@", o, first, second); return YES; } } break; } } else if (([object isKindOfClass: numberClass]) && ([o isKindOfClass: numberClass])) { comparisonResult = [(NSNumber *)o compare: object]; switch (verb) { case CriterionIsVerb: if (comparisonResult == NSOrderedSame) { //NSLog(@"%@ is %@", o, object); return YES; } break; case CriterionIsNotVerb: if (comparisonResult != NSOrderedSame) { //NSLog(@"%@ is not %@", o, object); return YES; } break; case CriterionIsGreaterThanVerb: if (comparisonResult == NSOrderedDescending) { //NSLog(@"%@ is greater than %@", o, object); return YES; } break; case CriterionIsLessThanVerb: if (comparisonResult == NSOrderedAscending) { //NSLog(@"%@ is less than %@", o, object); return YES; } break; } } else if (([object isKindOfClass: arrayClass]) && ([o isKindOfClass: numberClass])) { first = [object objectAtIndex: 0]; second = [object lastObject]; if (([first isKindOfClass: numberClass]) && ([second isKindOfClass: numberClass])) { comparisonResult = [(NSNumber *)first compare: second]; switch (verb) { case CriterionIsInTheRangeVerb: if (([(NSNumber *)o compare: first] == NSOrderedDescending) && ([(NSNumber *)o compare: second] == NSOrderedAscending) && (comparisonResult == NSOrderedAscending)) { //NSLog(@"%@ is in the range %@ to %@", o, first, second); return YES; } else if (([(NSNumber *)o compare: first] == NSOrderedAscending) && ([(NSNumber *)o compare: second] == NSOrderedDescending) && (comparisonResult == NSOrderedDescending)) { //NSLog(@"%@ is in the range %@ to %@", o, first, second); return YES; } break; } } } return NO; } -(BOOL) _matchCriterionAtIndex: (unsigned int) index withContentAtPath: (id) o { //NSLog(@"Criterion: _matchCriterionAtIndex %d withContentAtPath %@", index, o); id object = [[criterions objectAtIndex: index] objectAtIndex: 2]; NSArray *files = [[ContentIndexer sharedIndexer] filesContain: object]; if ([files containsObject: o]) return YES; else return NO; } - (BOOL) match: (NSDictionary *) information { unsigned int i, icount = [criterions count]; unsigned int j, jcount; NSString *title; CriterionType criterionType; BOOL result, jresult; NSArray *allKeys; id key, object; if (matchAll == YES) { //NSLog(@"Match All %d", count); result = NO; for(i = 0; i < icount; i++) { title = [[criterions objectAtIndex: i] objectAtIndex: 0]; criterionType = [self typeOfSubject: title]; //NSLog(@"Title %@, type %d", title, criterionType); if (criterionType == CriterionAnyStringType) { /* Go throught all information */ allKeys = [information allKeys]; jcount = [allKeys count]; jresult = NO; for(j = 0; j < jcount; j++) { key = [allKeys objectAtIndex: j]; object = [information objectForKey: key]; jresult = [self _matchCriterionAtIndex: i withObject: object]; if (jresult == YES) { result = YES; break; } } if (jresult == NO) return NO; } else { object = [information objectForKey: title]; //NSLog(@"title %@, object %@", title, object); if (object) { //if ([title isEqualToString: sFileContent]) if (criterionType == CriterionContentType) { result = [self _matchCriterionAtIndex: i withContentAtPath: object]; } else { result = [self _matchCriterionAtIndex: i withObject: object]; //NSLog(@"Match <%@> (%@) %d", title, object, result); } if (result == NO) { //NSLog(@"%@ match NO", title); return NO; } result = YES; } else { return NO; /* attributes missing */ } } } return result; } else { //NSLog(@"Match Any %d", count); result = NO; for(i = 0; i < icount; i++) { title = [[criterions objectAtIndex: i] objectAtIndex: 0]; criterionType = [self typeOfSubject: title]; if (criterionType == CriterionAnyStringType) { /* Go throught all information */ allKeys = [information allKeys]; jcount = [allKeys count]; jresult = NO; for(j = 0; j < jcount; j++) { key = [allKeys objectAtIndex: j]; object = [information objectForKey: key]; jresult = [self _matchCriterionAtIndex: i withObject: object]; if (jresult == YES) { return YES; } } if (jresult == NO) result = NO; } else { object = [information objectForKey: title]; if (object) { if (criterionType == CriterionContentType) { result = [self _matchCriterionAtIndex: i withContentAtPath: object]; } else { //NSLog(@"Match <%@> (%@)", title, object); result = [self _matchCriterionAtIndex: i withObject: object]; } if (result == YES) { //NSLog(@"%@ match YES", title); return YES; } result = NO; } else { /* Attribute missing */ result = NO; } } } return result; } return NO; } - (id) init { self = [super init]; criterions = [[NSMutableArray alloc] init]; selections = [[NSMutableDictionary alloc] init]; anyStringSet = [[NSMutableSet alloc] init]; stringSet = [[NSMutableSet alloc] init]; dateSet = [[NSMutableSet alloc] init]; numberSet = [[NSMutableSet alloc] init]; selectionSet = [[NSMutableSet alloc] init]; contentSet = [[NSMutableSet alloc] init]; matchAll = NO; return self; } - (void) dealloc { RELEASE(criterions); RELEASE(selections); RELEASE(anyStringSet); RELEASE(stringSet); RELEASE(dateSet); RELEASE(numberSet); RELEASE(selectionSet); RELEASE(contentSet); [super dealloc]; } @end