-
Notifications
You must be signed in to change notification settings - Fork 113
Expand file tree
/
Copy pathScopeImpl.java
More file actions
589 lines (536 loc) · 24.1 KB
/
ScopeImpl.java
File metadata and controls
589 lines (536 loc) · 24.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
package toothpick;
import java.lang.annotation.Annotation;
import toothpick.config.Binding;
import toothpick.config.Module;
import toothpick.configuration.ConfigurationHolder;
import toothpick.configuration.IllegalBindingException;
import toothpick.locators.FactoryLocator;
import javax.inject.Provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import static java.lang.String.format;
/**
* {@inheritDoc}
* <p>
* A note on concurrency :
* <ul>
* <li> all operations related to the scope tree are synchronized on the {@code Toothpick} class.
* <li> all operations related to a scope's content (binding & providers) are synchronized on the key (class) of the binding/injection.
* <li> all providers provided by the public API (including Lazy) should return a thread safe provider (done)
* but internally, we can live with a non synchronized provider.
* </ul>
* <em>All operations on the scope itself are non thread-safe. They <em>must</em> be used via the {@code Toothpick} class
* or <em>must</em> be synchronized using the {@code Toothpick} class if used concurrently.</em>
* </p>
*/
public class ScopeImpl extends ScopeNode {
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static IdentityHashMap<Class, InternalProviderImpl> mapClassesToUnNamedUnBoundProviders = new IdentityHashMap<>();
private IdentityHashMap<Class, Map<String, InternalProviderImpl>> mapClassesToNamedBoundProviders = new IdentityHashMap<>();
private IdentityHashMap<Class, InternalProviderImpl> mapClassesToUnNamedBoundProviders = new IdentityHashMap<>();
private boolean hasTestModules;
public ScopeImpl(Object name) {
super(name);
installBindingForScope();
}
@Override
public <T> T getInstance(Class<T> clazz) {
return getInstance(clazz, (String) null);
}
@Override
public <T> T getInstance(Class<T> clazz, String name) {
crashIfClosed();
ConfigurationHolder.configuration.checkCyclesStart(clazz, name);
T t;
try {
t = lookupProvider(clazz, name).get(this);
} finally {
ConfigurationHolder.configuration.checkCyclesEnd(clazz, name);
}
return t;
}
@Override
public <T> T getInstance(Class<T> clazz, Class<? extends Annotation> name) {
if (name == null) {
return getInstance(clazz, (String) null);
}
return getInstance(clazz, name.getCanonicalName());
}
@Override
public <T> Provider<T> getProvider(Class<T> clazz) {
return getProvider(clazz, (String) null);
}
@Override
public <T> Provider<T> getProvider(Class<T> clazz, String name) {
crashIfClosed();
return new ThreadSafeProviderImpl<>(this, clazz, name, false);
}
@Override
public <T> Provider<T> getProvider(Class<T> clazz, Class<? extends Annotation> name) {
if (name == null) {
return getProvider(clazz, (String) null);
}
return getProvider(clazz, name.getCanonicalName());
}
@Override
public <T> Lazy<T> getLazy(Class<T> clazz) {
return getLazy(clazz, (String) null);
}
@Override
public <T> Lazy<T> getLazy(Class<T> clazz, String name) {
crashIfClosed();
return new ThreadSafeProviderImpl<>(this, clazz, name, true);
}
@Override
public <T> Lazy<T> getLazy(Class<T> clazz, Class<? extends Annotation> name) {
if (name == null) {
return getLazy(clazz, (String) null);
}
return getLazy(clazz, name.getCanonicalName());
}
@Override
public synchronized void installTestModules(Module... modules) {
if (hasTestModules) {
throw new IllegalStateException("TestModules can only be installed once per scope.");
}
installModules(true, modules);
hasTestModules = true;
}
@Override
public void installModules(Module... modules) {
installModules(false, modules);
}
@Override
public String toString() {
final String branch = "---";
final char lastNode = '\\';
final char node = '+';
final String indent = " ";
StringBuilder builder = new StringBuilder();
builder.append(name);
builder.append(':');
builder.append(System.identityHashCode(this));
builder.append(LINE_SEPARATOR);
builder.append("Providers: [");
ArrayList<Class> sortedBoundProviderClassesList;
synchronized (mapClassesToNamedBoundProviders) {
sortedBoundProviderClassesList = new ArrayList(mapClassesToNamedBoundProviders.keySet());
}
synchronized (mapClassesToUnNamedBoundProviders) {
sortedBoundProviderClassesList.addAll(mapClassesToUnNamedBoundProviders.keySet());
}
Collections.sort(sortedBoundProviderClassesList, new ClassNameComparator());
for (Class aClass : sortedBoundProviderClassesList) {
builder.append(aClass.getName());
builder.append(',');
}
if (!sortedBoundProviderClassesList.isEmpty()) {
builder.deleteCharAt(builder.length() - 1);
}
builder.append(']');
builder.append(LINE_SEPARATOR);
Iterator<ScopeNode> iterator = childrenScopes.values().iterator();
while (iterator.hasNext()) {
Scope scope = iterator.next();
boolean isLast = !iterator.hasNext();
builder.append(isLast ? lastNode : node);
builder.append(branch);
String childString = scope.toString();
String[] split = childString.split(LINE_SEPARATOR);
for (int i = 0; i < split.length; i++) {
String childLine = split[i];
if (i != 0) {
builder.append(indent);
}
builder.append(childLine);
builder.append(LINE_SEPARATOR);
}
}
if (getRootScope() == this) {
builder.append("Unbound providers: [");
ArrayList<Class> sortedUnboundProviderClassesList;
synchronized (mapClassesToUnNamedUnBoundProviders) {
sortedUnboundProviderClassesList = new ArrayList(mapClassesToUnNamedUnBoundProviders.keySet());
}
Collections.sort(sortedUnboundProviderClassesList, new ClassNameComparator());
for (Class aClass : sortedUnboundProviderClassesList) {
builder.append(aClass.getName());
builder.append(',');
}
if (!sortedUnboundProviderClassesList.isEmpty()) {
builder.deleteCharAt(builder.length() - 1);
}
builder.append(']');
builder.append(LINE_SEPARATOR);
}
return builder.toString();
}
private void installModules(boolean isTestModule, Module... modules) {
for (Module module : modules) {
try {
installModule(isTestModule, module);
} catch (Exception e) {
throw new IllegalStateException(format("Module %s couldn't be installed", module.getClass().getName()), e);
}
}
}
private void installModule(boolean isTestModule, Module module) {
for (Binding binding : module.getBindingSet()) {
if (binding == null) {
throw new IllegalStateException("A module can't have a null binding : " + module);
}
Class clazz = binding.getKey();
String bindingName = binding.getName();
try {
if (isTestModule || getBoundProvider(clazz, bindingName) == null) {
InternalProviderImpl provider = toProvider(binding);
if (binding.isCreatingInstancesInScope()) {
installScopedProvider(clazz, bindingName, (ScopedProviderImpl) provider, isTestModule);
} else {
installBoundProvider(clazz, bindingName, provider, isTestModule);
}
}
} catch (Exception e) {
throw new IllegalBindingException(format("Binding %s couldn't be installed", bindingName), e);
}
}
}
//do not change the return type to Provider<? extends T>.
//it would be cool and more convenient for bindings, but it would
//make the APIs very unstable as you could not get any instance of the
//implementation class via an scope, it would fail but be syntactically valid.
//only creating an instance of the interface is valid with this syntax.
/*VisibleForTesting*/ <T> InternalProviderImpl<T> toProvider(Binding<T> binding) {
if (binding == null) {
throw new IllegalStateException("null binding are not allowed. Should not happen unless getBindingSet is overridden.");
}
ConfigurationHolder.configuration.checkIllegalBinding(binding, this);
switch (binding.getMode()) {
case SIMPLE:
return createInternalProvider(this,
binding.getKey(),
false,
binding.isCreatingInstancesInScope(),
binding.isCreatingSingletonInScope(),
false);
case CLASS:
return createInternalProvider(this,
binding.getImplementationClass(),
false,
binding.isCreatingInstancesInScope(),
binding.isCreatingSingletonInScope(),
false);
case INSTANCE:
return new InternalProviderImpl<>(binding.getInstance());
case PROVIDER_INSTANCE:
// to ensure providers do not have to deal with concurrency, we wrap them in a thread safe provider
// We do not need to pass the scope here because the provider won't use any scope to create the instance
return new InternalProviderImpl<>(binding.getProviderInstance(), binding.isProvidingSingletonInScope());
case PROVIDER_CLASS:
return createInternalProvider(this,
binding.getProviderClass(),
true,
binding.isCreatingInstancesInScope(),
binding.isCreatingSingletonInScope(),
binding.isProvidingSingletonInScope());
//JACOCO:OFF
default:
throw new IllegalStateException(format("mode is not handled: %s. This should not happen.", binding.getMode()));
//JACOCO:ON
}
}
private <T> InternalProviderImpl<T> createInternalProvider(Scope scope, Class<?> factoryKeyClass,
boolean isProviderClass,
boolean isCreatingInstancesInScope,
boolean isCreatingSingletonInScope,
boolean isProvidingInstancesInScope) {
if (isCreatingInstancesInScope) {
return new ScopedProviderImpl<>(scope,
factoryKeyClass,
isProviderClass,
isCreatingSingletonInScope,
isProvidingInstancesInScope);
} else {
return new InternalProviderImpl<>(factoryKeyClass,
isProviderClass,
isCreatingSingletonInScope,
isProvidingInstancesInScope);
}
}
/**
* The core of Toothpick internals : the provider lookup.
* It will look for a scoped provider, bubbling up in the scope hierarchy.
* If one is found, we return it. If not, we look in the un-scoped provider pool,
* if one is found, we return it. If not, we create a provider dynamically, using a factory. Depending
* on the whether or not the discovered factory for this class is scoped (={@link javax.inject.Scope} annotated),
* the provider will be scoped or not. If it is scoped, it will be scoped in the appropriate scope, if not
* it will be added to the pool of un-scoped providers.
* Note that
*
* @param clazz the {@link Class} of {@code T} for which we lookup an {@link InternalProviderImpl}.
* @param bindingName the potential name of the provider when it was bound (which means we always returned a scoped provider if
* name is not null).
* @param <T> the type for which we lookup an {@link InternalProviderImpl}.
* @return a provider associated to the {@code T}. The returned provider is un-scoped (remember that {@link ScopedProviderImpl} is a subclass of
* {@link InternalProviderImpl}). The returned provider will be scoped by the public methods to use the current scope.
*/
/* @VisibleForTesting */ <T> InternalProviderImpl<? extends T> lookupProvider(Class<T> clazz, String bindingName) {
if (clazz == null) {
throw new IllegalArgumentException("TP can't get an instance of a null class.");
}
InternalProviderImpl<? extends T> scopedProvider = getBoundProvider(clazz, bindingName);
if (scopedProvider != null) {
return scopedProvider;
}
Iterator<ScopeNode> iterator = parentScopes.iterator();
while (iterator.hasNext()) {
Scope parentScope = iterator.next();
ScopeImpl parentScopeImpl = (ScopeImpl) parentScope;
InternalProviderImpl<? extends T> parentScopedProvider = parentScopeImpl.getBoundProvider(clazz, bindingName);
if (parentScopedProvider != null) {
return parentScopedProvider;
}
}
//if the binding is named
//we couldn't find it in any scope, we must fail
//as only unnamed bindings can be created dynamically
if (bindingName != null) {
throw new RuntimeException(format("No binding was defined for class %s and name %s " //
+ "in scope %s and its parents %s", clazz.getName(), bindingName, getName(), getParentScopesNames()));
}
//we now look for an unnamed binding
//bindingName = null; <-- valid but fails checkstyle as we use null directly
//check if we have a cached un-scoped provider
InternalProviderImpl unScopedProviderInPool = getUnBoundProvider(clazz, null);
if (unScopedProviderInPool != null) {
return unScopedProviderInPool;
}
//classes discovered at runtime, not bound by any module
//they will be a bit slower as we need to get the factory first
//we need to know whether they are scoped or not, if so we scope them
//if not, they are place in the pool
Factory<T> factory = FactoryLocator.getFactory(clazz);
if (factory.hasScopeAnnotation()) {
//the new provider will have to work in the current scope
Scope targetScope = factory.getTargetScope(this);
ScopedProviderImpl<? extends T> newProvider = new ScopedProviderImpl<>(targetScope, factory, false);
//it is bound to its target scope only if it has a scope annotation.
//lock free installing a provider means there could have been one set concurrently since last testing
//its value. We allow to return it here
ScopeImpl targetScopeImpl = (ScopeImpl) targetScope;
return targetScopeImpl.installScopedProvider(clazz, null, newProvider, false);
} else {
//the provider is but in a pool of unbound providers for later reuse
final InternalProviderImpl<T> newProvider = new InternalProviderImpl<>(factory,
false);
//the pool is static as it is accessible from all scopes
//lock free installing a provider means there could have been one set concurrently since last testing
//its value. We allow to return it here
return installUnBoundProvider(clazz, null, newProvider);
}
}
/**
* Obtains the provider of the class {@code clazz} and name {@code bindingName}, if any. The returned provider
* will be bound to the scope. It can be {@code null} if there is no such provider.
* Ancestors are not taken into account.
*
* @param clazz the class for which to obtain the bound provider.
* @param bindingName the name, possibly {@code null}, for which to obtain the bound provider.
* @param <T> the type of {@code clazz}.
* @return the bound provider for class {@code clazz} and {@code bindingName}. Returns {@code null} is there
* is no such bound provider.
*/
private <T> InternalProviderImpl<? extends T> getBoundProvider(Class<T> clazz, String bindingName) {
return getInternalProvider(clazz, bindingName, true);
}
/**
* Obtains the provider of the class {@code clazz} and name {@code bindingName}, if any. The returned provider
* will belong to the pool of unbound providers. It can be {@code null} if there is no such provider.
*
* @param clazz the class for which to obtain the unbound provider.
* @param bindingName the name, possibly {@code null}, for which to obtain the unbound provider.
* @param <T> the type of {@code clazz}.
* @return the unbound provider for class {@code clazz} and {@code bindingName}. Returns {@code null} is there
* is no such unbound provider.
*/
private <T> InternalProviderImpl<? extends T> getUnBoundProvider(Class<T> clazz, String bindingName) {
return getInternalProvider(clazz, bindingName, false);
}
/**
* Obtains the provider of the class {@code clazz} and name {@code bindingName}. The returned provider
* can either be bound to the scope or not depending on {@code isBound}.
* Ancestors are not taken into account.
*
* @param clazz the class for which to obtain the provider.
* @param bindingName the name, possibly {@code null}, for which to obtain the provider.
* @param <T> the type of {@code clazz}.
* @return the provider for class {@code clazz} and {@code bindingName},
* either from the set of providers bound to the scope or from the pool of unbound providers.
* If there is no such provider, returns {@code null}.
*
* Note to maintainers : we don't use this method directly, both {@link #getBoundProvider} and {@link #getUnBoundProvider}
* are a facade of this method and make the calls more clear.
*/
private <T> InternalProviderImpl<? extends T> getInternalProvider(Class<T> clazz, String bindingName, boolean isBound) {
if (bindingName == null) {
if (isBound) {
synchronized (mapClassesToUnNamedBoundProviders) {
return mapClassesToUnNamedBoundProviders.get(clazz);
}
} else {
synchronized (mapClassesToUnNamedUnBoundProviders) {
return mapClassesToUnNamedUnBoundProviders.get(clazz);
}
}
} else {
synchronized (mapClassesToNamedBoundProviders) {
Map<String, InternalProviderImpl> mapNameToProvider = mapClassesToNamedBoundProviders.get(clazz);
if (mapNameToProvider == null) {
return null;
}
return mapNameToProvider.get(bindingName);
}
}
}
/**
* Install the provider of the class {@code clazz} and name {@code bindingName}
* in the current scope.
*
* @param clazz the class for which to install the scoped provider of this scope.
* @param bindingName the name, possibly {@code null}, for which to install the scoped provider.
* @param scopedProvider the internal provider to install.
* @param isTestProvider whether or not is a test provider, installed through a Test Module that should override
* existing providers for the same class-bindingname.
* @param <T> the type of {@code clazz}.
* @return the provider that will be installed, if one was previously installed, it is returned, in a lock-free way.
*/
private <T> InternalProviderImpl<? extends T> installScopedProvider(Class<T> clazz, String bindingName,
ScopedProviderImpl<? extends T> scopedProvider, boolean isTestProvider) {
return installBoundProvider(clazz, bindingName, scopedProvider, isTestProvider);
}
/**
* Install the provider of the class {@code clazz} and name {@code bindingName}
* in the current scope.
*
* @param clazz the class for which to install the scoped provider.
* @param bindingName the name, possibly {@code null}, for which to install the scoped provider.
* @param internalProvider the internal provider to install.
* @param isTestProvider whether or not is a test provider, installed through a Test Module that should override
* existing providers for the same class-bindingname.
* @param <T> the type of {@code clazz}.
*/
private <T> InternalProviderImpl<? extends T> installBoundProvider(Class<T> clazz, String bindingName,
InternalProviderImpl<? extends T> internalProvider, boolean isTestProvider) {
return installInternalProvider(clazz, bindingName, internalProvider, true, isTestProvider);
}
/**
* Install the provider of the class {@code clazz} and name {@code bindingName}
* in the pool of unbound providers.
*
* @param clazz the class for which to install the provider.
* @param bindingName the name, possibly {@code null}, for which to install the scoped provider.
* @param internalProvider the internal provider to install.
* @param <T> the type of {@code clazz}.
*/
private <T> InternalProviderImpl<? extends T> installUnBoundProvider(Class<T> clazz, String bindingName,
InternalProviderImpl<? extends T> internalProvider) {
return installInternalProvider(clazz, bindingName, internalProvider, false, false);
}
/**
* Installs a provider either in the scope or the pool of unbound providers.
*
* @param clazz the class for which to install the provider.
* @param bindingName the name, possibly {@code null}, for which to install the scoped provider.
* @param internalProvider the internal provider to install.
* @param isBound whether or not the provider is bound to the scope or belongs to the pool of unbound providers.
* @param isTestProvider whether or not is a test provider, installed through a Test Module that should override
* existing providers for the same class-bindingname.
* @param <T> the type of {@code clazz}.
*
* Note to maintainers : we don't use this method directly, both {@link #installBoundProvider(Class, String, InternalProviderImpl, boolean)}
* and {@link #installUnBoundProvider(Class, String, InternalProviderImpl)}
* are a facade of this method and make the calls more clear.
*/
private <T> InternalProviderImpl installInternalProvider(Class<T> clazz, String bindingName, InternalProviderImpl<? extends T> internalProvider,
boolean isBound, boolean isTestProvider) {
if (bindingName == null) {
if (isBound) {
return installUnNamedProvider(mapClassesToUnNamedBoundProviders, clazz, internalProvider, isTestProvider);
} else {
return installUnNamedProvider(mapClassesToUnNamedUnBoundProviders, clazz, internalProvider, isTestProvider);
}
} else {
return installNamedProvider(mapClassesToNamedBoundProviders, clazz, bindingName, internalProvider, isTestProvider);
}
}
private <T> InternalProviderImpl installNamedProvider(IdentityHashMap<Class, Map<String, InternalProviderImpl>> mapClassesToNamedBoundProviders,
Class<T> clazz, String bindingName, InternalProviderImpl<? extends T> internalProvider, boolean isTestProvider) {
synchronized (mapClassesToNamedBoundProviders) {
Map<String, InternalProviderImpl> mapNameToProvider = mapClassesToNamedBoundProviders.get(clazz);
if (mapNameToProvider == null) {
mapNameToProvider = new HashMap<>(1);
mapClassesToNamedBoundProviders.put(clazz, mapNameToProvider);
mapNameToProvider.put(bindingName, internalProvider);
return internalProvider;
}
InternalProviderImpl previous = mapNameToProvider.get(bindingName);
if (previous == null || isTestProvider) {
mapNameToProvider.put(bindingName, internalProvider);
return internalProvider;
} else {
return previous;
}
}
}
private <T> InternalProviderImpl installUnNamedProvider(IdentityHashMap<Class, InternalProviderImpl> mapClassesToUnNamedProviders, Class<T> clazz,
InternalProviderImpl<? extends T> internalProvider, boolean isTestProvider) {
synchronized (mapClassesToUnNamedProviders) {
InternalProviderImpl previous = mapClassesToUnNamedProviders.get(clazz);
if (previous == null || isTestProvider) {
mapClassesToUnNamedProviders.put(clazz, internalProvider);
return internalProvider;
} else {
return previous;
}
}
}
private void crashIfClosed() {
if (!isOpen) {
throw new IllegalStateException(String.format("The scope with name %s has been already closed."
+ " It can't be used to create new instances.", name));
}
}
static void resetUnBoundProviders() {
mapClassesToUnNamedUnBoundProviders.clear();
}
/**
* Resets the state of the scope.
* Useful for automation testing when we want to reset the scope used to install test modules.
*/
@Override
protected void reset() {
super.reset();
mapClassesToNamedBoundProviders.clear();
mapClassesToUnNamedBoundProviders.clear();
hasTestModules = false;
installBindingForScope();
}
/**
* Install bindings for scope.
*/
private void installBindingForScope() {
//it's always possible to get access to the scope that contains an injected object.
installBoundProvider(Scope.class, null, new InternalProviderImpl<>(this), false);
}
private static class ClassNameComparator implements Comparator<Class> {
@Override
public int compare(Class o1, Class o2) {
return o1.getName().compareTo(o2.getName());
}
}
}