Skip to content

Commit

Permalink
Merge pull request #29 from RomainLo/master
Browse files Browse the repository at this point in the history
Configuration for lazely-instantiated components
  • Loading branch information
jasperblues committed Jul 7, 2013
2 parents 8004f30 + 1cc415b commit ccfd371
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 41 deletions.
5 changes: 5 additions & 0 deletions Source/Component/TyphoonDefinition.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ typedef enum
@property(nonatomic) TyphoonScope scope;
@property(nonatomic, strong) TyphoonDefinition* factory;

/**
* Say if the componant (scoped as a singleton) should be lazily instantiated.
*/
@property(nonatomic, assign, getter = isLazy) BOOL lazy;


/* ====================================================================================================================================== */
#pragma mark Factory methods
Expand Down
23 changes: 21 additions & 2 deletions Source/Factory/TyphoonComponentFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,29 @@

NSMutableDictionary* _currentlyResolvingReferences;
NSMutableArray* _mutators;
BOOL _isLoading;
}

BOOL _hasPerformedMutations;
/**
* The instantiated singletons.
*/
@property (nonatomic, strong, readonly) NSArray *singletons;

}
/**
* Say if the factory has been loaded.
*/
@property (nonatomic, assign, getter = isLoaded) BOOL loaded;

/**
* Mutate the component definitions with the mutators and
* build the not-lazy singletons.
*/
- (void)load;

/**
* Dump all the singletons.
*/
- (void)unload;

/**
* Returns the default component factory, if one has been set. (See makeDefault ).
Expand Down
89 changes: 67 additions & 22 deletions Source/Factory/TyphoonComponentFactory.m
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,56 @@ - (id)init
_singletons = [[NSMutableDictionary alloc] init];
_currentlyResolvingReferences = [[NSMutableDictionary alloc] init];
_mutators = [[NSMutableArray alloc] init];
_hasPerformedMutations = NO;
}
return self;
}


/* ========================================================== Interface Methods ========================================================= */
- (NSArray *)singletons {
return [_singletons copy];
}

- (void)load
{
@synchronized (self)
{
if (!_isLoading && ![self isLoaded])
{
// ensure that the method won't be call recursively.
_isLoading = YES;

// First, we call the mutator on every registered definition.
[_mutators enumerateObjectsUsingBlock:^(id<TyphoonComponentFactoryMutator> mutator, NSUInteger idx, BOOL *stop) {
[mutator mutateComponentDefinitionsIfRequired:[self registry]];
}];

// Then, we instanciate the not-lazy singletons.
[_registry enumerateObjectsUsingBlock:^(id definition, NSUInteger idx, BOOL *stop) {
if (([definition scope] == TyphoonScopeSingleton) && ![definition isLazy]) {
[self singletonForDefinition:definition];
}

}];

_isLoading = NO;
[self setLoaded:YES];
}
}
}

- (void)unload
{
@synchronized (self)
{
if ([self isLoaded])
{
[_singletons removeAllObjects];
[self setLoaded:NO];
}
}
}

- (void)register:(TyphoonDefinition*)definition
{
if ([definition.key length] == 0)
Expand All @@ -71,16 +114,34 @@ - (void)register:(TyphoonDefinition*)definition
}
NSLog(@"Registering: %@ with key: %@", NSStringFromClass(definition.type), definition.key);
[_registry addObject:definition];

// I would handle it via an exception but, in order to keep
// the contract of the class, I have implemented another
// strategy: since the not-lazy singletons have to be built once
// the factory has been loaded, we build it directly in
// the register method if the factory is already loaded.
if ([self isLoaded])
{
[_mutators enumerateObjectsUsingBlock:^(id<TyphoonComponentFactoryMutator> mutator, NSUInteger idx, BOOL *stop) {
[mutator mutateComponentDefinitionsIfRequired:@[definition]];
}];

if (([definition scope] == TyphoonScopeSingleton) && ![definition isLazy])
{
[self singletonForDefinition:definition];
}
}
}

- (id)componentForType:(id)classOrProtocol
{
if (! [self isLoaded]) [self load];
return [self objectForDefinition:[self definitionForType:classOrProtocol]];
}

- (NSArray*)allComponentsForType:(id)classOrProtocol
{
[self performMutationsIfRequired];
if (! [self isLoaded]) [self load];
NSMutableArray* results = [[NSMutableArray alloc] init];
NSArray* definitions = [self allDefinitionsForType:classOrProtocol];
NSLog(@"Definitions: %@", definitions);
Expand All @@ -97,7 +158,7 @@ - (id)componentForKey:(NSString*)key
{
if (key)
{
[self performMutationsIfRequired];
if (! [self isLoaded]) [self load];
TyphoonDefinition* definition = [self definitionForKey:key];
if (!definition)
{
Expand All @@ -121,6 +182,7 @@ - (void)makeDefault

- (NSArray*)registry
{
if (! [self isLoaded]) [self load];
return [_registry copy];
}

Expand All @@ -130,7 +192,8 @@ - (void)attachMutator:(id)mutator
[_mutators addObject:mutator];
}

- (void)injectProperties:(id)instance {
- (void)injectProperties:(id)instance
{
Class class = [instance class];
for (TyphoonDefinition* definition in _registry)
{
Expand Down Expand Up @@ -190,22 +253,4 @@ - (TyphoonDefinition*)definitionForKey:(NSString*)key
return nil;
}

- (void)performMutationsIfRequired
{
@synchronized (self)
{
if (!_hasPerformedMutations)
{
NSLog(@"Running mutators. . . %@", _mutators);
for (id <TyphoonComponentFactoryMutator> mutator in _mutators)
{
[mutator mutateComponentDefinitionsIfRequired:_registry];
}
_hasPerformedMutations = YES;
}
}
}



@end
39 changes: 23 additions & 16 deletions Source/Factory/Xml/TyphoonRXMLElement+XmlComponentFactory.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,20 @@ - (TyphoonDefinition*)asComponentDefinition
{
[NSException raise:NSInvalidArgumentException format:@"Class '%@' can't be resolved.", [self attribute:@"class"]];
}

NSString* key = [self attribute:@"key"];
NSString* factory = [self attributeOrNilIfEmpty:@"factory-component"];
TyphoonDefinition* definition = [[TyphoonDefinition alloc] initWithClass:clazz key:key factoryComponent:factory];

TyphoonScope scope = [self scopeForStringValue:[[self attribute:@"scope"] lowercaseString]];
BOOL isLazy = (scope == TyphoonScopeSingleton) && [self attributeAsBool:@"lazy-init"];
// Don't throw exception if a lazy init is set to a prototype.
// Even if the input is wrong, this won't set the definition
// in an unstable statement.

TyphoonDefinition* definition = [[TyphoonDefinition alloc] initWithClass:clazz key:key factoryComponent:factory];
[definition setBeforePropertyInjection:NSSelectorFromString([self attribute:@"before-property-injection"])];
[definition setAfterPropertyInjection:NSSelectorFromString([self attribute:@"after-property-injection"])];
[self setScopeForDefinition:definition withStringValue:[[self attribute:@"scope"] lowercaseString]];
[definition setLazy:isLazy];
[definition setScope:scope];
[self parseComponentDefinitionChildren:definition];
return definition;
}
Expand Down Expand Up @@ -140,21 +147,21 @@ - (void)assertTagName:(NSString*)tagName
}
}

- (void)setScopeForDefinition:(TyphoonDefinition*)definition withStringValue:(NSString*)scope;
- (TyphoonScope)scopeForStringValue:(NSString*)scope
{

if ([scope isEqualToString:@"singleton"])
{
[definition setScope:TyphoonScopeSingleton];
}
else if ([scope isEqualToString:@"prototype"])
{
[definition setScope:TyphoonScopeDefault];
}
else if ([scope length] > 0)
{
[NSException raise:NSInvalidArgumentException format:@"Scope was '%@', but can only be 'singleton' or 'prototype'", scope];
NSArray *acceptedScopes = @[@"prototype", @"singleton"];
if (([scope length] > 0) && (! [acceptedScopes containsObject:scope])) {
[NSException raise:NSInvalidArgumentException format:@"Scope was '%@', but can only be 'singleton' or 'prototype'", scope];
}

// Here, we don't follow the Spring's implementation :
// the "default" scope is the prototype.
TyphoonScope result = TyphoonScopeDefault;
if ([scope isEqualToString:@"singleton"]) {
result = TyphoonScopeSingleton;
}

return result;
}


Expand Down
3 changes: 3 additions & 0 deletions Source/Factory/Xml/TyphoonRXMLElement.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
- (double)attributeAsDouble:(NSString *)attributeName;
- (double)attributeAsDouble:(NSString *)attributeName inNamespace:(NSString *)ns;

- (BOOL)attributeAsBool:(NSString *)attName;
- (BOOL)attributeAsBool:(NSString *)attName inNamespace:(NSString *)ns;

- (TyphoonRXMLElement*)child:(NSString *)tag;
- (TyphoonRXMLElement*)child:(NSString *)tag inNamespace:(NSString *)ns;

Expand Down
15 changes: 15 additions & 0 deletions Source/Factory/Xml/TyphoonRXMLElement.m
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,21 @@ - (double)attributeAsDouble:(NSString *)attName inNamespace:(NSString *)ns {
return [[self attribute:attName inNamespace:ns] doubleValue];
}

- (BOOL)attributeAsBool:(NSString *)attName {
// if the string value is different from true or yes, we considere it as NO.
BOOL result = NO;
result |= [[[self attribute:attName] lowercaseString] isEqualTo:@"true"];
result |= [[[self attribute:attName] lowercaseString] isEqualTo:@"yes"];
return result;
}

- (BOOL)attributeAsBool:(NSString *)attName inNamespace:(NSString *)ns {
BOOL result = NO;
result |= [[[self attribute:attName inNamespace:ns] lowercaseString] isEqualTo:@"true"];
result |= [[[self attribute:attName inNamespace:ns] lowercaseString] isEqualTo:@"yes"];
return result;
}

- (BOOL)isValid {
return (doc_ != nil);
}
Expand Down
41 changes: 40 additions & 1 deletion Tests/Factory/TyphoonComponentFactoryTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,47 @@ - (void)test_injectProperties_subclassing
[_componentFactory injectProperties:knight];

assertThat(knight.quest, notNilValue());
}


- (void)test_load_isLoad {
[_componentFactory load];
assertThatBool([_componentFactory isLoaded], is(@YES));
}

- (void)test_unload_isLoad {
[_componentFactory load];
[_componentFactory unload];
assertThatBool([_componentFactory isLoaded], is(@NO));
}

- (void)test_registery_isLoad {
[_componentFactory registry];
assertThatBool([_componentFactory isLoaded], is(@YES));
}

- (void)test_load_mutators {
id<TyphoonComponentFactoryMutator> mutator = mockProtocol(@protocol(TyphoonComponentFactoryMutator));
[_componentFactory attachMutator:mutator];
[_componentFactory load];
[_componentFactory load]; // Should do nothing
[verifyCount(mutator, times(1)) mutateComponentDefinitionsIfRequired:[_componentFactory registry]];
}

- (void)test_load_singleton {
[_componentFactory register:[TyphoonDefinition withClass:[CampaignQuest class] properties:^(TyphoonDefinition *definition) {
[definition setScope:TyphoonScopeSingleton];
[definition setLazy:NO];
}]];
[_componentFactory register:[TyphoonDefinition withClass:[Knight class] properties:^(TyphoonDefinition *definition) {
[definition setScope:TyphoonScopeSingleton];
[definition setLazy:YES];
}]];
[_componentFactory register:[TyphoonDefinition withClass:[CavalryMan class] properties:^(TyphoonDefinition *definition) {
[definition setScope:TyphoonScopeDefault];
[definition setLazy:YES];
}]];
[_componentFactory load];
assertThatUnsignedInteger([[_componentFactory singletons] count], is(@1));
}


Expand Down
Loading

0 comments on commit ccfd371

Please sign in to comment.