├── .gitignore ├── Makefile ├── Makefile.win ├── PKGBUILD ├── README.md ├── auto_type_registration.vala ├── collection_dependencies.vala ├── container_builder.vala ├── creation_strategy.vala ├── creator_index.vala ├── default_container.vala ├── delegate_registration.vala ├── i_registration_context.vala ├── indexed_dependencies.vala ├── instance_registration.vala ├── lazy_dependencies.vala ├── resolve_error.vala └── test ├── collection_tests.vala ├── creation_strategy_tests.vala ├── decorator_tests.vala ├── error_tests.vala ├── index_tests.vala ├── lazy_tests.vala ├── module_tests.vala ├── property_tests.vala ├── registration_errors.vala ├── test_fixture.vala └── test_main.vala /.gitignore: -------------------------------------------------------------------------------- 1 | diva.h 2 | diva.so 3 | diva.vapi 4 | diva_test 5 | pkg 6 | src 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | LIBRARY_SOURCES = $(wildcard *.vala) 3 | 4 | TEST_SOURCES = $(wildcard test/*.vala) 5 | 6 | TEST_EXECUTABLE_NAME = diva_test 7 | LIBRARY_NAME = diva 8 | LIBRARY_API_VERSION = 1 9 | LIBRARY_VERSION = 0.1.0 10 | 11 | LIBRARY_SONAME = lib$(LIBRARY_NAME).so.$(LIBRARY_API_VERSION) 12 | LIBRARY_FILENAME = lib$(LIBRARY_NAME).so.$(LIBRARY_VERSION) 13 | 14 | ifndef VALAC 15 | VALAC := $(shell find $${PATH//:/ } -name valac-* | sort -r | head -n 1) 16 | endif 17 | 18 | ifndef VALAC 19 | $(error Could not find Vala compiler) 20 | endif 21 | 22 | COMMON_C_OPTIONS= -w 23 | VALA_COMMON_OPTIONS= $(foreach opt, $(COMMON_C_OPTIONS), -X $(opt)) -g 24 | 25 | all: $(LIBRARY_FILENAME) $(TEST_EXECUTABLE_NAME) 26 | export LD_LIBRARY_PATH=`pwd`; \ 27 | ./$(TEST_EXECUTABLE_NAME) --verbose 28 | 29 | $(LIBRARY_FILENAME): $(LIBRARY_SOURCES) 30 | $(VALAC) --pkg=gee-0.8 --library=$(LIBRARY_NAME) -H $(LIBRARY_NAME).h \ 31 | $(LIBRARY_SOURCES) -X -fpic -X -shared -X -Wl,-soname,$(LIBRARY_SONAME) \ 32 | -o $(LIBRARY_FILENAME) $(VALA_COMMON_OPTIONS) 33 | 34 | $(LIBRARY_SONAME): $(LIBRARY_FILENAME) 35 | ln -f $(LIBRARY_FILENAME) $(LIBRARY_SONAME) 36 | 37 | $(TEST_EXECUTABLE_NAME): $(LIBRARY_SONAME) $(TEST_SOURCES) 38 | $(VALAC) --pkg=gee-0.8 --pkg=$(LIBRARY_NAME) --vapidir=. \ 39 | -X ./$(LIBRARY_FILENAME) -X -I. $(TEST_SOURCES) $(VALA_COMMON_OPTIONS) \ 40 | -o $(TEST_EXECUTABLE_NAME) 41 | 42 | clean: 43 | rm -f $(TEST_EXECUTABLE_NAME) $(LIBRARY_NAME).{so,vapi,h} 44 | 45 | PREFIX ?= /usr/local 46 | 47 | 48 | install: $(LIBRARY_FILENAME) 49 | cp $(LIBRARY_FILENAME) $(PREFIX)/lib/$(LIBRARY_FILENAME) 50 | ldconfig 51 | mkdir -p $(PREFIX)/share/vala/vapi 52 | cp $(LIBRARY_NAME).vapi $(PREFIX)/share/vala/vapi/ 53 | mkdir -p $(PREFIX)/include 54 | cp $(LIBRARY_NAME).h $(PREFIX)/include 55 | -------------------------------------------------------------------------------- /Makefile.win: -------------------------------------------------------------------------------- 1 | 2 | LIBRARY_SOURCES = $(wildcard *.vala) 3 | 4 | TEST_SOURCES = $(wildcard test/*.vala) 5 | 6 | TEST_EXECUTABLE_NAME = diva_test 7 | LIBRARY_NAME = diva 8 | LIBRARY_API_VERSION = 1 9 | LIBRARY_VERSION = 0.1.0 10 | 11 | LIBRARY_SONAME = lib$(LIBRARY_NAME).so.$(LIBRARY_API_VERSION) 12 | LIBRARY_FILENAME = lib$(LIBRARY_NAME).dll 13 | 14 | VALAC := valac.exe 15 | 16 | COMMON_C_OPTIONS= -w 17 | VALA_COMMON_OPTIONS= $(foreach opt, $(COMMON_C_OPTIONS), -X $(opt)) -g 18 | 19 | all: $(LIBRARY_FILENAME) 20 | 21 | $(LIBRARY_FILENAME): $(LIBRARY_SOURCES) 22 | $(VALAC) --pkg=gee-0.8 --library=$(LIBRARY_NAME) -H $(LIBRARY_NAME).h \ 23 | $(LIBRARY_SOURCES) -X -fpic -X -shared -X -Wl,-soname,$(LIBRARY_SONAME) \ 24 | -o $(LIBRARY_FILENAME) $(VALA_COMMON_OPTIONS) 25 | 26 | $(LIBRARY_SONAME): $(LIBRARY_FILENAME) 27 | ln -f $(LIBRARY_FILENAME) $(LIBRARY_SONAME) 28 | 29 | $(TEST_EXECUTABLE_NAME): $(LIBRARY_SONAME) $(TEST_SOURCES) 30 | $(VALAC) --pkg=gee-0.8 --pkg=$(LIBRARY_NAME) --vapidir=. \ 31 | -X ./$(LIBRARY_FILENAME) -X -I. $(TEST_SOURCES) $(VALA_COMMON_OPTIONS) \ 32 | -o $(TEST_EXECUTABLE_NAME) 33 | 34 | clean: 35 | rm -f $(TEST_EXECUTABLE_NAME) $(LIBRARY_NAME).{so,vapi,h} 36 | 37 | PREFIX ?= /usr/local 38 | 39 | 40 | install: $(LIBRARY_FILENAME) 41 | mkdir -p $(PREFIX)/lib 42 | cp $(LIBRARY_FILENAME) $(PREFIX)/lib/$(LIBRARY_FILENAME) 43 | mkdir -p $(PREFIX)/share/vala/vapi 44 | cp $(LIBRARY_NAME).vapi $(PREFIX)/share/vala/vapi/ 45 | mkdir -p $(PREFIX)/include 46 | cp $(LIBRARY_NAME).h $(PREFIX)/include 47 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | pkgname=diva 2 | pkgver=0.1 3 | pkgrel=1 4 | arch=('x86_64') 5 | 6 | pkgdesc='Dependency injection library for vala/glib' 7 | depends=("${MINGW_PACKAGE_PREFIX}-libgee") 8 | source=(*.vala Makefile.win) 9 | 10 | build() { 11 | make -f Makefile.win 12 | } 13 | 14 | package() { 15 | make PREFIX=${pkgdir} install -f Makefile.win 16 | } 17 | md5sums=('235fd18da0e98bbed466d085e60983ca' 18 | '190f0174d8cc9e4b20cfa280838c7793' 19 | '4e841f1188f6b646d39dc3acc2681ead' 20 | '614851a2b3ad732612262baaf56c4cf9' 21 | 'c7c0a9e7008861e664e51825b1a6af18' 22 | '606ce86a3c4a781c82bbe5e5ece45dbe' 23 | 'bef707ef4078a1bfeac9125d53dd99d5' 24 | '636943623843f887092300e80ce6a3d5' 25 | '8555220ed4700bcf3d2be64b5deb7d03' 26 | '80299013990691e4d955c47ec5d58c42' 27 | '76af44b80f8faffb5c3bd2bd3436e59b' 28 | 'c1ec3912a920c011357c1da35fa42d6d' 29 | '1416ff73f76e1860303f4081509be8c3') 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diva 2 | 3 | Diva is a dependency injection framework for GLib-based applications. 4 | Dependencies between classes can be managed so that apllications stay maintainable as they grow. 5 | Diva is designed to be used with the Vala programming language 6 | 7 | ### Using Diva 8 | 9 | #### Registering components 10 | 11 | Components that provide services are registered with a ```ContainerBuilder``` 12 | 13 | var builder = new ContainerBuilder(); 14 | Diva can use a delegate, a type, or an exising instance: 15 | 16 | builder.register(ctx => new FooComponent()); 17 | builder.register().as(); 18 | builder.registerInstance(new FooComponent()); 19 | calling ```Build()``` creates a container 20 | 21 | var container = builder.build(); 22 | instances of a service can then be requested using resolve() 23 | 24 | var fooService = container.resolve(); 25 | 26 | #### Expressing dependencies 27 | 28 | Dependencies are expressed as public properties of the component 29 | 30 | public class FooComponent : Object, FooService 31 | { 32 | public BarService bar { construct; private get; } 33 | } 34 | When constructing a FooComponent, Diva will look for a component that provides BarService and 35 | set the property bar to a new instance of it. 36 | -------------------------------------------------------------------------------- /auto_type_registration.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | internal class AutoTypeRegistration : Object, IRegistrationContext 6 | { 7 | private Collection _services = new LinkedList(); 8 | private Collection _decorations = new LinkedList(); 9 | 10 | internal Collection services {get{return _services;}} 11 | internal Collection decorations {get{return _decorations;}} 12 | internal CreationStrategy creation_strategy {get; set;} 13 | 14 | private Collection ignored_properties = new ArrayList(); 15 | 16 | public ICreator get_creator() 17 | { 18 | return creation_strategy.get_final_creator(new AutoTypeCreator(this, ignored_properties)); 19 | } 20 | 21 | public IDecoratorCreator get_decorator_creator() 22 | { 23 | return creation_strategy.get_final_decorator_creator(new AutoTypeCreator(this, ignored_properties)); 24 | } 25 | 26 | public IRegistrationContext ignore_property(string property) 27 | { 28 | ignored_properties.add(property); 29 | return this; 30 | } 31 | 32 | private class AutoTypeCreator : Object, ICreator, IDecoratorCreator 33 | { 34 | private AutoTypeRegistration registration; 35 | private Collection ignored_properties; 36 | 37 | public AutoTypeCreator(AutoTypeRegistration registration, Collection ignored_properties) 38 | { 39 | this.registration = registration; 40 | this.ignored_properties = ignored_properties; 41 | } 42 | 43 | public T create(ComponentContext context) 44 | throws ResolveError 45 | { 46 | var cls = typeof(T).class_ref(); 47 | var properties = ((ObjectClass)cls).list_properties(); 48 | var params = new Parameter[] {}; 49 | foreach(var prop in properties) 50 | { 51 | if(ignored_properties.contains(prop.name)) 52 | continue; 53 | if(can_inject_property(prop)) 54 | { 55 | var p = Parameter(); 56 | var t = prop.value_type; 57 | p.name = prop.name; 58 | 59 | p.value = Value(t); 60 | 61 | try 62 | { 63 | CreatorFunc func; 64 | if(is_special(t, out func)) 65 | func(prop, context, ref p); 66 | else 67 | p.value.set_object(context.resolve_typed(t)); 68 | } 69 | catch(ResolveError e) 70 | { 71 | throw new ResolveError.InnerError(@"Cannot satify parameter $(prop.name) [$(t.name())]: $(e.message)"); 72 | } 73 | 74 | params += p; 75 | 76 | } 77 | } 78 | return (T) Object.newv(typeof(T), params); 79 | } 80 | 81 | public T create_decorator(ComponentContext context, T inner) 82 | throws ResolveError 83 | { 84 | var cls = typeof(T).class_ref(); 85 | var properties = ((ObjectClass)cls).list_properties(); 86 | var params = new Parameter[] {}; 87 | foreach(var prop in properties) 88 | { 89 | if(ignored_properties.contains(prop.name)) 90 | continue; 91 | if(can_inject_property(prop)) 92 | { 93 | var p = Parameter(); 94 | var t = prop.value_type; 95 | p.name = prop.name; 96 | p.value = Value(t); 97 | 98 | try 99 | { 100 | CreatorFunc func; 101 | if(prop.name == "Inner") 102 | p.value.set_object((Object)inner); 103 | else if(is_special(t, out func)) 104 | func(prop, context, ref p); 105 | else 106 | p.value.set_object(context.resolve_typed(t)); 107 | } 108 | catch(ResolveError e) 109 | { 110 | throw new ResolveError.InnerError(@"Cannot satify parameter $(prop.name) [$(t.name())]: $(e.message)"); 111 | } 112 | 113 | params += p; 114 | 115 | } 116 | } 117 | return (T) Object.newv(typeof(T), params); 118 | } 119 | 120 | public Lazy create_lazy(ComponentContext context) 121 | { 122 | return new Lazy(() => { return create(context); }); 123 | } 124 | 125 | private bool can_inject_property(ParamSpec p) 126 | { 127 | var flags = p.flags; 128 | return ( ((flags & ParamFlags.CONSTRUCT) == ParamFlags.CONSTRUCT) 129 | || ((flags & ParamFlags.CONSTRUCT_ONLY) == ParamFlags.CONSTRUCT_ONLY)); 130 | } 131 | 132 | private bool is_special(Type t, out CreatorFunc func) 133 | { 134 | if(t == typeof(Lazy)) 135 | { 136 | func = lazy_creator; 137 | return true; 138 | } 139 | if(t == typeof(Index)) 140 | { 141 | func = index_creator; 142 | return true; 143 | } 144 | if(t == typeof(Collection)) 145 | { 146 | func = collection_creator; 147 | return true; 148 | } 149 | func = null; 150 | return false; 151 | } 152 | 153 | private delegate void CreatorFunc(ParamSpec p, ComponentContext context, ref Parameter param) 154 | throws ResolveError; 155 | 156 | private void lazy_creator(ParamSpec p, ComponentContext context, ref Parameter param) 157 | throws ResolveError 158 | { 159 | // get the type 160 | var lazy_data = (LazyPropertyData)p.get_qdata(LazyPropertyData.q); 161 | if(lazy_data == null) 162 | throw new ResolveError.BadDeclaration("To support injection of lazy properties, call SetLazyInjection in your static construct block."); 163 | Type t = lazy_data.dep_type; 164 | 165 | 166 | param.value.set_instance(context.resolve_lazy_typed(t)); 167 | } 168 | 169 | private void collection_creator(ParamSpec p, ComponentContext context, ref Parameter param) 170 | throws ResolveError 171 | { 172 | // get the type 173 | var collection_data = (CollectionPropertyData)p.get_qdata(CollectionPropertyData.q); 174 | if(collection_data == null) 175 | throw new ResolveError.BadDeclaration("To support injection of collection properties, call SetCollectionInjection in your static construct block."); 176 | Type t = collection_data.dep_type; 177 | 178 | 179 | param.value.set_instance(context.resolve_collection_typed(t)); 180 | } 181 | 182 | private void index_creator(ParamSpec p, ComponentContext context, ref Parameter param) 183 | throws ResolveError 184 | { 185 | var index_data = (IndexPropertyData)p.get_qdata(IndexPropertyData.q); 186 | if(index_data == null) 187 | throw new ResolveError.BadDeclaration("To support injection of index properties, call SetIndexedInjection in your static construct block."); 188 | 189 | param.value.set_instance(context.resolve_index_typed(index_data.dependency, index_data.key)); 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /collection_dependencies.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | public void set_collection_injection(ObjectClass cls, string property) 6 | { 7 | var pspec = cls.find_property(property); 8 | 9 | if(pspec == null) assert_not_reached(); 10 | if(pspec.value_type != typeof(Collection)) assert_not_reached(); 11 | 12 | var dep_type = typeof(TDependency); // apparently, if I inline this then it becomes G_TYPE_INVALID 13 | 14 | var data = (CollectionPropertyData)Object.new(typeof(CollectionPropertyData), dep_type: dep_type); 15 | CollectionPropertyData.refs.add(data); // need to store a reference somewhere as the qdata doesn't hold the reference! 16 | pspec.set_qdata(CollectionPropertyData.q, data); 17 | } 18 | 19 | internal class CollectionPropertyData : Object 20 | { 21 | public Type dep_type {get; construct;} 22 | 23 | public static Collection refs = new LinkedList(); 24 | public static Quark q = Quark.from_string("diva.collection"); 25 | } 26 | } 27 | 28 | 29 | -------------------------------------------------------------------------------- /container_builder.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | public class ContainerBuilder : Object 6 | { 7 | private Gee.List registrations = new LinkedList(); 8 | 9 | public IRegistrationContext register(owned ResolveFunc? resolver = null) 10 | { 11 | if(resolver == null) 12 | { 13 | var registration = new AutoTypeRegistration(); 14 | registrations.add(registration); 15 | return registration; 16 | } 17 | 18 | var registration = new DelegateRegistrationContext((owned) resolver); 19 | registrations.add(registration); 20 | return registration; 21 | } 22 | 23 | public IRegistrationContext register_instance(T instance) 24 | { 25 | var registration = new InstanceRegistrationContext(instance); 26 | registrations.add(registration); 27 | return registration; 28 | } 29 | 30 | public void register_module(T module = null) 31 | requires(typeof(T).is_a(typeof(Module))) 32 | { 33 | var real_module = module as Module; 34 | if(real_module == null) 35 | real_module = (Module)Object.new(typeof(T)); 36 | 37 | real_module.load(this); 38 | } 39 | 40 | public IContainer build() 41 | { 42 | var services = new HashMap(); 43 | var all_services = new HashMultiMap(); 44 | var keyed_services = new HashMap>(); 45 | var decorators = new HashMultiMap(); 46 | foreach(var registration in registrations) 47 | { 48 | var creator = registration.get_creator(); 49 | services[registration.component_type] = creator; 50 | foreach(var service in registration.services) 51 | { 52 | services[service.service_type] = creator; 53 | all_services[service.service_type] = creator; 54 | if(service.keys != null) 55 | foreach(var key in service.keys) 56 | { 57 | var s = keyed_services[service.service_type]; 58 | if(s == null) 59 | { 60 | s = new HashMap(); 61 | keyed_services[service.service_type] = s; 62 | } 63 | s[key] = creator; 64 | } 65 | } 66 | foreach(var decoration in registration.decorations) 67 | { 68 | decorators[decoration] = registration.get_decorator_creator(); 69 | } 70 | } 71 | 72 | return new DefaultContainer(services, all_services, keyed_services, decorators); 73 | } 74 | } 75 | 76 | public delegate T ResolveFunc(ComponentContext context) 77 | throws ResolveError; 78 | 79 | public interface ComponentContext : Object 80 | { 81 | internal abstract Object resolve_typed(Type t) 82 | throws ResolveError; 83 | 84 | internal abstract Lazy resolve_lazy_typed(Type t) 85 | throws ResolveError; 86 | 87 | internal abstract Index resolve_index_typed(Type t_service, Type t_key) 88 | throws ResolveError; 89 | 90 | internal abstract Collection resolve_collection_typed(Type t) 91 | throws ResolveError; 92 | } 93 | 94 | public interface IContainer : Object 95 | { 96 | public abstract T resolve() 97 | throws ResolveError; 98 | 99 | public abstract Lazy resolve_lazy() 100 | throws ResolveError; 101 | public abstract Collection resolve_collection() 102 | throws ResolveError; 103 | 104 | public abstract Index resolve_indexed() 105 | throws ResolveError; 106 | } 107 | 108 | internal interface ICreator : Object 109 | { 110 | public abstract T create(ComponentContext context) 111 | throws ResolveError; 112 | 113 | public abstract Lazy create_lazy(ComponentContext context) 114 | throws ResolveError; 115 | } 116 | 117 | internal interface IDecoratorCreator : Object 118 | { 119 | public abstract T create_decorator(ComponentContext context, T inner) 120 | throws ResolveError; 121 | 122 | /*public abstract Lazy CreateLazy(ComponentContext context, Lazy inner) 123 | throws ResolveError; */ 124 | } 125 | 126 | public abstract class Module : Object 127 | { 128 | public abstract void load(ContainerBuilder builder); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /creation_strategy.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | internal enum CreationStrategy 6 | { 7 | PER_DEPENDENCY, 8 | SINGLE_INSTANCE; 9 | 10 | public ICreator get_final_creator(ICreator creator) 11 | { 12 | switch(this) 13 | { 14 | case PER_DEPENDENCY: 15 | return creator; 16 | case SINGLE_INSTANCE: 17 | return new CachingCreator(creator); 18 | default: 19 | assert_not_reached(); 20 | } 21 | } 22 | 23 | public IDecoratorCreator get_final_decorator_creator(IDecoratorCreator creator) 24 | { 25 | switch(this) 26 | { 27 | case PER_DEPENDENCY: 28 | return creator; 29 | case SINGLE_INSTANCE: 30 | return new CachingDecoratorCreator(creator); 31 | default: 32 | assert_not_reached(); 33 | } 34 | } 35 | } 36 | 37 | internal class CachingCreator : Object, ICreator 38 | { 39 | private T cachedValue; 40 | private bool has_value = false; 41 | private ICreator inner; 42 | 43 | public CachingCreator(ICreator inner) 44 | { 45 | this.inner = inner; 46 | } 47 | 48 | public T create(ComponentContext context) 49 | throws ResolveError 50 | { 51 | if(!has_value) 52 | { 53 | cachedValue = inner.create(context); 54 | has_value = true; 55 | } 56 | return cachedValue; 57 | } 58 | 59 | public Lazy create_lazy(ComponentContext context) 60 | { 61 | if(has_value) 62 | return new Lazy.from_value(cachedValue); 63 | 64 | return new Lazy(() => {return create(context);}); 65 | } 66 | } 67 | 68 | internal class CachingDecoratorCreator : Object, IDecoratorCreator 69 | { 70 | private T cached_value; 71 | private bool has_value = false; 72 | private IDecoratorCreator inner_creator; 73 | 74 | public CachingDecoratorCreator(IDecoratorCreator inner) 75 | { 76 | inner_creator = inner; 77 | } 78 | 79 | public T create_decorator(ComponentContext context, T inner) 80 | throws ResolveError 81 | { 82 | if(!has_value) 83 | { 84 | cached_value = inner_creator.create_decorator(context, inner); 85 | has_value = true; 86 | } 87 | return cached_value; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /creator_index.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | internal class CreatorIndex: Object, Index 6 | { 7 | public CreatorIndex(Map> keyedCreators, ComponentContext context) 8 | { 9 | this.context = context; 10 | this.keyedCreators = keyedCreators; 11 | } 12 | 13 | private Map> keyedCreators {set; get;} 14 | public ComponentContext context {construct set; private get;} 15 | 16 | public new TService @get(TKey key) 17 | { 18 | var creator = keyedCreators[key]; 19 | if(creator == null) 20 | return null; 21 | return creator.create(context); 22 | } 23 | } 24 | 25 | internal class CreatorTypedIndex: Object, Index 26 | { 27 | private Map> keyedCreators {set; get;} 28 | public ComponentContext context {construct set; private get;} 29 | public Map keysForService {construct; private get;} 30 | 31 | construct 32 | { 33 | var t = typeof(TService); 34 | var tkey = typeof(TKey); 35 | if(tkey == typeof(string)) 36 | { 37 | keyedCreators = new HashMap>(x => str_hash((string) x), (x, y) => str_equal((string)x, (string)y)); 38 | } 39 | else 40 | { 41 | keyedCreators = new HashMap>(); 42 | } 43 | 44 | foreach(var v in keysForService.entries) 45 | { 46 | if(v.key.type() != tkey) 47 | continue; 48 | keyedCreators[ExtractKey(v.key)] = v.value; 49 | } 50 | } 51 | 52 | public new TService @get(TKey key) 53 | { 54 | var creator = keyedCreators[key]; 55 | if(creator == null) 56 | return null; 57 | return creator.create(context); 58 | } 59 | 60 | private T ExtractKey(Value v) 61 | { 62 | var valueType = v.type(); 63 | if(valueType.is_enum()) 64 | { 65 | var key = (T)v.get_enum(); 66 | return key; 67 | } 68 | if(valueType == (typeof(string))) 69 | { 70 | var s = v.get_string(); 71 | return s; 72 | } 73 | return (T)v.get_pointer; 74 | } 75 | } 76 | } 77 | 78 | 79 | -------------------------------------------------------------------------------- /default_container.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | internal class DefaultContainer : IContainer, ComponentContext, Object 6 | { 7 | private Map services; 8 | private Map> keyed_services; 9 | private MultiMap decorators; 10 | private MultiMap all_services; 11 | 12 | private Deque current_creations = new LinkedList(); 13 | 14 | public DefaultContainer(Map services, 15 | MultiMap all_services, 16 | Map> keyed_services, 17 | MultiMap? decorators 18 | ) 19 | { 20 | this.services = services; 21 | this.all_services = all_services; 22 | this.keyed_services = keyed_services; 23 | this.decorators = decorators; 24 | } 25 | 26 | public T resolve() 27 | throws ResolveError 28 | { 29 | var t = typeof(T); 30 | return (T) resolve_typed(t); 31 | } 32 | 33 | public Lazy resolve_lazy() 34 | throws ResolveError 35 | { 36 | var t = typeof(T); 37 | return (Lazy) resolve_lazy_typed(t); 38 | } 39 | 40 | public Collection resolve_collection() 41 | throws ResolveError 42 | { 43 | var t = typeof(T); 44 | return (Collection) resolve_collection_typed(t); 45 | } 46 | 47 | public Index resolve_indexed() 48 | { 49 | var t = typeof(TService); 50 | var tkey = typeof(TKey); 51 | var keys_for_service = keyed_services[t]; 52 | Map keyed_creators; 53 | if(tkey == typeof(string)) 54 | { 55 | keyed_creators = new HashMap>(x => str_hash((string) x), (x, y) => str_equal((string)x, (string)y)); 56 | } 57 | else 58 | { 59 | keyed_creators = new HashMap>(); 60 | } 61 | 62 | foreach(var v in keys_for_service.entries) 63 | { 64 | if(v.key.type() != tkey) 65 | continue; 66 | keyed_creators[extract_key(v.key)] = v.value; 67 | } 68 | var index = new CreatorIndex(keyed_creators, this); 69 | return index; 70 | } 71 | 72 | private T extract_key(Value v) 73 | { 74 | var value_type = v.type(); 75 | if(value_type.is_enum()) 76 | { 77 | var key = (T)v.get_enum(); 78 | return key; 79 | } 80 | if(value_type == (typeof(string))) 81 | { 82 | var s = v.get_string(); 83 | return s; 84 | } 85 | return (T)v.get_pointer; 86 | } 87 | 88 | internal Object resolve_typed(Type t) 89 | throws ResolveError 90 | { 91 | check_for_loop(t); 92 | var creator = services[t]; 93 | if(creator == null) 94 | throw new ResolveError.UnknownService(@"No component has been registered providing the service $(t.name())."); 95 | ICreator real_creator = creator; 96 | var o = real_creator.create(this); 97 | var decorator_creators = decorators[t]; 98 | if(decorator_creators == null) 99 | { 100 | finished_creating(t); 101 | return o; 102 | } 103 | 104 | var decorated = o; 105 | foreach(var decorator_creator in decorator_creators) 106 | { 107 | IDecoratorCreator real_decorator_creator = decorator_creator; 108 | decorated = real_decorator_creator.create_decorator(this, decorated); 109 | } 110 | finished_creating(t); 111 | return decorated; 112 | } 113 | 114 | internal Lazy resolve_lazy_typed(Type t) 115 | throws ResolveError 116 | { 117 | check_for_loop(t); 118 | var creator = services[t]; 119 | if(creator == null) 120 | throw new ResolveError.UnknownService(@"No component has been registered providing the service $(t.name())."); 121 | ICreator real_creator = creator; 122 | 123 | var o = real_creator.create_lazy(this); 124 | finished_creating(t); 125 | return o; 126 | } 127 | 128 | internal Collection resolve_collection_typed(Type t) 129 | throws ResolveError 130 | { 131 | check_for_loop(t); 132 | var collection = new LinkedList(); 133 | var creators = all_services[t]; 134 | foreach(var creator in creators) 135 | { 136 | ICreator real_creator = creator; 137 | 138 | var o = real_creator.create(this); 139 | collection.add(o); 140 | } 141 | finished_creating(t); 142 | return collection; 143 | } 144 | 145 | internal Index resolve_index_typed(Type t_service, Type t_key) 146 | throws ResolveError 147 | { 148 | var keys_for_service = keyed_services[t_service]; 149 | 150 | var index = (CreatorTypedIndex)Object.new(typeof(CreatorTypedIndex), 151 | tkey_type: t_key, 152 | tservice_type: t_service, 153 | context: this, 154 | keysForService: keys_for_service 155 | ); 156 | return index; 157 | } 158 | 159 | private void check_for_loop(Type t) 160 | throws ResolveError 161 | { 162 | if(current_creations.contains(t)) 163 | throw new ResolveError.CyclicDependencies("Whee!! - I'm in a loop."); 164 | 165 | current_creations.offer_head(t); 166 | } 167 | 168 | private void finished_creating(Type t) 169 | { 170 | var head = current_creations.poll_head(); 171 | if(head != t) 172 | assert_not_reached(); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /delegate_registration.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | internal class DelegateRegistrationContext : IRegistrationContext, Object 6 | { 7 | private ResolveFunc resolve_func; 8 | private Collection _services = new LinkedList(); 9 | private Collection _decorations = new LinkedList(); 10 | 11 | internal CreationStrategy creation_strategy {get; set;} 12 | 13 | public DelegateRegistrationContext(owned ResolveFunc resolver) 14 | { 15 | resolve_func = (owned) resolver; 16 | } 17 | 18 | internal Collection services {get{return _services;}} 19 | internal Collection decorations {get{return _decorations;}} 20 | 21 | public ICreator get_creator() 22 | { 23 | return creation_strategy.get_final_creator(new DelegateCreator(this)); 24 | } 25 | 26 | public IDecoratorCreator get_decorator_creator() 27 | { 28 | return creation_strategy.get_final_decorator_creator(new DelegateCreator(this)); 29 | } 30 | 31 | public IRegistrationContext ignore_property(string property) 32 | { 33 | return this; 34 | } 35 | 36 | private class DelegateCreator : ICreator, IDecoratorCreator, Object 37 | { 38 | private DelegateRegistrationContext registration; 39 | 40 | public DelegateCreator(DelegateRegistrationContext registration) 41 | { 42 | this.registration = registration; 43 | } 44 | 45 | public T create_decorator(ComponentContext context, T inner) 46 | throws ResolveError 47 | { 48 | return create(context); 49 | } 50 | 51 | public T create(ComponentContext context) 52 | throws ResolveError 53 | { 54 | try 55 | { 56 | return registration.resolve_func(context); 57 | } 58 | catch(ResolveError e) 59 | { 60 | throw new ResolveError.InnerError(@"Unable to create $(typeof(T).name()): $(e.message)"); 61 | } 62 | } 63 | 64 | public Lazy create_lazy(ComponentContext context) 65 | throws ResolveError 66 | { 67 | return new Lazy(() => {return create(context);}); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /i_registration_context.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | [GenericAccessors] 6 | public interface IRegistrationContext : Object 7 | { 8 | internal abstract ICreator get_creator(); 9 | internal abstract IDecoratorCreator get_decorator_creator(); 10 | 11 | public Type component_type {get {return typeof(T);}} 12 | 13 | internal abstract Collection services {get;} 14 | internal abstract Collection decorations {get;} 15 | internal abstract CreationStrategy creation_strategy {get; set;} 16 | 17 | public IRegistrationContext as() 18 | requires(typeof(T).is_a(typeof(TInterface))) 19 | { 20 | add_service_registration(typeof(TInterface)); 21 | return this; 22 | } 23 | 24 | public IRegistrationContext single_instance() 25 | { 26 | creation_strategy = CreationStrategy.SINGLE_INSTANCE; 27 | return this; 28 | } 29 | 30 | public IRegistrationContext keyed(TKey key) 31 | requires(typeof(T).is_a(typeof(TService))) 32 | { 33 | var t = typeof(TKey); 34 | var key_value = Value(t); 35 | if(t.is_object()) 36 | key_value.set_object((Object)key); 37 | if(t.is_enum()) 38 | key_value.set_enum((int) key); 39 | if(t == typeof(string)) 40 | key_value.set_string((string) key); 41 | add_service_registration(typeof(TService), key_value); 42 | return this; 43 | } 44 | 45 | public IRegistrationContext as_decorator() 46 | requires(typeof(T).is_a(typeof(TService))) 47 | { 48 | decorations.add(typeof(TService)); 49 | return this; 50 | } 51 | 52 | public abstract IRegistrationContext ignore_property(string property); 53 | 54 | private void add_service_registration(Type service, Value? key = null) 55 | { 56 | var existing_regs = services.filter(s => s.service_type == service).chop(0, 1); 57 | if(existing_regs.next()) 58 | { 59 | var reg = existing_regs.get(); 60 | if(key == null) 61 | return; 62 | 63 | reg.keys.add(key); 64 | return; 65 | } 66 | 67 | var new_reg = (ServiceRegistration) Object.new(typeof(ServiceRegistration), service_type: service, keys: new LinkedList()); 68 | if(key != null) 69 | new_reg.keys.add(key); 70 | 71 | services.add(new_reg); 72 | } 73 | } 74 | 75 | internal class ServiceRegistration : Object 76 | { 77 | public Type service_type {get; construct set;} 78 | public Collection keys {get; construct; default = new LinkedList();} 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /indexed_dependencies.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | public interface Index : Object 6 | { 7 | public abstract TDependency @get(TKey key); 8 | } 9 | 10 | public void set_indexed_injection(ObjectClass cls, string property) 11 | { 12 | var pspec = cls.find_property(property); 13 | if(pspec == null) assert_not_reached(); 14 | if(pspec.value_type != typeof(Index)) assert_not_reached(); 15 | 16 | var key_type = typeof(TKey); // apparently, if I inline this then it becomes G_TYPE_INVALID 17 | var dep_type = typeof(TDependency); 18 | 19 | var data = (IndexPropertyData)Object.new(typeof(IndexPropertyData), key: key_type, dependency: dep_type); 20 | IndexPropertyData.refs.add(data); // need to store a reference somewhere as the qdata doesn't hold the reference! 21 | pspec.set_qdata(IndexPropertyData.q, data); 22 | } 23 | 24 | 25 | private class IndexPropertyData : Object 26 | { 27 | public static Quark q = Quark.from_string("diva.indexed"); 28 | 29 | public Type key {get; construct;} 30 | public Type dependency {get; construct;} 31 | 32 | public static Collection refs = new LinkedList(); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /instance_registration.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | private class InstanceRegistrationContext : Object, IRegistrationContext 6 | { 7 | private Collection _services = new LinkedList(); 8 | private Collection _decorations = new LinkedList(); 9 | 10 | internal Collection services {get{return _services;}} 11 | internal Collection decorations {get{return _decorations;}} 12 | internal CreationStrategy creation_strategy {get; set;} 13 | 14 | private T instance; 15 | 16 | public InstanceRegistrationContext(T instance) 17 | { 18 | this.instance = instance; 19 | } 20 | 21 | public ICreator get_creator() 22 | { 23 | return new InstanceCreator(instance); 24 | } 25 | 26 | public IDecoratorCreator get_decorator_creator() 27 | { 28 | assert_not_reached(); 29 | } 30 | 31 | public IRegistrationContext ignore_property(string property) 32 | { 33 | return this; 34 | } 35 | 36 | private class InstanceCreator : Object, ICreator 37 | { 38 | private T instance; 39 | 40 | public InstanceCreator(T instance) 41 | { 42 | this.instance = instance; 43 | } 44 | 45 | public T create(ComponentContext context) 46 | throws ResolveError 47 | { 48 | return instance; 49 | } 50 | 51 | public Lazy create_lazy(ComponentContext context) 52 | throws ResolveError 53 | { 54 | return new Lazy.from_value(instance); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lazy_dependencies.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | 3 | namespace Diva 4 | { 5 | public void set_lazy_injection(ObjectClass cls, string property) 6 | { 7 | var pspec = cls.find_property(property); 8 | 9 | if(pspec == null) assert_not_reached(); 10 | if(pspec.value_type != typeof(Lazy)) assert_not_reached(); 11 | 12 | var dep_type = typeof(TDependency); // apparently, if I inline this then it becomes G_TYPE_INVALID 13 | 14 | var data = (LazyPropertyData)Object.new(typeof(LazyPropertyData), dep_type: dep_type); 15 | LazyPropertyData.refs.add(data); // need to store a reference somewhere as the qdata doesn't hold the reference! 16 | pspec.set_qdata(LazyPropertyData.q, data); 17 | } 18 | 19 | internal class LazyPropertyData : Object 20 | { 21 | public Type dep_type {get; construct;} 22 | 23 | public static Collection refs = new LinkedList(); 24 | public static Quark q = Quark.from_string("diva.lazy"); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /resolve_error.vala: -------------------------------------------------------------------------------- 1 | namespace Diva 2 | { 3 | public errordomain ResolveError 4 | { 5 | UnknownService, 6 | InnerError, 7 | BadDeclaration, 8 | CyclicDependencies 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/collection_tests.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | using GLib.Test; 3 | 4 | using Diva; 5 | 6 | namespace Diva.Tests 7 | { 8 | public class CollectionTests : TestFixture 9 | { 10 | public CollectionTests() 11 | { 12 | base("CollectionTests"); 13 | 14 | add_test("ResolveDirectly", ResolveDirectly); 15 | add_test("ResolveAsComponent", ResolveAsComponent); 16 | add_test("CanBeEmpty", CanBeEmpty); 17 | } 18 | 19 | private void ResolveDirectly() 20 | { 21 | var builder = new ContainerBuilder(); 22 | builder.register().as(); 23 | builder.register().as(); 24 | var container = builder.build(); 25 | 26 | try 27 | { 28 | var resolved = container.resolve_collection(); 29 | if(resolved.size != 2) 30 | { 31 | stderr.printf(@"Expected to get 2 items, actually $(resolved.size).\n"); 32 | fail(); 33 | } 34 | 35 | var a = resolved.to_array()[0]; 36 | if(a == null) 37 | { 38 | stderr.printf("Unable to create for A\n"); 39 | fail(); 40 | } 41 | 42 | var b = resolved.to_array()[1]; 43 | if(b == null) 44 | { 45 | stderr.printf("Unable to create for B\n"); 46 | fail(); 47 | } 48 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 49 | } 50 | 51 | private void ResolveAsComponent() 52 | { 53 | var builder = new ContainerBuilder(); 54 | builder.register().as(); 55 | builder.register().as(); 56 | builder.register(); 57 | var container = builder.build(); 58 | 59 | try { 60 | var resolved = container.resolve(); 61 | var a = resolved.Collection.to_array()[0]; 62 | if(a == null) 63 | fail(); 64 | 65 | var b = resolved.Collection.to_array()[1]; 66 | if(b == null) 67 | fail(); 68 | 69 | } catch (ResolveError e) { 70 | stderr.printf("error 3: %s\n", e.message);Test.message(@"ResolveError: $(e.message)"); fail(); } 71 | } 72 | 73 | private void CanBeEmpty() 74 | { 75 | var builder = new ContainerBuilder(); 76 | builder.register(); 77 | var container = builder.build(); 78 | 79 | try { 80 | var resolved = container.resolve(); 81 | if(resolved.Collection.size != 0) 82 | { 83 | fail(); 84 | } 85 | 86 | } catch (ResolveError e) { 87 | stderr.printf("error 3: %s\n", e.message);Test.message(@"ResolveError: $(e.message)"); fail(); } 88 | } 89 | 90 | private class ServiceA : Object, InterfaceA {} 91 | 92 | private class ServiceB : Object, InterfaceA {} 93 | 94 | private class RequiresCollection : Object 95 | { 96 | static construct 97 | { 98 | var cls = (ObjectClass)typeof(RequiresCollection).class_ref(); 99 | set_collection_injection(cls, "Collection"); 100 | } 101 | 102 | public Collection Collection {construct; get;} 103 | } 104 | } 105 | } 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /test/creation_strategy_tests.vala: -------------------------------------------------------------------------------- 1 | using GLib.Test; 2 | 3 | using Diva; 4 | 5 | namespace Diva.Tests 6 | { 7 | public class CreationStrategyTests : TestFixture 8 | { 9 | public CreationStrategyTests() 10 | { 11 | base("CreationStrategyTests"); 12 | 13 | add_test("PerDependencyDefault", PerDependencyDefault); 14 | add_test("SingleInstance", SingleInstance); 15 | } 16 | 17 | private void PerDependencyDefault() 18 | { 19 | InstantiationCounter.ResetCount(); 20 | 21 | var builder = new ContainerBuilder(); 22 | builder.register(); 23 | var container = builder.build(); 24 | try { 25 | var counter = container.resolve(); 26 | counter = container.resolve(); 27 | 28 | 29 | if(InstantiationCounter.InstantiationCount != 2) 30 | fail(); 31 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 32 | } 33 | 34 | private void SingleInstance() 35 | { 36 | InstantiationCounter.ResetCount(); 37 | 38 | var builder = new ContainerBuilder(); 39 | builder.register().single_instance(); 40 | var container = builder.build(); 41 | try { 42 | var counter = container.resolve(); 43 | counter = container.resolve(); 44 | 45 | 46 | if(InstantiationCounter.InstantiationCount != 1) 47 | fail(); 48 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 49 | } 50 | 51 | private class InstantiationCounter : Object 52 | { 53 | public static int InstantiationCount = 0; 54 | 55 | public static void ResetCount() {InstantiationCount = 0;} 56 | 57 | construct {InstantiationCount++;} 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/decorator_tests.vala: -------------------------------------------------------------------------------- 1 | 2 | using GLib.Test; 3 | 4 | using Diva; 5 | 6 | namespace Diva.Tests 7 | { 8 | public class DecoratorTests : TestFixture 9 | { 10 | public DecoratorTests() 11 | { 12 | base("DecoratorTests"); 13 | add_test("CanResolveDecorator", CanResolveDecorator); 14 | } 15 | 16 | private void CanResolveDecorator() 17 | { 18 | var builder = new ContainerBuilder(); 19 | builder.register().as(); 20 | builder.register().as_decorator(); 21 | 22 | var container = builder.build(); 23 | try 24 | { 25 | var testClass = container.resolve(); 26 | var decorator = testClass as TestDecorator; 27 | if(decorator == null) 28 | {fail(); return;} 29 | if(decorator.Inner == null) 30 | {fail(); return;} 31 | if(!(decorator.Inner is TestClass)) 32 | {fail(); return;} 33 | } 34 | catch (ResolveError e) 35 | { 36 | Test.message(@"ResolveError: $(e.message)"); 37 | fail(); 38 | } 39 | } 40 | 41 | private class TestClass : Object, TestInterface 42 | { 43 | 44 | } 45 | 46 | private class TestDecorator : Object, TestInterface 47 | { 48 | public TestInterface Inner {construct; get;} 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/error_tests.vala: -------------------------------------------------------------------------------- 1 | using GLib.Test; 2 | 3 | using Diva; 4 | 5 | namespace Diva.Tests 6 | { 7 | public class ErrorTests : TestFixture 8 | { 9 | public ErrorTests() 10 | { 11 | base("ErrorTests"); 12 | add_test("ErrorsOnPrimitiveCycle", PrimitiveCycle); 13 | } 14 | 15 | private void PrimitiveCycle() 16 | { 17 | var builder = new ContainerBuilder(); 18 | builder.register(); 19 | 20 | var container = builder.build(); 21 | try { 22 | var testClass = container.resolve(); 23 | fail(); 24 | } 25 | catch (ResolveError e) 26 | { 27 | Test.message(@"ResolveError: $(e.message)"); 28 | } 29 | } 30 | 31 | private class TestClass : Object 32 | { 33 | public TestClass Recursive {get; construct;} 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/index_tests.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | using GLib.Test; 3 | 4 | using Diva; 5 | 6 | namespace Diva.Tests 7 | { 8 | public class IndexTests : TestFixture 9 | { 10 | public IndexTests() 11 | { 12 | base("IndexTests"); 13 | 14 | add_test("ResolveDirectly", ResolveDirectly); 15 | add_test("ResolveAsComponent", ResolveAsComponent); 16 | add_test("CanIndexOnStrings", CanIndexOnStrings); 17 | add_test("CanIndexComponentsOnStrings", CanIndexComponentsOnStrings); 18 | } 19 | 20 | private void ResolveDirectly() 21 | { 22 | var builder = new ContainerBuilder(); 23 | builder.register().keyed(ServiceEnum.A); 24 | builder.register().keyed(ServiceEnum.B); 25 | var container = builder.build(); 26 | 27 | try 28 | { 29 | var resolved = container.resolve_indexed(); 30 | var a = resolved[ServiceEnum.A]; 31 | if(a == null) 32 | { 33 | stderr.printf("Unable to create for A\n"); 34 | fail(); 35 | } 36 | 37 | var b = resolved[ServiceEnum.B]; 38 | if(b == null) 39 | { 40 | stderr.printf("Unable to create for B\n"); 41 | fail(); 42 | } 43 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 44 | } 45 | 46 | private void ResolveAsComponent() 47 | { 48 | var builder = new ContainerBuilder(); 49 | builder.register().keyed(ServiceEnum.A); 50 | builder.register().keyed(ServiceEnum.B); 51 | builder.register(); 52 | var container = builder.build(); 53 | 54 | try { 55 | var resolved = container.resolve(); 56 | var a = resolved.Indexed[ServiceEnum.A]; 57 | if(a == null) 58 | fail(); 59 | 60 | var b = resolved.Indexed[ServiceEnum.B]; 61 | if(b == null) 62 | fail(); 63 | 64 | } catch (ResolveError e) { 65 | stderr.printf("error 3: %s\n", e.message);Test.message(@"ResolveError: $(e.message)"); fail(); } 66 | } 67 | 68 | private void CanIndexOnStrings() 69 | { 70 | var builder = new ContainerBuilder(); 71 | builder.register().keyed("A"); 72 | builder.register().keyed("B"); 73 | var container = builder.build(); 74 | 75 | try 76 | { 77 | var resolved = container.resolve_indexed(); 78 | var a = resolved["A"]; 79 | if(a == null) 80 | { 81 | stderr.printf("Unable to create for A\n"); 82 | fail(); 83 | } 84 | 85 | var b = resolved["B"]; 86 | if(b == null) 87 | { 88 | stderr.printf("Unable to create for B\n"); 89 | fail(); 90 | } 91 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 92 | } 93 | 94 | private void CanIndexComponentsOnStrings() 95 | { 96 | var builder = new ContainerBuilder(); 97 | builder.register().keyed("A"); 98 | builder.register().keyed("B"); 99 | builder.register(); 100 | var container = builder.build(); 101 | 102 | try { 103 | var resolved = container.resolve(); 104 | var a = resolved.Indexed["A"]; 105 | if(a == null) 106 | fail(); 107 | 108 | var b = resolved.Indexed["B"]; 109 | if(b == null) 110 | fail(); 111 | 112 | } catch (ResolveError e) { 113 | stderr.printf("error 3: %s\n", e.message);Test.message(@"ResolveError: $(e.message)"); fail(); } 114 | } 115 | 116 | private class ServiceA : Object, InterfaceA {} 117 | 118 | private class ServiceB : Object, InterfaceA {} 119 | 120 | private enum ServiceEnum {A, B} 121 | 122 | private class RequiresIndex : Object 123 | { 124 | static construct 125 | { 126 | var cls = (ObjectClass)typeof(RequiresIndex).class_ref(); 127 | set_indexed_injection(cls, "Indexed"); 128 | } 129 | 130 | public Index Indexed {construct; get;} 131 | } 132 | 133 | private class RequiresStringIndex : Object 134 | { 135 | static construct 136 | { 137 | var cls = (ObjectClass)typeof(RequiresStringIndex).class_ref(); 138 | set_indexed_injection(cls, "Indexed"); 139 | } 140 | 141 | public Index Indexed {construct; get;} 142 | } 143 | } 144 | 145 | private interface InterfaceA : Object {} 146 | } 147 | 148 | 149 | -------------------------------------------------------------------------------- /test/lazy_tests.vala: -------------------------------------------------------------------------------- 1 | using Gee; 2 | using GLib.Test; 3 | 4 | using Diva; 5 | 6 | namespace Diva.Tests 7 | { 8 | public class LazyTests : TestFixture 9 | { 10 | public LazyTests() 11 | { 12 | base("LazyTests"); 13 | 14 | add_test("ResolveDirectly", ResolveDirectly); 15 | add_test("ResolveAsComponent", ResolveAsComponent); 16 | } 17 | 18 | private void ResolveDirectly() 19 | { 20 | InstantiationCounter.ResetCount(); 21 | 22 | var builder = new ContainerBuilder(); 23 | builder.register(); 24 | var container = builder.build(); 25 | 26 | try { 27 | Lazy resolved = container.resolve_lazy(); 28 | 29 | if(InstantiationCounter.InstantiationCount != 0) 30 | { 31 | Test.message(@"Should not have created the object yet. $(InstantiationCounter.InstantiationCount)."); 32 | fail(); 33 | } 34 | 35 | var counter = resolved.value; 36 | counter = resolved.value; 37 | if(InstantiationCounter.InstantiationCount != 1) 38 | { 39 | fail(); 40 | } 41 | 42 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 43 | } 44 | 45 | private void ResolveAsComponent() 46 | { 47 | InstantiationCounter.ResetCount(); 48 | 49 | var builder = new ContainerBuilder(); 50 | builder.register(); 51 | builder.register(); 52 | var container = builder.build(); 53 | 54 | try { 55 | var resolved = container.resolve(); 56 | 57 | if(InstantiationCounter.InstantiationCount != 0) 58 | { 59 | Test.message(@"Should not have created the object yet. $(InstantiationCounter.InstantiationCount)."); 60 | fail(); 61 | } 62 | 63 | resolved.UseLazy(); 64 | 65 | if(InstantiationCounter.InstantiationCount != 1) 66 | { 67 | Test.message(@"Should have created the counter once, actually: $(InstantiationCounter.InstantiationCount)."); 68 | fail(); 69 | } 70 | 71 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 72 | } 73 | 74 | private class InstantiationCounter : Object 75 | { 76 | public static int InstantiationCount = 0; 77 | 78 | public static void ResetCount() {InstantiationCount = 0;} 79 | 80 | construct {InstantiationCount++;} 81 | } 82 | 83 | private class RequiresLazy : Object 84 | { 85 | static construct 86 | { 87 | var cls = (ObjectClass)typeof(RequiresLazy).class_ref(); 88 | set_lazy_injection(cls, "Lazy"); 89 | } 90 | 91 | public Lazy Lazy {construct; private get;} 92 | 93 | public void UseLazy() 94 | { 95 | var c = Lazy.value; 96 | c = Lazy.value; 97 | } 98 | } 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /test/module_tests.vala: -------------------------------------------------------------------------------- 1 | using GLib.Test; 2 | 3 | using Diva; 4 | 5 | namespace Diva.Tests 6 | { 7 | public class ModuleTests : TestFixture 8 | { 9 | public ModuleTests() 10 | { 11 | base("ModuleTests"); 12 | add_test("SimpleResolve", SimpleResolve); 13 | } 14 | 15 | private void SimpleResolve() 16 | { 17 | var builder = new ContainerBuilder(); 18 | builder.register_module(); 19 | 20 | var container = builder.build(); 21 | try { 22 | var testClass = container.resolve(); 23 | if(testClass == null) 24 | fail(); 25 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 26 | } 27 | 28 | private class SimpleModule : Module 29 | { 30 | public override void load(ContainerBuilder builder) 31 | { 32 | builder.register(); 33 | } 34 | } 35 | 36 | private class TestClass : Object, TestInterface {} 37 | 38 | private class TestClassWithDependencies : Object 39 | { 40 | public TestClass Dependency {get; construct;} 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/property_tests.vala: -------------------------------------------------------------------------------- 1 | 2 | using GLib.Test; 3 | 4 | using Diva; 5 | 6 | namespace Diva.Tests 7 | { 8 | public class PropertyTests : TestFixture 9 | { 10 | public PropertyTests() 11 | { 12 | base("PropertyTests"); 13 | add_test("IgnoredProperty", IgnoredProperty); 14 | } 15 | 16 | private void IgnoredProperty() 17 | { 18 | var builder = new ContainerBuilder(); 19 | builder.register() 20 | .ignore_property("ignore-this"); 21 | 22 | var container = builder.build(); 23 | try { 24 | var testClass = container.resolve(); 25 | if(testClass == null) 26 | fail(); 27 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 28 | } 29 | 30 | private class TestClass : Object 31 | { 32 | public int ignore_this {get; construct;} 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/registration_errors.vala: -------------------------------------------------------------------------------- 1 | using GLib.Test; 2 | 3 | namespace Diva.Tests 4 | { 5 | class RegistrationErrors : TestFixture 6 | { 7 | public RegistrationErrors() 8 | { 9 | base("Registration Errors"); 10 | add_test("ErrorIfRegisteredAsInterfaceNotImplemented", ErrorIfRegisteredAsInterfaceNotImplemented); 11 | add_test("ErrorIfRegisteredAsDecoratorForInterfaceNotImplemented", ErrorIfRegisteredAsDecoratorForInterfaceNotImplemented); 12 | add_test("ErrorIfRegisteredKeyedForInterfaceNotImplemented", ErrorIfRegisteredKeyedForInterfaceNotImplemented); 13 | } 14 | 15 | private void ErrorIfRegisteredAsInterfaceNotImplemented() 16 | { 17 | var builder = new ContainerBuilder(); 18 | var reg = builder.register(); 19 | 20 | assert_traps(() => reg.as()); 21 | } 22 | 23 | private void ErrorIfRegisteredAsDecoratorForInterfaceNotImplemented() 24 | { 25 | var builder = new ContainerBuilder(); 26 | var reg = builder.register(); 27 | 28 | assert_traps(() => reg.as_decorator()); 29 | } 30 | 31 | private void ErrorIfRegisteredKeyedForInterfaceNotImplemented() 32 | { 33 | var builder = new ContainerBuilder(); 34 | var reg = builder.register(); 35 | 36 | assert_traps(() => reg.keyed("foo")); 37 | } 38 | 39 | private class NoInterface {} 40 | 41 | private void assert_traps(TrappingFunc func) 42 | { 43 | if(subprocess()) 44 | { 45 | func(); 46 | return; 47 | } 48 | trap_subprocess(null, 0, (TestSubprocessFlags) 0); 49 | trap_assert_failed(); 50 | } 51 | } 52 | 53 | private delegate void TrappingFunc(); 54 | } 55 | -------------------------------------------------------------------------------- /test/test_fixture.vala: -------------------------------------------------------------------------------- 1 | using GLib.Test; 2 | 3 | namespace Diva.Tests 4 | { 5 | public abstract class TestFixture : Object 6 | { 7 | private GLib.TestSuite suite; 8 | private Adaptor[] adaptors = new Adaptor[0]; 9 | 10 | public delegate void TestMethod (); 11 | public delegate void TheoryMethod(T datapoint); 12 | public delegate string TheoryName(T datapoint); 13 | 14 | protected TestFixture (string name) 15 | { 16 | this.suite = new GLib.TestSuite (name); 17 | } 18 | 19 | protected void add_test (string name, owned TestMethod test) 20 | { 21 | var adaptor = new Adaptor (name, (owned)test, this); 22 | this.adaptors += adaptor; 23 | 24 | this.suite.add (new GLib.TestCase (adaptor.name, 25 | adaptor.set_up, 26 | adaptor.run, 27 | adaptor.tear_down )); 28 | } 29 | 30 | public virtual void set_up () { 31 | } 32 | 33 | public virtual void tear_down () { 34 | } 35 | 36 | public GLib.TestSuite get_suite () { 37 | return this.suite; 38 | } 39 | 40 | private class Adaptor { 41 | 42 | public string name { get; private set; } 43 | private TestMethod test; 44 | private TestFixture test_case; 45 | 46 | public Adaptor (string name, 47 | owned TestMethod test, 48 | TestFixture test_case) { 49 | this.name = name; 50 | this.test = (owned)test; 51 | this.test_case = test_case; 52 | } 53 | 54 | public void set_up (void* fixture) { 55 | this.test_case.set_up (); 56 | } 57 | 58 | public void run (void* fixture) { 59 | this.test (); 60 | } 61 | 62 | public void tear_down (void* fixture) { 63 | this.test_case.tear_down (); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/test_main.vala: -------------------------------------------------------------------------------- 1 | using GLib.Test; 2 | 3 | using Diva; 4 | 5 | namespace Diva.Tests 6 | { 7 | static int main(string[] args) 8 | { 9 | Test.init(ref args); 10 | var rootSuite = TestSuite.get_root(); 11 | 12 | rootSuite.add_suite(new SimpleTest().get_suite()); 13 | rootSuite.add_suite(new CreationStrategyTests().get_suite()); 14 | rootSuite.add_suite(new LazyTests().get_suite()); 15 | rootSuite.add_suite(new IndexTests().get_suite()); 16 | rootSuite.add_suite(new PropertyTests().get_suite()); 17 | rootSuite.add_suite(new ErrorTests().get_suite()); 18 | rootSuite.add_suite(new DecoratorTests().get_suite()); 19 | rootSuite.add_suite(new CollectionTests().get_suite()); 20 | rootSuite.add_suite(new ModuleTests().get_suite()); 21 | rootSuite.add_suite(new RegistrationErrors().get_suite()); 22 | 23 | Test.run(); 24 | return 0; 25 | } 26 | 27 | public class SimpleTest : TestFixture 28 | { 29 | public SimpleTest() 30 | { 31 | base("SimpleTest"); 32 | add_test("SimpleResolve", SimpleResolve); 33 | add_test("ResolveTypeAuto", ResolveTypeAuto); 34 | add_test("ResolveTypeWithDependency", ResolveTypeWithDependency); 35 | add_test("ResolveByInterface", ResolveByInterface); 36 | } 37 | 38 | private void SimpleResolve() 39 | { 40 | var builder = new ContainerBuilder(); 41 | builder.register(_ => new TestClass()); 42 | 43 | var container = builder.build(); 44 | try { 45 | var testClass = container.resolve(); 46 | if(testClass == null) 47 | fail(); 48 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 49 | } 50 | 51 | private void ResolveTypeAuto() 52 | { 53 | var builder = new ContainerBuilder(); 54 | builder.register(); 55 | 56 | var container = builder.build(); 57 | try { 58 | var testClass = container.resolve(); 59 | if(testClass == null) 60 | fail(); 61 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 62 | } 63 | 64 | private void ResolveTypeWithDependency() 65 | { 66 | var builder = new ContainerBuilder(); 67 | builder.register(); 68 | builder.register(); 69 | 70 | var container = builder.build(); 71 | try { 72 | var testClassWithDependencies = container.resolve(); 73 | if(testClassWithDependencies == null) 74 | fail(); 75 | if(testClassWithDependencies.Dependency == null) 76 | fail(); 77 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 78 | } 79 | 80 | private void ResolveByInterface() 81 | { 82 | var builder = new ContainerBuilder(); 83 | builder.register().as(); 84 | 85 | var container = builder.build(); 86 | try { 87 | var testInterface = container.resolve(); 88 | if(testInterface == null) 89 | fail(); 90 | } catch (ResolveError e) {Test.message(@"ResolveError: $(e.message)"); fail(); } 91 | } 92 | 93 | 94 | private class TestClass : Object, TestInterface {} 95 | 96 | private class TestClassWithDependencies : Object 97 | { 98 | public TestClass Dependency {get; construct;} 99 | } 100 | } 101 | 102 | private interface TestInterface : Object {} 103 | } 104 | --------------------------------------------------------------------------------