Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuration for lazely-instantiated components #29

Merged
merged 3 commits into from
Jul 7, 2013
Merged
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
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