diff options
author | Max <dmoagx@users.noreply.github.com> | 2018-05-13 20:01:14 +0200 |
---|---|---|
committer | Max <dmoagx@users.noreply.github.com> | 2018-05-13 20:01:14 +0200 |
commit | c2fe67ba840a5f4eaa2aed786da3e559b3a8b150 (patch) | |
tree | 82aa3750540dc147dd70054a2543c96a424f2419 /Source/SPTableContentFilterController.m | |
parent | e49d33edfb80f7de00b9971240a428cc72829421 (diff) | |
download | sequelpro-c2fe67ba840a5f4eaa2aed786da3e559b3a8b150.tar.gz sequelpro-c2fe67ba840a5f4eaa2aed786da3e559b3a8b150.tar.bz2 sequelpro-c2fe67ba840a5f4eaa2aed786da3e559b3a8b150.zip |
#63: Rename class SPTableContentFilterController to SPRuleFilterController to reduce confusion
Diffstat (limited to 'Source/SPTableContentFilterController.m')
-rw-r--r-- | Source/SPTableContentFilterController.m | 1355 |
1 files changed, 0 insertions, 1355 deletions
diff --git a/Source/SPTableContentFilterController.m b/Source/SPTableContentFilterController.m deleted file mode 100644 index a2d6b2d2..00000000 --- a/Source/SPTableContentFilterController.m +++ /dev/null @@ -1,1355 +0,0 @@ -// -// SPTableContentFilterController.m -// sequel-pro -// -// Created by Max Lohrmann on 04.05.18. -// Copyright (c) 2018 Max Lohrmann. All rights reserved. -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// More info at <https://github.com/sequelpro/sequelpro> - -#import "SPTableContentFilterController.h" -#import "SPQueryController.h" -#import "SPDatabaseDocument.h" -#import "RegexKitLite.h" -#import "SPContentFilterManager.h" -#import "SPFunctions.h" -#import "SPTableFilterParser.h" - -typedef NS_ENUM(NSInteger, RuleNodeType) { - RuleNodeTypeColumn, - RuleNodeTypeString, - RuleNodeTypeOperator, - RuleNodeTypeArgument, - RuleNodeTypeConnector, -}; - -NSString * const SPTableContentFilterHeightChangedNotification = @"SPTableContentFilterHeightChanged"; - -/** - * The type of filter rule that the current item represents. - */ -const NSString * const SerFilterClass = @"filterClass"; -/** - * The current rule is a group row (an "AND" or "OR" expression with children) - */ -const NSString * const SerFilterClassGroup = @"groupNode"; -/** - * The current rule is a filter expression - */ -const NSString * const SerFilterClassExpression = @"expressionNode"; -/** - * Group Nodes only: - * Indicates whether the group is a conjunction. - * If YES, the children will be combined using "AND", otherwise using "OR". - */ -const NSString * const SerFilterGroupIsConjunction = @"isConjunction"; -/** - * Group Nodes only: - * An array of child filter rules (which again can be group or expression rules) - */ -const NSString * const SerFilterGroupChildren = @"children"; -/** - * Expression Nodes only: - * The name of the column to filter in (left side expression) - * - * Legacy names: - * @"filterField", fieldField - */ -const NSString * const SerFilterExprColumn = @"column"; -/** - * Expression Nodes only: - * The data type grouping of the column for applicable filters - */ -const NSString * const SerFilterExprType = @"filterType"; -/** - * Expression Nodes only: - * The title of the filter operator to apply - * - * Legacy names: - * @"filterComparison", compareField - */ -const NSString * const SerFilterExprComparison = @"filterComparison"; -/** - * Expression Nodes only: - * The values to apply the filter with (an array of 0 or more elements) - * - * Legacy names: - * @"filterValue", argumentField - * @"firstBetweenField", @"secondBetweenField", firstBetweenField, secondBetweenField - */ -const NSString * const SerFilterExprValues = @"filterValues"; -/** - * Expression Nodes only: - * the filter definition dictionary (as in ContentFilters.plist) - * for the filter represented by SerFilterExprComparison. - * - * This item is not designed to be serialized to disk - */ -const NSString * const SerFilterExprDefinition = @"_filterDefinition"; - -#pragma mark - - -@interface RuleNode : NSObject { - RuleNodeType type; -} -@property(assign, nonatomic) RuleNodeType type; -@end - -@interface ColumnNode : RuleNode { - NSString *name; - NSString *typegrouping; - NSArray *operatorCache; -} -@property(copy, nonatomic) NSString *name; -@property(copy, nonatomic) NSString *typegrouping; -@property(retain, nonatomic) NSArray *operatorCache; -@end - -@interface StringNode : RuleNode { - NSString *value; -} -@property(copy, nonatomic) NSString *value; -@end - -@interface OpNode : RuleNode { - // Note: The main purpose of this field is to have @"=" for column A and @"=" for column B to return NO in -isEqual: - // because otherwise NSRuleEditor will get confused and blow up. - ColumnNode *parentColumn; - NSDictionary *settings; - NSDictionary *filter; -} -@property (assign, nonatomic) ColumnNode *parentColumn; -@property (retain, nonatomic) NSDictionary *settings; -@property (retain, nonatomic) NSDictionary *filter; -@end - -@interface ArgNode : RuleNode { - NSDictionary *filter; - NSUInteger argIndex; - NSString *initialValue; -} -@property (copy, nonatomic) NSString *initialValue; -@property (retain, nonatomic) NSDictionary *filter; -@property (assign, nonatomic) NSUInteger argIndex; -@end - -@interface ConnectorNode : RuleNode { - NSDictionary *filter; - NSUInteger labelIndex; -} -@property (retain, nonatomic) NSDictionary *filter; -@property (assign, nonatomic) NSUInteger labelIndex; -@end - -#pragma mark - - -@interface SPTableContentFilterController () <NSRuleEditorDelegate> - -@property (readwrite, assign, nonatomic) CGFloat preferredHeight; - -// This is the binding used by NSRuleEditor for the current state -@property (retain, nonatomic) NSMutableArray *model; - -- (NSArray *)_compareTypesForColumn:(ColumnNode *)colNode; -- (IBAction)_textFieldAction:(id)sender; -- (IBAction)_editFiltersAction:(id)sender; -- (void)_contentFiltersHaveBeenUpdated:(NSNotification *)notification; -+ (NSDictionary *)_flattenSerializedFilter:(NSDictionary *)in; -static BOOL SerIsGroup(NSDictionary *dict); -- (NSDictionary *)_serializedFilterIncludingFilterDefinition:(BOOL)includeDefinition; -+ (void)_writeFilterTree:(NSDictionary *)in toString:(NSMutableString *)out wrapInParenthesis:(BOOL)wrap binary:(BOOL)isBINARY error:(NSError **)err; -- (NSMutableDictionary *)_restoreSerializedFilter:(NSDictionary *)serialized; -static void _addIfNotNil(NSMutableArray *array, id toAdd); -- (ColumnNode *)_columnForName:(NSString *)name; -- (OpNode *)_operatorNamed:(NSString *)title forColumn:(ColumnNode *)col; -- (BOOL)_focusOnFieldInSubtree:(NSDictionary *)dict; -- (void)_resize; -- (void)openContentFilterManagerForFilterType:(NSString *)filterType; -- (IBAction)filterTable:(id)sender; - -@end - -@implementation SPTableContentFilterController - -@synthesize model = model; -@synthesize preferredHeight = preferredHeight; -@synthesize target = target; -@synthesize action = action; - -- (instancetype)init -{ - if((self = [super init])) { - columns = [[NSMutableArray alloc] init]; - model = [[NSMutableArray alloc] init]; - preferredHeight = 0.0; - target = nil; - action = NULL; - - // Init default filters for Content Browser - contentFilters = [[NSMutableDictionary alloc] init]; - numberOfDefaultFilters = [[NSMutableDictionary alloc] init]; - - NSError *readError = nil; - NSString *filePath = [NSBundle pathForResource:@"ContentFilters.plist" ofType:nil inDirectory:[[NSBundle mainBundle] bundlePath]]; - NSData *defaultFilterData = [NSData dataWithContentsOfFile:filePath - options:NSMappedRead - error:&readError]; - - if(defaultFilterData && !readError) { - NSDictionary *defaultFilterDict = [NSPropertyListSerialization propertyListWithData:defaultFilterData - options:NSPropertyListMutableContainersAndLeaves - format:NULL - error:&readError]; - - if(defaultFilterDict && !readError) { - [contentFilters setDictionary:defaultFilterDict]; - } - } - - if (readError) { - NSLog(@"Error while reading 'ContentFilters.plist':\n%@", readError); - NSBeep(); - } - else { - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"number"] count]] forKey:@"number"]; - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"date"] count]] forKey:@"date"]; - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"string"] count]] forKey:@"string"]; - [numberOfDefaultFilters setObject:[NSNumber numberWithInteger:[[contentFilters objectForKey:@"spatial"] count]] forKey:@"spatial"]; - } - } - return self; -} - -- (void)awakeFromNib -{ - [filterRuleEditor bind:@"rows" toObject:self withKeyPath:@"model" options:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(_contentFiltersHaveBeenUpdated:) - name:SPContentFiltersHaveBeenUpdatedNotification - object:nil]; -} - -- (void)focusFirstInputField -{ - for(NSDictionary *rootItem in model) { - if([self _focusOnFieldInSubtree:rootItem]) return; - } -} - -- (BOOL)_focusOnFieldInSubtree:(NSDictionary *)dict -{ - //if we are a simple row we might have an input field ourself, otherwise search among our children - if([[dict objectForKey:@"rowType"] unsignedIntegerValue] == NSRuleEditorRowTypeSimple) { - for(id obj in [dict objectForKey:@"displayValues"]) { - if([obj isKindOfClass:[NSTextField class]]) { - [[(NSTextField *)obj window] makeFirstResponder:obj]; - return YES; - } - } - } - else { - for(NSDictionary *child in [dict objectForKey:@"subrows"]) { - if([self _focusOnFieldInSubtree:child]) return YES; - } - } - return NO; -} - -- (void)setColumns:(NSArray *)dataColumns; -{ - [self willChangeValueForKey:@"model"]; // manual KVO is needed for filter rule editor to notice change - [model removeAllObjects]; - [self didChangeValueForKey:@"model"]; - - [columns removeAllObjects]; - - //without a table there is nothing to filter - if(dataColumns) { - //sort column names if enabled - NSArray *columnDefinitions = dataColumns; - if ([[NSUserDefaults standardUserDefaults] boolForKey:SPAlphabeticalTableSorting]) { - NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]; - columnDefinitions = [columnDefinitions sortedArrayUsingDescriptors:@[sortDescriptor]]; - } - - // get the columns - for (NSDictionary *colDef in columnDefinitions) { - ColumnNode *node = [[ColumnNode alloc] init]; - [node setName:[colDef objectForKey:@"name"]]; - [node setTypegrouping:[colDef objectForKey:@"typegrouping"]]; - [columns addObject:node]; - [node release]; - } - } - - // make the rule editor reload the criteria - [filterRuleEditor reloadCriteria]; - - // disable UI if no criteria exist - [self setEnabled:([columns count] != 0)]; -} - -- (NSInteger)ruleEditor:(NSRuleEditor *)editor numberOfChildrenForCriterion:(nullable id)criterion withRowType:(NSRuleEditorRowType)rowType -{ - // nil criterion is always the first element in a row, compound rows are only for "AND"/"OR" groups - if(!criterion && rowType == NSRuleEditorRowTypeCompound) { - return 2; - } - else if(!criterion && rowType == NSRuleEditorRowTypeSimple) { - return [columns count]; - } - else if(rowType == NSRuleEditorRowTypeSimple) { - // the children of the columns are their operators - RuleNodeType type = [(RuleNode *)criterion type]; - if(type == RuleNodeTypeColumn) { - ColumnNode *node = (ColumnNode *)criterion; - if(![node operatorCache]) { - NSArray *ops = [self _compareTypesForColumn:node]; - [node setOperatorCache:ops]; - } - return [[node operatorCache] count]; - } - // the first child of an operator is the first argument (if it has one) - else if(type == RuleNodeTypeOperator) { - OpNode *node = (OpNode *)criterion; - NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; - return (numOfArgs > 0) ? 1 : 0; - } - // the child of an argument can only be the conjunction label if more arguments follow - else if(type == RuleNodeTypeArgument) { - ArgNode *node = (ArgNode *)criterion; - NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; - return (numOfArgs > [node argIndex]+1) ? 1 : 0; - } - // the child of a conjunction is the next argument, if we have one - else if(type == RuleNodeTypeConnector) { - ConnectorNode *node = (ConnectorNode *)criterion; - NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; - return (numOfArgs > [node labelIndex]+1) ? 1 : 0; - } - } - return 0; -} - -- (id)ruleEditor:(NSRuleEditor *)editor child:(NSInteger)index forCriterion:(nullable id)criterion withRowType:(NSRuleEditorRowType)rowType -{ - // nil criterion is always the first element in a row, compound rows are only for "AND"/"OR" groups - if(!criterion && rowType == NSRuleEditorRowTypeCompound) { - StringNode *node = [[StringNode alloc] init]; - switch(index) { - case 0: [node setValue:@"AND"]; break; - case 1: [node setValue:@"OR"]; break; - } - return [node autorelease]; - } - // this is the column field - else if(!criterion && rowType == NSRuleEditorRowTypeSimple) { - return [columns objectAtIndex:index]; - } - else if(rowType == NSRuleEditorRowTypeSimple) { - // the children of the columns are their operators - RuleNodeType type = [(RuleNode *) criterion type]; - if (type == RuleNodeTypeColumn) { - return [[criterion operatorCache] objectAtIndex:index]; - } - // the first child of an operator is the first argument - else if(type == RuleNodeTypeOperator) { - NSDictionary *filter = [(OpNode *)criterion filter]; - if([[filter objectForKey:@"NumberOfArguments"] integerValue]) { - ArgNode *arg = [[ArgNode alloc] init]; - [arg setFilter:filter]; - [arg setArgIndex:0]; - return [arg autorelease]; - } - } - // the child of an argument can only be the conjunction label if more arguments follow - else if(type == RuleNodeTypeArgument) { - NSDictionary *filter = [(ArgNode *)criterion filter]; - NSUInteger argIndex = [(ArgNode *)criterion argIndex]; - if([[filter objectForKey:@"NumberOfArguments"] integerValue] > argIndex +1) { - ConnectorNode *node = [[ConnectorNode alloc] init]; - [node setFilter:filter]; - [node setLabelIndex:argIndex]; // label 0 follows argument 0 - return [node autorelease]; - } - } - // the child of a conjunction is the next argument, if we have one - else if(type == RuleNodeTypeConnector) { - ConnectorNode *node = (ConnectorNode *)criterion; - NSInteger numOfArgs = [[[node filter] objectForKey:@"NumberOfArguments"] integerValue]; - if(numOfArgs > [node labelIndex]+1) { - ArgNode *arg = [[ArgNode alloc] init]; - [arg setFilter:[node filter]]; - [arg setArgIndex:([node labelIndex]+1)]; - return [arg autorelease]; - } - } - } - return nil; -} - -- (id)ruleEditor:(NSRuleEditor *)editor displayValueForCriterion:(id)criterion inRow:(NSInteger)row -{ - switch([(RuleNode *)criterion type]) { - case RuleNodeTypeString: return [(StringNode *)criterion value]; - case RuleNodeTypeColumn: return [(ColumnNode *)criterion name]; - case RuleNodeTypeOperator: { - OpNode *node = (OpNode *)criterion; - NSMenuItem *item; - if ([[[node settings] objectForKey:@"isSeparator"] boolValue]) { - item = [NSMenuItem separatorItem]; - } - else { - item = [[NSMenuItem alloc] initWithTitle:[[node settings] objectForKey:@"title"] action:NULL keyEquivalent:@""]; - [item setToolTip:[[node settings] objectForKey:@"tooltip"]]; - [item setTag:[[[node settings] objectForKey:@"tag"] integerValue]]; - //TODO the following seems to be mentioned exactly nowhere on the internet/in documentation, but without it NSMenuItems won't work properly, even though Apple says they are supported - [item setRepresentedObject:@{ - @"item": node, - @"value": [item title], - // this one is needed by the "Edit filters…" item for context - @"filterType": SPBoxNil([[node settings] objectForKey:@"filterType"]), - }]; - // override the default action from the rule editor if given (used to open the edit content filters sheet) - id _target = [[node settings] objectForKey:@"target"]; - SEL _action = (SEL)[(NSValue *)[[node settings] objectForKey:@"action"] pointerValue]; - if(_target && _action) { - [item setTarget:_target]; - [item setAction:_action]; - } - [item autorelease]; - } - return item; - } - case RuleNodeTypeArgument: { - //an argument is a textfield - ArgNode *node = (ArgNode *)criterion; - NSTextField *textField = [[NSTextField alloc] init]; - [[textField cell] setSendsActionOnEndEditing:YES]; - [[textField cell] setUsesSingleLineMode:YES]; - [textField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - [textField sizeToFit]; - [textField setTarget:self]; - [textField setAction:@selector(_textFieldAction:)]; - if([node initialValue]) [textField setStringValue:[node initialValue]]; - NSRect frame = [textField frame]; - //adjust width, to make the field wider - frame.size.width = 500; //TODO determine a good width (possibly from the field type size) - how to access the rule editors bounds? - [textField setFrame:frame]; - return [textField autorelease]; - } - case RuleNodeTypeConnector: { - // a simple string for once - ConnectorNode *node = (ConnectorNode *)criterion; - NSArray* labels = [[node filter] objectForKey:@"ConjunctionLabels"]; - return (labels && [labels count] == 1)? [labels objectAtIndex:0] : @""; - } - } - - return nil; -} - -- (IBAction)_textFieldAction:(id)sender -{ - // if the action was caused by pressing return or enter, trigger filtering - NSEvent *event = [NSApp currentEvent]; - if(event && [event type] == NSKeyDown && ([event keyCode] == 36 || [event keyCode] == 76)) { - [self filterTable:nil]; - } -} - -- (IBAction)filterTable:(id)sender -{ - if(target && action) [target performSelector:action withObject:self]; -} - -- (void)_resize -{ - // The situation with the sizing is a bit f'ed up: - // - When -ruleEditorRowsDidChange: is invoked the NSRuleEditor has not yet updated its required frame size - // - We can't use KVO on -frame either, because SPTableContent will update the container size which - // ultimately also updates the NSRuleEditor's frame, causing a loop - // - Calling -sizeToFit works, but only when the NSRuleEditor is growing. It won't shrink - // after removing rows. - // - -intrinsicContentSize is what we want, but that method is 10.7+, so on 10.6 let's do the - // easiest workaround (note that both -intrinsicContentSize and -sizeToFit internally use -[NSRuleEditor _minimumFrameHeight]) - CGFloat wantsHeight; - if([filterRuleEditor respondsToSelector:@selector(intrinsicContentSize)]) { - NSSize sz = [filterRuleEditor intrinsicContentSize]; - wantsHeight = sz.height; - } - else { - wantsHeight = [filterRuleEditor rowHeight] * [filterRuleEditor numberOfRows]; - } - if(wantsHeight != preferredHeight) { - [self setPreferredHeight:wantsHeight]; - [[NSNotificationCenter defaultCenter] postNotificationName:SPTableContentFilterHeightChangedNotification object:self]; - } -} - -- (void)ruleEditorRowsDidChange:(NSNotification *)notification -{ - //TODO find a better way to trigger resize - // We can't do this here, because it will cause rows to jump around when removing them (the add case works fine, though) - [self performSelector:@selector(_resize) withObject:nil afterDelay:0.2]; - //[self _resize]; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; - SPClear(model); - SPClear(columns); - SPClear(contentFilters); - SPClear(numberOfDefaultFilters); - [super dealloc]; -} - -/** - * Sets the compare types for the filter and the appropriate formatter for the textField - */ -- (NSArray *)_compareTypesForColumn:(ColumnNode *)colNode -{ - if(contentFilters == nil - || ![contentFilters objectForKey:@"number"] - || ![contentFilters objectForKey:@"string"] - || ![contentFilters objectForKey:@"date"]) { - NSLog(@"Error while setting filter types."); - NSBeep(); - return @[]; - } - - NSString *fieldTypeGrouping; - if([colNode typegrouping]) { - fieldTypeGrouping = [NSString stringWithString:[colNode typegrouping]]; - } - else { - return @[]; - } - - NSMutableArray *compareItems = [NSMutableArray array]; - - NSString *compareType; - - if ( [fieldTypeGrouping isEqualToString:@"date"] ) { - compareType = @"date"; - - /* - if ([fieldType isEqualToString:@"timestamp"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] - initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"datetime"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d %H:%M:%S" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"date"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y-%m-%d" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"time"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%H:%M:%S" allowNaturalLanguage:YES]]; - } - if ([fieldType isEqualToString:@"year"]) { - [argumentField setFormatter:[[NSDateFormatter alloc] initWithDateFormat:@"%Y" allowNaturalLanguage:YES]]; - } - */ - - // TODO: A bug in the framework previously meant enum fields had to be treated as string fields for the purposes - // of comparison - this can now be split out to support additional comparison fucntionality if desired. - } - else if ([fieldTypeGrouping isEqualToString:@"string"] || [fieldTypeGrouping isEqualToString:@"binary"] - || [fieldTypeGrouping isEqualToString:@"textdata"] || [fieldTypeGrouping isEqualToString:@"blobdata"] - || [fieldTypeGrouping isEqualToString:@"enum"]) { - - compareType = @"string"; - // [argumentField setFormatter:nil]; - - } - else if ([fieldTypeGrouping isEqualToString:@"bit"] || [fieldTypeGrouping isEqualToString:@"integer"] - || [fieldTypeGrouping isEqualToString:@"float"]) { - compareType = @"number"; - // [argumentField setFormatter:numberFormatter]; - - } - else if ([fieldTypeGrouping isEqualToString:@"geometry"]) { - compareType = @"spatial"; - - } - else { - compareType = @""; - NSBeep(); - NSLog(@"ERROR: unknown type for comparision: in %@", fieldTypeGrouping); - } - - // Add IS NULL and IS NOT NULL as they should always be available - // [compareField addItemWithTitle:@"IS NULL"]; - // [compareField addItemWithTitle:@"IS NOT NULL"]; - - // Remove user-defined filters first - if([numberOfDefaultFilters objectForKey:compareType]) { - NSUInteger cycles = [[contentFilters objectForKey:compareType] count] - [[numberOfDefaultFilters objectForKey:compareType] integerValue]; - while(cycles > 0) { - [[contentFilters objectForKey:compareType] removeLastObject]; - cycles--; - } - } - - NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults]; - -#ifndef SP_CODA /* content filters */ - // Load global user-defined content filters - if([prefs objectForKey:SPContentFilters] - && [contentFilters objectForKey:compareType] - && [[prefs objectForKey:SPContentFilters] objectForKey:compareType]) - { - [[contentFilters objectForKey:compareType] addObjectsFromArray:[[prefs objectForKey:SPContentFilters] objectForKey:compareType]]; - } - - // Load doc-based user-defined content filters - if([[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]) { - id filters = [[SPQueryController sharedQueryController] contentFilterForFileURL:[tableDocumentInstance fileURL]]; - if([filters objectForKey:compareType]) - [[contentFilters objectForKey:compareType] addObjectsFromArray:[filters objectForKey:compareType]]; - } -#endif - - NSUInteger i = 0; - if([contentFilters objectForKey:compareType]) { - for (id filter in [contentFilters objectForKey:compareType]) { - // Create the tooltip - NSString *tooltip; - if ([filter objectForKey:@"Tooltip"]) - tooltip = [filter objectForKey:@"Tooltip"]; - else { - NSMutableString *tip = [[NSMutableString alloc] init]; - if ([filter objectForKey:@"Clause"] && [(NSString *) [filter objectForKey:@"Clause"] length]) { - [tip setString:[[filter objectForKey:@"Clause"] stringByReplacingOccurrencesOfRegex:@"(?<!\\\\)(\\$\\{.*?\\})" withString:@"[arg]"]]; - if ([tip isMatchedByRegex:@"(?<!\\\\)\\$BINARY"]) { - [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$BINARY" withString:@""]; - [tip appendString:NSLocalizedString(@"\n\nPress ⇧ for binary search (case-sensitive).", @"\n\npress shift for binary search tooltip message")]; - } - [tip flushCachedRegexData]; - [tip replaceOccurrencesOfRegex:@"(?<!\\\\)\\$CURRENT_FIELD" withString:[[colNode name] backtickQuotedString]]; - [tip flushCachedRegexData]; - tooltip = [NSString stringWithString:tip]; - } else { - tooltip = @""; - } - [tip release]; - } - - OpNode *node = [[OpNode alloc] init]; - [node setParentColumn:colNode]; - [node setSettings:@{ - @"title": ([filter objectForKey:@"MenuLabel"] ? [filter objectForKey:@"MenuLabel"] : @"not specified"), - @"tooltip": tooltip, - @"tag": @(i), - @"filterType": compareType, - }]; - [node setFilter:filter]; - [compareItems addObject:node]; - [node release]; - i++; - } - } - - { - OpNode *node = [[OpNode alloc] init]; - [node setParentColumn:colNode]; - [node setSettings:@{ - @"isSeparator": @YES, - }]; - [compareItems addObject:node]; - [node release]; - } - - { - OpNode *node = [[OpNode alloc] init]; - [node setParentColumn:colNode]; - [node setSettings:@{ - @"title": NSLocalizedString(@"Edit Filters…", @"edit filter"), - @"tooltip": NSLocalizedString(@"Edit user-defined Filters…", @"edit user-defined filter"), - @"tag": @(i), - @"target": self, - @"action": [NSValue valueWithPointer:@selector(_editFiltersAction:)], - @"filterType": compareType, - }]; - [compareItems addObject:node]; - [node release]; - } - - return compareItems; -} - -- (IBAction)_editFiltersAction:(id)sender -{ - if([sender isKindOfClass:[NSMenuItem class]]) { - NSMenuItem *menuItem = (NSMenuItem *)sender; - NSString *filterType = [(NSDictionary *)[menuItem representedObject] objectForKey:@"filterType"]; - if([filterType unboxNull]) [self openContentFilterManagerForFilterType:filterType]; - } -} - -- (void)openContentFilterManagerForFilterType:(NSString *)filterType -{ - // init query favorites controller -#ifndef SP_CODA - [[NSUserDefaults standardUserDefaults] synchronize]; -#endif - if(contentFilterManager) [contentFilterManager release]; - contentFilterManager = [[SPContentFilterManager alloc] initWithDatabaseDocument:tableDocumentInstance forFilterType:filterType]; - - // Open query favorite manager - [NSApp beginSheet:[contentFilterManager window] - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:contentFilterManager - didEndSelector:nil - contextInfo:nil]; -} - -- (void)_contentFiltersHaveBeenUpdated:(NSNotification *)notification -{ - //tell the rule editor to reload its criteria - [filterRuleEditor reloadCriteria]; -} - -- (BOOL)isEmpty -{ - return ([[self model] count] == 0); -} - -- (void)addFilterExpression -{ - [filterRuleEditor insertRowAtIndex:0 withType:NSRuleEditorRowTypeSimple asSubrowOfRow:-1 animate:NO]; -} - -- (NSRuleEditor *)view -{ - return filterRuleEditor; -} - -- (BOOL)isEnabled -{ - return enabled; -} - -- (void)setEnabled:(BOOL)_enabled -{ - enabled = _enabled; - [filterButton setEnabled:_enabled]; - [filterRuleEditor setEnabled:_enabled]; -} - -- (NSString *)sqlWhereExpressionWithBinary:(BOOL)isBINARY error:(NSError **)err -{ - NSMutableString *filterString = [[NSMutableString alloc] init]; - NSError *innerError = nil; - - @autoreleasepool { - //get the serialized filter and try to optimise it - NSDictionary *filterTree = [[self class] _flattenSerializedFilter:[self _serializedFilterIncludingFilterDefinition:YES]]; - - // build it recursively - [[self class] _writeFilterTree:filterTree toString:filterString wrapInParenthesis:NO binary:isBINARY error:&innerError]; - - [innerError retain]; // carry the error (if any) outside of the scope of the autoreleasepool - } - - if(innerError) { - [filterString release]; - if(err) *err = [innerError autorelease]; - return nil; - } - - if(err) *err = nil; - - NSString *out = [filterString copy]; - [filterString release]; - - return [out autorelease]; -} - -- (NSDictionary *)serializedFilter -{ - return [self _serializedFilterIncludingFilterDefinition:NO]; -} - -- (NSDictionary *)_serializedFilterIncludingFilterDefinition:(BOOL)includeDefinition -{ - NSMutableArray *rootItems = [NSMutableArray arrayWithCapacity:[model count]]; - for(NSDictionary *item in model) { - [rootItems addObject:[self _serializeSubtree:item includingDefinition:includeDefinition]]; - } - //the root serialized filter can either be an AND of multiple root items or a single root item - if([rootItems count] == 1) { - return [rootItems objectAtIndex:0]; - } - else { - return @{ - SerFilterClass: SerFilterClassGroup, - SerFilterGroupIsConjunction: @YES, - SerFilterGroupChildren: rootItems, - }; - } -} - -- (NSDictionary *)_serializeSubtree:(NSDictionary *)item includingDefinition:(BOOL)includeDefinition -{ - NSRuleEditorRowType rowType = (NSRuleEditorRowType)[[item objectForKey:@"rowType"] unsignedIntegerValue]; - // check if we have an AND or OR compound row - if(rowType == NSRuleEditorRowTypeCompound) { - // process all children - NSArray *subrows = [item objectForKey:@"subrows"]; - NSMutableArray *children = [[NSMutableArray alloc] initWithCapacity:[subrows count]]; - for(NSDictionary *subitem in subrows) { - [children addObject:[self _serializeSubtree:subitem includingDefinition:includeDefinition]]; - } - StringNode *node = [[item objectForKey:@"criteria"] objectAtIndex:0]; - BOOL isConjunction = [@"AND" isEqualToString:[node value]]; - NSDictionary *out = @{ - SerFilterClass: SerFilterClassGroup, - SerFilterGroupIsConjunction: @(isConjunction), - SerFilterGroupChildren: children, - }; - [children release]; - return out; - } - else { - NSArray *criteria = [item objectForKey:@"criteria"]; - NSArray *displayValues = [item objectForKey:@"displayValues"]; - ColumnNode *col = [criteria objectAtIndex:0]; - OpNode *op = [criteria objectAtIndex:1]; - NSMutableArray *filterValues = [[NSMutableArray alloc] initWithCapacity:2]; - for (NSUInteger i = 2; i < [criteria count]; ++i) { // the first two must always be column and operator - if([(RuleNode *)[criteria objectAtIndex:i] type] != RuleNodeTypeArgument) continue; - // if we found an argument, the displayValue will be an NSTextField we can ask for the value - NSString *value = [(NSTextField *)[displayValues objectAtIndex:i] stringValue]; - [filterValues addObject:value]; - } - NSDictionary *out = @{ - SerFilterClass: SerFilterClassExpression, - SerFilterExprColumn: [col name], - SerFilterExprType: [[op settings] objectForKey:@"filterType"], - SerFilterExprComparison: [[op filter] objectForKey:@"MenuLabel"], - SerFilterExprValues: filterValues, - }; - if(includeDefinition) { - out = [NSMutableDictionary dictionaryWithDictionary:out]; - [(NSMutableDictionary *)out setObject:[op filter] forKey:SerFilterExprDefinition]; - } - [filterValues release]; - return out; - } -} - -void _addIfNotNil(NSMutableArray *array, id toAdd) -{ - if(toAdd != nil) [array addObject:toAdd]; -} - -- (void)restoreSerializedFilters:(NSDictionary *)serialized -{ - if(!serialized) return; - - // we have to exchange the whole model object or NSRuleEditor will get confused - NSMutableArray *newModel = [[NSMutableArray alloc] init]; - - @autoreleasepool { - // if the root object is an AND group directly restore its contents, otherwise restore the object - if(SerIsGroup(serialized) && [[serialized objectForKey:SerFilterGroupIsConjunction] boolValue]) { - for(NSDictionary *child in [serialized objectForKey:SerFilterGroupChildren]) { - _addIfNotNil(newModel, [self _restoreSerializedFilter:child]); - } - } - else { - _addIfNotNil(newModel, [self _restoreSerializedFilter:serialized]); - } - } - - [self setModel:newModel]; - [newModel release]; -} - -- (NSMutableDictionary *)_restoreSerializedFilter:(NSDictionary *)serialized -{ - NSMutableDictionary *obj = [[NSMutableDictionary alloc] initWithCapacity:4]; - - if(SerIsGroup(serialized)) { - [obj setObject:@(NSRuleEditorRowTypeCompound) forKey:@"rowType"]; - - StringNode *sn = [[StringNode alloc] init]; - [sn setValue:([[serialized objectForKey:SerFilterGroupIsConjunction] boolValue] ? @"AND" : @"OR")]; - // those have to be mutable arrays for the rule editor to work - NSMutableArray *criteria = [NSMutableArray arrayWithObject:sn]; - [obj setObject:criteria forKey:@"criteria"]; - - id displayValue = [self ruleEditor:filterRuleEditor displayValueForCriterion:sn inRow:-1]; - NSMutableArray *displayValues = [NSMutableArray arrayWithObject:displayValue]; - [obj setObject:displayValues forKey:@"displayValues"]; - [sn release]; - - NSArray *children = [serialized objectForKey:SerFilterGroupChildren]; - NSMutableArray *subrows = [[NSMutableArray alloc] initWithCapacity:[children count]]; - for(NSDictionary *child in children) { - _addIfNotNil(subrows, [self _restoreSerializedFilter:child]); - } - [obj setObject:subrows forKey:@"subrows"]; - [subrows release]; - } - else { - [obj setObject:@(NSRuleEditorRowTypeSimple) forKey:@"rowType"]; - //simple rows can't have child rows - [obj setObject:[NSMutableArray array] forKey:@"subrows"]; - - NSMutableArray *criteria = [NSMutableArray arrayWithCapacity:5]; - - //first look up the column, bail if it doesn't exist anymore or types changed - NSString *columnName = [serialized objectForKey:SerFilterExprColumn]; - ColumnNode *col = [self _columnForName:columnName]; - if(!col) { - SPLog(@"cannot deserialize unknown column: %@", columnName); - goto fail; - } - [criteria addObject:col]; - - //next try to find the given operator - NSString *operatorName = [serialized objectForKey:SerFilterExprComparison]; - OpNode *op = [self _operatorNamed:operatorName forColumn:col]; - if(!op) { - SPLog(@"cannot deserialize unknown operator: %@",operatorName); - goto fail; - } - [criteria addObject:op]; - - // we still have to check if the current column type is the same as when we serialized because an operator - // with the same name can still act differently for different types - NSString *curFilterType = [[op settings] objectForKey:@"filterType"]; - NSString *serFilterType = [serialized objectForKey:SerFilterExprType]; // this is optional - if(serFilterType && ![curFilterType isEqualToString:serFilterType]) { - SPLog(@"mismatch in filter types for operator %@: current=%@, serialized=%@",op,curFilterType,serFilterType); - goto fail; - } - - //now we have to create the argument node(s) - NSInteger numOfArgs = [[[op filter] objectForKey:@"NumberOfArguments"] integerValue]; - //fail if the current op requires more arguments than we have stored values for - NSArray *values = [serialized objectForKey:SerFilterExprValues]; - if(numOfArgs > [values count]) { - SPLog(@"filter operator %@ requires %ld arguments, but only have %ld stored values!",op,numOfArgs,[values count]); - goto fail; - } - - // otherwise add them - for (NSUInteger i = 0; i < numOfArgs; ++i) { - // insert connector node between args? - if(i > 0) { - ConnectorNode *node = [[ConnectorNode alloc] init]; - [node setFilter:[op filter]]; - [node setLabelIndex:(i-1)]; // label 0 follows argument 0 - [criteria addObject:node]; - [node release]; - } - ArgNode *arg = [[ArgNode alloc] init]; - [arg setArgIndex:i]; - [arg setFilter:[op filter]]; - [arg setInitialValue:[values objectAtIndex:i]]; - [criteria addObject:arg]; - [arg release]; - } - - [obj setObject:criteria forKey:@"criteria"]; - - //the last thing that remains is creating the displayValues for all criteria - NSMutableArray *displayValues = [NSMutableArray arrayWithCapacity:[criteria count]]; - for(id criterion in criteria) { - id dispValue = [self ruleEditor:filterRuleEditor displayValueForCriterion:criterion inRow:-1]; - if(!dispValue) { - SPLog(@"got nil displayValue for criterion %@ on deserialization!",criterion); - goto fail; - } - [displayValues addObject:dispValue]; - } - [obj setObject:displayValues forKey:@"displayValues"]; - } - - return [obj autorelease]; - -fail: - [obj release]; - return nil; -} - -+ (NSDictionary *)makeSerializedFilterForColumn:(NSString *)colName operator:(NSString *)opName values:(NSArray *)values -{ - return @{ - SerFilterClass: SerFilterClassExpression, - SerFilterExprColumn: colName, - SerFilterExprComparison: opName, - SerFilterExprValues: values, - }; -} - -- (ColumnNode *)_columnForName:(NSString *)name -{ - if([name length]) { - for (ColumnNode *col in columns) { - if ([name isEqualToString:[col name]]) return col; - } - } - return nil; -} - -- (OpNode *)_operatorNamed:(NSString *)title forColumn:(ColumnNode *)col -{ - if([title length]) { - // check if we have the operator cache, otherwise build it - if(![col operatorCache]) { - NSArray *ops = [self _compareTypesForColumn:col]; - [col setOperatorCache:ops]; - } - // try to find it in the operator cache - for(OpNode *node in [col operatorCache]) { - if([[[node filter] objectForKey:@"MenuLabel"] isEqualToString:title]) return node; - } - } - return nil; -} - -BOOL SerIsGroup(NSDictionary *dict) -{ - return [SerFilterClassGroup isEqual:[dict objectForKey:SerFilterClass]]; -} - -/** - * This method looks at the given serialized filter in a recursive manner and - * when it encounters - * - a group node with only a single child or - * - a child that is a group node of the same kind as the parent one - * it will pull the child(ren) up - * - * So for example: - * AND(expr1) => expr1 - * AND(expr1,AND(expr2,expr3)) => AND(expr1,expr2,expr3) - * - * The input dict is not modified, the returned dict will be equal to the input - * dict or have parts of it removed or replaced with new dicts. - */ -+ (NSDictionary *)_flattenSerializedFilter:(NSDictionary *)in -{ - // return non-group-nodes as is - if(!SerIsGroup(in)) return in; - - NSNumber *inIsConjunction = [in objectForKey:SerFilterGroupIsConjunction]; - - // first give all children the chance to flatten (depth first) - NSArray *children = [in objectForKey:SerFilterGroupChildren]; - NSMutableArray *flatChildren = [NSMutableArray arrayWithCapacity:[children count]]; - NSUInteger changed = 0; - for(NSDictionary *child in children) { - NSDictionary *flattened = [self _flattenSerializedFilter:child]; - //take a closer look at the (possibly changed) child - is it a group node of the same kind as us? - if(SerIsGroup(flattened) && [inIsConjunction isEqual:[flattened objectForKey:SerFilterGroupIsConjunction]]) { - [flatChildren addObjectsFromArray:[flattened objectForKey:SerFilterGroupChildren]]; - changed++; - } - else if(flattened != child) { - changed++; - } - [flatChildren addObject:flattened]; - } - // if there is only a single child, return it (flattening) - if([flatChildren count] == 1) return [flatChildren objectAtIndex:0]; - // if none of the children changed return the original input - if(!changed) return in; - // last variant: some of our children changed, but we remain - return @{ - SerFilterClass: SerFilterClassGroup, - SerFilterGroupIsConjunction: inIsConjunction, - SerFilterGroupChildren: flatChildren - }; -} - -+ (void)_writeFilterTree:(NSDictionary *)in toString:(NSMutableString *)out wrapInParenthesis:(BOOL)wrap binary:(BOOL)isBINARY error:(NSError **)err -{ - NSError *myErr = nil; - - if(wrap) [out appendString:@"("]; - - if(SerIsGroup(in)) { - BOOL isConjunction = [[in objectForKey:SerFilterGroupIsConjunction] boolValue]; - NSString *connector = isConjunction ? @"AND" : @"OR"; - BOOL first = YES; - NSArray *children = [in objectForKey:SerFilterGroupChildren]; - for(NSDictionary *child in children) { - if(!first) [out appendFormat:@" %@ ",connector]; - else first = NO; - // if the child is a group node but of a different kind we want to wrap it in order to prevent operator precedence confusion - // expression children will always be wrapped for clarity, except if there is only a single one and we are already wrapped - BOOL wrapChild = YES; - if(SerIsGroup(child)) { - BOOL childIsConjunction = [[child objectForKey:SerFilterGroupIsConjunction] boolValue]; - if(isConjunction == childIsConjunction) wrapChild = NO; - } - else { - if(wrap && [children count] == 1) wrapChild = NO; - } - [self _writeFilterTree:child toString:out wrapInParenthesis:wrapChild binary:isBINARY error:&myErr]; - if(myErr) { - if(err) *err = myErr; - return; - } - } - } - else { - // finally - build a SQL filter expression - NSDictionary *filter = [in objectForKey:SerFilterExprDefinition]; - if(!filter) { - if(err) *err = [NSError errorWithDomain:SPErrorDomain code:0 userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedString(@"Fatal error while retrieving content filter. No filter definition found.", @"filter to sql conversion : internal error : 0"), - }]; - return; - } - - if(![filter objectForKey:@"NumberOfArguments"]) { - if(err) *err = [NSError errorWithDomain:SPErrorDomain code:1 userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedString(@"Error while retrieving filter clause. No “NumberOfArguments” key found.", @"filter to sql conversion : internal error : invalid filter definition (1)"), - }]; - return; - } - - if(![filter objectForKey:@"Clause"] || ![(NSString *)[filter objectForKey:@"Clause"] length]) { - if(err) *err = [NSError errorWithDomain:SPErrorDomain code:2 userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedString(@"Content Filter clause is empty.", @"filter to sql conversion : internal error : invalid filter definition (2)"), - }]; - return; - } - - NSArray *values = [in objectForKey:SerFilterExprValues]; - - SPTableFilterParser *parser = [[SPTableFilterParser alloc] initWithFilterClause:[filter objectForKey:@"Clause"] - numberOfArguments:[[filter objectForKey:@"NumberOfArguments"] integerValue]]; - [parser setArgument:[values objectOrNilAtIndex:0]]; - [parser setFirstBetweenArgument:[values objectOrNilAtIndex:0]]; - [parser setSecondBetweenArgument:[values objectOrNilAtIndex:1]]; - [parser setSuppressLeadingTablePlaceholder:[[filter objectForKey:@"SuppressLeadingFieldPlaceholder"] boolValue]]; - [parser setCaseSensitive:isBINARY]; - [parser setCurrentField:[in objectForKey:SerFilterExprColumn]]; - - NSString *sql = [parser filterString]; - // SPTableFilterParser will return nil if it doesn't like the arguments and NSMutableString doesn't like nil - if(!sql) { - if(err) *err = [NSError errorWithDomain:SPErrorDomain code:3 userInfo:@{ - NSLocalizedDescriptionKey: NSLocalizedString(@"No valid SQL expression could be generated. Make sure that you have filled in all required fields.", @"filter to sql conversion : internal error : SPTableFilterParser failed"), - }]; - [parser release]; - return; - } - [out appendString:sql]; - - [parser release]; - } - - if(wrap) [out appendString:@")"]; -} - -@end - -#pragma mark - - -@implementation RuleNode - -@synthesize type = type; - -- (NSUInteger)hash { - return type; -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [(RuleNode *)other type] == type) return YES; - - return NO; -} - -@end - -@implementation ColumnNode - -@synthesize name = name; -@synthesize typegrouping = typegrouping; -@synthesize operatorCache = operatorCache; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeColumn; - } - return self; -} - -- (NSString *)description -{ - return [NSString stringWithFormat:@"ColumnNode<%@@%p>",[self name],self]; -} - -- (NSUInteger)hash { - return ([name hash] ^ [typegrouping hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [name isEqualToString:[other name]] && [typegrouping isEqualToString:[other typegrouping]]) return YES; - - return NO; -} - -@end - - -@implementation StringNode - -@synthesize value = value; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeString; - } - return self; -} - -- (NSUInteger)hash { - return ([value hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [value isEqualToString:[(StringNode *)other value]]) return YES; - - return NO; -} - -@end - -@implementation OpNode - -@synthesize parentColumn = parentColumn; -@synthesize settings = settings; -@synthesize filter = filter; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeOperator; - } - return self; -} - -- (void)dealloc -{ - [self setFilter:nil]; - [self setSettings:nil]; - [super dealloc]; -} - -- (NSUInteger)hash { - return (([parentColumn hash] << 16) ^ [settings hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [settings isEqualToDictionary:[(OpNode *)other settings]] && [parentColumn isEqual:[other parentColumn]]) return YES; - - return NO; -} - -@end - -@implementation ArgNode - -@synthesize filter = filter; -@synthesize argIndex = argIndex; -@synthesize initialValue = initialValue; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeArgument; - } - return self; -} - -- (void)dealloc -{ - [self setInitialValue:nil]; - [self setFilter:nil]; - [super dealloc]; -} - -- (NSUInteger)hash { - // initialValue does not count towards hash because two Args are not different if only the initialValue differs - return ((argIndex << 16) ^ [filter hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - // initialValue does not count towards isEqual: because two Args are not different if only the initialValue differs - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ArgNode *)other filter]] && argIndex == [(ArgNode *)other argIndex]) return YES; - - return NO; -} - -@end - -@implementation ConnectorNode - -@synthesize filter = filter; -@synthesize labelIndex = labelIndex; - -- (instancetype)init -{ - if((self = [super init])) { - type = RuleNodeTypeConnector; - } - return self; -} - -- (void)dealloc -{ - [self setFilter:nil]; - [super dealloc]; -} - -- (NSUInteger)hash { - return ((labelIndex << 16) ^ [filter hash] ^ [super hash]); -} - -- (BOOL)isEqual:(id)other { - if (other == self) return YES; - if (other && [[other class] isEqual:[self class]] && [filter isEqualToDictionary:[(ConnectorNode *)other filter]] && labelIndex == [(ConnectorNode *)other labelIndex]) return YES; - - return NO; -} - -@end |