Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Code/TKStateMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
*/
Expand Down
29 changes: 29 additions & 0 deletions Code/TKStateMachine.m
Original file line number Diff line number Diff line change
Expand Up @@ -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."];
Expand Down Expand Up @@ -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) {
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand Down
49 changes: 49 additions & 0 deletions Specs/TKStateMachineSpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down