diff --git a/angel_container/lib/src/container.dart b/angel_container/lib/src/container.dart index c08b3205..f8178aef 100644 --- a/angel_container/lib/src/container.dart +++ b/angel_container/lib/src/container.dart @@ -16,13 +16,17 @@ class Container { var named = {}; if (reflectedType is ReflectedClass) { + bool isDefault(String name) { + return name.isEmpty || name == reflectedType.name; + } + var constructor = reflectedType.constructors.firstWhere( - (c) => c.name.isEmpty, - orElse: () => throw new ReflectionException('${reflectedType - .name} has no default constructor, and therefore cannot be instantiated.')); + (c) => isDefault(c.name), + orElse: () => throw new ReflectionException( + '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.')); for (var param in constructor.parameters) { - var value = make(param.type); + var value = make(param.type.reflectedType); if (param.isNamed) { named[param.name] = value; @@ -31,8 +35,10 @@ class Container { } } - return reflectedType - .newInstance(constructor.name, positional, named, []); + return reflectedType.newInstance( + isDefault(constructor.name) ? '' : constructor.name, + positional, + named, []); } else { throw new ReflectionException( '$type is not a class, and therefore cannot be instantiated.'); @@ -42,8 +48,8 @@ class Container { void singleton(Object object, {Type as}) { if (_singletons.containsKey(as ?? object.runtimeType)) { - throw new StateError('This container already has a singleton for ${as ?? - object.runtimeType}.'); + throw new StateError( + 'This container already has a singleton for ${as ?? object.runtimeType}.'); } _singletons[as ?? object.runtimeType] = object; diff --git a/angel_container/lib/src/mirrors/reflector.dart b/angel_container/lib/src/mirrors/reflector.dart index 4dbba446..f8539e79 100644 --- a/angel_container/lib/src/mirrors/reflector.dart +++ b/angel_container/lib/src/mirrors/reflector.dart @@ -1,9 +1,14 @@ import 'dart:mirrors' as dart; + import 'package:angel_container/angel_container.dart'; import 'package:angel_container/src/reflector.dart'; -import 'package:angel_container/src/reflector.dart'; +/// A [Reflector] implementation that forwards to `dart:mirrors`. +/// +/// Useful on the server, where reflection is supported. class MirrorsReflector implements Reflector { + const MirrorsReflector(); + @override String getName(Symbol symbol) => dart.MirrorSystem.getName(symbol); @@ -20,7 +25,8 @@ class MirrorsReflector implements Reflector { @override ReflectedFunction reflectFunction(Function function) { - // TODO: implement reflectFunction + var closure = dart.reflect(function) as dart.ClosureMirror; + return new _ReflectedMethodMirror(closure.function); } @override @@ -33,6 +39,11 @@ class MirrorsReflector implements Reflector { return new _ReflectedTypeMirror(mirror); } } + + @override + ReflectedInstance reflectInstance(Object object) { + return new _ReflectedInstanceMirror(object); + } } class _ReflectedTypeParameter extends ReflectedTypeParameter { @@ -52,6 +63,7 @@ class _ReflectedTypeMirror extends ReflectedType { mirror.typeVariables .map((m) => new _ReflectedTypeParameter(m)) .toList(), + mirror.reflectedType, ); @override @@ -61,7 +73,7 @@ class _ReflectedTypeMirror extends ReflectedType { @override T newInstance(String constructorName, List positionalArguments, - Map namedArguments, List typeArguments) { + [Map namedArguments, List typeArguments]) { throw new ReflectionException( '$name is not a class, and therefore cannot be instantiated.'); } @@ -79,6 +91,7 @@ class _ReflectedClassMirror extends ReflectedClass { mirror.metadata.map((m) => new _ReflectedInstanceMirror(m)).toList(), _constructorsOf(mirror), _declarationsOf(mirror), + mirror.reflectedType, ); static List _constructorsOf(dart.ClassMirror mirror) { @@ -86,6 +99,10 @@ class _ReflectedClassMirror extends ReflectedClass { for (var key in mirror.declarations.keys) { var value = mirror.declarations[key]; + + if (value is dart.MethodMirror && value.isConstructor) { + out.add(new _ReflectedMethodMirror(value)); + } } return out; @@ -96,6 +113,11 @@ class _ReflectedClassMirror extends ReflectedClass { for (var key in mirror.declarations.keys) { var value = mirror.declarations[key]; + + if (value is dart.MethodMirror && !value.isConstructor) { + out.add(new ReflectedDeclaration(dart.MirrorSystem.getName(key), + value.isStatic, new _ReflectedMethodMirror(value))); + } } return out; @@ -108,8 +130,10 @@ class _ReflectedClassMirror extends ReflectedClass { @override T newInstance(String constructorName, List positionalArguments, - Map namedArguments, List typeArguments) { - // TODO: implement newInstance + [Map namedArguments, List typeArguments]) { + return mirror + .newInstance(new Symbol(constructorName), positionalArguments) + .reflectee as T; } } @@ -125,3 +149,31 @@ class _ReflectedInstanceMirror extends ReflectedInstance { return mirror.delegate(invocation) as T; } } + +class _ReflectedMethodMirror extends ReflectedFunction { + final dart.MethodMirror mirror; + + _ReflectedMethodMirror(this.mirror) + : super( + dart.MirrorSystem.getName(mirror.simpleName), + [], + mirror.metadata + .map((mirror) => new _ReflectedInstanceMirror(mirror)) + .toList(), + const MirrorsReflector() + .reflectType(mirror.returnType.reflectedType), + mirror.parameters.map(_reflectParameter).toList(), + mirror.isGetter, + mirror.isSetter); + + static ReflectedParameter _reflectParameter(dart.ParameterMirror mirror) { + return new ReflectedParameter( + dart.MirrorSystem.getName(mirror.simpleName), + mirror.metadata + .map((mirror) => new _ReflectedInstanceMirror(mirror)) + .toList(), + const MirrorsReflector().reflectType(mirror.type.reflectedType), + !mirror.isOptional, + mirror.isNamed); + } +} diff --git a/angel_container/lib/src/reflector.dart b/angel_container/lib/src/reflector.dart index 45eeaffa..49cdcefb 100644 --- a/angel_container/lib/src/reflector.dart +++ b/angel_container/lib/src/reflector.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart'; -import 'package:quiver_hashcode/hashcode.dart'; +import 'package:quiver/core.dart'; abstract class Reflector { String getName(Symbol symbol); @@ -9,6 +9,8 @@ abstract class Reflector { ReflectedFunction reflectFunction(Function function); ReflectedType reflectType(Type type); + + ReflectedInstance reflectInstance(Object object); } abstract class ReflectedInstance { @@ -30,21 +32,23 @@ abstract class ReflectedInstance { abstract class ReflectedType { final String name; final List typeParameters; + final Type reflectedType; - const ReflectedType(this.name, this.typeParameters); + const ReflectedType(this.name, this.typeParameters, this.reflectedType); @override - int get hashCode => hash2(name, typeParameters); + int get hashCode => hash3(name, typeParameters, reflectedType); @override bool operator ==(other) => other is ReflectedType && other.name == name && const ListEquality() - .equals(other.typeParameters, typeParameters); + .equals(other.typeParameters, typeParameters) && + other.reflectedType == reflectedType; T newInstance(String constructorName, List positionalArguments, - Map namedArguments, List typeArguments); + [Map namedArguments, List typeArguments]); bool isAssignableTo(ReflectedType other); } @@ -54,9 +58,14 @@ abstract class ReflectedClass extends ReflectedType { final List constructors; final List declarations; - const ReflectedClass(String name, List typeParameters, - this.annotations, this.constructors, this.declarations) - : super(name, typeParameters); + const ReflectedClass( + String name, + List typeParameters, + this.annotations, + this.constructors, + this.declarations, + Type reflectedType) + : super(name, typeParameters, reflectedType); @override int get hashCode => @@ -96,7 +105,7 @@ class ReflectedFunction { final String name; final List typeParameters; final List annotations; - final Type returnType; + final ReflectedType returnType; final List parameters; final bool isGetter, isSetter; @@ -132,7 +141,7 @@ class ReflectedFunction { class ReflectedParameter { final String name; final List annotations; - final Type type; + final ReflectedType type; final bool isRequired; final bool isNamed; diff --git a/angel_container/pubspec.yaml b/angel_container/pubspec.yaml index 6e3292a5..9f5a7521 100644 --- a/angel_container/pubspec.yaml +++ b/angel_container/pubspec.yaml @@ -1,4 +1,12 @@ name: angel_container +version: 1.0.0-alpha +author: Tobe O +description: "A better IoC container for Angel, ultimately allowing Angel to be used without dart:mirrors." +homepage: https://github.com/angel-dart/container.git +environment: + sdk: ">=1.8.0 <3.0.0" dependencies: collection: ^1.0.0 - quiver_hashcode: ^1.0.0 \ No newline at end of file + quiver: ^2.0.0 +dev_dependencies: + test: \ No newline at end of file diff --git a/angel_container/test/common.dart b/angel_container/test/common.dart new file mode 100644 index 00000000..bb346ee4 --- /dev/null +++ b/angel_container/test/common.dart @@ -0,0 +1,59 @@ +import 'package:angel_container/angel_container.dart'; +import 'package:test/test.dart'; + +void testReflector(Reflector reflector) { + var blaziken = new Pokemon('Blaziken', PokemonType.fire); + Container container; + + setUp(() { + container = new Container(reflector); + container.singleton(blaziken); + }); + + test('make on singleton type returns singleton', () { + expect(container.make(Pokemon), blaziken); + }); + + test('make on aliased singleton returns singleton', () { + container.singleton(blaziken, as: StateError); + expect(container.make(StateError), blaziken); + }); + + test('constructor injects singleton', () { + var lower = container.make(LowerPokemon) as LowerPokemon; + expect(lower.lowercaseName, blaziken.name.toLowerCase()); + }); + + test('newInstance works', () { + var type = container.reflector.reflectType(Pokemon); + var instance = + type.newInstance('changeName', [blaziken, 'Charizard']) as Pokemon; + print(instance); + expect(instance.name, 'Charizard'); + expect(instance.type, PokemonType.fire); + }); +} + +class LowerPokemon { + final Pokemon pokemon; + + LowerPokemon(this.pokemon); + + String get lowercaseName => pokemon.name.toLowerCase(); +} + +class Pokemon { + final String name; + final PokemonType type; + + Pokemon(this.name, this.type); + + factory Pokemon.changeName(Pokemon other, String name) { + return new Pokemon(name, other.type); + } + + @override + String toString() => 'NAME: $name, TYPE: $type'; +} + +enum PokemonType { water, fire, grass, ice, poison, flying } diff --git a/angel_container/test/mirrors_test.dart b/angel_container/test/mirrors_test.dart new file mode 100644 index 00000000..5122c918 --- /dev/null +++ b/angel_container/test/mirrors_test.dart @@ -0,0 +1,7 @@ +import 'package:angel_container/mirrors.dart'; + +import 'common.dart'; + +void main() { + testReflector(const MirrorsReflector()); +}