1 //          Copyright Orfeo Da Via 2014.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 module endovena.container;
7 import std.conv : to;
8 import std.functional : toDelegate;
9 import std.stdio;
10 import std.traits;
11 
12 import std.algorithm;
13 import std.array;
14 import std.range;
15 
16 import endovena.provider;
17 import endovena.reuse;
18 
19 interface Module {
20    void configure(Container dejector);
21 }
22 
23 class Container {
24    private Binding[] bindings;
25    private Reuse[string] scopes;
26 
27    this(Module[] modules) {
28       this.bindReuse!Transient;
29       this.bindReuse!Singleton;
30 
31       foreach(module_; modules) {
32          module_.configure(this);
33       }
34    }
35 
36    this() {
37       this([]);
38    }
39 
40    void bindReuse(Class)() {
41       immutable key = fullyQualifiedName!Class;
42       assert(key.length > 0);
43       if (key in this.scopes) {
44          throw new Exception("Scope already bound");
45       }
46       this.scopes[key] = new Class();
47    }
48 
49    void register(C, R: Reuse = Transient)() {
50       this.register!(C, C, R)("");
51    }
52 
53    void register(C, R: Reuse = Transient)(C instance) {
54       this.register!(C, R)(new InstanceProvider(instance), "");
55    }
56 
57    void register(I, C, R: Reuse = Transient)() {
58       this.register!(I, R)(new ClassProvider!C(this), "");
59    }
60 
61    void register(I, R: Reuse = Transient)(Provider provider) {
62       this.register!(I, R)(provider, "");
63    }
64 
65    void register(I, R: Reuse = Transient)(Object delegate() provide) {
66       this.register!(I, R)(new FunctionProvider(provide), "");
67    }
68 
69    void register(I, R: Reuse = Transient)(Object delegate(Container) factory) {
70       this.register!(I, R)(new FactoryProvider(this, factory), "");
71    }
72 
73    void registerFunc(I, R: Reuse = Transient)(Object function() provide) {
74       this.register!(I, R)(toDelegate(provide));
75    }
76 
77    void register(C, R: Reuse = Transient)(string name) {
78       this.register!(C, C, R)(name);
79    }
80 
81    void register(I, C, R: Reuse = Transient)(string name) {
82       this.register!(I, R)(new ClassProvider!C(this), name);
83    }
84 
85    void register(I, R: Reuse = Transient)(Provider provider, string name) {
86       if (exists!I(name)) {
87          throw new RegistrationException("Interface already bound", fullyQualifiedName!I);
88       }
89       this.bindings ~= createBinding!(I, R)(provider, name);
90    }
91 
92    void register(I, R: Reuse = Transient)(Object delegate() provide, string name) {
93       this.register!(I, R)(new FunctionProvider(provide), name);
94    }
95 
96    void register(I, R: Reuse = Transient)(Object delegate(Container) factory,
97          string name) {
98       this.register!(I, R)(new FactoryProvider(this, factory), name);
99    }
100 
101    private bool exists(I)(string name) {
102       return !filterExactly!I(name).empty;
103    }
104 
105    private Binding createBinding(I, R)(Provider provider, string name) {
106       auto reuse = this.scopes[fullyQualifiedName!R];
107       return Binding(fullyQualifiedName!I
108             , name
109             , provider
110             , reuse);
111    }
112 
113    I get(I)(string name) {
114       Binding[] binding = filterExactly!I(name);
115       if (binding.empty) {
116          throw new ResolveException("Type not registered."
117                , fullyQualifiedName!I
118                , name);
119       }
120       return resolve!I(binding[0]);
121    }
122 
123 
124    T get(T)() {
125       static if(is(T t == I[], I)) {
126          I[] array;
127          Binding[] filtered = filterByInterface!I;
128          if (filtered.empty) {
129             throw new ResolveException("Type not registered.", fullyQualifiedName!T);
130          }
131 
132          foreach (Binding binding; filtered) {
133             array ~= resolve!I(binding);
134          }
135          return array;
136       } else {
137          Binding[] binding = filterExactly!T("");
138          if (binding.empty) {
139             throw new ResolveException("Type not registered.", fullyQualifiedName!T);
140          }
141          return resolve!T(binding[0]);
142       }
143    }
144 
145    private Binding[] filterByInterface(I)() {
146       string requestedFQN = fullyQualifiedName!I;
147       return bindings.filter!(a => a.fqn == requestedFQN).array;
148    }
149 
150    private Binding[] filterExactly(I)(string name) {
151       string requestedFQN = fullyQualifiedName!I;
152       return bindings.filter!(a => a.fqn == requestedFQN && a.name == name).array;
153    }
154 
155    private I resolve(I)(Binding binding) {
156       string key = fullyQualifiedName!I;
157       if (binding.name.length > 0) {
158          key ~= binding.name;
159       }
160       return cast(I) binding.reuse.get(key, binding.provider);
161    }
162 
163    I delegate() getDelegate(I)() {
164       return delegate() { return this.get!I; };
165    }
166 }
167 
168 import std.string;
169 class ResolveException: Exception {
170    this(string message, string key, string name) {
171       super(format("Exception while resolving type %s named %s: %s", key, name, message));
172    }
173    this(string message, string key) {
174       super(format("Exception while resolving type %s: %s", key, message));
175    }
176 }
177 
178 class RegistrationException : Exception {
179    this(string message, string key) {
180       super(format("Exception while registering type %s: %s", key, message));
181    }
182 }
183 
184 package struct Binding {
185    string fqn; // fullyQualifiedName
186    string name; // name in named instance
187    Provider provider;
188    Reuse reuse;
189 }