diff --git a/angel_container/CHANGELOG.md b/angel_container/CHANGELOG.md index 8903d8dc..9e776273 100644 --- a/angel_container/CHANGELOG.md +++ b/angel_container/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.0.0-alpha.7 +* Add `EmptyReflector`. +* `ReflectedType.newInstance` now returns a `ReflectedInstance`. +* Moved `ReflectedInstance.invoke` to `ReflectedFunction.invoke`. + # 1.0.0-alpha.6 * Add `getField` to `ReflectedInstance`. diff --git a/angel_container/lib/angel_container.dart b/angel_container/lib/angel_container.dart index 9029d12a..1395c3ea 100644 --- a/angel_container/lib/angel_container.dart +++ b/angel_container/lib/angel_container.dart @@ -1,3 +1,4 @@ +export 'src/empty/empty.dart'; export 'src/container.dart'; export 'src/exception.dart'; export 'src/reflector.dart'; diff --git a/angel_container/lib/src/container.dart b/angel_container/lib/src/container.dart index 621fc4d7..19844b0e 100644 --- a/angel_container/lib/src/container.dart +++ b/angel_container/lib/src/container.dart @@ -76,7 +76,7 @@ class Container { return reflectedType.newInstance( isDefault(constructor.name) ? '' : constructor.name, positional, - named, []); + named, []).reflectee; } else { throw new ReflectionException( '$type is not a class, and therefore cannot be instantiated.'); diff --git a/angel_container/lib/src/empty/empty.dart b/angel_container/lib/src/empty/empty.dart new file mode 100644 index 00000000..12e40d22 --- /dev/null +++ b/angel_container/lib/src/empty/empty.dart @@ -0,0 +1,126 @@ +import 'package:angel_container/angel_container.dart'; + +final Map _symbolNames = {}; + +/// A [Reflector] implementation that performs no actual reflection, +/// instead returning empty objects on every invocation. +/// +/// Use this in contexts where you know you won't need any reflective capabilities. +class EmptyReflector implements Reflector { + /// A [RegExp] that can be used to extract the name of a symbol without reflection. + static final RegExp symbolRegex = new RegExp(r'Symbol\("([^"]+)"\)'); + + const EmptyReflector(); + + @override + String getName(Symbol symbol) { + return _symbolNames.putIfAbsent( + symbol, () => symbolRegex.firstMatch(symbol.toString()).group(1)); + } + + @override + ReflectedClass reflectClass(Type clazz) { + return const _EmptyReflectedClass(); + } + + @override + ReflectedInstance reflectInstance(Object object) { + return const _EmptyReflectedInstance(); + } + + @override + ReflectedType reflectType(Type type) { + return const _EmptyReflectedType(); + } + + @override + ReflectedFunction reflectFunction(Function function) { + return const _EmptyReflectedFunction(); + } +} + +class _EmptyReflectedClass extends ReflectedClass { + const _EmptyReflectedClass() + : super( + '(empty)', + const [], + const [], + const [], + const [], + dynamic); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments, List typeArguments]) { + throw new UnsupportedError( + 'Classes reflected via an EmptyReflector cannot be instantiated.'); + } + + @override + bool isAssignableTo(ReflectedType other) { + return other == this; + } + + @override + bool operator ==(other) { + return other is ReflectedClass && other.hashCode == hashCode; + } +} + +class _EmptyReflectedType extends ReflectedType { + const _EmptyReflectedType() + : super('(empty)', const [], dynamic); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments, List typeArguments]) { + throw new UnsupportedError( + 'Types reflected via an EmptyReflector cannot be instantiated.'); + } + + @override + bool isAssignableTo(ReflectedType other) { + return other == this; + } + + @override + bool operator ==(other) { + return other is ReflectedType && other.hashCode == hashCode; + } +} + +class _EmptyReflectedInstance extends ReflectedInstance { + const _EmptyReflectedInstance() + : super(const _EmptyReflectedType(), const _EmptyReflectedClass(), null); + + @override + bool operator ==(other) { + return other is ReflectedInstance && other.hashCode == hashCode; + } + + @override + ReflectedInstance getField(String name) { + throw new UnsupportedError( + 'Instances reflected via an EmptyReflector cannot call getField().'); + } +} + +class _EmptyReflectedFunction extends ReflectedFunction { + const _EmptyReflectedFunction() + : super( + '(empty)', + const [], + const [], + const _EmptyReflectedType(), + const [], + false, + false); + + @override + ReflectedInstance invoke(Invocation invocation) { + throw new UnsupportedError( + 'Instances reflected via an EmptyReflector cannot call invoke().'); + } +} diff --git a/angel_container/lib/src/mirrors/reflector.dart b/angel_container/lib/src/mirrors/reflector.dart index 49ced6a2..2212977f 100644 --- a/angel_container/lib/src/mirrors/reflector.dart +++ b/angel_container/lib/src/mirrors/reflector.dart @@ -26,7 +26,7 @@ class MirrorsReflector implements Reflector { @override ReflectedFunction reflectFunction(Function function) { var closure = dart.reflect(function) as dart.ClosureMirror; - return new _ReflectedMethodMirror(closure.function); + return new _ReflectedMethodMirror(closure.function, closure); } @override @@ -75,7 +75,8 @@ class _ReflectedTypeMirror extends ReflectedType { } @override - T newInstance(String constructorName, List positionalArguments, + ReflectedInstance newInstance( + String constructorName, List positionalArguments, [Map namedArguments, List typeArguments]) { throw new ReflectionException( '$name is not a class, and therefore cannot be instantiated.'); @@ -139,11 +140,11 @@ class _ReflectedClassMirror extends ReflectedClass { } @override - T newInstance(String constructorName, List positionalArguments, + ReflectedInstance newInstance( + String constructorName, List positionalArguments, [Map namedArguments, List typeArguments]) { - return mirror - .newInstance(new Symbol(constructorName), positionalArguments) - .reflectee as T; + return new _ReflectedInstanceMirror( + mirror.newInstance(new Symbol(constructorName), positionalArguments)); } @override @@ -174,20 +175,16 @@ class _ReflectedInstanceMirror extends ReflectedInstance { new _ReflectedClassMirror(mirror.type), mirror.reflectee); @override - T invoke(Invocation invocation) { - return mirror.delegate(invocation) as T; - } - - @override - T getField(String name) { - return mirror.getField(new Symbol(name)).reflectee as T; + ReflectedInstance getField(String name) { + return new _ReflectedInstanceMirror(mirror.getField(new Symbol(name))); } } class _ReflectedMethodMirror extends ReflectedFunction { final dart.MethodMirror mirror; + final dart.ClosureMirror closureMirror; - _ReflectedMethodMirror(this.mirror) + _ReflectedMethodMirror(this.mirror, [this.closureMirror]) : super( dart.MirrorSystem.getName(mirror.simpleName), [], @@ -212,4 +209,17 @@ class _ReflectedMethodMirror extends ReflectedFunction { !mirror.isOptional, mirror.isNamed); } + + @override + ReflectedInstance invoke(Invocation invocation) { + if (closureMirror == null) { + throw new StateError( + 'This object was reflected without a ClosureMirror, and therefore cannot be directly invoked.'); + } + + return new _ReflectedInstanceMirror(closureMirror.invoke( + invocation.memberName, + invocation.positionalArguments, + invocation.namedArguments)); + } } diff --git a/angel_container/lib/src/reflector.dart b/angel_container/lib/src/reflector.dart index adefac79..9bf7028d 100644 --- a/angel_container/lib/src/reflector.dart +++ b/angel_container/lib/src/reflector.dart @@ -27,9 +27,7 @@ abstract class ReflectedInstance { bool operator ==(other) => other is ReflectedInstance && other.type == type && other.clazz == clazz; - T invoke(Invocation invocation); - - T getField(String name); + ReflectedInstance getField(String name); } abstract class ReflectedType { @@ -50,7 +48,8 @@ abstract class ReflectedType { .equals(other.typeParameters, typeParameters) && other.reflectedType == reflectedType; - T newInstance(String constructorName, List positionalArguments, + ReflectedInstance newInstance( + String constructorName, List positionalArguments, [Map namedArguments, List typeArguments]); bool isAssignableTo(ReflectedType other); @@ -104,7 +103,7 @@ class ReflectedDeclaration { other.function == function; } -class ReflectedFunction { +abstract class ReflectedFunction { final String name; final List typeParameters; final List annotations; @@ -139,6 +138,8 @@ class ReflectedFunction { .equals(other.parameters, other.parameters) && other.isGetter == isGetter && other.isSetter == isSetter; + + ReflectedInstance invoke(Invocation invocation); } class ReflectedParameter { diff --git a/angel_container/pubspec.yaml b/angel_container/pubspec.yaml index 05554bac..b86fd23a 100644 --- a/angel_container/pubspec.yaml +++ b/angel_container/pubspec.yaml @@ -1,5 +1,5 @@ name: angel_container -version: 1.0.0-alpha.6 +version: 1.0.0-alpha.7 author: Tobe O description: "A better IoC container and dependency injector for Angel, ultimately allowing Angel to be used without dart:mirrors." homepage: https://github.com/angel-dart/container.git diff --git a/angel_container/test/common.dart b/angel_container/test/common.dart index 0eaf2423..0f2d8043 100644 --- a/angel_container/test/common.dart +++ b/angel_container/test/common.dart @@ -14,7 +14,7 @@ void testReflector(Reflector reflector) { test('get field', () { var blazikenMirror = reflector.reflectInstance(blaziken); - expect(blazikenMirror.getField('type'), blaziken.type); + expect(blazikenMirror.getField('type').reflectee, blaziken.type); }); group('reflectFunction', () { @@ -63,7 +63,7 @@ void testReflector(Reflector reflector) { test('newInstance works', () { var type = container.reflector.reflectType(Pokemon); var instance = - type.newInstance('changeName', [blaziken, 'Charizard']) as Pokemon; + type.newInstance('changeName', [blaziken, 'Charizard']).reflectee as Pokemon; print(instance); expect(instance.name, 'Charizard'); expect(instance.type, PokemonType.fire); diff --git a/angel_container/test/empty_reflector_test.dart b/angel_container/test/empty_reflector_test.dart new file mode 100644 index 00000000..ba314590 --- /dev/null +++ b/angel_container/test/empty_reflector_test.dart @@ -0,0 +1,138 @@ +import 'package:angel_container/angel_container.dart'; +import 'package:test/test.dart'; + +void main() { + var reflector = const EmptyReflector(); + + test('getName', () { + expect(reflector.getName(#foo), 'foo'); + expect(reflector.getName(#==), '=='); + }); + + group('reflectClass', () { + var mirror = reflector.reflectClass(Truck); + + test('name returns empty', () { + expect(mirror.name, '(empty)'); + }); + + test('annotations returns empty', () { + expect(mirror.annotations, isEmpty); + }); + + test('typeParameters returns empty', () { + expect(mirror.typeParameters, isEmpty); + }); + + test('declarations returns empty', () { + expect(mirror.declarations, isEmpty); + }); + + test('constructors returns empty', () { + expect(mirror.constructors, isEmpty); + }); + + test('reflectedType returns dynamic', () { + expect(mirror.reflectedType, dynamic); + }); + + test('cannot call newInstance', () { + expect(() => mirror.newInstance('', []), throwsUnsupportedError); + }); + + test('isAssignableTo self', () { + expect(mirror.isAssignableTo(mirror), true); + }); + }); + + group('reflectType', () { + var mirror = reflector.reflectType(Truck); + + test('name returns empty', () { + expect(mirror.name, '(empty)'); + }); + + test('typeParameters returns empty', () { + expect(mirror.typeParameters, isEmpty); + }); + + test('reflectedType returns dynamic', () { + expect(mirror.reflectedType, dynamic); + }); + + test('cannot call newInstance', () { + expect(() => mirror.newInstance('', []), throwsUnsupportedError); + }); + + test('isAssignableTo self', () { + expect(mirror.isAssignableTo(mirror), true); + }); + }); + + group('reflectFunction', () { + void doIt(int x) {} + + var mirror = reflector.reflectFunction(doIt); + + test('name returns empty', () { + expect(mirror.name, '(empty)'); + }); + + test('annotations returns empty', () { + expect(mirror.annotations, isEmpty); + }); + + test('typeParameters returns empty', () { + expect(mirror.typeParameters, isEmpty); + }); + + test('parameters returns empty', () { + expect(mirror.parameters, isEmpty); + }); + + test('return type is dynamic', () { + expect(mirror.returnType, reflector.reflectType(dynamic)); + }); + + test('isGetter returns false', () { + expect(mirror.isGetter, false); + }); + + test('isSetter returns false', () { + expect(mirror.isSetter, false); + }); + + test('cannot invoke', () { + var invocation = new Invocation.method(#drive, []); + expect(() => mirror.invoke(invocation), throwsUnsupportedError); + }); + }); + + group('reflectInstance', () { + var mirror = reflector.reflectInstance(new Truck()); + + test('reflectee returns null', () { + expect(mirror.reflectee, null); + }); + + test('type returns empty', () { + expect(mirror.type.name, '(empty)'); + }); + + test('clazz returns empty', () { + expect(mirror.clazz.name, '(empty)'); + }); + + test('cannot getField', () { + expect(() => mirror.getField('wheelCount'), throwsUnsupportedError); + }); + }); +} + +class Truck { + int get wheelCount => 4; + + void drive() { + print('Vroom!!!'); + } +}