diff --git a/Code/TKStateMachine.h b/Code/TKStateMachine.h index 06969d4..371127e 100644 --- a/Code/TKStateMachine.h +++ b/Code/TKStateMachine.h @@ -152,6 +152,18 @@ */ - (void)addEvents:(NSArray *)arrayOfEvents; +/** + Adds an event to the receiver using notation (FromState1,FromState2->EventName->ToState). + + The state names referenced by the notation must be registered with the receiver. + + @param notation The event notation to be parsed and added. + @raises TKStateMachineIsImmutableException Raised if an attempt is made to modify the state machine after it has been activated. + @raises TKStateMachineMalformedEventNotationException Raised if the notation is not properly formed + @raises NSInternalInconsistencyException Raised if the given notation references a `TKState` name that has not been registered with the receiver. + */ +- (void)addEventWithNotation:(NSString *)notation; + /** Retrieves the event with the given name from the receiver. @@ -252,6 +264,11 @@ extern NSString *const TKStateMachineDidChangeStateTransitionUserInfoKey; */ extern NSString *const TKStateMachineIsImmutableException; +/** + An exception raised when adding an event using notations with a malformed notation string. + */ +extern NSString *const TKStateMachineMalformedEventNotationException; + /** Error Codes */ diff --git a/Code/TKStateMachine.m b/Code/TKStateMachine.m index c90a621..8a25644 100644 --- a/Code/TKStateMachine.m +++ b/Code/TKStateMachine.m @@ -44,6 +44,7 @@ @interface TKState () NSString *const TKStateMachineDidChangeStateTransitionUserInfoKey = @"transition"; NSString *const TKStateMachineIsImmutableException = @"TKStateMachineIsImmutableException"; +NSString *const TKStateMachineMalformedEventNotationException = @"TKStateMachineMalformedEventNotationException"; #define TKRaiseIfActive() \ if ([self isActive]) [NSException raise:TKStateMachineIsImmutableException format:@"Unable to modify state machine: The state machine has already been activated."]; @@ -172,6 +173,34 @@ - (void)addEvents:(NSArray *)arrayOfEvents } } +- (void)addEventWithNotation:(NSString *)notation +{ + TKRaiseIfActive(); + NSArray *splits = [notation componentsSeparatedByString:@"->"]; + if (splits.count != 3) { + [NSException raise:TKStateMachineMalformedEventNotationException format:@"The notation provided does not conform to (FromState1,FromState2->EventName->ToState)."]; + } + NSString *fromStatesString = [splits firstObject]; + NSArray *fromStatesNames = [fromStatesString componentsSeparatedByString:@","]; + NSString *eventName = splits[1]; + NSString *toStateName = [splits lastObject]; + + NSMutableArray *fromStates = [[NSMutableArray alloc] initWithCapacity:fromStatesNames.count]; + for (NSString *fromStateName in fromStatesNames) { + TKState *fromState = [self stateNamed:fromStateName]; + if (!fromState) { + [NSException raise:NSInvalidArgumentException format:@"Cannot find a State named '%@'", fromStateName]; + } + [fromStates addObject:fromState]; + } + TKState *toState = [self stateNamed:toStateName]; + if (toState == nil) { + [NSException raise:NSInvalidArgumentException format:@"Cannot find a State named '%@'", toStateName]; + } + TKEvent *event = [TKEvent eventWithName:eventName transitioningFromStates:fromStates toState:toState]; + [self addEvent:event]; +} + - (TKEvent *)eventNamed:(NSString *)name { for (TKEvent *event in self.mutableEvents) { diff --git a/README.md b/README.md index 299f983..95d8141 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,9 @@ TKEvent *markAsUnread = [TKEvent eventWithName:@"Mark as Unread" transitioningFr [inboxStateMachine addEvents:@[ viewMessage, deleteMessage, markAsUnread ]]; +// Or add events using notations +[inboxStateMachine addEventWithNotation:@"Deleted->UnDelete->Read"]; + // Activate the state machine [inboxStateMachine activate]; diff --git a/Specs/TKStateMachineSpec.m b/Specs/TKStateMachineSpec.m index ffce220..db81bb6 100644 --- a/Specs/TKStateMachineSpec.m +++ b/Specs/TKStateMachineSpec.m @@ -141,6 +141,55 @@ - (void)startTryingToPickUpCollegeGirls {} }); }); +context(@"when state is initilized and an event is added via annotation", ^{ + __block TKStateMachine *notationMachine = nil; + __block TKState *singleState = nil; + __block TKState *datingState = nil; + __block TKState *marriedState = nil; + + beforeEach(^{ + notationMachine = [TKStateMachine new]; + singleState = [TKState stateWithName:@"Single"]; + datingState = [TKState stateWithName:@"Dating"]; + marriedState = [TKState stateWithName:@"Married"]; + [notationMachine addStates:@[ singleState, datingState, marriedState ]]; + }); + + it(@"adds an event using notation", ^{ + [notationMachine addEventWithNotation:@"Dating,Married->BreakUp->Single"]; + TKEvent *breakUpEvent = [notationMachine eventNamed:@"BreakUp"]; + [[breakUpEvent.sourceStates should] contain:marriedState]; + [[breakUpEvent.sourceStates should] contain:datingState]; + [[breakUpEvent.sourceStates should] haveCountOf:2]; + [[breakUpEvent.destinationState should] equal:singleState]; + }); + + it(@"raise an exception if notation is incorrect", ^{ + [[theBlock(^{ + [notationMachine addEventWithNotation:@"Dating>BreakUp>Single"]; + }) should] raiseWithName:TKStateMachineMalformedEventNotationException reason:@"The notation provided does not conform to (FromState1,FromState2->EventName->ToState)."]; + }); + + it(@"raise an exception if a from state is not found", ^{ + [[theBlock(^{ + [notationMachine addEventWithNotation:@"Widowed->FindGirlfriend->Dating"]; + }) should] raiseWithName:NSInvalidArgumentException reason:@"Cannot find a State named 'Widowed'"]; + }); + + it(@"raise an exception if the to state is not found", ^{ + [[theBlock(^{ + [notationMachine addEventWithNotation:@"Dating->OneNightStand->BigTrouble"]; + }) should] raiseWithName:NSInvalidArgumentException reason:@"Cannot find a State named 'BigTrouble'"]; + }); + + it(@"raise an exception if already activated", ^{ + [[theBlock(^{ + [notationMachine activate]; + [notationMachine addEventWithNotation:@"Dating->Propose->Married"]; + }) should] raiseWithName:TKStateMachineIsImmutableException reason:@"Unable to modify state machine: The state machine has already been activated."]; + }); +}); + context(@"when a state machine is copied", ^{ __block TKState *firstState; __block TKState *secondState;