diff --git a/.gitignore b/.gitignore index abcd3af..4388305 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,7 @@ test/main.dart.js.deps test/main.dart.js.map test/main.dart.precompiled.js test/transformer/build +benchmark/generated_files/ +examples/build out build diff --git a/CHANGELOG.md b/CHANGELOG.md index bba0f64..73e4cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,66 @@ +# 2.0.0-alpha.1 + +## Breaking Changes + +### Calls to StaticInjector and DynamicInjector should be replaced with ModuleInjector + - There are no longer `StaticInjectors` and `DynamicInjectors`. They have been replaced + by a new `ModuleInjector` class that acts as both types of injectors. + +### ModuleInjectors have no visibility + - All bindings and instances of parent injectors are now visible in child injectors. + - The optional argument `forceNewInstances` of `Injector.createChild` has been removed + Instead, create a new module with bindings of the types that require new instances + and pass that to the child injector, and the child injector will create new + instances instead of returning the instance of the parent injector. + +### Use “new ModuleInjector(modules, parent)” instead of “Injector.createChild(modules)” + - The latter is still available but deprecated. + - Injectors with no parent now have a dummy RootInjector instance as the parent + Instead of checking “parent == null”, check for “parent == rootInjector”. + +### Injectors no longer have a name field + +### typeFactories have changed + - Old type factories had the form `(injector) => new Instance(injector.get(dep1), … )` + - New factories have one of these two forms: + - `toFactory(a0, a1, …) => new Instance(a0, a1, …)` + - `toFactoryPos(List p) => new Instance(p[0], p[1] …)` + - When calling `Module.bind(toFactory: factory)`, there is an additional argument `inject` + of a list of types or keys (preferred for performance) whose instances should be + passed to the factory. The array `p` passed to the factory function will be instances + of the types in `inject`. + + Example: + - Old code `module.bind(Car, toFactory: (i) => new Car(i.get(Engine)));` + - New code + - `module.bind(Car, toFactoryPos: (p) => new Car(p[0]), inject: [Engine]);` + - `module.bind(Car, toFactory: (engine) => new Car(engine), inject: [Engine]);` + + There is also some syntactic sugar for this special case. + - Old code `module.bind(Engine, toFactory: (i) => i.get(Engine));` + - New code `module.bind(Engine, toFactory: (e) => e, inject: [Engine]);` + - With sugar `module.bind(Engine, inject: [Engine]);` + +### Modules have a TypeReflector instance attached + - The `TypeReflector` is how the module will find the `toFactoryPos` and `inject` + arguments when not explicitly specified. This is either done with mirroring or code + generation via transformers. Typical use will not need to worry about this at all, and + should initialize it as shown below. If needed, implement `TypeReflector` and use + `new Module.withReflector(reflector)`. + - Modules need a `TypeReflector` instance initialized: + + import 'package:di/di_dynamic.dart'; + main() { + setupModuleTypeReflector(); + } + +### The transformer has been updated + - Running the transformer will do the necessary code generation and edits to switch the + default `TypeReflector` from mirroring to static factories. Enable transformer to use + static factories, disable to use mirrors. More docs on the transformer can be found in + `transformer.dart` + - Annotating types for injection has not changed. + # 1.0.0 Starting with this release DI is following [semver](http://semver.org). diff --git a/LICENSE b/LICENSE index e950988..47c5c22 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2013 Google, Inc. +Copyright (c) 2014 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2609ba0..046e485 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,19 @@ Add dependency to your pubspec.yaml. dependencies: - di: ">=0.0.39 <0.1.0" + di: ">=2.0.0 <3.0.0" Then, run `pub install`. Import di. import 'package:di/di.dart'; - import 'package:di/auto_injector.dart'; ## Example ```dart import 'package:di/di.dart'; -import 'package:di/auto_injector.dart'; +import 'package:di/di_dynamic.dart'; abstract class Engine { go(); @@ -64,14 +63,18 @@ class ElectricCar { } void main() { - var injector = defaultInjector(modules: [new Module() + setupModuleTypeReflector(); + var injector = new ModuleInjector(modules: [new Module() ..bind(GenericCar) ..bind(ElectricCar) - ..bind(Engine, toFactory: (i) => new V8Engine()) + ..bind(Engine, toFactory: () => new V8Engine()) ..bind(Engine, toImplementation: ElectricEngine, withAnnotation: Electric) ]); - injector.get(GenericCar).drive(); - injector.get(ElectricCar).drive(); + injector.get(GenericCar).drive(); // Vroom... + injector.get(ElectricCar).drive(); // Hum... } ``` +## Contributing + +Refer to the guidelines for [contributing to AngularDart](http://goo.gl/nrXVgm). diff --git a/benchmark/dynamic_injector_benchmark.dart b/benchmark/dynamic_injector_benchmark.dart index a5fa472..216831c 100644 --- a/benchmark/dynamic_injector_benchmark.dart +++ b/benchmark/dynamic_injector_benchmark.dart @@ -1,10 +1,8 @@ import 'package:benchmark_harness/benchmark_harness.dart'; -import 'package:di/dynamic_injector.dart'; import 'injector_benchmark_common.dart'; - main() { new InjectorBenchmark('DynamicInjectorBenchmark', - (m) => new DynamicInjector(modules: m)).report(); -} \ No newline at end of file + new DynamicTypeFactories()).report(); +} diff --git a/benchmark/injector_benchmark_common.dart b/benchmark/injector_benchmark_common.dart index ce1cbd9..deca47b 100644 --- a/benchmark/injector_benchmark_common.dart +++ b/benchmark/injector_benchmark_common.dart @@ -6,32 +6,43 @@ import 'package:di/di.dart'; int count = 0; class InjectorBenchmark extends BenchmarkBase { - var injectorFactory; var module; + var typeReflector; + Key KEY_A; + Key KEY_B; + Key KEY_C; + Key KEY_D; + Key KEY_E; - InjectorBenchmark(name, this.injectorFactory) : super(name); + InjectorBenchmark(name, this.typeReflector) : super(name); void run() { - Injector injector = injectorFactory([module]); - injector.get(A); - injector.get(B); + Injector injector = new ModuleInjector([module]); + injector.getByKey(KEY_A); + injector.getByKey(KEY_B); - var childInjector = injector.createChild([module]); - childInjector.get(A); - childInjector.get(B); + var childInjector = new ModuleInjector([module], injector); + childInjector.getByKey(KEY_A); + childInjector.getByKey(KEY_B); } setup() { - module = new Module() - ..type(A) - ..type(B) - ..type(C) - ..type(C, withAnnotation: AnnOne, implementedBy: COne ) - ..type(D) - ..type(E) - ..type(E, withAnnotation: AnnTwo, implementedBy: ETwo ) - ..type(F) - ..type(G); + module = new Module.withReflector(typeReflector) + ..bind(A) + ..bind(B) + ..bind(C) + ..bind(C, withAnnotation: AnnOne, toImplementation: COne ) + ..bind(D) + ..bind(E) + ..bind(E, withAnnotation: AnnTwo, toImplementation: ETwo ) + ..bind(F) + ..bind(G); + + KEY_A = new Key(A); + KEY_B = new Key(B); + KEY_C = new Key(C); + KEY_D = new Key(D); + KEY_E = new Key(E); } teardown() { @@ -96,7 +107,31 @@ class F { } class G { - G(@AnnTwo() E) { + G(@AnnTwo() E e) { count++; } } + +var typeFactories = { + A: (p) => new A(p[0], p[1]), + B: (p) => new B(p[0], p[1]), + C: (p) => new C(), + D: (p) => new D(), + E: (p) => new E(), + COne: (p) => new COne(), + ETwo: (p) => new ETwo(), + F: (p) => new F(p[0], p[1]), + G: (p) => new G(p[0]), +}; + +var paramKeys = { + A: [new Key(B), new Key(C)], + B: [new Key(D), new Key(E)], + C: const [], + D: const [], + E: const [], + COne: const [], + ETwo: const [], + F: [new Key(C, AnnOne), new Key(D)], + G: [new Key(G, AnnTwo)], +}; diff --git a/benchmark/instance_benchmark.dart b/benchmark/instance_benchmark.dart index 291048e..7fef2eb 100644 --- a/benchmark/instance_benchmark.dart +++ b/benchmark/instance_benchmark.dart @@ -1,13 +1,14 @@ -import 'package:di/static_injector.dart'; +import 'package:di/di.dart'; +import 'package:di/di_static.dart'; import 'injector_benchmark_common.dart'; // tests the speed of cached getInstanceByKey requests class InstanceBenchmark extends InjectorBenchmark{ - InstanceBenchmark(name, injectorFactory) : super(name, injectorFactory); + InstanceBenchmark(name, typeReflector) : super(name, typeReflector); void run(){ - Injector injector = injectorFactory([module]); + Injector injector = new ModuleInjector([module]); for (var i = 0; i < 30; i++) { injector.get(A); } @@ -15,15 +16,7 @@ class InstanceBenchmark extends InjectorBenchmark{ } main() { - var typeFactories = { - A: (f) => new A(f(B), f(C)), - B: (f) => new B(f(D), f(E)), - C: (f) => new C(), - D: (f) => new D(), - E: (f) => new E(), - }; - new InstanceBenchmark('InstanceBenchmark', - (m) => new StaticInjector(modules: m, typeFactories: typeFactories) + new GeneratedTypeFactories(typeFactories, paramKeys) ).report(); } diff --git a/benchmark/large_benchmark.dart b/benchmark/large_benchmark.dart new file mode 100644 index 0000000..d506e49 --- /dev/null +++ b/benchmark/large_benchmark.dart @@ -0,0 +1,70 @@ +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:di/di.dart'; +import 'package:di/di_static.dart'; +import 'generated_files/factories.dart'; + +import 'dart:math'; + +class LargeBenchmark extends BenchmarkBase { + var injectorFactory; + Injector rootInjector; + Injector leafInjector; + var leafKey; + var rng = new Random(); + var numInjectors = 1; + var allLeaves = []; + + LargeBenchmark(name, this.injectorFactory) : super("Large" + name); + + setup() { + var rootModule = new Module() + ..bindByKey(key999) + ..bindByKey(key998) + ..bindByKey(key997) + ..bindByKey(key996) + ..bindByKey(key995); + rootInjector = injectorFactory([rootModule]); + + createChildren (injector, depth, width) { + if (depth <= 0){ + allLeaves.add(injector); + return; + } + for (var i=0; i new ModuleInjector(m)); + + run() { + leafInjector.getByKey(key999); + } +} + +class GetFromLeaf extends LargeBenchmark { + GetFromLeaf() : super('FromLeaf', (m) => new ModuleInjector(m)); + + run() { + leafInjector.getByKey(leafKey); + } +} + +main() { + Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys); + new GetFromRoot().report(); + new GetFromLeaf().report(); +} diff --git a/benchmark/module_benchmark.dart b/benchmark/module_benchmark.dart index cb18a5a..aeb34a4 100644 --- a/benchmark/module_benchmark.dart +++ b/benchmark/module_benchmark.dart @@ -3,6 +3,11 @@ import 'package:di/di.dart'; import 'injector_benchmark_common.dart'; +/** + * tests the speed of looking up typeFactories and binding them to + * a module. Mirroring time is not counted because DynamicTypeFactories + * caches the results. + */ class ModuleBenchmark extends BenchmarkBase { var injectorFactory; @@ -10,14 +15,14 @@ class ModuleBenchmark extends BenchmarkBase { void run() { var m = new Module() - ..type(A) - ..type(B) - ..type(C) - ..type(D) - ..type(E); + ..bind(A) + ..bind(B) + ..bind(C) + ..bind(D) + ..bind(E); } } main() { new ModuleBenchmark().report(); -} \ No newline at end of file +} diff --git a/benchmark/static_injector_baseline_benchmark.dart b/benchmark/static_injector_baseline_benchmark.dart new file mode 100644 index 0000000..bc52321 --- /dev/null +++ b/benchmark/static_injector_baseline_benchmark.dart @@ -0,0 +1,119 @@ +import 'package:di/di.dart'; +import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:di/di_static.dart'; + +import 'injector_benchmark_common.dart'; +import 'static_injector_benchmark.dart'; + +import 'dart:profiler'; + +/** + * This benchmark creates the same objects as the StaticInjectorBenchmark + * without using DI, to serve as a baseline for comparison. + */ +class CreateObjectsOnly extends BenchmarkBase { + CreateObjectsOnly(name) : super(name); + + void run() { + var b1 = new B(new D(), new E()); + var c1 = new C(); + var d1 = new D(); + var e1 = new E(); + + var a = new A(b1, c1); + var b = new B(d1, e1); + + var c = new A(b1, c1); + var d = new B(d1, e1); + } + + void teardown() { + print(count); + } +} + +class CreateSingleInjector extends InjectorBenchmark { + + CreateSingleInjector(name, injectorFactory) : super(name, injectorFactory); + + void run() { + Injector injector = new ModuleInjector([module]); + + var b1 = new B(new D(), new E()); + var c1 = new C(); + var d1 = new D(); + var e1 = new E(); + + var a = new A(b1, c1); + var b = new B(d1, e1); + + var c = new A(b1, c1); + var d = new B(d1, e1); + } +} + +class CreateInjectorAndChild extends InjectorBenchmark { + + CreateInjectorAndChild(name, injectorFactory) : super(name, injectorFactory); + + void run() { + Injector injector = new ModuleInjector([module]); + var childInjector = injector.createChild([module]); + + var b1 = new B(new D(), new E()); + var c1 = new C(); + var d1 = new D(); + var e1 = new E(); + + var a = new A(b1, c1); + var b = new B(d1, e1); + + var c = new A(b1, c1); + var d = new B(d1, e1); + } +} + +class InjectByKey extends InjectorBenchmark { + + InjectByKey(name, injectorFactory) + : super(name, injectorFactory); + + void run() { + var injector = new ModuleInjector([module]); + var childInjector = new ModuleInjector([module], injector); + + injector.getByKey(KEY_A); + injector.getByKey(KEY_B); + + childInjector.getByKey(KEY_A); + childInjector.getByKey(KEY_B); + } +} + +main() { + var oldTypeFactories = { + A: (f) => new A(f(B), f(C)), + B: (f) => new B(f(D), f(E)), + C: (f) => new C(), + D: (f) => new D(), + E: (f) => new E(), + }; + + const PAD_LENGTH = 35; + GeneratedTypeFactories generatedTypeFactories = + new GeneratedTypeFactories(typeFactories, paramKeys); + + new CreateObjectsOnly("Create objects manually without DI".padRight(PAD_LENGTH)).report(); + new CreateSingleInjector('.. and create an injector'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); + new CreateInjectorAndChild('.. and a child injector'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); + new InjectorBenchmark('DI using ModuleInjector'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); + new InjectByKey('.. and precompute keys'.padRight(PAD_LENGTH), + generatedTypeFactories + ).report(); +} diff --git a/benchmark/static_injector_benchmark.dart b/benchmark/static_injector_benchmark.dart index b9d4efb..9d4a9fa 100644 --- a/benchmark/static_injector_benchmark.dart +++ b/benchmark/static_injector_benchmark.dart @@ -1,17 +1,9 @@ -import 'package:di/static_injector.dart'; - +import 'package:di/di.dart'; +import 'package:di/di_static.dart'; import 'injector_benchmark_common.dart'; main() { - var typeFactories = { - A: (f) => new A(f(B), f(C)), - B: (f) => new B(f(D), f(E)), - C: (f) => new C(), - D: (f) => new D(), - E: (f) => new E(), - }; - new InjectorBenchmark('StaticInjectorBenchmark', - (m) => new StaticInjector(modules: m, typeFactories: typeFactories) + new GeneratedTypeFactories(typeFactories, paramKeys) ).report(); -} \ No newline at end of file +} diff --git a/class_gen.dart b/class_gen.dart new file mode 100644 index 0000000..25c0c9f --- /dev/null +++ b/class_gen.dart @@ -0,0 +1,40 @@ +import 'dart:io'; +import 'dart:async'; + +main() { + int numClasses = 1000; + + File file = new File('benchmark/generated_files/classes.dart'); + var sink = file.openWrite(mode: WRITE); + sink.write('int c=0;\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('class Test$i{Test$i(){c++;}}\n'); + } + sink.close(); + + file = new File('benchmark/generated_files/factories.dart');; + sink = file.openWrite(mode: WRITE); + sink.write('import "package:di/key.dart";\n'); + sink.write('import "package:di/di.dart";\n'); + sink.write('import "classes.dart";\n'); + sink.write('export "classes.dart";\n'); + for (var i = 0; i< numClasses; i++) { + sink.write('final Key key$i = new Key(Test$i);\n'); + } + sink.write('List allKeys = [\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('key$i, '); + } + sink.write('];\n'); + sink.write('Map typeFactories = {\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('Test$i: (p) => new Test$i(),\n'); + } + sink.write('};\n'); + sink.write('Map> parameterKeys = {\n'); + for (var i = 0; i < numClasses; i++) { + sink.write('Test$i: const[], '); + } + sink.write('\n};\n'); + sink.close(); +} diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..6e89349 --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,10 @@ +name: di_example +version: 0.0.1 +dependencies: + browser: any + web_components: any + di: + path: ../ + +transformers: +- di diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 0000000..fada8c8 --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,11 @@ + + + + Example + + + + +Check console output for build success. + + diff --git a/example/web/main.dart b/example/web/main.dart new file mode 100644 index 0000000..d5e0160 --- /dev/null +++ b/example/web/main.dart @@ -0,0 +1,16 @@ +import 'package:di/di.dart'; +import 'package:di/annotations.dart'; +import 'dart:html'; + +@Injectable() +class Application { + run() { + print('Success'); + } +} + +main() { + Module module = new Module(); + module.bind(Application); + new ModuleInjector([module]).get(Application).run(); +} diff --git a/lib/auto_injector.dart b/lib/auto_injector.dart deleted file mode 100644 index 622ed25..0000000 --- a/lib/auto_injector.dart +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Library for using a pub transformer to automatically switch between - * dynamic and static injection. - * - * ## Step 1: Hook up the build step - * Edit ```pubspec.yaml``` to add the di transformer to the list of - * transformers. - * - * name: transformer_demo - * version: 0.0.1 - * dependencies: - * di: any - * inject: any - * transformers: - * - di: - * dart_entry: web/main.dart - * injectable_annotations: transformer_demo.Injectable - * - * ## Step 2: Annotate your types - * - * class Engine { - * @inject - * Engine(); - * } - * - * or - * - * @Injectable // custom annotation specified in pubspec.yaml - * class Car {} - * - * - * ## Step 3: Use the auto injector - * Modify your entry script to use [defaultInjector] as the injector. - * - * This must be done from the file registered as the dart_entry in pubspec.yaml - * as this is the only file which will be modified to include the generated - * injector. - * - * import 'package:di/auto_injector' as auto; - * main() { - * var injector = auto.defaultInjector(modules: ...); - * } - } - */ -library di.auto_injector; - -import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; - -@MirrorsUsed(override: '*') -import 'dart:mirrors' show MirrorsUsed; - -Injector defaultInjector({List modules, String name, - bool allowImplicitInjection: false}) => - new DynamicInjector( - modules: modules, - name: name, - allowImplicitInjection: allowImplicitInjection); diff --git a/lib/check_bind_args.dart b/lib/check_bind_args.dart new file mode 100644 index 0000000..75b7dde --- /dev/null +++ b/lib/check_bind_args.dart @@ -0,0 +1,85 @@ +library di.check_bind_args; + +import "src/module.dart"; +export "src/module.dart" show DEFAULT_VALUE, IDENTITY, isSet, isNotSet; + +checkBindArgs(dynamic toValue, Function toFactory, + Type toImplementation, List inject) { + int count = 0; + bool argCountMatch = true; + if (isSet(toValue)) count++; + if (isSet(toFactory)) { + count++; + var len = inject.length; + switch (len) { + case 0: argCountMatch = toFactory is _0; break; + case 1: argCountMatch = toFactory is _1; break; + case 2: argCountMatch = toFactory is _2; break; + case 3: argCountMatch = toFactory is _3; break; + case 4: argCountMatch = toFactory is _4; break; + case 5: argCountMatch = toFactory is _5; break; + case 6: argCountMatch = toFactory is _6; break; + case 7: argCountMatch = toFactory is _7; break; + case 8: argCountMatch = toFactory is _8; break; + case 9: argCountMatch = toFactory is _9; break; + case 10: argCountMatch = toFactory is _10; break; + case 11: argCountMatch = toFactory is _11; break; + case 12: argCountMatch = toFactory is _12; break; + case 13: argCountMatch = toFactory is _13; break; + case 14: argCountMatch = toFactory is _14; break; + case 15: argCountMatch = toFactory is _15; break; + } + if (!argCountMatch) throw "toFactory's argument count does not match amount provided by inject"; + } + + if (toImplementation != null) count++; + if (count > 1) { + throw 'Only one of following parameters can be specified: ' + 'toValue, toFactory, toFactoryPos, toImplementation'; + } + + if (inject.length > 0 && !isSet(toFactory)) { + throw "Received inject list but toFactory is not set."; + } + + return true; +} + +typedef _0(); +typedef _1(a1); +typedef _2(a1, a2); +typedef _3(a1, a2, a3); +typedef _4(a1, a2, a3, a4); +typedef _5(a1, a2, a3, a4, a5); +typedef _6(a1, a2, a3, a4, a5, a6); +typedef _7(a1, a2, a3, a4, a5, a6, a7); +typedef _8(a1, a2, a3, a4, a5, a6, a7, a8); +typedef _9(a1, a2, a3, a4, a5, a6, a7, a8, a9); +typedef _10(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); +typedef _11(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); +typedef _12(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); +typedef _13(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); +typedef _14(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); +typedef _15(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + +// Generation script + +//main() { +// var s = new StringBuffer(); +// +// int max_arg_count = 15; +// +// for (var i = 0; i <= max_arg_count; i++) { +// s.write("typedef _$i("); +// s.write(new List.generate(i, (c) => "a${c+1}").join(", ")); +// s.write(");\n"); +// } +// +// s.write("switch (len) {\n"); +// for (var i = 0; i <= max_arg_count; i++) { +// s.write("case $i: argCountMatch = toFactory is _$i; break;\n"); +// } +// s.write('}'); +// +// print(s); +//} diff --git a/lib/di.dart b/lib/di.dart index 54f28fa..237f673 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,11 +1,7 @@ library di; -import 'src/provider.dart'; -import 'key.dart'; - -export 'key.dart' show Key; - -part 'src/injector.dart'; -part 'src/module.dart'; -part 'src/errors.dart'; - +export 'key.dart' show Key, key; +export 'src/injector.dart' show Injector, ModuleInjector; +export 'src/module.dart' show Module, Binding, DEFAULT_VALUE; +export 'src/reflector.dart' show TypeReflector; +export 'src/errors.dart' hide BaseError; diff --git a/lib/dynamic_injector.dart b/lib/dynamic_injector.dart deleted file mode 100644 index 3349ea2..0000000 --- a/lib/dynamic_injector.dart +++ /dev/null @@ -1,99 +0,0 @@ -library di.dynamic_injector; - -import 'di.dart'; -import 'src/mirrors.dart'; -import 'src/base_injector.dart'; -import 'src/error_helper.dart'; -import 'src/provider.dart'; - -export 'di.dart'; - -/** - * Dynamic implementation of [Injector] that uses mirrors. - */ -class DynamicInjector extends BaseInjector { - - DynamicInjector({List modules, String name, - bool allowImplicitInjection: false}) - : super(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection); - - DynamicInjector._fromParent(List modules, Injector parent, {name}) - : super.fromParent(modules, parent, name: name); - - newFromParent(List modules, String name) => - new DynamicInjector._fromParent(modules, this, name: name); - - Object newInstanceOf(Type type, ObjectFactory objFactory, Injector requestor, - ResolutionContext resolving) { - var classMirror = reflectType(type); - if (classMirror is TypedefMirror) { - throw new NoProviderError(error(resolving, 'No implementation provided ' - 'for ${getSymbolName(classMirror.qualifiedName)} typedef!')); - } - - MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; - - if (ctor == null) { - throw new NoProviderError('Unable to find default constructor for $type. ' - 'Make sure class has a default constructor.' + (1.0 is int ? - 'Make sure you have correctly configured @MirrorsUsed.' : '')); - } - - resolveArgument(int pos) { - ParameterMirror p = ctor.parameters[pos]; - if (p.type.qualifiedName == #dynamic) { - var name = MirrorSystem.getName(p.simpleName); - throw new NoProviderError( - error(resolving, "The '$name' parameter must be typed")); - } - if (p.type is TypedefMirror) { - throw new NoProviderError( - error(resolving, - 'Cannot create new instance of a typedef ${p.type}')); - } - if (p.metadata.isNotEmpty) { - assert(p.metadata.length == 1); - var type = p.metadata.first.type.reflectedType; - return objFactory.getInstanceByKey( - new Key((p.type as ClassMirror).reflectedType, type), - requestor, resolving); - } else { - return objFactory.getInstanceByKey( - new Key((p.type as ClassMirror).reflectedType), - requestor, resolving); - } - } - - var args = new List.generate(ctor.parameters.length, resolveArgument, - growable: false); - return classMirror.newInstance(ctor.constructorName, args).reflectee; - } - - /** - * Invoke given function and inject all its arguments. - * - * Returns whatever the function returns. - */ - dynamic invoke(Function fn) { - ClosureMirror cm = reflect(fn); - MethodMirror mm = cm.function; - int position = 0; - List args = mm.parameters.map((ParameterMirror parameter) { - try { - if (parameter.metadata.isNotEmpty) { - var annotation = parameter.metadata[0].type.reflectedType; - return get((parameter.type as ClassMirror).reflectedType, annotation); - } else { - return get((parameter.type as ClassMirror).reflectedType); - } - } on NoProviderError catch (e) { - throw new NoProviderError(e.message); - } finally { - position++; - } - }).toList(); - - return cm.apply(args).reflectee; - } -} diff --git a/lib/generator.dart b/lib/generator.dart index 7ebb1a9..2b8c8a7 100644 --- a/lib/generator.dart +++ b/lib/generator.dart @@ -1,3 +1,11 @@ +/** + * Generates Factory & paramKeys maps into a file by crawling source files and + * finding all constructors that are annotated for injection. Does the same thing as + * transformer.dart (see there for more info), except without modifications + * to the main function. As such, the user needs to import the generated file, and run + * `Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys)` + * with `di_static.dart` imported, before any modules are initialized. + */ library di.generator; import 'package:analyzer/src/generated/java_io.dart'; @@ -48,10 +56,12 @@ main(List args) { Map generateCode(String entryPoint, List classAnnotations, String pathToSdk, List packageRoots, String outputFilename) { + var c = new SourceCrawler(pathToSdk, packageRoots); List imports = []; Map> typeFactoryTypes = >{}; Map typeToImport = new Map(); + c.crawl(entryPoint, (CompilationUnitElement compilationUnit, SourceFile source) { new CompilationUnitVisitor(c.context, source, classAnnotations, imports, typeToImport, typeFactoryTypes, outputFilename).visit(compilationUnit, source); @@ -61,8 +71,12 @@ Map generateCode(String entryPoint, List classAnnotations Map printLibraryCode(Map typeToImport, List imports, Map> typeFactoryTypes) { + Map factories = {}; + Map keys = {}; + Map paramLists = {}; Map result = {}; + typeFactoryTypes.forEach((Chunk chunk, List classes) { List requiredImports = []; String resolveClassIdentifier(InterfaceType type) { @@ -76,75 +90,129 @@ Map printLibraryCode(Map typeToImport, String prefix = _calculateImportPrefix(import, imports); return '$prefix.${type.name}'; } + factories[chunk] = new StringBuffer(); - classes.forEach((ClassElement clazz) { - StringBuffer factory = new StringBuffer(); - bool skip = false; - factory.write('${resolveClassIdentifier(clazz.type)}: (f) => '); - factory.write('new ${resolveClassIdentifier(clazz.type)}('); - ConstructorElement constr = - clazz.constructors.firstWhere((c) => c.name.isEmpty, - orElse: () { - throw 'Unable to find default constructor for ' - '$clazz in ${clazz.source}'; - }); - factory.write(constr.parameters.map((param) { + keys[chunk] = new StringBuffer(); + paramLists[chunk] = new StringBuffer(); + + process_classes(classes, keys[chunk], factories[chunk], paramLists[chunk], + resolveClassIdentifier); + + StringBuffer code = new StringBuffer(); + String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; + code.write('library di.generated.type_factories$libSuffix;\n'); + requiredImports.forEach((import) { + String prefix = _calculateImportPrefix(import, imports); + code.write ('import "$import" as $prefix;\n'); + }); + code..write('import "package:di/key.dart" show Key;\n') + ..write(keys[chunk]) + ..write('Map typeFactories = {\n${factories[chunk]}};\n') + ..write('Map> parameterKeys = {\n${paramLists[chunk]}};\n') + ..write('main() {}\n'); + result[chunk] = code.toString(); + }); + + return result; +} + +typedef String IdentifierResolver(InterfaceType type); +/** + * Takes classes and writes to StringBuffers the corresponding keys, factories, + * and paramLists needed for static injection. + * + * resolveClassIdentifier is a function passed in to be called to resolve imports + */ +void process_classes(Iterable classes, StringBuffer keys, + StringBuffer factories, StringBuffer paramLists, + IdentifierResolver resolveClassIdentifier) { + + Map toBeAdded = new Map(); + Set addedKeys = new Set(); + classes.forEach((ClassElement clazz) { + StringBuffer factory = new StringBuffer(); + StringBuffer paramList = new StringBuffer(); + List factoryKeys = new List(); + bool skip = false; + if (addedKeys.add(clazz.type.name)){ + toBeAdded[clazz.type.name]= + 'final Key _KEY_${clazz.type.name} = new Key(${resolveClassIdentifier(clazz.type)});\n'; + } + factoryKeys.add('${clazz.type.name}'); + + ConstructorElement constr = + clazz.constructors.firstWhere((c) => c.name.isEmpty, + orElse: () { + throw 'Unable to find default constructor for ' + '$clazz in ${clazz.source}'; + }); + var args = new List.generate(constr.parameters.length, (i) => 'a$i').join(', '); + factory.write('${resolveClassIdentifier(clazz.type)}: ($args) => ' + 'new ${resolveClassIdentifier(clazz.type)}($args),\n'); + + paramList.write('${resolveClassIdentifier(clazz.type)}: '); + if (constr.parameters.isEmpty){ + paramList.write('const ['); + } else { + paramList.write('['); + paramList.write(constr.parameters.map((param) { if (param.type.element is! ClassElement) { throw 'Unable to resolve type for constructor parameter ' - '"${param.name}" for type "$clazz" in ${clazz.source}'; + '"${param.name}" for type "$clazz" in ${clazz.source}'; } if (_isParameterized(param)) { print('WARNING: parameterized types are not supported: ' - '$param in $clazz in ${clazz.source}. Skipping!'); + '$param in $clazz in ${clazz.source}. Skipping!'); skip = true; } var annotations = []; if (param.metadata.isNotEmpty) { annotations = param.metadata.map( - (item) => resolveClassIdentifier(item.element.returnType)); + (item) => item.element.returnType.name); } - StringBuffer output = - new StringBuffer('f(${resolveClassIdentifier(param.type)}'); - if (annotations.isNotEmpty) { - output.write(', ${annotations.first}'); + String key_name = annotations.isNotEmpty ? + '${param.type.name}_${annotations.first}' : param.type.name; + String output = '_KEY_${key_name}'; + if (addedKeys.add(key_name)){ + var annotationParam = ""; + if (param.metadata.isNotEmpty) { + annotationParam = ", ${resolveClassIdentifier(param.metadata.first.element.returnType)}"; + } + toBeAdded['$key_name']='final Key _KEY_${key_name} = '+ + 'new Key(${resolveClassIdentifier(param.type)}$annotationParam);\n'; } - output.write(')'); return output; }).join(', ')); - factory.write('),\n'); - if (!skip) { - factories[chunk].write(factory); - } - }); - StringBuffer code = new StringBuffer(); - String libSuffix = chunk.library == null ? '' : '.${chunk.library.name}'; - code.write('library di.generated.type_factories$libSuffix;\n'); - requiredImports.forEach((import) { - String prefix = _calculateImportPrefix(import, imports); - code.write ('import "$import" as $prefix;\n'); - }); - code..write('var typeFactories = {\n${factories[chunk]}\n};\n') - ..write('main() {}\n'); - result[chunk] = code.toString(); + } + paramList.write('],\n'); + if (!skip) { + factoryKeys.forEach((key) { + var keyString = toBeAdded.remove(key); + keys.write(keyString); + }); + factories.write(factory); + paramLists.write(paramList); + } }); - - return result; + keys.writeAll(toBeAdded.values); + toBeAdded.clear(); } -String _calculateImportPrefix(String import, List imports) => - 'import_${imports.indexOf(import)}'; - _isParameterized(ParameterElement param) { String typeName = param.type.toString(); if (typeName.indexOf('<') > -1) { String parameters = - typeName.substring(typeName.indexOf('<') + 1, typeName.length - 1); + typeName.substring(typeName.indexOf('<') + 1, typeName.length - 1); return parameters.split(', ').any((p) => p != 'dynamic'); } return false; } + +String _calculateImportPrefix(String import, List imports) => + 'import_${imports.indexOf(import)}'; + class CompilationUnitVisitor { List imports; Map typeToImport; diff --git a/lib/key.dart b/lib/key.dart index d488212..2b61de3 100644 --- a/lib/key.dart +++ b/lib/key.dart @@ -1,10 +1,14 @@ library di.key; +import 'dart:collection'; + /** * Key to which an [Injector] binds a [Provider]. This is a pair consisting of * a [type] and an optional [annotation]. */ class Key { + // TODO: experiment with having a separate map for non-annotated types (perf) + // While Map.identity is faster here, it's not supported in dart2js (dart issue 19622 wontfix) static Map> _typeToAnnotationToKey = {}; static int _numInstances = 0; /// The number of instances of [Key] created. @@ -16,6 +20,18 @@ class Key { /// Assigned via auto-increment. final int id; + int _data; + @deprecated + int get uid => _data; + @deprecated + set uid(int d) { + if (_data == null) { + _data = d; + return; + } + throw "Key($type).uid has already been set to $_data."; + } + int get hashCode => id; /** @@ -26,7 +42,7 @@ class Key { // Don't use Map.putIfAbsent -- too slow! var annotationToKey = _typeToAnnotationToKey[type]; if (annotationToKey == null) { - _typeToAnnotationToKey[type] = annotationToKey = {}; + _typeToAnnotationToKey[type] = annotationToKey = new Map(); } Key key = annotationToKey[annotation]; if (key == null) { @@ -46,3 +62,6 @@ class Key { return asString; } } + +/// shortcut function +Key key(Type type, [Type annotation]) => new Key(type, annotation); diff --git a/lib/module_transformer.dart b/lib/module_transformer.dart new file mode 100644 index 0000000..d0ec8a7 --- /dev/null +++ b/lib/module_transformer.dart @@ -0,0 +1,40 @@ +library di.transformer.export_transformer; + +import 'dart:async'; +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/element.dart'; +import 'package:barback/barback.dart'; +import 'package:code_transformers/resolver.dart'; +import 'package:path/path.dart' as path; + +/** + * Pub transformer that changes reflector in Module to null instead of importing + * the dynamic reflector which imports mirrors. Another di transformer run by the app + * will import the static reflector. + */ +class ModuleTransformerGroup implements TransformerGroup { + final Iterable phases; + + ModuleTransformerGroup.asPlugin(BarbackSettings settings) + : phases = [[new ModuleTransformer()]]; +} + +class ModuleTransformer extends Transformer { + + ModuleTransformer(); + + isPrimary(AssetId id) { + return new Future.value(id == new AssetId.parse("di|lib/src/module.dart")); + } + + Future apply(Transform transform) { + var id = transform.primaryInput.id; + return transform.primaryInput.readAsString().then((code) { + // Note: this rewrite is coupled with how module.dart is + // written. Make sure both are updated in sync. + transform.addOutput(new Asset.fromString(id, code + .replaceAll(new RegExp('import "reflector_dynamic.dart";'), + 'import "reflector_null.dart";'))); + }); + } +} diff --git a/lib/src/base_injector.dart b/lib/src/base_injector.dart deleted file mode 100644 index fd8fe4d..0000000 --- a/lib/src/base_injector.dart +++ /dev/null @@ -1,258 +0,0 @@ -library di.base_injector; - -import 'provider.dart'; -import 'error_helper.dart'; - -import 'package:collection/collection.dart'; -import 'package:di/di.dart'; -import 'package:di/key.dart'; - -List _PRIMITIVE_TYPES = new UnmodifiableListView([ - new Key(num), new Key(int), new Key(double), new Key(String), - new Key(bool) -]); - -abstract class BaseInjector implements Injector, ObjectFactory { - - @override - final String name; - - @override - final BaseInjector parent; - - Injector _root; - - List _providers; - - /** - * _instances is a List when implicit injection is not allowed - * for performance because all types would have been seen and the - * List would not need to be expanded. In dynamic injection with - * implicit injection turned on it is a Map instead. - */ - List _instancesList; - Map _instancesMap; - - @override - final bool allowImplicitInjection; - - Iterable _typesCache; - - Iterable get _types { - if (_providers == null) return []; - - if (_typesCache == null) { - _typesCache = _providers - .where((p) => p != null) - .map((p) => p.type); - } - return _typesCache; - } - - BaseInjector({List modules, String name, - bool allowImplicitInjection: false}) - : this.fromParent(modules, null, - name: name, allowImplicitInjection: allowImplicitInjection); - - BaseInjector.fromParent(List modules, - BaseInjector this.parent, {this.name, this.allowImplicitInjection: false}) { - _root = parent == null ? this : parent._root; - var injectorId = new Key(Injector).id; - _providers = new List(Key.numInstances); - - if (allowImplicitInjection) { - _instancesMap = new Map(); - } else { - _instancesList = new List(Key.numInstances); - } - if (modules != null) { - modules.forEach((module) { - module.bindings.forEach((k, v) { - _providers[k] = v; - }); - }); - } - _providers[injectorId] = new ValueProvider(Injector, this); - } - - @override - Injector get root => _root; - - @override - Set get types { - var types = new Set(); - for (var node = this; node != null; node = node.parent) { - types.addAll(node._types); - } - return types; - } - - @override - Object getInstanceByKey(Key key, Injector requester, ResolutionContext resolving) { - assert(_checkKeyConditions(key, resolving)); - - // Do not bother checking the array until we are fairly deep. - if (resolving.depth > 30 && resolving.ancestorKeys.contains(key)) { - throw new CircularDependencyError( - error(resolving, 'Cannot resolve a circular dependency!', key)); - } - - var providerWithInjector = _getProviderWithInjectorForKey(key, resolving); - var provider = providerWithInjector.provider; - var injector = providerWithInjector.injector; - var visible = provider.visibility == null || - provider.visibility(requester, injector); - - assert(allowImplicitInjection || key.id < _instancesList.length); - if (visible){ - var instance = allowImplicitInjection ? - _instancesMap[key.id] : _instancesList[key.id]; - if (instance != null){ - return instance; - } - } - - if (injector != this || !visible) { - if (!visible) { - if (injector.parent == null) { - throw new NoProviderError( - error(resolving, 'No provider found for ${key}!', key)); - } - injector = injector.parent - ._getProviderWithInjectorForKey(key, resolving).injector; - } - return injector.getInstanceByKey(key, requester, resolving); - } - - resolving = new ResolutionContext(resolving.depth + 1, key, resolving); - var value = provider.get(this, requester, this, resolving); - - // cache the value. - if (allowImplicitInjection == true) { - providerWithInjector.injector._instancesMap[key.id] = value; - } else { - providerWithInjector.injector._instancesList[key.id] = value; - } - return value; - } - - /** - * Finds the nearest ancestor injector that binds a [Provider] to [key] and - * returns that [Provider] and its binding [Injector]. If there is no such - * [Injector], then - * - * - if [allowImplicitInjection] is true for the root injector (not this - * injector), returns a default [Provider] and the root injector. - * - if [allowImplicitInjector] is false for the root injector, throws - * [NoProviderError]. - * - * [resolving] is only used for error reporting. - */ - _ProviderWithInjector _getProviderWithInjectorForKey( - Key key, ResolutionContext resolving) { - if (key.id < _providers.length) { - var provider = _providers[key.id]; - if (provider != null) { - return new _ProviderWithInjector(provider, this); - } - } - if (parent != null) { - return parent._getProviderWithInjectorForKey(key, resolving); - } - if (allowImplicitInjection) { - return new _ProviderWithInjector(new TypeProvider(key.type), this); - } - throw new NoProviderError( - error(resolving, 'No provider found for ${key}!', key)); - } - - bool _checkKeyConditions(Key key, ResolutionContext resolving) { - if (_PRIMITIVE_TYPES.contains(key)) { - throw new NoProviderError( - error(resolving, - 'Cannot inject a primitive type of ${key.type}!', key)); - } - return true; - } - - @override - dynamic get(Type type, [Type annotation]) => - getInstanceByKey(new Key(type, annotation), this, ResolutionContext.ROOT); - - @override - dynamic getByKey(Key key) => - getInstanceByKey(key, this, ResolutionContext.ROOT); - - @override - Injector createChild(List modules, - {List forceNewInstances, String name}) => - createChildWithResolvingHistory(modules, ResolutionContext.ROOT, - forceNewInstances: forceNewInstances, - name: name); - - Injector createChildWithResolvingHistory( - List modules, - ResolutionContext resolving, - {List forceNewInstances, String name}) { - if (forceNewInstances != null) { - Module forceNew = new Module(); - forceNewInstances.forEach((key) { - if (key is Type) { - key = new Key(key); - } else if (key is! Key) { - throw 'forceNewInstances must be List'; - } - var providerWithInjector = - _getProviderWithInjectorForKey(key, resolving); - var provider = providerWithInjector.provider; - forceNew.bindByKey(key, - toFactory: (Injector inj) => - provider.get(this, inj, inj as ObjectFactory, resolving), - visibility: provider.visibility); - }); - - modules = modules.toList(); // clone - modules.add(forceNew); - } - - return newFromParent(modules, name); - } - - newFromParent(List modules, String name); - - Object newInstanceOf(Type type, ObjectFactory factory, Injector requestor, - ResolutionContext resolving); -} - -class _ProviderWithInjector { - final Provider provider; - final BaseInjector injector; - _ProviderWithInjector(this.provider, this.injector); -} - -/** - * A node in a depth-first search tree of the dependency DAG. - */ -class ResolutionContext { - static final ResolutionContext ROOT = new ResolutionContext(0, null, null); - - /// Distance from the [ROOT]. - final int depth; - /// Key at this node or null if this is the [ROOT]. - final Key key; - /// Parent node or null if this is the [ROOT]. This node is a dependency of - /// the parent. - final ResolutionContext parent; - - ResolutionContext(this.depth, this.key, this.parent); - - /// Returns the [key]s of the ancestors of this node (including this node) in - /// the order that ascends the tree. Note that [ROOT] has no [key]. - List get ancestorKeys { - var keys = []; - for (var node = this; node.parent != null; node = node.parent) { - keys.add(node.key); - } - return keys; - } -} diff --git a/lib/src/error_helper.dart b/lib/src/error_helper.dart deleted file mode 100644 index bacde95..0000000 --- a/lib/src/error_helper.dart +++ /dev/null @@ -1,31 +0,0 @@ -library di.error_helper; - -import 'package:di/di.dart'; -import 'package:di/src/base_injector.dart'; - -/** - * Returns an error message for the given dependency chain. - * If [addDependency] is given, its [String] representation gets appended - * to the error message. - * - * Example: - * If [resolving]'s ancestors have keys k1, k2, k3 and [message] is 'foo', - * then this looks like 'foo (resolving k3 -> k2 -> k1)'. - */ -String error(ResolutionContext resolving, String message, [Key appendDependency]) { - if (appendDependency != null) { - resolving = new ResolutionContext(resolving.depth + 1, appendDependency, resolving); - } - List resolvingKeys = resolving.ancestorKeys.reversed.toList(growable: false); - // De-duplicate keys when there is a circular dependency - // This is required because we don't check for circular dependencies before the - // a depth threshold which would lead to msg like "A -> B -> A -> B -> ... -> A" - for (var i = 1; i < resolvingKeys.length - 1; i++) { - if (resolvingKeys[i] == resolvingKeys.first) { - resolvingKeys = resolvingKeys.sublist(0, i + 1); - break; - } - } - - return '$message (resolving ${resolvingKeys.join(" -> ")})'; -} diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 4b78a83..5cf9347 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -1,13 +1,68 @@ -part of di; +library di.errors; -class InvalidBindingError extends ArgumentError { - InvalidBindingError(message) : super(message); +import '../key.dart'; + +abstract class BaseError extends Error { + final String message; + String toString() => message; + BaseError(this.message); +} + +class DynamicReflectorError extends BaseError { + DynamicReflectorError(message) : super(message); +} + +abstract class ResolvingError extends Error { + + List keys; + ResolvingError(key): keys = [key]; + + String get resolveChain { + var keysToPrint = []; + var seenKeys = new Set(); + for (Key key in keys.reversed) { + keysToPrint.add(key); + if (!seenKeys.add(key)) break; + } + + StringBuffer buffer = new StringBuffer(); + buffer.write("(resolving "); + buffer.write(keysToPrint.join(' -> ')); + buffer.write(")"); + return buffer.toString(); + } + + void appendKey(Key key) { + keys.add(key); + } + + String toString(); +} + +class NoProviderError extends ResolvingError { + static final List _PRIMITIVE_TYPES = [ + new Key(num), new Key(int), new Key(double), new Key(String), + new Key(bool) + ]; + + String toString(){ + var root = keys.first; + if (_PRIMITIVE_TYPES.contains(root)) { + return 'Cannot inject a primitive type of $root! $resolveChain'; + } + return "No provider found for $root! $resolveChain"; + } + NoProviderError(key): super(key); } -class NoProviderError extends ArgumentError { - NoProviderError(message) : super(message); +class CircularDependencyError extends ResolvingError { + String toString() => "Cannot resolve a circular dependency! $resolveChain"; + CircularDependencyError(key) : super(key); } -class CircularDependencyError extends ArgumentError { - CircularDependencyError(message) : super(message); +class NoGeneratedTypeFactoryError extends BaseError { + NoGeneratedTypeFactoryError(Type type): super(type.toString()); + String toString() => + "Type '$message' not found in generated typeFactory maps. Is the type's" + "constructor annotated for injection?"; } diff --git a/lib/src/injector.dart b/lib/src/injector.dart index 1a6228e..c89840e 100644 --- a/lib/src/injector.dart +++ b/lib/src/injector.dart @@ -1,84 +1,180 @@ -part of di; +library di.injector; -abstract class Injector { - /** - * Name of the injector or null if none was given. - */ - String get name; +import '../key.dart'; +import 'module.dart'; +import 'errors.dart'; - /** - * The parent injector or null if root. - */ - Injector get parent; +Key _INJECTOR_KEY = new Key(Injector); - /** - * The root injector. - */ - Injector get root; - - /** - * [Type]s of the [Provider]s explicitly bound to the injector or an ancestor. - * If the root injector sets [allowImplicitInjection] to false, then this - * is simply all the types that the injector can return. - */ - Set get types; +abstract class Injector { /** - * Whether the injector allows injecting a type to which no [Provider] is - * bound. Note that this setting only matters for the root injector. + * The parent injector. */ - bool get allowImplicitInjection; + final Injector parent = null; /** * Returns the instance associated with the given key (i.e. [type] and * [annotation]) according to the following rules. * - * Let I be the nearest ancestor injector (possibly this one) that both - * - * - binds some [Provider] P to [key] and - * - P's visibility declares that I is visible to this injector. + * Let I be the nearest ancestor injector (possibly this one) + * that has either a cached instance or a binding for [key] * - * If there is no such I, then - * - * - if [allowImplicitInjection] is true for the root injector, let I be the - * root injector and P be a default [Provider] for [type]. - * - if [allowImplicitInjection] is false for the root injector, throw + * If there is no such I, then throw * [NoProviderError]. * - * Once I and P are found, if I already created an instance for the key, - * it is returned. Otherwise, P is used to create an instance, using I - * as an [ObjectFactory] to resolve the necessary dependencies. + * Once I is found, if I already created an instance for the key, + * it is returned. Otherwise, the typeFactory of the binding is + * used to create an instance, using I as to resolve the dependencies. */ - dynamic get(Type type, [Type annotation]); + dynamic get(Type type, [Type annotation]) + => getByKey(new Key(type, annotation)); /** - * See [get]. + * Faster version of [get]. */ - dynamic getByKey(Key key); + dynamic getByKey(Key key, [int depth]); /** * Creates a child injector. * * [modules] overrides bindings of the parent. - * - * [forceNewInstances] is a list, each element of which is a [Key] or a - * [Type] (for convenience when no annotation is needed). For each element K, - * the child injector will have a new binding to the same [Provider] P that - * the parent injector would have provided, that is, P is the [Provider] of - * the nearest ancestor of the parent injector that binds K. Note that this - * differs from how [get] finds P in that visibility is not taken into - * account. - * - * Thus, if a descendant D of the child requests an instance for K, the child - * will mask any binding for K made by a proper ancestor injector, provided - * that P's visibility reveals the child's binding to D. - * For example, if the child has no proper descendant and P's visibility - * deems that the child is visible to the child itself, then the first - * request for the child to get an instance for K will trigger the creation of - * a new instance. - * - * [name] is used for error reporting. */ - Injector createChild(List modules, - {List forceNewInstances, String name}); + @deprecated + Injector createChild(List modules); +} + + +/** + * The RootInjector serves as an alternative to having a null parent for + * injectors that have no parent. This allows us to bypass checking for a null + * parent, and instead RootInjector will start the exception chain that reports + * the resolution context tree when no providers are found. + */ +class RootInjector extends Injector { + Injector get parent => null; + List get _instances => null; + dynamic getByKey(key, [depth]) => throw new NoProviderError(key); + Injector createChild(m) => null; + RootInjector(); +} + +class ModuleInjector extends Injector { + + static final rootInjector = new RootInjector(); + final Injector parent; + String name; + + List _bindings; + List _instances; + + ModuleInjector(List modules, [Injector parent]) + : parent = parent == null ? rootInjector : parent, + _bindings = new List(Key.numInstances + 1), // + 1 for injector itself + _instances = new List(Key.numInstances + 1) { + + if (modules != null) { + modules.forEach((module) { + module.bindings.forEach((Key key, Binding binding) + => _bindings[key.id] = binding); + }); + } + _instances[_INJECTOR_KEY.id] = this; + } + + Iterable _typesCache; + + Iterable get _types { + if (_bindings == null) return []; + + if (_typesCache == null) { + _typesCache = _bindings + .where((p) => p != null) + .map((p) => p.key.type); + } + return _typesCache; + } + + Set get types { + var types = new Set(); + for (var node = this; node.parent != null; node = node.parent) { + types.addAll(node._types); + } + types.add(Injector); + return types; + } + + dynamic getByKey(Key key, [int depth = 0]){ + var id = key.id; + if (id < _instances.length) { + var instance = _instances[id]; + if (instance != null) return instance; + + Binding binding = _bindings[id]; + if (binding != null) { + if (depth > 42) throw new CircularDependencyError(key); + try { + var paramKeys = binding.parameterKeys; + var length = paramKeys.length; + var factory = binding.factory; + + if (length > 15) { + var params = new List(length); + for (var i = 0; i < length; i++) { + params[i] = getByKey(paramKeys[i], depth + 1); + } + return _instances[id] = Function.apply(factory, params); + } + + var a1 = length >= 1 ? getByKey(paramKeys[0], depth + 1) : null; + var a2 = length >= 2 ? getByKey(paramKeys[1], depth + 1) : null; + var a3 = length >= 3 ? getByKey(paramKeys[2], depth + 1) : null; + var a4 = length >= 4 ? getByKey(paramKeys[3], depth + 1) : null; + var a5 = length >= 5 ? getByKey(paramKeys[4], depth + 1) : null; + var a6 = length >= 6 ? getByKey(paramKeys[5], depth + 1) : null; + var a7 = length >= 7 ? getByKey(paramKeys[6], depth + 1) : null; + var a8 = length >= 8 ? getByKey(paramKeys[7], depth + 1) : null; + var a9 = length >= 9 ? getByKey(paramKeys[8], depth + 1) : null; + var a10 = length >= 10 ? getByKey(paramKeys[9], depth + 1) : null; + var a11 = length >= 11 ? getByKey(paramKeys[10], depth + 1) : null; + var a12 = length >= 12 ? getByKey(paramKeys[11], depth + 1) : null; + var a13 = length >= 13 ? getByKey(paramKeys[12], depth + 1) : null; + var a14 = length >= 14 ? getByKey(paramKeys[13], depth + 1) : null; + var a15 = length >= 15 ? getByKey(paramKeys[14], depth + 1) : null; + + switch (length) { + case 0: return _instances[id] = factory(); + case 1: return _instances[id] = factory(a1); + case 2: return _instances[id] = factory(a1, a2); + case 3: return _instances[id] = factory(a1, a2, a3); + case 4: return _instances[id] = factory(a1, a2, a3, a4); + case 5: return _instances[id] = factory(a1, a2, a3, a4, a5); + case 6: return _instances[id] = factory(a1, a2, a3, a4, a5, a6); + case 7: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7); + case 8: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8); + case 9: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9); + case 10: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); + case 11: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); + case 12: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); + case 13: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); + case 14: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); + case 15: return _instances[id] = factory(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); + } + } on ResolvingError catch (e) { + e.appendKey(key); + rethrow; // to preserve stack trace + } + } + } + // recursion instead of iteration because it: + // 1. tracks key history on the stack for error reporting + // 2. allows different types of ancestor injectors with alternative implementations. + // An alternative could be to recurse only when parent is not a ModuleInjector + return _instances[id] = parent.getByKey(key); + } + + @deprecated + Injector createChild(List modules) { + return new ModuleInjector(modules,this); + } } diff --git a/lib/src/injector_delagate.dart b/lib/src/injector_delagate.dart deleted file mode 100644 index c05b3ca..0000000 --- a/lib/src/injector_delagate.dart +++ /dev/null @@ -1,63 +0,0 @@ -library di.injector_delegate; - -import 'base_injector.dart'; -import 'provider.dart'; -import 'package:di/di.dart'; - -class InjectorDelagate implements BaseInjector, ObjectFactory { - BaseInjector _injector; - ResolutionContext _resolving; - - InjectorDelagate(this._injector, this._resolving); - - @override - bool get allowImplicitInjection => _injector.allowImplicitInjection; - - @override - String get name => _injector.name; - - @override - Injector get root => _injector.root; - - @override - Set get types => _injector.types; - - @override - Injector get parent => _injector.parent; - - @override - dynamic get(Type type, [Type annotation]) => - _injector.getInstanceByKey(new Key(type, annotation), this, _resolving); - - @override - dynamic getInstanceByKey(Key key, Injector requester, ResolutionContext resolving) => - _injector.getInstanceByKey(key, requester, resolving); - - @override - dynamic getByKey(Key key) => - _injector.getInstanceByKey(key, this, _resolving); - - @override - Injector createChild(List modules, - {List forceNewInstances, String name}) => - _injector.createChildWithResolvingHistory(modules, _resolving, - forceNewInstances: forceNewInstances, - name: name); - - @override - newFromParent(List modules, String name) => - _injector.newFromParent(modules, name); - - @override - Object newInstanceOf(Type type, ObjectFactory factory, - Injector requestor, ResolutionContext resolving) => - _injector.newInstanceOf(type, factory, requestor, resolving); - - @override - Injector createChildWithResolvingHistory( - List modules, ResolutionContext resolving, - {List forceNewInstances, String name}) { - throw new UnsupportedError( - 'can\'t call createChildWithResolvingHistory on delegate'); - } -} diff --git a/lib/src/module.dart b/lib/src/module.dart index 020ea8d..2c4bc57 100644 --- a/lib/src/module.dart +++ b/lib/src/module.dart @@ -1,21 +1,49 @@ -part of di; +library di.module; -_DEFAULT_VALUE(_) => null; +import "../key.dart"; +import "../check_bind_args.dart" show checkBindArgs; +import "reflector.dart"; +import "reflector_dynamic.dart"; -typedef dynamic FactoryFn(Injector injector); +DEFAULT_VALUE(_) => null; +IDENTITY(p) => p; -/** - * If owned by a [Provider] P bound by the [defining] injector, then this - * returns whether P is visible to the [requesting] injector. - * See [Injector.get]. - */ -typedef bool Visibility(Injector requesting, Injector defining); +class Binding { + Key key; + List parameterKeys; + Function factory; -/** - * Produces an instance of some type, provided [factory] produces instances of - * the dependencies that type. - */ -typedef Object TypeFactory(factory(Type type, Type annotation)); + Binding(); + + void bind(k, TypeReflector reflector, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, Type toImplementation, + List inject: const[]}) { + key = k; + if (inject.length == 1 && isNotSet(toFactory)) { + toFactory = IDENTITY; + } + assert(checkBindArgs(toValue, toFactory, toImplementation, inject)); + + if (isSet(toValue)) { + factory = () => toValue; + parameterKeys = const []; + } else if (isSet(toFactory)) { + factory = toFactory; + parameterKeys = inject.map((t) { + if (t is Key) return t; + if (t is Type) return new Key(t); + throw "inject must be Keys or Types. '$t' is not an instance of Key or Type."; + }).toList(growable: false); + } else { + var implementationType = toImplementation == null ? key.type : toImplementation; + parameterKeys = reflector.parameterKeysFor(implementationType); + factory = reflector.factoryFor(implementationType); + } + } +} + +bool isSet(val) => !identical(val, DEFAULT_VALUE); +bool isNotSet(val) => identical(val, DEFAULT_VALUE); /** * Module contributes configuration information to an [Injector] by providing @@ -26,40 +54,18 @@ typedef Object TypeFactory(factory(Type type, Type annotation)); * no effect on that injector. */ class Module { - final _providers = {}; - final _childModules = []; - Map _typeFactories = {}; + static TypeReflector DEFAULT_REFLECTOR = getReflector(); + final TypeReflector reflector; - Map get typeFactories { - if (_childModules.isEmpty) return _typeFactories; + Module(): reflector = DEFAULT_REFLECTOR; + Module.withReflector(this.reflector); - var factories = new Map.from(_typeFactories); - _childModules.forEach((m) { - if (m.typeFactories != null) { - factories.addAll(m.typeFactories); - } - }); - return factories; - } - - set typeFactories(Map factories) { - _typeFactories = factories; - } - - Map _providersCache; + Map bindings = new Map(); /** - * Compiles and returns a map of type bindings by performing depth-first - * traversal of the child (installed) modules. + * Copies all bindings of [module] into this one. Overwriting when conflicts are found. */ - Map get bindings { - if (_isDirty) { - _providersCache = {}; - _childModules.forEach((child) => _providersCache.addAll(child.bindings)); - _providersCache.addAll(_providers); - } - return _providersCache; - } + install(Module module) => module.bindings.forEach((key, binding) => bindings[key] = binding); /** * Registers a binding for a given [type]. @@ -74,113 +80,27 @@ class Module { * be injected. * * [toValue]: The given value will be injected. * * [withAnnotation]: Type decorated with additional annotation. - * * [visibility]: Function which determines if the requesting injector can - * see the type in the current injector. * * Up to one (0 or 1) of the following parameters can be specified at the * same time: [toImplementation], [toFactory], [toValue]. */ - void bind(Type type, {dynamic toValue: _DEFAULT_VALUE, - FactoryFn toFactory: _DEFAULT_VALUE, Type toImplementation, - Type withAnnotation, Visibility visibility}) { + void bind(Type type, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, Type toImplementation, + List inject: const [], Type withAnnotation}) { bindByKey(new Key(type, withAnnotation), toValue: toValue, - toFactory: toFactory, toImplementation: toImplementation, - visibility: visibility); + toFactory: toFactory, toImplementation: toImplementation, inject: inject); } /** * Same as [bind] except it takes [Key] instead of - * [Type] [withAnnotation] combination. - */ - void bindByKey(Key key, {dynamic toValue: _DEFAULT_VALUE, - FactoryFn toFactory: _DEFAULT_VALUE, Type toImplementation, - Visibility visibility}) { - _checkBindArgs(toValue, toFactory, toImplementation); - _dirty(); - if (!identical(toValue, _DEFAULT_VALUE)) { - _providers[key.id] = new ValueProvider(key.type, toValue, visibility); - } else if (!identical(toFactory, _DEFAULT_VALUE)) { - _providers[key.id] = new FactoryProvider(key.type, toFactory, visibility); - } else { - _providers[key.id] = new TypeProvider( - toImplementation == null ? key.type : toImplementation, visibility); - } - } - - _checkBindArgs(toValue, toFactory, toImplementation) { - int count = 0; - if (!identical(toValue, _DEFAULT_VALUE)) count++; - if (!identical(toFactory, _DEFAULT_VALUE)) count++; - if (toImplementation != null) count++; - if (count > 1) { - throw 'Only one of following parameters can be specified: ' - 'toValue, toFactory, toImplementation'; - } - return true; - } - - /** - * Register a binding to a concrete value. - * - * The [value] is what actually will be injected. + * [Type] [withAnnotation] combination. Faster. */ - @Deprecated("Use bind(type, toValue: value)") - void value(Type id, value, {Type withAnnotation, Visibility visibility}) { - bind(id, toValue: value, withAnnotation: withAnnotation, - visibility: visibility); - } + void bindByKey(Key key, {dynamic toValue: DEFAULT_VALUE, + Function toFactory: DEFAULT_VALUE, List inject: const [], Type toImplementation}) { - /** - * Registers a binding for a [Type]. - * - * The default behavior is to simply instantiate the type. - * - * The following parameters can be specified: - * - * * [withAnnotation]: Type decorated with additional annotation. - * * [implementedBy]: The type will be instantiated using the [new] operator - * and the resulting instance will be injected. If no type is provided, - * then it's implied that [type] should be instantiated. - * * [visibility]: Function which determines fi the requesting injector can - * see the type in the current injector. - */ - @Deprecated("Use bind(type, implementedBy: impl)") - void type(Type type, {Type withAnnotation, Type implementedBy, Visibility visibility}) { - bind(type, withAnnotation: withAnnotation, visibility: visibility, - toImplementation: implementedBy); + var binding = new Binding(); + binding.bind(key, reflector, toValue: toValue, toFactory: toFactory, + toImplementation: toImplementation, inject: inject); + bindings[key] = binding; } - - /** - * Register a binding to a factory function. - * - * The [factoryFn] will be called and the result of that function is the value - * that will be injected. - */ - @Deprecated("Use bind(type, toFactory: factory)") - void factory(Type id, FactoryFn factoryFn, {Type withAnnotation, - Visibility visibility}) { - bind(id, withAnnotation: withAnnotation, visibility: visibility, - toFactory: factoryFn); - } - - @Deprecated("Use bindByKey(type, toFactory: factory)") - void factoryByKey(Key key, FactoryFn factoryFn, {Visibility visibility}) { - bindByKey(key, visibility: visibility, toFactory: factoryFn); - } - - /** - * Installs another module into this module. Bindings defined on this module - * take precidence over the installed module. - */ - void install(Module module) { - _childModules.add(module); - _dirty(); - } - - _dirty() { - _providersCache = null; - } - - bool get _isDirty => - _providersCache == null || _childModules.any((m) => m._isDirty); } diff --git a/lib/src/provider.dart b/lib/src/provider.dart deleted file mode 100644 index 662f197..0000000 --- a/lib/src/provider.dart +++ /dev/null @@ -1,51 +0,0 @@ -library di.provider; - -import 'injector_delagate.dart'; -import 'base_injector.dart'; -import 'package:di/di.dart'; - -abstract class ObjectFactory { - Object getInstanceByKey(Key key, BaseInjector requester, ResolutionContext resolving); -} - -abstract class Provider { - final Visibility visibility; - final Type type; - - Provider(this.type, this.visibility); - - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving); -} - -class ValueProvider extends Provider { - dynamic value; - - ValueProvider(type, this.value, [Visibility visibility]) - : super(type, visibility); - - @override - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving) => value; -} - -class TypeProvider extends Provider { - TypeProvider(type, [Visibility visibility]) : super(type, visibility); - - @override - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving) => - injector.newInstanceOf(type, objFactory, requestor, resolving); -} - -class FactoryProvider extends Provider { - final Function factoryFn; - - FactoryProvider(type, this.factoryFn, [Visibility visibility]) - : super(type, visibility); - - @override - dynamic get(BaseInjector injector, BaseInjector requestor, - ObjectFactory objFactory, ResolutionContext resolving) => - factoryFn(new InjectorDelagate(injector, resolving)); -} diff --git a/lib/src/reflector.dart b/lib/src/reflector.dart new file mode 100644 index 0000000..d6684d6 --- /dev/null +++ b/lib/src/reflector.dart @@ -0,0 +1,34 @@ +library di.reflector; + +import "../key.dart"; +import "errors.dart"; +import "module.dart"; + +abstract class TypeReflector { + /** + * Returns a [Factory] that knows how to construct an instance of a type. + * + * This interface is type based because there is only one factory for each + * type, no matter what the annotations are. However, the parameters returned + * are keys because annotations matter in that case so the injector knows + * what to inject. This leads to some performance loss from type comparison + * and key creation in DynamicTypeFactories but TypeReflector should only be + * used during module binding. + */ + Function factoryFor(Type type); + + /** + * Returns keys of the items that must be injected into the corresponding + * Factory that TypeReflector.factoryFor returns. + */ + List parameterKeysFor(Type type); + + /** + * Adds these factories and parameterKeys to the reflector, so that future calls + * to factoryFor and parameterKeysFor will return these new values. + * + * Overwrites in static implementation, no-op in dynamic implementation + */ + void addAll(Map factories, Map> parameterKeys); + void add(Type type, Function factory, List parameterKeys); +} diff --git a/lib/src/reflector_dynamic.dart b/lib/src/reflector_dynamic.dart new file mode 100644 index 0000000..a6d8d73 --- /dev/null +++ b/lib/src/reflector_dynamic.dart @@ -0,0 +1,246 @@ +library di.reflector_dynamic; + +import '../di.dart'; +import 'mirrors.dart'; + +TypeReflector getReflector() => new DynamicTypeFactories(); + +class DynamicTypeFactories extends TypeReflector { + /// caches of results calculated from mirroring + final List _factories = new List(); + final List> _parameterKeys = new List>(); + static List lists = new List.generate(26, (i) => new List(i)); + + Function factoryFor(Type type) { + var key = new Key(type); + _resize(key.id); + Function factory = _factories[key.id]; + if (factory == null) { + factory = _factories[key.id] = _generateFactory(type); + } + return factory; + } + + List parameterKeysFor(Type type) { + var key = new Key(type); + _resize(key.id); + List parameterKeys = _parameterKeys[key.id]; + if (parameterKeys == null) { + parameterKeys = _parameterKeys[key.id] = _generateParameterKeys(type); + } + return parameterKeys; + } + + _resize(int maxId) { + if (_factories.length <= maxId) { + _factories.length = maxId + 1; + _parameterKeys.length = maxId + 1; + } + } + + Function _generateFactory(Type type) { + ClassMirror classMirror = _reflectClass(type); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + int length = ctor.parameters.length; + Function create = classMirror.newInstance; + Symbol name = ctor.constructorName; + Function factory; + if (length > 25) throw "Too many arguments in $name constructor for dynamic DI to handle :("; + List l = lists[length]; + switch (length) { + case 0: + return () { + return create(name, l).reflectee; + }; + case 1: + return (a1) { + l[0]=a1; + return create(name, l).reflectee; + }; + case 2: + return (a1, a2) { + l[0]=a1;l[1]=a2; + return create(name, l).reflectee; + }; + case 3: + return (a1, a2, a3) { + l[0]=a1;l[1]=a2;l[2]=a3; + return create(name, l).reflectee; + }; + case 4: + return (a1, a2, a3, a4) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4; + return create(name, l).reflectee; + }; + case 5: + return (a1, a2, a3, a4, a5) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5; + return create(name, l).reflectee; + }; + case 6: + return (a1, a2, a3, a4, a5, a6) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6; + return create(name, l).reflectee; + }; + case 7: + return (a1, a2, a3, a4, a5, a6, a7) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7; + return create(name, l).reflectee; + }; + case 8: + return (a1, a2, a3, a4, a5, a6, a7, a8) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8; + return create(name, l).reflectee; + }; + case 9: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9; + return create(name, l).reflectee; + }; + case 10: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10; + return create(name, l).reflectee; + }; + case 11: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11; + return create(name, l).reflectee; + }; + case 12: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12; + return create(name, l).reflectee; + }; + case 13: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13; + return create(name, l).reflectee; + }; + case 14: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14; + return create(name, l).reflectee; + }; + case 15: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15; + return create(name, l).reflectee; + }; + case 16: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16; + return create(name, l).reflectee; + }; + case 17: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17; + return create(name, l).reflectee; + }; + case 18: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18; + return create(name, l).reflectee; + }; + case 19: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19; + return create(name, l).reflectee; + }; + case 20: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20; + return create(name, l).reflectee; + }; + case 21: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21; + return create(name, l).reflectee; + }; + case 22: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22; + return create(name, l).reflectee; + }; + case 23: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23; + return create(name, l).reflectee; + }; + case 24: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23;l[23]=a24; + return create(name, l).reflectee; + }; + case 25: + return (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) { + l[0]=a1;l[1]=a2;l[2]=a3;l[3]=a4;l[4]=a5;l[5]=a6;l[6]=a7;l[7]=a8;l[8]=a9;l[9]=a10;l[10]=a11;l[11]=a12;l[12]=a13;l[13]=a14;l[14]=a15;l[15]=a16;l[16]=a17;l[17]=a18;l[18]=a19;l[19]=a20;l[20]=a21;l[21]=a22;l[22]=a23;l[23]=a24;l[24]=a25; + return create(name, l).reflectee; + }; + } + } + + List _generateParameterKeys(Type type) { + ClassMirror classMirror = _reflectClass(type); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + + return new List.generate(ctor.parameters.length, (int pos) { + ParameterMirror p = ctor.parameters[pos]; + if (p.type.qualifiedName == #dynamic) { + var name = MirrorSystem.getName(p.simpleName); + throw new DynamicReflectorError("Error getting params for '$type': " + "The '$name' parameter must be typed"); + } + if (p.type is TypedefMirror) { + throw new DynamicReflectorError("Typedef '${p.type}' in constructor " + "'${classMirror.simpleName}' is not supported."); + } + if (p.metadata.length > 1) { + throw new DynamicReflectorError( + "Constructor '${classMirror.simpleName}' parameter $pos of type " + "'${p.type}' can have only zero on one annotation, but it has " + "'${p.metadata}'."); + } + var pType = (p.type as ClassMirror).reflectedType; + var annotationType = p.metadata.isNotEmpty ? p.metadata.first.type.reflectedType : null; + return new Key(pType, annotationType); + }, growable:false); + } + + ClassMirror _reflectClass(Type type) { + ClassMirror classMirror = reflectType(type); + if (classMirror is TypedefMirror) { + throw new DynamicReflectorError('No implementation provided for ' + '${getSymbolName(classMirror.qualifiedName)} typedef!'); + } + + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + + if (ctor == null) { + throw new DynamicReflectorError('Unable to find default constructor for $type. ' + 'Make sure class has a default constructor.' + (1.0 is int ? + 'Make sure you have correctly configured @MirrorsUsed.' : '')); + } + return classMirror; + } + + addAll(Map factories, Map> parameterKeys) => null; + add(Type type, Function factory, List parameterKeys) => null; +} + +/// script for generating the giant switch statement +/*main() { + var args; + for (var i = 0; i <= 25; i++) { + print("case $i:"); + args = new List.generate(i, (j) => "a${j+1}").join(', '); + print("return ($args) {"); + var buffer = new StringBuffer(); + for (var j = 0; j < i; j++){ + buffer.write("l[$j]=a${j+1};"); + } + print(buffer); + print("return create(name, l).reflectee;"); + print("};"); + } +}*/ diff --git a/lib/src/reflector_null.dart b/lib/src/reflector_null.dart new file mode 100644 index 0000000..5a6d8df --- /dev/null +++ b/lib/src/reflector_null.dart @@ -0,0 +1,22 @@ +library di.reflector_null; + +import '../key.dart'; +import 'reflector.dart'; +import 'errors.dart'; + +TypeReflector getReflector() => new NullReflector(); + +class NullReflector extends TypeReflector { + factoryFor(Type type) => throw new NullReflectorError(); + parameterKeysFor(Type type) => throw new NullReflectorError(); + addAll(Map factories, Map> parameterKeys) => + throw new NullReflectorError(); + add(Type type, Function factory, List parameterKeys) => + throw new NullReflectorError(); +} + +class NullReflectorError extends BaseError { + NullReflectorError() + : super("Module.DEFAULT_REFLECTOR not initialized for dependency injection." + "http://goo.gl/XFXx9G"); +} diff --git a/lib/src/reflector_static.dart b/lib/src/reflector_static.dart new file mode 100644 index 0000000..3aab5ff --- /dev/null +++ b/lib/src/reflector_static.dart @@ -0,0 +1,38 @@ +library di.reflector_static; + +import '../di.dart'; + +class GeneratedTypeFactories extends TypeReflector { + + Map _factories; + Map> _parameterKeys; + + /** + * This constructor should be called by code generated by transformer in main + * with generated factories and paramKeys to initialize it. + */ + GeneratedTypeFactories(Map this._factories, + Map>this._parameterKeys); + + Function factoryFor(Type type) { + var keys = _factories[type]; + if (keys != null) return keys; + throw new NoGeneratedTypeFactoryError(type); + } + + List parameterKeysFor(Type type) { + var keys = _parameterKeys[type]; + if (keys != null) return keys; + throw new NoGeneratedTypeFactoryError(type); + } + + addAll(Map factories, Map> parameterKeys) { + _factories.addAll(factories); + _parameterKeys.addAll(parameterKeys); + } + + add(Type type, Function factory, List paramKeys) { + _factories[type] = factory; + _parameterKeys[type] = paramKeys; + } +} diff --git a/lib/static_injector.dart b/lib/static_injector.dart deleted file mode 100644 index fe0c6f3..0000000 --- a/lib/static_injector.dart +++ /dev/null @@ -1,64 +0,0 @@ -library di.static_injector; - -import 'di.dart'; -import 'src/error_helper.dart'; -import 'src/base_injector.dart'; -import 'src/provider.dart'; - -export 'annotations.dart'; -export 'di.dart'; - -/** - * Static implementation of [Injector] that uses type factories - */ -class StaticInjector extends BaseInjector { - Map typeFactories; - - StaticInjector({List modules, String name, - bool allowImplicitInjection: false, typeFactories}) - : super(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection) { - this.typeFactories = _extractTypeFactories(modules, typeFactories); - } - - StaticInjector._fromParent(List modules, Injector parent, {name}) - : super.fromParent(modules, parent, name: name) { - this.typeFactories = _extractTypeFactories(modules); - } - - newFromParent(List modules, String name) => - new StaticInjector._fromParent(modules, this, name: name); - - Object newInstanceOf(Type type, ObjectFactory objFactory, - Injector requestor, ResolutionContext resolving) { - TypeFactory typeFactory = _getFactory(type); - if (typeFactory == null) { - throw new NoProviderError( - error(resolving, 'No type factory provided for $type!')); - } - return typeFactory((type, [annotation]) => - objFactory.getInstanceByKey( - new Key(type, annotation), requestor, resolving)); - } - - TypeFactory _getFactory(Type key) { - var cursor = this; - while (cursor != null) { - if (cursor.typeFactories.containsKey(key)) { - return cursor.typeFactories[key]; - } - cursor = cursor.parent; - } - return null; - } -} - -Map _extractTypeFactories(List modules, - [Map initial = const {}]) { - if (modules == null || modules.isEmpty) return initial; - var factories = new Map.from(initial == null ? {} : initial); - modules.forEach((m) { - factories.addAll(m.typeFactories); - }); - return factories; -} diff --git a/lib/transformer.dart b/lib/transformer.dart index e7bb1ed..ac1e422 100644 --- a/lib/transformer.dart +++ b/lib/transformer.dart @@ -1,5 +1,23 @@ /** - * Transformer which generates type factories for static injection. + * Static injection transformer which generates, for each injectable type: + * + * - typeFactory: which is a closure (a1, a2...) => new Type(a1, a2...) where + * args are injected dependency instances, as specified by + * - paramKeys: List corresponding to the dependency needing to be injected + * in the positional arguments of the typeFactory. + * + * These two give an injector the information needed to construct an instance of a + * type without using mirrors. They are stored as a Map + * and outputted to a file [entry_point_name]_generated_type_factory_maps.dart. Multiple + * entry points (main functions) is not supported. + * + * The import in main is also modified to use import di_static.dart instead + * of di.dart, and imports module_dynamic.dart. + * + * All of the above is taken care of by the transformer. The user needs to call + * setupModuleTypeReflector in main, before any modules are initialized. User must also + * annotate types for the transformer to add them to the generated type factories file, + * in addition to enabling the transformer in pubspec.yaml. * * Types which are considered injectable can be annotated in the following ways: * @@ -51,12 +69,18 @@ library di.transformer; import 'dart:io'; +import 'dart:async'; +import 'package:analyzer/src/generated/ast.dart'; +import 'package:analyzer/src/generated/element.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; -import 'package:di/transformer/injector_generator.dart'; -import 'package:di/transformer/options.dart'; import 'package:path/path.dart' as path; +import 'package:source_maps/refactor.dart'; +import 'transformer/injector_generator.dart'; +import 'transformer/options.dart'; +export 'transformer/options.dart'; +export 'transformer/injector_generator.dart'; /** * The transformer, which will extract all classes being dependency injected @@ -66,10 +90,10 @@ class DependencyInjectorTransformerGroup implements TransformerGroup { final Iterable phases; DependencyInjectorTransformerGroup(TransformOptions options) - : phases = _createPhases(options); + : phases = _createPhases(options); DependencyInjectorTransformerGroup.asPlugin(BarbackSettings settings) - : this(_parseSettings(settings.configuration)); + : this(_parseSettings(settings.configuration)); } TransformOptions _parseSettings(Map args) { @@ -123,5 +147,7 @@ _readStringListValue(Map args, String name) { return results; } -List> _createPhases(TransformOptions options) => - [[new InjectorGenerator(options, new Resolvers(options.sdkDirectory))]]; +List> _createPhases(TransformOptions options) { + var resolvers = new Resolvers(options.sdkDirectory); + return [[new InjectorGenerator(options, resolvers)]]; +} diff --git a/lib/transformer/injector_generator.dart b/lib/transformer/injector_generator.dart index a6af126..5ebe47e 100644 --- a/lib/transformer/injector_generator.dart +++ b/lib/transformer/injector_generator.dart @@ -5,10 +5,8 @@ import 'package:analyzer/src/generated/ast.dart'; import 'package:analyzer/src/generated/element.dart'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; -import 'package:di/transformer/options.dart'; import 'package:path/path.dart' as path; - -import 'refactor.dart'; +import 'options.dart'; /** * Pub transformer which generates type factories for all injectable types @@ -21,7 +19,9 @@ class InjectorGenerator extends Transformer with ResolverTransformer { this.resolvers = resolvers; } - Future shouldApplyResolver(Asset asset) => options.isDartEntry(asset); + Future shouldApplyResolver(Asset asset) { + return options.isDartEntry(asset); + } applyResolver(Transform transform, Resolver resolver) => new _Processor(transform, resolver, options).process(); @@ -61,21 +61,18 @@ class _Processor { var id = transform.primaryInput.id; var outputFilename = '${path.url.basenameWithoutExtension(id.path)}' - '_static_injector.dart'; + '_generated_type_factory_maps.dart'; var outputPath = path.url.join(path.url.dirname(id.path), outputFilename); _generatedAssetId = new AssetId(id.package, outputPath); var constructors = _gatherConstructors(); + // generates typeFactory file var injectLibContents = _generateInjectLibrary(constructors); transform.addOutput( new Asset.fromString(_generatedAssetId, injectLibContents)); - transformIdentifiers(transform, resolver, - identifier: 'di.auto_injector.defaultInjector', - replacement: 'createStaticInjector', - importPrefix: 'generated_static_injector', - importUrl: outputFilename); + _editMain(); } /** Resolves the classes for the injectable annotations in the current AST. */ @@ -184,7 +181,7 @@ class _Processor { } /** - * Checks if the element is annotated with one of the known injectablee + * Checks if the element is annotated with one of the known injectable * annotations. */ bool _isElementAnnotated(Element e) { @@ -306,36 +303,56 @@ class _Processor { return '$prefix${type.name}'; } + var keysBuffer = new StringBuffer(); var factoriesBuffer = new StringBuffer(); + var paramsBuffer = new StringBuffer(); + var addedKeys = new Set(); for (var ctor in constructors) { var type = ctor.enclosingElement; var typeName = resolveClassName(type); - factoriesBuffer.write(' $typeName: (f) => new $typeName('); + + String args = new List.generate(ctor.parameters.length, (i) => 'a${i+1}').join(', '); + factoriesBuffer.write(' $typeName: ($args) => new $typeName($args),\n'); + + paramsBuffer.write(' $typeName: '); + paramsBuffer.write(ctor.parameters.length == 0 ? 'const[' : '['); var params = ctor.parameters.map((param) { var typeName = resolveClassName(param.type.element); - var annotations = []; + Iterable annotations = []; if (param.metadata.isNotEmpty) { annotations = param.metadata.map( - (item) => resolveClassName(item.element.returnType.element)); + (item) => item.element.returnType.element); } - var annotationsSuffix = - annotations.isNotEmpty ? ', ${annotations.first}' : ''; - return 'f($typeName$annotationsSuffix)'; + + var keyName = '_KEY_${param.type.name}' + + (annotations.isNotEmpty ? '_${annotations.first}' : ''); + if (addedKeys.add(keyName)) { + keysBuffer.writeln('final Key $keyName = new Key($typeName' + + (annotations.isNotEmpty ? ', ${resolveClassName(annotations.first)});' : ');')); + } + return keyName; }); - factoriesBuffer.write('${params.join(', ')}),\n'); + paramsBuffer.write('${params.join(', ')}],\n'); } var outputBuffer = new StringBuffer(); - _writeStaticInjectorHeader(transform.primaryInput.id, outputBuffer); + _writeHeader(transform.primaryInput.id, outputBuffer); usedLibs.forEach((lib) { if (lib.isDartCore) return; var uri = resolver.getImportUri(lib, from: _generatedAssetId); outputBuffer.write('import \'$uri\' as ${prefixes[lib]};\n'); }); - _writePreamble(outputBuffer); + outputBuffer.write('\n'); + outputBuffer.write(keysBuffer); + outputBuffer.write('final Map typeFactories = {\n'); outputBuffer.write(factoriesBuffer); - _writeFooter(outputBuffer); + outputBuffer.write('};\nfinal Map> parameterKeys = {\n'); + outputBuffer.write(paramsBuffer); + outputBuffer.write('};\n'); + outputBuffer.write('setStaticReflectorAsDefault() => ' + 'Module.DEFAULT_REFLECTOR = ' + 'new GeneratedTypeFactories(typeFactories, parameterKeys);\n'); return outputBuffer.toString(); } @@ -344,34 +361,48 @@ class _Processor { logger.warning(msg, asset: resolver.getSourceAssetId(element), span: resolver.getSourceSpan(element)); } + + /// import generated_type_factory_maps and call initialize + void _editMain() { + AssetId id = transform.primaryInput.id; + var lib = resolver.getLibrary(id); + var unit = lib.definingCompilationUnit.node; + var transaction = resolver.createTextEditTransaction(lib); + + var imports = unit.directives.where((d) => d is ImportDirective); + transaction.edit(imports.last.end, imports.last.end, '\nimport ' + "'${path.url.basenameWithoutExtension(id.path)}" + "_generated_type_factory_maps.dart' show setStaticReflectorAsDefault;"); + + FunctionExpression main = unit.declarations.where((d) => + d is FunctionDeclaration && d.name.toString() == 'main') + .first.functionExpression; + var body = main.body; + if (body is BlockFunctionBody) { + var location = body.beginToken.end; + transaction.edit(location, location, '\n setStaticReflectorAsDefault();'); + } else if (body is ExpressionFunctionBody) { + transaction.edit(body.beginToken.offset, body.endToken.end, + "{\n setStaticReflectorAsDefault();\n" + " return ${body.expression};\n}"); + } // EmptyFunctionBody can only appear as abstract methods and constructors. + + var printer = transaction.commit(); + var url = id.path.startsWith('lib/') ? + 'package:${id.package}/${id.path.substring(4)}' : id.path; + printer.build(url); + transform.addOutput(new Asset.fromString(id, printer.text)); + } } -void _writeStaticInjectorHeader(AssetId id, StringSink sink) { +void _writeHeader(AssetId id, StringSink sink) { var libName = path.withoutExtension(id.path).replaceAll('/', '.'); libName = libName.replaceAll('-', '_'); sink.write(''' -library ${id.package}.$libName.generated_static_injector; +library ${id.package}.$libName.generated_type_factory_maps; import 'package:di/di.dart'; -import 'package:di/static_injector.dart'; +import 'package:di/src/reflector_static.dart'; '''); } - -void _writePreamble(StringSink sink) { - sink.write(''' -Injector createStaticInjector({List modules, String name, - bool allowImplicitInjection: false}) => - new StaticInjector(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection, - typeFactories: factories); - -final Map factories = { -'''); -} - -void _writeFooter(StringSink sink) { - sink.write(''' -}; -'''); -} diff --git a/lib/transformer/options.dart b/lib/transformer/options.dart index a9d5b17..7fa5c7e 100644 --- a/lib/transformer/options.dart +++ b/lib/transformer/options.dart @@ -1,7 +1,6 @@ library di.transformer.options; import 'dart:async'; - import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; @@ -36,14 +35,14 @@ class TransformOptions { final String sdkDirectory; TransformOptions({EntryFilter entryFilter, String sdkDirectory, - List injectableAnnotations, List injectedTypes}) - : entryFilter = entryFilter != null ? entryFilter : isPossibleDartEntry, - sdkDirectory = sdkDirectory, - injectableAnnotations = - (injectableAnnotations != null ? injectableAnnotations : []) - ..addAll(DEFAULT_INJECTABLE_ANNOTATIONS), - injectedTypes = - new Set.from(injectedTypes != null ? injectedTypes : []) { + List injectableAnnotations, List injectedTypes}) + : entryFilter = entryFilter != null ? entryFilter : isPossibleDartEntry, + sdkDirectory = sdkDirectory, + injectableAnnotations = + (injectableAnnotations != null ? injectableAnnotations : []) + ..addAll(DEFAULT_INJECTABLE_ANNOTATIONS), + injectedTypes = + new Set.from(injectedTypes != null ? injectedTypes : []) { if (sdkDirectory == null) throw new ArgumentError('sdkDirectory must be provided.'); } diff --git a/lib/transformer/refactor.dart b/lib/transformer/refactor.dart deleted file mode 100644 index 8d5d70e..0000000 --- a/lib/transformer/refactor.dart +++ /dev/null @@ -1,136 +0,0 @@ -library di.transformers.refactor; - -import 'package:analyzer/src/generated/ast.dart'; -import 'package:analyzer/src/generated/element.dart'; -import 'package:barback/barback.dart'; -import 'package:code_transformers/resolver.dart'; -import 'package:source_maps/refactor.dart'; - - -/// Transforms all simple identifiers of [identifier] to be -/// [importPrefix].[replacement] in the entry point of the application. -/// -/// When the identifier is replaced, this function also adds a prefixed import -/// of the form `import "[importUrl]" as [importPrefix]`. -/// -/// This will resolve the full name of [identifier] and warn if it cannot be -/// resolved. This will only modify the main entry point of the application and -/// will not traverse into parts. -void transformIdentifiers(Transform transform, Resolver resolver, - {String identifier, String replacement, String importUrl, - String importPrefix}) { - - var identifierElement = resolver.getLibraryVariable(identifier); - if (identifierElement != null) { - identifierElement = identifierElement.getter; - } else { - identifierElement = resolver.getLibraryFunction(identifier); - } - - if (identifierElement == null) { - // TODO(blois) enable once on barback 0.12.0 - // transform.logger.fine('Unable to resolve $identifier, not ' - // 'transforming entry point.'); - transform.addOutput(transform.primaryInput); - return; - } - - var lib = resolver.getLibrary(transform.primaryInput.id); - var transaction = resolver.createTextEditTransaction(lib); - var unit = lib.definingCompilationUnit.node; - - unit.accept(new _IdentifierTransformer(transaction, identifierElement, - '$importPrefix.$replacement', transform.logger)); - - if (transaction.hasEdits) { - _addImport(transaction, unit, importUrl, importPrefix); - } - _commitTransaction(transaction, transform); -} - -/// Commits the transaction if there have been edits, otherwise just adds -/// the input as an output. -void _commitTransaction(TextEditTransaction transaction, Transform transform) { - var id = transform.primaryInput.id; - - if (transaction.hasEdits) { - var printer = transaction.commit(); - var url = id.path.startsWith('lib/') - ? 'package:${id.package}/${id.path.substring(4)}' : id.path; - printer.build(url); - transform.addOutput(new Asset.fromString(id, printer.text)); - } else { - // No modifications, so just pass the source through. - transform.addOutput(transform.primaryInput); - } -} - -/// Injects an import into the list of imports in the file. -void _addImport(TextEditTransaction transaction, CompilationUnit unit, - String uri, String prefix) { - var libDirective; - for (var directive in unit.directives) { - if (directive is ImportDirective) { - transaction.edit(directive.keyword.offset, directive.keyword.offset, - 'import \'$uri\' as $prefix;\n'); - return; - } else if (directive is LibraryDirective) { - libDirective = directive; - } - } - - // No imports, add after the library directive if there was one. - if (libDirective != null) { - transaction.edit(libDirective.endToken.offset + 2, - libDirective.endToken.offset + 2, - 'import \'$uri\' as $prefix;\n'); - } -} - -/// Vistior which changes every reference to a resolved element to a specific -/// string value. -class _IdentifierTransformer extends GeneralizingAstVisitor { - final TextEditTransaction transaction; - /// The element which all references to should be replaced. - final Element original; - /// The text which should replace [original]. - final String replacement; - /// The current logger. - final TransformLogger logger; - - _IdentifierTransformer(this.transaction, this.original, this.replacement, - this.logger); - - visitIdentifier(Identifier node) { - if (node.bestElement == original) { - transaction.edit(node.beginToken.offset, node.endToken.end, replacement); - return; - } - - super.visitIdentifier(node); - } - - // Top-level methods are not treated as prefixed identifiers, so handle those - // here. - visitMethodInvocation(MethodInvocation m) { - if (m.methodName.bestElement == original) { - if (m.target is SimpleIdentifier) { - // Include the prefix in the rename. - transaction.edit(m.target.beginToken.offset, m.methodName.endToken.end, - replacement); - } else { - transaction.edit(m.methodName.beginToken.offset, - m.methodName.endToken.end, replacement); - } - return; - } - super.visitMethodInvocation(m); - } - - // Skip the contents of imports/exports/parts - visitUriBasedDirective(ImportDirective d) {} - - visitPartDirective(PartDirective node) { - logger.warning('Not transforming code within ${node.uri}.'); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 6b48454..147a160 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,22 +1,22 @@ name: di -version: 1.0.0 +version: 2.0.0-alpha.10 authors: - Vojta Jina - Pavel Jbanov +- Anting Shen +- Misko Hevery description: Dependency Injection framework homepage: https://github.com/angular/di.dart environment: - sdk: '>=0.8.10' + sdk: '>=1.0.0' dependencies: - analyzer: '>=0.13.0 <0.14.0' - barback: '>=0.13.0 <0.14.0' - code_transformers: '>=0.1.3 <0.2.0' + analyzer: '>=0.15.0 <0.19.0' + barback: '>=0.13.0 <0.17.0' + code_transformers: '>=0.1.4+2 <0.2.0' path: ">=1.2.0 <2.0.0" dev_dependencies: benchmark_harness: ">=1.0.4 <2.0.0" browser: ">=0.10.0 <0.11.0" - guinness: ">=0.1.7 <0.2.0" - + guinness: ">=0.1.10 <0.2.0" transformers: -- di: - injectable_annotations: di.tests.InjectableTest +- di/module_transformer diff --git a/run-benchmarks.sh b/run-benchmarks.sh index 9dda711..26e8338 100755 --- a/run-benchmarks.sh +++ b/run-benchmarks.sh @@ -4,8 +4,10 @@ set -e BENCHMARKS="module_benchmark.dart dynamic_injector_benchmark.dart static_injector_benchmark.dart -instance_benchmark.dart" +instance_benchmark.dart +large_benchmark.dart" +dart class_gen.dart # run tests in dart for b in $BENCHMARKS @@ -15,9 +17,10 @@ done # run dart2js on tests mkdir -p out +echo "running dart2js" for b in $BENCHMARKS do - dart2js --minify benchmark/$b -o out/$b.js + dart2js --minify benchmark/$b -o out/$b.js > /dev/null done # run tests in node diff --git a/run-tests.sh b/run-tests.sh index 3341ae8..c01f660 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,33 +1,50 @@ - #!/bin/sh set -e -echo "run type factories generator for tests" +echo "Running generator..." ./test_tf_gen.sh -echo "run tests in dart" +echo "Running tests in Dart..." dart --checked test/main.dart -dart --checked test/generator_test.dart -dart --checked test/injector_generator_spec.dart - -echo "run dart2js on tests" +dart --checked test/transformer_test.dart + +# Example app test +echo "Building example..." +rm -rf example/build/ +cd example +pub_out=$(pub build | tee /dev/tty | grep -F "mirror" || : ) +cd .. +echo "--------" + +if [[ -n $pub_out ]] +then + echo "FAIL: Example transformer should not import dart:mirror" + failed=1 +else + echo "PASS: Example transformer should not import dart:mirror" +fi + +output=$(node example/build/web/main.dart.js || : ) +if [ $output == "Success" ] +then + echo "PASS: Example transformer should inject dependencies" +else + echo "FAIL: Example transformer should inject dependencies" + failed=1 +fi + +if [[ -n $failed ]] +then + echo "Tests failed. Build /example with \`pub build example/ --mode debug\` to debug." + exit 1 +fi +echo "" + +echo "Compiling tests to JavaScript with dart2js..." mkdir -p out dart2js --minify -c test/main.dart -o out/main.dart.js -echo "run tests in node" +echo "Running compiled tests in node..." node out/main.dart.js -echo "run transformer tests" -pub build --mode=debug test - -echo "running transformer test (uncompiled, Dynamic DI)" -dart --checked test/auto_injector_test.dart - -echo "running transformer test (Static DI, Dart VM)" -dart --checked build/test/auto_injector_test.dart - -echo "running transformer test (Static DI, dart2js)" -# TODO(blois) dart2js compilation is not picking up transformed files, so -# run dart2js manually. dartbug.com/17198 -dart2js -c build/test/auto_injector_test.dart -o build/test/auto_injector_test.dart.js; -node build/test/auto_injector_test.dart.js +echo "Testing complete." diff --git a/test/auto_injector_test.dart b/test/auto_injector_test.dart deleted file mode 100644 index ce86d62..0000000 --- a/test/auto_injector_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -library auto_injector_test; - -import 'package:di/auto_injector.dart' as auto; -import 'main.dart'; - - -void main() { - createInjectorSpec('Injector', - (modules, [name]) => auto.defaultInjector(modules: modules, name: name)); -} diff --git a/test/generator_test.dart b/test/generator_test.dart deleted file mode 100644 index 3b90f4e..0000000 --- a/test/generator_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'dart:io'; -import 'package:analyzer/analyzer.dart'; -import 'package:di/generator.dart' as generator; -import 'package:guinness/guinness.dart'; -import 'package:matcher/matcher.dart' as matcher; - -main(args) { - - describe('generator', () { - - it('should codesplit deferred libraries', () { - Map code = generator.generateCode( - 'test_assets/gen_test1/main.dart', ['annotations.InjectableTest'], - Platform.environment['DART_SDK'], [Platform.packageRoot], 'main.dart'); - - expect(code.keys.map((chunk) => chunk.library == null ? null : chunk.library.name)) - .to(matcher.unorderedEquals([null, 'lib_a', 'lib_b', 'lib_c'])); - - code.forEach((chunk, code) { - var cu = parseCompilationUnit(code); - if (chunk.library == null) { - expectHasImports(cu, ['main.dart', 'common1.dart']); - } else if (chunk.library.name.endsWith('lib_a')) { - expectHasImports(cu, ['a.dart', 'a2.dart', 'common2.dart']); - } else if (chunk.library.name.endsWith('lib_b')) { - expectHasImports(cu, ['b.dart', 'b2.dart', 'common2.dart']); - } else if (chunk.library.name.endsWith('lib_c')) { - expectHasImports(cu, []); - } - }); - }); - }); -} - -expectHasImports(CompilationUnit cu, List expectedImports) { - var imports = []; - cu.directives.forEach((Directive directive) { - if (directive is NamespaceDirective) { - // 'Only expecting import, no exports.' - expect(directive).toBeAnInstanceOf(ImportDirective); - ImportDirective import = directive; - imports.add(import.uri.stringValue); - } - }); - expect(imports.length).toEqual(expectedImports.length); - for (int i = 0; i < imports.length; i++) { - expect(imports[i]).to(matcher.endsWith(expectedImports[i])); - } -} diff --git a/test/main.dart b/test/main.dart index b700d72..4cbb6a0 100644 --- a/test/main.dart +++ b/test/main.dart @@ -14,14 +14,18 @@ library di.tests; import 'package:guinness/guinness.dart'; import 'package:matcher/matcher.dart' as matcher; import 'package:di/di.dart'; -import 'package:di/dynamic_injector.dart'; -import 'package:di/static_injector.dart'; import 'package:di/annotations.dart'; +import 'package:di/src/reflector_static.dart'; +import 'package:di/src/reflector_dynamic.dart'; +import 'package:di/check_bind_args.dart'; +import 'package:di/src/module.dart'; import 'test_annotations.dart'; // Generated file. Run ../test_tf_gen.sh. import 'type_factories_gen.dart' as type_factories_gen; +import 'dart:mirrors'; + /** * Annotation used to mark classes for which static type factory must be * generated. For testing purposes not all classes are marked with this @@ -175,7 +179,7 @@ class AnnotatedPrimitiveDependency { } class EmulatedMockEngineFactory { - call(Injector i) => new MockEngine(); + call() => new MockEngine(); } bool throwOnceShouldThrow = true; @@ -190,26 +194,27 @@ class ThrowOnce { } void main() { - moduleTest(); + testModule(); - createInjectorSpec('DynamicInjector', - (modules, [name]) => new DynamicInjector(modules: modules, name: name)); + var static_factory = new GeneratedTypeFactories( + type_factories_gen.typeFactories, type_factories_gen.parameterKeys); + createInjectorSpec('Static ModuleInjector ', + () => new Module.withReflector(static_factory)); - createInjectorSpec('StaticInjector', - (modules, [name]) => new StaticInjector(modules: modules, name: name, - typeFactories: type_factories_gen.typeFactories)); + TypeReflector reflector = new DynamicTypeFactories(); + createInjectorSpec('Dynamic ModuleInjector ', + () => new Module.withReflector(reflector)); - dynamicInjectorTest(); - staticInjectorTest(); - createKeySpec(); + testKey(); + testCheckBindArgs(); } -moduleTest() { +testModule() { describe('Module', () { const BIND_ERROR = 'Only one of following parameters can be specified: ' - 'toValue, toFactory, toImplementation'; + 'toValue, toFactory, toFactoryPos, toImplementation'; describe('bind', () { @@ -221,19 +226,19 @@ moduleTest() { it('should throw if incorrect combination of parameters passed (2)', () { expect(() { - new Module().bind(Engine, toValue: new Engine(), toFactory: (_) => null); + new Module().bind(Engine, toValue: new Engine(), toFactory: () => null); }).toThrowWith(message: BIND_ERROR); }); it('should throw if incorrect combination of parameters passed (3)', () { expect(() { - new Module().bind(Engine, toValue: new Engine(), toImplementation: MockEngine, toFactory: (_) => null); + new Module().bind(Engine, toValue: new Engine(), toImplementation: MockEngine, toFactory: () => null); }).toThrowWith(message: BIND_ERROR); }); it('should throw if incorrect combination of parameters passed (4)', () { expect(() { - new Module().bind(Engine, toImplementation: MockEngine, toFactory: (_) => null); + new Module().bind(Engine, toImplementation: MockEngine, toFactory: () => null); }).toThrowWith(message: BIND_ERROR); }); @@ -242,14 +247,14 @@ moduleTest() { }); } -typedef Injector InjectorFactory(List modules, [String name]); +typedef Module ModuleFactory(); -createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { +createInjectorSpec(String injectorName, ModuleFactory moduleFactory) { describe(injectorName, () { it('should instantiate a type', () { - var injector = injectorFactory([new Module()..bind(Engine)]); + var injector = new ModuleInjector([moduleFactory()..bind(Engine)]); var instance = injector.get(Engine); expect(instance).toBeAnInstanceOf(Engine); @@ -257,7 +262,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should instantiate an annotated type', () { - var injector = injectorFactory([new Module() + var injector = new ModuleInjector([moduleFactory() ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) ..bind(Car, toValue: new Engine()) ]); @@ -268,16 +273,20 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should fail if no binding is found', () { - var injector = injectorFactory([]); + var injector = new ModuleInjector([moduleFactory()..bind(Car)]); expect(() { - injector.get(Engine); + injector.get(Car); }).toThrowWith(message: 'No provider found for Engine! ' - '(resolving Engine)'); + '(resolving Car -> Engine)'); }); it('should resolve basic dependencies', () { - var injector = injectorFactory([new Module()..bind(Car)..bind(Engine)]); + var injector = new ModuleInjector([ + moduleFactory() + ..bind(Car) + ..bind(Engine) + ]); var instance = injector.get(Car); expect(instance).toBeAnInstanceOf(Car); @@ -285,7 +294,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should resolve complex dependencies', () { - var injector = injectorFactory([new Module() + var injector = new ModuleInjector([moduleFactory() ..bind(Porsche) ..bind(TurboEngine) ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine) @@ -297,7 +306,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should resolve annotated primitive type', () { - var injector = injectorFactory([new Module() + var injector = new ModuleInjector([moduleFactory() ..bind(AnnotatedPrimitiveDependency) ..bind(String, toValue: 'Worked!', withAnnotation: Turbo) ]); @@ -308,7 +317,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should inject generic parameterized types', () { - var injector = injectorFactory([new Module() + var injector = new ModuleInjector([moduleFactory() ..bind(ParameterizedType) ..bind(GenericParameterizedDependency) ]); @@ -317,22 +326,24 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); - xit('should error while resolving parameterized types', () { - var injector = injectorFactory([new Module() - ..bind(ParameterizedType) - ..bind(ParameterizedDependency) - ]); - expect(() => injector.get(ParameterizedDependency)).toThrowWith(); + it('should error while resolving parameterized types', () { + expect((){ + var injector = new ModuleInjector([moduleFactory() + ..bind(ParameterizedType) + ..bind(ParameterizedDependency) + ]); + injector.get(ParameterizedDependency); + }).toThrowWith(); }); it('should allow modules and overriding providers', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); + var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); // injector is immutable // you can't load more modules once it's instantiated // (you can create a child injector) - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance.id).toEqual('mock-id'); @@ -340,7 +351,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should only create a single instance', () { - var injector = injectorFactory([new Module()..bind(Engine)]); + var injector = new ModuleInjector([moduleFactory()..bind(Engine)]); var first = injector.get(Engine); var second = injector.get(Engine); @@ -349,11 +360,11 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should allow providing values', () { - var module = new Module() + var module = moduleFactory() ..bind(Engine, toValue: 'str value') ..bind(Car, toValue: 123); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var abcInstance = injector.get(Engine); var complexInstance = injector.get(Car); @@ -363,10 +374,10 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should allow providing null values', () { - var module = new Module() + var module = moduleFactory() ..bind(Engine, toValue: null); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var engineInstance = injector.get(Engine); expect(engineInstance).toBeNull(); @@ -374,11 +385,11 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should allow providing factory functions', () { - var module = new Module()..bind(Engine, toFactory: (Injector injector) { + var module = moduleFactory()..bind(Engine, toFactory: () { return 'factory-product'; }); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance).toEqual('factory-product'); @@ -386,10 +397,10 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should allow providing with emulated factory functions', () { - var module = new Module(); + var module = moduleFactory(); module.bind(Engine, toFactory: new EmulatedMockEngineFactory()); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Engine); expect(instance).toBeAnInstanceOf(MockEngine); @@ -397,13 +408,13 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should inject injector into factory function', () { - var module = new Module() + var module = moduleFactory() ..bind(Engine) - ..bind(Car, toFactory: (Injector injector) { - return new Car(injector.get(Engine), injector); - }); + ..bind(Car, toFactory: (Engine engine, Injector injector) { + return new Car(engine, injector); + }, inject: [Engine, Injector]); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var instance = injector.get(Car); expect(instance).toBeAnInstanceOf(Car); @@ -412,8 +423,8 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should throw an exception when injecting a primitive type', () { - var injector = injectorFactory([ - new Module() + var injector = new ModuleInjector([ + moduleFactory() ..bind(NumDependency) ..bind(IntDependency) ..bind(DoubleDependency) @@ -459,7 +470,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should throw an exception when circular dependency', () { - var injector = injectorFactory([new Module()..bind(CircularA) + var injector = new ModuleInjector([moduleFactory()..bind(CircularA) ..bind(CircularB)]); expect(() { @@ -471,8 +482,8 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); it('should throw an exception when circular dependency in factory', () { - var injector = injectorFactory([new Module() - ..bind(CircularA, toFactory: (i) => i.get(CircularA)) + var injector = new ModuleInjector([moduleFactory() + ..bind(CircularA, inject: [CircularA]) ]); expect(() { @@ -485,7 +496,7 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should recover from errors', () { - var injector = injectorFactory([new Module()..bind(ThrowOnce)]); + var injector = new ModuleInjector([moduleFactory()..bind(ThrowOnce)]); throwOnceShouldThrow = true; var caught = false; @@ -500,16 +511,16 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should provide the injector as Injector', () { - var injector = injectorFactory([]); + var injector = new ModuleInjector([]); expect(injector.get(Injector)).toBe(injector); }); it('should inject a typedef', () { - var module = new Module()..bind(CompareInt, toValue: compareIntAsc); + var module = moduleFactory()..bind(CompareInt, toValue: compareIntAsc); - var injector = injectorFactory([module]); + var injector = new ModuleInjector([module]); var compare = injector.get(CompareInt); expect(compare(1, 2)).toEqual(1); @@ -518,26 +529,25 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should throw an exception when injecting typedef without providing it', () { - var injector = injectorFactory([new Module()..bind(WithTypeDefDependency)]); - expect(() { + var injector = new ModuleInjector([moduleFactory()..bind(WithTypeDefDependency)]); injector.get(WithTypeDefDependency); }).toThrowWith(); }); it('should instantiate via the default/unnamed constructor', () { - var injector = injectorFactory([new Module()..bind(MultipleConstructors)]); + var injector = new ModuleInjector([moduleFactory()..bind(MultipleConstructors)]); MultipleConstructors instance = injector.get(MultipleConstructors); expect(instance.instantiatedVia).toEqual('default'); }); // CHILD INJECTORS it('should inject from child', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); + var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); - var parent = injectorFactory([new Module()..bind(Engine)]); - var child = parent.createChild([module]); + var parent = new ModuleInjector([moduleFactory()..bind(Engine)]); + var child = new ModuleInjector([module], parent); var abcFromParent = parent.get(Engine); var abcFromChild = child.get(Engine); @@ -548,8 +558,8 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should enumerate across children', () { - var parent = injectorFactory([new Module()..bind(Engine)]); - var child = parent.createChild([new Module()..bind(MockEngine)]); + var parent = new ModuleInjector([moduleFactory()..bind(Engine)]); + var child = new ModuleInjector([moduleFactory()..bind(MockEngine)], parent); expect(parent.types).to(matcher.unorderedEquals([Engine, Injector])); expect(child.types).to(matcher.unorderedEquals([Engine, MockEngine, Injector])); @@ -557,10 +567,10 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should inject instance from parent if not provided in child', () { - var module = new Module()..bind(Car); + var module = moduleFactory()..bind(Car); - var parent = injectorFactory([new Module()..bind(Car)..bind(Engine)]); - var child = parent.createChild([module]); + var parent = new ModuleInjector([moduleFactory()..bind(Car)..bind(Engine)]); + var child = new ModuleInjector([module], parent); var complexFromParent = parent.get(Car); var complexFromChild = child.get(Car); @@ -573,10 +583,10 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { it('should inject instance from parent but never use dependency from child', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); + var module = moduleFactory()..bind(Engine, toImplementation: MockEngine); - var parent = injectorFactory([new Module()..bind(Car)..bind(Engine)]); - var child = parent.createChild([module]); + var parent = new ModuleInjector([moduleFactory()..bind(Car)..bind(Engine)]); + var child = new ModuleInjector([module], parent); var complexFromParent = parent.get(Car); var complexFromChild = child.get(Car); @@ -589,266 +599,31 @@ createInjectorSpec(String injectorName, InjectorFactory injectorFactory) { }); - it('should force new instance in child even if already instantiated in parent', () { - var parent = injectorFactory([new Module()..bind(Engine)]); - var abcAlreadyInParent = parent.get(Engine); - - var child = parent.createChild([], forceNewInstances: [new Key(Engine)]); - var abcFromChild = child.get(Engine); - - expect(abcFromChild).not.toBe(abcAlreadyInParent); - }); - - - it('should force new instance in child using provider from grand parent', () { - var module = new Module()..bind(Engine, toImplementation: MockEngine); - - var grandParent = injectorFactory([module]); - var parent = grandParent.createChild([]); - var child = parent.createChild([], forceNewInstances: [new Key(Engine)]); - - var abcFromGrandParent = grandParent.get(Engine); - var abcFromChild = child.get(Engine); - - expect(abcFromChild.id).toEqual('mock-id'); - expect(abcFromChild).not.toBe(abcFromGrandParent); - }); - - it('should provide child injector as Injector', () { - var injector = injectorFactory([]); - var child = injector.createChild([]); + var injector = new ModuleInjector([]); + var child = new ModuleInjector([], injector); expect(child.get(Injector)).toBe(child); }); - it('should set the injector name', () { - var injector = injectorFactory([], 'foo'); - expect(injector.name).toEqual('foo'); - }); - - - it('should set the child injector name', () { - var injector = injectorFactory([], 'foo'); - var childInjector = injector.createChild(null, name: 'bar'); - expect(childInjector.name).toEqual('bar'); - }); - - it('should instantiate class only once (Issue #18)', () { - var rootInjector = injectorFactory([]); - var injector = rootInjector.createChild([ - new Module() + var rootInjector = new ModuleInjector([]); + var injector = new ModuleInjector([ + moduleFactory() ..bind(Log) ..bind(ClassOne) - ..bind(InterfaceOne, toFactory: (i) => i.get(ClassOne)) - ]); + ..bind(InterfaceOne, inject: [ClassOne]) + ], rootInjector); expect(injector.get(InterfaceOne)).toBe(injector.get(ClassOne)); expect(injector.get(Log).log.join(' ')).toEqual('ClassOne'); }); - - - describe('visiblity', () { - - it('should hide instances', () { - - var rootMock = new MockEngine(); - var childMock = new MockEngine(); - - var parentModule = new Module() - ..bind(Engine, toValue: rootMock); - var childModule = new Module() - ..bind(Engine, toValue: childMock, visibility: (_, __) => false); - - var parentInjector = injectorFactory([parentModule]); - var childInjector = parentInjector.createChild([childModule]); - - var val = childInjector.get(Engine); - expect(val).toBe(rootMock); - }); - - it('should throw when an instance in not visible in the root injector', () { - var module = new Module() - ..bind(Car, toValue: 'Invisible', visibility: (_, __) => false); - - var injector = injectorFactory([module]); - - expect(() { - injector.get(Car); - }).toThrowWith( - anInstanceOf: NoProviderError, - message: 'No provider found for Car! (resolving Car)' - ); - }); - }); - - }); -} - -void dynamicInjectorTest() { - describe('DynamicInjector', () { - - it('should throw a comprehensible error message on untyped argument', () { - var module = new Module()..bind(Lemon)..bind(Engine); - var injector = new DynamicInjector(modules : [module]); - - expect(() { - injector.get(Lemon); - }).toThrowWith( - anInstanceOf: NoProviderError, - message: "The 'engine' parameter must be typed " - "(resolving Lemon)"); - }); - - it('should throw a comprehensible error message when no default constructor found', () { - var module = new Module()..bind(HiddenConstructor); - var injector = new DynamicInjector(modules: [module]); - - expect(() { - injector.get(HiddenConstructor); - }).toThrowWith( - anInstanceOf: NoProviderError, - message: 'Unable to find default constructor for HiddenConstructor.' - ' Make sure class has a default constructor.'); - }); - - it('should inject parameters into function and invoke it', () { - var module = new Module() - ..bind(Engine); - var id; - var injector = new DynamicInjector(modules: [module]); - injector.invoke((Engine e) => id = e.id); - expect(id).toEqual('v8-id'); - }); - - it('should inject annotated parameters into function and invoke it', () { - var module = new Module() - ..bind(Engine, withAnnotation: Turbo, toImplementation: TurboEngine); - var id; - var injector = new DynamicInjector(modules: [module]); - injector.invoke((@Turbo() Engine e) => id = e.id); - expect(id).toEqual('turbo-engine-id'); - }); - - it('should get an instance using implicit injection for an unseen type', (){ - var module = new Module() - ..bind(Engine); - var injector = - new DynamicInjector(modules: [module], allowImplicitInjection: true); - - expect(injector.get(SpecialEngine).id).toEqual('special-id'); - }); - }); } -void staticInjectorTest() { - describe('StaticInjector', () { - - it('should use type factories passed in the constructor', () { - var module = new Module() - ..bind(Engine); - var injector = new StaticInjector(modules: [module], typeFactories: { - Engine: (f) => new Engine() - }); - - var engine; - expect(() { - engine = injector.get(Engine); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Engine); - }); - - it('should use type factories passes in one module', () { - var module = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - var injector = new StaticInjector(modules: [module]); - - var engine; - expect(() { - engine = injector.get(Engine); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Engine); - }); - - it('should use type factories passes in many modules', () { - var module1 = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - var module2 = new Module() - ..bind(Car) - ..typeFactories = { - Car: (f) => new Car(f(Engine), f(Injector)) - }; - - var injector = new StaticInjector(modules: [module1, module2]); - - var engine; - expect(() { - engine = injector.get(Car); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Car); - }); - - it('should use type factories passes in hierarchical module', () { - var module = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - - module.install(new Module() - ..bind(Car) - ..typeFactories = { - Car: (f) => new Car(f(Engine), f(Injector)) - }); - var injector = new StaticInjector(modules: [module]); - - var engine; - expect(() { - engine = injector.get(Car); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Car); - }); - - it('should find type factories from parent injector', () { - var module1 = new Module() - ..bind(Engine) - ..typeFactories = { - Engine: (f) => new Engine() - }; - var module2 = new Module() - ..bind(Car) - ..typeFactories = { - Car: (f) => new Car(f(Engine), f(Injector)) - }; - - var rootInjector = new StaticInjector(modules: [module1]); - var childInjector = rootInjector.createChild([module2]); - - expect(() { - rootInjector.get(Car); - }).toThrowWith(); - - var engine; - expect(() { - engine = childInjector.get(Car); - }).not.toThrow(); - expect(engine).toBeAnInstanceOf(Car); - }); - - }); -} - -createKeySpec() { +testKey() { describe('Key', () { void expectEquals(x, y, bool truthValue) { expect(x == y).toEqual(truthValue); @@ -881,5 +656,38 @@ createKeySpec() { ' is same', () { expectEquals(new Key(Engine, Old), new Key(Car, Old), false); }); + + it('should be equal to a mirrored key of the same type', () { + ClassMirror classMirror = reflectType(Car); + MethodMirror ctor = classMirror.declarations[classMirror.simpleName]; + + ParameterMirror p = ctor.parameters[0]; + var pType = (p.type as ClassMirror).reflectedType; + + expectEquals(new Key(Engine), new Key(pType), true); + }); + }); +} + + +testCheckBindArgs() { + describe('CheckBindArgs', () { + var _ = DEFAULT_VALUE; + it('should return true when args are well formed', () { + expect(checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine, Car])).toBeTrue(); + expect(checkBindArgs(_, () => 0, null, [])).toBeTrue(); + expect(checkBindArgs(0, _, null, [])).toBeTrue(); + expect(checkBindArgs(_, _, Car, [])).toBeTrue(); + }); + + it('should error when wrong number of args have been set', () { + expect(() => checkBindArgs(_, () => 0, Car, [])).toThrowWith(); + expect(() => checkBindArgs(0, _, null, [Engine, Car])).toThrowWith(); + }); + + it('should error when toFactory argument count does not match inject length', () { + expect(() => checkBindArgs(_, (Engine e, Car c) => 0, null, [Engine])).toThrowWith(); + expect(() => checkBindArgs(_, () => 0, null, [Engine, Car])).toThrowWith(); + }); }); } diff --git a/test/injector_generator_spec.dart b/test/transformer_test.dart similarity index 75% rename from test/injector_generator_spec.dart rename to test/transformer_test.dart index 72972cf..dde949f 100644 --- a/test/injector_generator_spec.dart +++ b/test/transformer_test.dart @@ -5,8 +5,9 @@ import 'dart:async'; import 'package:barback/barback.dart'; import 'package:code_transformers/resolver.dart'; import 'package:code_transformers/tests.dart' as tests; -import 'package:di/transformer/injector_generator.dart'; +import 'package:di/transformer.dart'; import 'package:di/transformer/options.dart'; +import 'package:di/transformer/injector_generator.dart'; import 'package:guinness/guinness.dart'; @@ -51,11 +52,19 @@ main() { "import 'package:a/engine.dart' as import_1;", "import 'package:a/seat.dart' as import_2;", ], - generators: [ - 'import_0.Car: (f) => new import_0.Car(f(import_1.Engine), ' - 'f(import_2.Seat)),', - 'import_1.Engine: (f) => new import_1.Engine(),', - 'import_2.Seat: (f) => new import_2.Seat(),', + keys: [ + 'Engine = new Key(import_1.Engine);', + 'Seat = new Key(import_2.Seat);', + ], + factories: [ + 'import_0.Car: (a1, a2) => new import_0.Car(a1, a2),', + 'import_1.Engine: () => new import_1.Engine(),', + 'import_2.Seat: () => new import_2.Seat(),', + ], + paramKeys: [ + 'import_0.Car: [_KEY_Engine, _KEY_Seat],', + 'import_1.Engine: const[],', + 'import_2.Seat: const[],' ]); }); @@ -74,8 +83,11 @@ main() { imports: [ "import 'package:a/a.dart' as import_0;", ], - generators: [ - 'import_0.Parameterized: (f) => new import_0.Parameterized(),', + factories: [ + 'import_0.Parameterized: () => new import_0.Parameterized(),', + ], + paramKeys: [ + 'import_0.Parameterized: const[],', ], messages: [ 'warning: Parameterized is a parameterized type. ' @@ -118,8 +130,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Bar: (f) => new import_0.Bar(f(import_0.Foo)),', + keys: [ + "Foo = new Key(import_0.Foo);" + ], + factories: [ + 'import_0.Bar: (a1) => new import_0.Bar(a1),', + ], + paramKeys: [ + 'import_0.Bar: [_KEY_Foo],' ]); }); @@ -133,8 +151,11 @@ main() { imports: [ "import 'package:a/b.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -156,8 +177,11 @@ main() { imports: [ "import 'package:a/a.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -171,8 +195,11 @@ main() { imports: [ "import 'package:a/b.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -194,9 +221,16 @@ main() { "import 'package:a/a.dart' as import_0;", "import 'package:a/b.dart' as import_1;", ], - generators: [ - 'import_0.Car: (f) => new import_0.Car(f(import_1.Engine)),', - 'import_1.Engine: (f) => new import_1.Engine(),', + keys: [ + "Engine = new Key(import_1.Engine);" + ], + factories: [ + 'import_0.Car: (a1) => new import_0.Car(a1),', + 'import_1.Engine: () => new import_1.Engine(),', + ], + paramKeys: [ + 'import_0.Car: [_KEY_Engine],', + 'import_1.Engine: const[],', ]); }); @@ -209,8 +243,11 @@ main() { imports: [ "import 'a.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -225,8 +262,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -261,8 +301,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -333,8 +376,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -357,8 +403,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(f(import_0.Fuel)),', + keys: [ + "Fuel = new Key(import_0.Fuel);", + ], + factories: [ + 'import_0.Engine: (a1) => new import_0.Engine(a1),', + ], + paramKeys: [ + 'import_0.Engine: [_KEY_Fuel],' ]); }); @@ -382,9 +434,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => ' - 'new import_0.Engine(f(import_0.JetFuel)),', + keys: [ + "JetFuel = new Key(import_0.JetFuel);", + ], + factories: [ + 'import_0.Engine: (a1) => new import_0.Engine(a1),', + ], + paramKeys: [ + 'import_0.Engine: [_KEY_JetFuel],' ]); }); @@ -436,9 +493,13 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', - 'import_0.Car: (f) => new import_0.Car(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + 'import_0.Car: () => new import_0.Car(),', + ], + paramKeys: [ + 'import_0.Engine: const[],', + 'import_0.Car: const[],' ]).whenComplete(() { injectableAnnotations.clear(); }); @@ -449,16 +510,16 @@ main() { inputs: { 'a|web/main.dart': ''' import "package:inject/inject.dart"; - class Engine { - final Car car; + class Car { + final Engine engine; @inject - Engine([Car this.car]); + Car([Engine this.engine]); } - class Car { + class Engine { @inject - Car(); + Engine(); } main() {} @@ -467,9 +528,16 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(f(import_0.Car)),', - 'import_0.Car: (f) => new import_0.Car(),', + keys: [ + "Engine = new Key(import_0.Engine);" + ], + factories: [ + 'import_0.Car: (a1) => new import_0.Car(a1),', + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Car: [_KEY_Engine],', + 'import_0.Engine: const[],', ]); }); @@ -493,8 +561,11 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + ], + paramKeys: [ + 'import_0.Engine: const[],' ]); }); @@ -514,8 +585,14 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(f(int)),', + keys: [ + "int = new Key(int);" + ], + factories: [ + 'import_0.Engine: (a1) => new import_0.Engine(a1),', + ], + paramKeys: [ + 'import_0.Engine: [_KEY_int],' ]); }); @@ -557,41 +634,6 @@ main() { 'annotated for injection. (web/main.dart 2 18)']); }); - it('transforms main', () { - return tests.applyTransformers(phases, - inputs: { - 'a|web/main.dart': ''' -library main; -import 'package:di/auto_injector.dart'; -import 'package:di/auto_injector.dart' as ai; - -main() { - var module = defaultInjector(modules: null, name: 'foo'); - print(module); - - var module2 = ai.defaultInjector(modules: null, name: 'foo'); - print(module2); -}''', - 'di|lib/auto_injector.dart': PACKAGE_AUTO - }, - results: { - 'a|web/main.dart': ''' -library main; -import 'main_static_injector.dart' as generated_static_injector; -import 'package:di/auto_injector.dart'; -import 'package:di/auto_injector.dart' as ai; - -main() { - var module = generated_static_injector.createStaticInjector(modules: null, name: 'foo'); - print(module); - - var module2 = generated_static_injector.createStaticInjector(modules: null, name: 'foo'); - print(module2); -}''' - - }); - }); - it('handles annotated dependencies', () { return generates(phases, inputs: { @@ -616,55 +658,82 @@ main() { imports: [ "import 'main.dart' as import_0;", ], - generators: [ - 'import_0.Engine: (f) => new import_0.Engine(),', - 'import_0.Car: (f) => new import_0.Car(f(import_0.Engine, import_0.Turbo)),', + keys: [ + "Engine_Turbo = new Key(import_0.Engine, import_0.Turbo);" + ], + factories: [ + 'import_0.Engine: () => new import_0.Engine(),', + 'import_0.Car: (a1) => new import_0.Car(a1),', + ], + paramKeys: [ + 'import_0.Engine: const[],', + 'import_0.Car: [_KEY_Engine_Turbo],' ]); }); + + it('transforms main', () { + return tests.applyTransformers(phases, + inputs: { + 'a|web/main.dart': ''' +library main; +import 'package:di/di.dart'; + +main() { + print('abc'); +}''' + }, + results: { + 'a|web/main.dart': ''' +library main; +import 'package:di/di.dart'; +import 'main_generated_type_factory_maps.dart' show setStaticReflectorAsDefault; + +main() { + setStaticReflectorAsDefault(); + print('abc'); +}''' + }); + }); }); } Future generates(List> phases, {Map inputs, Iterable imports: const [], - Iterable generators: const [], + Iterable keys: const [], + Iterable factories: const [], + Iterable paramKeys: const [], Iterable messages: const []}) { inputs['inject|lib/inject.dart'] = PACKAGE_INJECT; imports = imports.map((i) => '$i\n'); - generators = generators.map((t) => ' $t\n'); + keys = keys.map((t) => 'final Key _KEY_$t'); + factories = factories.map((t) => ' $t\n'); + paramKeys = paramKeys.map((t) => ' $t\n'); return tests.applyTransformers(phases, inputs: inputs, results: { - 'a|web/main_static_injector.dart': ''' + 'a|web/main_generated_type_factory_maps.dart': ''' $IMPORTS -${imports.join('')}$BOILER_PLATE -${generators.join('')}$FOOTER +${imports.join('')}${(keys.length != 0 ? '\n' : '')}${keys.join('\n')} +final Map typeFactories = { +${factories.join('')}}; +final Map> parameterKeys = { +${paramKeys.join('')}}; +setStaticReflectorAsDefault() => Module.DEFAULT_REFLECTOR = new GeneratedTypeFactories(typeFactories, parameterKeys); ''', }, messages: messages); } const String IMPORTS = ''' -library a.web.main.generated_static_injector; +library a.web.main.generated_type_factory_maps; import 'package:di/di.dart'; -import 'package:di/static_injector.dart'; +import 'package:di/src/reflector_static.dart'; '''; -const String BOILER_PLATE = ''' -Injector createStaticInjector({List modules, String name, - bool allowImplicitInjection: false}) => - new StaticInjector(modules: modules, name: name, - allowImplicitInjection: allowImplicitInjection, - typeFactories: factories); - -final Map factories = {'''; - -const String FOOTER = ''' -};'''; - const String CLASS_ENGINE = ''' import 'package:inject/inject.dart'; class Engine { @@ -697,10 +766,3 @@ class Injectables { const Injectables(this.types); } '''; - -const String PACKAGE_AUTO = ''' -library di.auto_injector; - -defaultInjector({List modules, String name, - bool allowImplicitInjection: false}) => null; -''';