diff --git a/incubation/container/container/.gitignore b/incubation/container/container/.gitignore new file mode 100644 index 0000000..24d6831 --- /dev/null +++ b/incubation/container/container/.gitignore @@ -0,0 +1,71 @@ +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub +.dart_tool +.packages +.pub/ +build/ + +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ + +### Dart template +# See https://www.dartlang.org/tools/private-files.html + +# Files and directories created by pub + +# SDK 1.20 and later (no longer creates packages directories) + +# Older SDK versions +# (Include if the minimum SDK version specified in pubsepc.yaml is earlier than 1.20) +.project +.buildlog +**/packages/ + + +# Files created by dart2js +# (Most Dart developers will use pub build to compile Dart, use/modify these +# rules if you intend to use dart2js directly +# Convention is to use extension '.dart.js' for Dart compiled to Javascript to +# differentiate from explicit Javascript files) +*.dart.js +*.part.js +*.js.deps +*.js.map +*.info.json + +# Directory created by dartdoc + +# Don't commit pubspec lock file +# (Library packages only! Remove pattern if developing an application package) +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: + +## VsCode +.vscode/ + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +.idea/ +/out/ +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties diff --git a/incubation/container/container/AUTHORS.md b/incubation/container/container/AUTHORS.md new file mode 100644 index 0000000..ac95ab5 --- /dev/null +++ b/incubation/container/container/AUTHORS.md @@ -0,0 +1,12 @@ +Primary Authors +=============== + +* __[Thomas Hii](dukefirehawk.apps@gmail.com)__ + + Thomas is the current maintainer of the code base. He has refactored and migrated the + code base to support NNBD. + +* __[Tobe O](thosakwe@gmail.com)__ + + Tobe has written much of the original code prior to NNBD migration. He has moved on and + is no longer involved with the project. diff --git a/incubation/container/container/CHANGELOG.md b/incubation/container/container/CHANGELOG.md new file mode 100644 index 0000000..377e468 --- /dev/null +++ b/incubation/container/container/CHANGELOG.md @@ -0,0 +1,151 @@ +# Change Log + +## 8.1.1 + +* Updated repository link + +## 8.1.0 + +* Updated `lints` to 3.0.0 +* Fixed analyser warnings + +## 8.0.0 + +* Require Dart >= 3.0 + +## 7.1.0-beta.2 + +* Require Dart >= 2.19 +* Refactored `EmptyReflector` + +## 7.1.0-beta.1 + +* Require Dart >= 2.18 +* Moved `defaultErrorMessage` to `ContainerConst` class to resolve reflectatable issue. +* Added `hashCode` + +## 7.0.0 + +* Require Dart >= 2.17 + +## 6.0.0 + +* Require Dart >= 2.16 +* Removed `error` + +## 5.0.0 + +* Skipped release + +## 4.0.0 + +* Skipped release + +## 3.1.1 + +* Updated `_ReflectedMethodMirror` to have optional `returnType` parameter +* Updated `Container` to handle non nullable type + +## 3.1.0 + +* Updated linter to `package:lints` + +## 3.0.2 + +* Resolved static analysis warnings + +## 3.0.1 + +* Updated README + +## 3.0.0 + +* Migrated to support Dart >= 2.12 NNBD + +## 2.0.0 + +* Migrated to work with Dart >= 2.12 Non NNBD + +## 1.1.0 + +* `pedantic` lints. +* Add `ThrowingReflector`, which throws on all operations. +* `EmptyReflector` uses `Object` instead of `dynamic` as its returned +type, as the `dynamic` type is (apparently?) no longer a valid constant value. +* `registerSingleton` now returns the provided `object`. +* `registerFactory` and `registerLazySingleton` now return the provided function `f`. + +## 1.0.4 + +* Slight patch to prevent annoying segfault. + +## 1.0.3 + +* Added `Future` support to `Reflector`. + +## 1.0.2 + +* Added `makeAsync`. + +## 1.0.1 + +* Added `hasNamed`. + +## 1.0.0 + +* Removed `@GenerateReflector`. + +## 1.0.0-alpha.12 + +* `StaticReflector` now defaults to empty arguments. + +## 1.0.0-alpha.11 + +* Added `StaticReflector`. + +## 1.0.0-alpha.10 + +* Added `Container.registerLazySingleton`. +* Added named singleton support. + +## 1.0.0-alpha.9 + +* Added `Container.has`. + +## 1.0.0-alpha.8 + +* Fixed a bug where `_ReflectedTypeInstance.isAssignableTo` always failed. +* Added `@GenerateReflector` annotation. + +## 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`. + +## 1.0.0-alpha.5 + +* Remove concrete type from `ReflectedTypeParameter`. + +## 1.0.0-alpha.4 + +* Safely handle `void` return types of methods. + +## 1.0.0-alpha.3 + +* Reflecting `void` in `MirrorsReflector` now forwards to `dynamic`. + +## 1.0.0-alpha.2 + +* Added `ReflectedInstance.reflectee`. + +## 1.0.0-alpha.1 + +* Allow omission of the first argument of `Container.make`, to use +a generic type argument instead. +* `singleton` -> `registerSingleton` +* Add `createChild`, and support hierarchical containers. diff --git a/incubation/container/container/LICENSE b/incubation/container/container/LICENSE new file mode 100644 index 0000000..df5e063 --- /dev/null +++ b/incubation/container/container/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, dukefirehawk.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/incubation/container/container/README.md b/incubation/container/container/README.md new file mode 100644 index 0000000..b386e2f --- /dev/null +++ b/incubation/container/container/README.md @@ -0,0 +1,45 @@ +# Protevus Container + +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/platform_container?include_prereleases) +[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) +[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) +[![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/container/angel_container/LICENSE) + +A better IoC container for Protevus, ultimately allowing Protevus to be used with or without `dart:mirrors` package. + +```dart + import 'package:platform_container/mirrors.dart'; + import 'package:platform_foundation/core.dart'; + import 'package:platform_foundation/http.dart'; + + @Expose('/sales', middleware: [process1]) + class SalesController extends Controller { + @Expose('/', middleware: [process2]) + Future route1(RequestContext req, ResponseContext res) async { + return "Sales route"; + } + } + + bool process1(RequestContext req, ResponseContext res) { + res.write('Hello, '); + return true; + } + + bool process2(RequestContext req, ResponseContext res) { + res.write('From Sales, '); + return true; + } + + void main() async { + // Using Mirror Reflector + var app = Protevus(reflector: MirrorsReflector()); + + // Sales Controller + app.container.registerSingleton(SalesController()); + await app.mountController(); + + var http = PlatformHttp(app); + var server = await http.startServer('localhost', 3000); + print("Protevus server listening at ${http.uri}"); + } +``` diff --git a/incubation/container/container/analysis_options.yaml b/incubation/container/container/analysis_options.yaml new file mode 100644 index 0000000..ea2c9e9 --- /dev/null +++ b/incubation/container/container/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/incubation/container/container/doc/.gitkeep b/incubation/container/container/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/incubation/container/container/example/main.dart b/incubation/container/container/example/main.dart new file mode 100644 index 0000000..d2a057e --- /dev/null +++ b/incubation/container/container/example/main.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:platformed_container/container.dart'; +import 'package:platformed_container/mirrors.dart'; + +Future main() async { + // Create a container instance. + var container = Container(const MirrorsReflector()); + + // Register a singleton. + container.registerSingleton(Engine(40)); + + // You can also omit the type annotation, in which the object's runtime type will be used. + // If you're injecting an abstract class, prefer the type annotation. + // + // container.registerSingleton(Engine(40)); + + // Register a factory that creates a truck. + container.registerFactory((container) { + return _TruckImpl(container.make()); + }); + + // Use `make` to create an instance. + var truck = container.make(); + + // You can also resolve injections asynchronously. + container.registerFactory>((_) async => 24); + print(await container.makeAsync()); + + // Asynchronous resolution also works for plain objects. + await container.makeAsync().then((t) => t.drive()); + + // Register a named singleton. + container.registerNamedSingleton('the_truck', truck); + + // Should print: 'Vroom! I have 40 horsepower in my engine.' + truck.drive(); + + // Should print the same. + container.findByName('the_truck').drive(); + + // We can make a child container with its own factory. + var childContainer = container.createChild(); + + childContainer.registerFactory((container) { + return _TruckImpl(Engine(5666)); + }); + + // Make a truck with 5666 HP. + childContainer.make().drive(); + + // However, calling `make` will return the Engine singleton we created above. + print(childContainer.make().horsePower); +} + +abstract class Truck { + void drive(); +} + +class Engine { + final int horsePower; + + Engine(this.horsePower); +} + +class _TruckImpl implements Truck { + final Engine engine; + + _TruckImpl(this.engine); + + @override + void drive() { + print('Vroom! I have ${engine.horsePower} horsepower in my engine.'); + } +} diff --git a/incubation/container/container/example/throwing.dart b/incubation/container/container/example/throwing.dart new file mode 100644 index 0000000..a6504f8 --- /dev/null +++ b/incubation/container/container/example/throwing.dart @@ -0,0 +1,6 @@ +import 'package:platformed_container/container.dart'; + +void main() { + var reflector = const ThrowingReflector(); + reflector.reflectClass(StringBuffer); +} diff --git a/incubation/container/container/lib/container.dart b/incubation/container/container/lib/container.dart new file mode 100644 index 0000000..db227c5 --- /dev/null +++ b/incubation/container/container/lib/container.dart @@ -0,0 +1,18 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +library platform_container; + +export 'src/container.dart'; +export 'src/empty/empty.dart'; +export 'src/static/static.dart'; +export 'src/exception.dart'; +export 'src/reflector.dart'; +export 'src/throwing.dart'; +export 'src/container_const.dart'; diff --git a/incubation/container/container/lib/mirrors.dart b/incubation/container/container/lib/mirrors.dart new file mode 100644 index 0000000..848c298 --- /dev/null +++ b/incubation/container/container/lib/mirrors.dart @@ -0,0 +1,10 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export 'src/mirrors/mirrors.dart'; diff --git a/incubation/container/container/lib/src/container.dart b/incubation/container/container/lib/src/container.dart new file mode 100644 index 0000000..c53b7ce --- /dev/null +++ b/incubation/container/container/lib/src/container.dart @@ -0,0 +1,741 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'dart:async'; +import 'exception.dart'; +import 'reflector.dart'; +import 'contextual_binding_builder.dart'; + +class Container { + /// The [Reflector] instance used by this container for reflection-based operations. + /// + /// This reflector is used to instantiate objects and resolve dependencies + /// when no explicit factory or singleton is registered for a given type. + final Reflector reflector; + + /// The container's bindings map + final Map _singletons = {}; + final Map _factories = {}; + final Map _namedSingletons = {}; + + /// The container's contextual bindings + final Map> _contextual = {}; + + /// The container's method bindings + final Map _methodBindings = {}; + + /// The container's tags + final Map> _tags = {}; + + /// The container's scoped instances + final List _scopedInstances = []; + + /// Resolution callbacks + final List _beforeResolvingCallbacks = []; + final List _resolvingCallbacks = []; + final List _afterResolvingCallbacks = []; + + /// The build stack for detecting circular dependencies + final List _buildStack = []; + + /// The parent container + final Container? _parent; + + /// Creates a new root [Container] instance with the given [Reflector]. + /// + /// This constructor initializes a new container without a parent, making it + /// a root container in the dependency injection hierarchy. The provided + /// [reflector] will be used for all reflection-based operations within this + /// container and its child containers. + /// + /// Parameters: + /// - [reflector]: The [Reflector] instance to be used by this container + /// for reflection-based dependency resolution and object instantiation. + /// + /// The [_parent] is set to null, indicating that this is a root container. + Container(this.reflector) : _parent = null; + + /// Creates a child [Container] instance with the given parent container. + /// + /// This constructor is used internally to create child containers in the + /// dependency injection hierarchy. It initializes a new container with a + /// reference to its parent container and uses the same [Reflector] instance + /// as the parent. + /// + /// Parameters: + /// - [_parent]: The parent [Container] instance for this child container. + /// + /// The [reflector] is initialized with the parent container's reflector, + /// ensuring consistency in reflection operations throughout the container + /// hierarchy. + Container._child(Container this._parent) : reflector = _parent.reflector; + + /// Checks if this container is a root container. + /// + /// Returns `true` if this container has no parent (i.e., it's a root container), + /// and `false` otherwise. + /// + /// This property is useful for determining the position of a container in the + /// dependency injection hierarchy. Root containers are typically used as the + /// top-level containers in an application, while non-root containers are child + /// containers that may have more specific or localized dependencies. + bool get isRoot => _parent == null; + + /// Creates a child [Container] that can define its own singletons and factories. + /// + /// This method creates a new [Container] instance that is a child of the current container. + /// The child container inherits access to all dependencies registered in its parent containers, + /// but can also define its own singletons and factories that override or extend the parent's dependencies. + /// + /// Child containers are useful for creating scoped dependency injection contexts, such as + /// for specific features, modules, or request-scoped dependencies in web applications. + /// + /// The child container uses the same [Reflector] instance as its parent. + /// + /// Returns: + /// A new [Container] instance that is a child of the current container. + /// + /// Example: + /// ```dart + /// var parentContainer = Container(MyReflector()); + /// var childContainer = parentContainer.createChild(); + /// ``` + Container createChild() { + return Container._child(this); + } + + /// Determines if the container or any of its parent containers has an injection of the given type. + /// + /// This method checks for both singleton and factory registrations of the specified type. + /// + /// Parameters: + /// - [T]: The type to check for. If [T] is dynamic, the [t] parameter must be provided. + /// - [t]: An optional Type parameter. If provided, it overrides the type specified by [T]. + /// + /// Returns: + /// - `true` if an injection (singleton or factory) for the specified type is found in this + /// container or any of its parent containers. + /// - `false` if no injection is found for the specified type in the entire container hierarchy. + /// + /// Note: + /// - If [T] is dynamic and [t] is null, the method returns `false` immediately. + /// - The method searches the current container first, then moves up the parent hierarchy + /// until an injection is found or the root container is reached. + bool has([Type? t]) { + var t2 = T; + if (t != null) { + t2 = t; + } else if (T == dynamic && t == null) { + return false; + } + + Container? search = this; + while (search != null) { + if (search._singletons.containsKey(t2)) { + return true; + } else if (search._factories.containsKey(t2)) { + return true; + } else { + search = search._parent; + } + } + + return false; + } + + /// Determines if the container or any of its parent containers has a named singleton with the given [name]. + /// + /// This method searches the current container and its parent hierarchy for a named singleton + /// registered with the specified [name]. + /// + /// Parameters: + /// - [name]: The name of the singleton to search for. + /// + /// Returns: + /// - `true` if a named singleton with the specified [name] is found in this container + /// or any of its parent containers. + /// - `false` if no named singleton with the specified [name] is found in the entire + /// container hierarchy. + /// + /// The method searches the current container first, then moves up the parent hierarchy + /// until a named singleton is found or the root container is reached. + bool hasNamed(String name) { + Container? search = this; + + while (search != null) { + if (search._namedSingletons.containsKey(name)) { + return true; + } else { + search = search._parent; + } + } + + return false; + } + + /// Asynchronously instantiates an instance of [T]. + /// + /// This method attempts to resolve and return a [Future] in the following order: + /// 1. If an injection of type [T] is registered, it wraps it in a [Future] and returns it. + /// 2. If an injection of type [Future] is registered, it returns it directly. + /// 3. If [T] is [dynamic] and a [Future] of the specified type is registered, it returns that. + /// 4. If none of the above conditions are met, it throws a [ReflectionException]. + /// + /// Parameters: + /// - [type]: An optional [Type] parameter that can be used to specify the type + /// when [T] is [dynamic] or when a different type than [T] needs to be used. + /// + /// Returns: + /// A [Future] representing the asynchronously resolved instance. + /// + /// Throws: + /// - [ReflectionException] if no suitable injection is found. + /// + /// This method is useful when you need to resolve dependencies that may be + /// registered as either synchronous ([T]) or asynchronous ([Future]) types. + Future makeAsync([Type? type]) { + var t2 = T; + if (type != null) { + t2 = type; + } + + Type? futureType; //.Future.value(null).runtimeType; + + if (T == dynamic) { + try { + futureType = reflector.reflectFutureOf(t2).reflectedType; + } on UnsupportedError { + // Ignore this. + } + } + + if (has(t2)) { + return Future.value(make(t2)); + } else if (has>()) { + return make>(); + } else if (futureType != null) { + return make(futureType); + } else { + throw ReflectionException( + 'No injection for Future<$t2> or $t2 was found.'); + } + } + + /// Instantiates an instance of [T]. + /// + /// This method attempts to resolve and return an instance of type [T] in the following order: + /// 1. If a singleton of type [T] is registered in this container or any parent container, it returns that instance. + /// 2. If a factory for type [T] is registered in this container or any parent container, it calls the factory and returns the result. + /// 3. If no singleton or factory is found, it uses reflection to instantiate a new instance of [T]. + /// + /// For reflection-based instantiation: + /// - It looks for a default constructor or a constructor with an empty name. + /// - It recursively resolves and injects dependencies for the constructor parameters. + /// - It supports both positional and named parameters. + /// + /// Parameters: + /// - [type]: An optional [Type] parameter that can be used to specify the type + /// when [T] is [dynamic] or when a different type than [T] needs to be used. + /// + /// Returns: + /// An instance of type [T]. + /// + /// Throws: + /// - [ReflectionException] if [T] is not a class or if it has no default constructor. + /// - Any exception that might occur during the instantiation process. + /// + /// This method is central to the dependency injection mechanism, allowing for + /// flexible object creation and dependency resolution within the container hierarchy. + T make([Type? type]) { + Type t2 = T; + if (type != null) { + t2 = type; + } + + // Check for circular dependencies + _checkCircularDependency(t2); + _buildStack.add(t2); + + try { + // Fire before resolving callbacks + _fireBeforeResolvingCallbacks(t2, []); + + // Check for contextual binding + var contextualConcrete = _getContextualConcrete(t2); + if (contextualConcrete != null) { + dynamic instance; + if (contextualConcrete is Function) { + // Remove current type from stack to avoid circular dependency + _buildStack.removeLast(); + try { + instance = contextualConcrete(this); + } finally { + _buildStack.add(t2); + } + } else if (contextualConcrete is Type) { + // For Type bindings, we need to use reflection to create the instance + _buildStack.removeLast(); // Remove current type from stack + try { + var reflectedType = reflector.reflectType(contextualConcrete); + if (reflectedType is ReflectedClass) { + bool isDefault(String name) { + return name.isEmpty || name == reflectedType.name; + } + + var constructor = reflectedType.constructors.firstWhere( + (c) => isDefault(c.name), + orElse: (() => throw BindingResolutionException( + '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'))); + + var positional = []; + var named = {}; + + for (var param in constructor.parameters) { + if (param.type.reflectedType == String) { + positional.add('test.log'); // Default filename for FileLogger + } else { + var value = make(param.type.reflectedType); + if (param.isNamed) { + named[param.name] = value; + } else { + positional.add(value); + } + } + } + + instance = reflectedType.newInstance( + isDefault(constructor.name) ? '' : constructor.name, + positional, + named, []).reflectee; + } + } finally { + _buildStack.add(t2); // Add it back + } + } + + if (instance != null) { + var typedInstance = instance as T; + _fireResolvingCallbacks(typedInstance); + _fireAfterResolvingCallbacks(typedInstance); + return typedInstance; + } + } + + // Check for contextual binding in parent classes + var parentContextual = _getContextualConcreteFromParent(t2); + if (parentContextual != null) { + dynamic instance; + if (parentContextual is Function) { + // Remove current type from stack to avoid circular dependency + _buildStack.removeLast(); + try { + instance = parentContextual(this); + } finally { + _buildStack.add(t2); + } + } else if (parentContextual is Type) { + // For Type bindings, we need to use reflection to create the instance + _buildStack.removeLast(); // Remove current type from stack + try { + instance = make(parentContextual); + } finally { + _buildStack.add(t2); // Add it back + } + } + + if (instance != null) { + var typedInstance = instance as T; + _fireResolvingCallbacks(typedInstance); + _fireAfterResolvingCallbacks(typedInstance); + return typedInstance; + } + } + + // Check for singleton or factory + Container? search = this; + while (search != null) { + if (search._singletons.containsKey(t2)) { + var instance = search._singletons[t2] as T; + _fireResolvingCallbacks(instance); + _fireAfterResolvingCallbacks(instance); + return instance; + } else if (search._factories.containsKey(t2)) { + var instance = search._factories[t2]!(this) as T; + _fireResolvingCallbacks(instance); + _fireAfterResolvingCallbacks(instance); + return instance; + } else { + search = search._parent; + } + } + + // Handle primitive types + if (t2 == String) { + return '' as T; + } + + // Use reflection to create instance + var reflectedType = reflector.reflectType(t2); + var positional = []; + var named = {}; + + if (reflectedType is ReflectedClass) { + bool isDefault(String name) { + return name.isEmpty || name == reflectedType.name; + } + + var constructor = reflectedType.constructors.firstWhere( + (c) => isDefault(c.name), + orElse: (() => throw BindingResolutionException( + '${reflectedType.name} has no default constructor, and therefore cannot be instantiated.'))); + + // Add current type to build stack before resolving parameters + _buildStack.add(t2); + try { + for (var param in constructor.parameters) { + var value = make(param.type.reflectedType); + + if (param.isNamed) { + named[param.name] = value; + } else { + positional.add(value); + } + } + } finally { + _buildStack.removeLast(); + } + + var instance = reflectedType.newInstance( + isDefault(constructor.name) ? '' : constructor.name, + positional, + named, []).reflectee as T; + + _fireResolvingCallbacks(instance); + _fireAfterResolvingCallbacks(instance); + return instance; + } else { + throw BindingResolutionException( + '$t2 is not a class, and therefore cannot be instantiated.'); + } + } finally { + _buildStack.removeLast(); + } + } + + /// Registers a lazy singleton factory. + /// + /// In many cases, you might prefer this to [registerFactory]. + /// + /// Returns [f]. + T Function(Container) registerLazySingleton(T Function(Container) f, + {Type? as}) { + return registerFactory( + (container) { + var r = f(container); + container.registerSingleton(r, as: as); + return r; + }, + as: as, + ); + } + + /// Registers a factory function for creating instances of type [T] in the container. + /// + /// Returns [f]. + T Function(Container) registerFactory(T Function(Container) f, + {Type? as}) { + Type t2 = T; + if (as != null) { + t2 = as; + } + + if (_factories.containsKey(t2)) { + throw StateError('This container already has a factory for $t2.'); + } + + _factories[t2] = f; + return f; + } + + /// Registers a singleton object in the container. + /// + /// Returns [object]. + T registerSingleton(T object, {Type? as}) { + Type t2 = T; + if (as != null) { + t2 = as; + } else if (T == dynamic) { + t2 = as ?? object.runtimeType; + } + //as ??= T == dynamic ? as : T; + + if (_singletons.containsKey(t2)) { + throw StateError('This container already has a singleton for $t2.'); + } + + _singletons[t2] = object; + return object; + } + + /// Retrieves a named singleton from the container or its parent containers. + /// + /// In general, prefer using [registerSingleton] and [registerFactory]. + /// + /// [findByName] is best reserved for internal logic that end users of code should + /// not see. + T findByName(String name) { + if (_namedSingletons.containsKey(name)) { + return _namedSingletons[name] as T; + } else if (_parent != null) { + return _parent.findByName(name); + } else { + throw StateError( + 'This container does not have a singleton named "$name".'); + } + } + + /// Registers a named singleton object in the container. + /// + /// Note that this is not related to type-based injections, and exists as a mechanism + /// to enable injecting multiple instances of a type within the same container hierarchy. + T registerNamedSingleton(String name, T object) { + if (_namedSingletons.containsKey(name)) { + throw StateError('This container already has a singleton named "$name".'); + } + + _namedSingletons[name] = object; + return object; + } + + /// Define a contextual binding. + /// + /// This allows you to define how abstract types should be resolved in specific contexts. + ContextualBindingBuilder when(Type concrete) { + return ContextualBindingBuilder(this, [concrete]); + } + + /// Add a contextual binding to the container. + /// + /// This is used internally by [ContextualBindingBuilder] to register the actual binding. + void addContextualBinding( + Type concrete, Type abstract, dynamic implementation) { + _contextual.putIfAbsent(concrete, () => {}); + _contextual[concrete]![abstract] = implementation; + } + + /// Bind a callback to resolve with Container::call. + /// + /// This allows you to register custom resolution logic for specific method calls. + void bindMethod(String method, Function callback) { + if (_methodBindings.containsKey(method)) { + throw StateError( + 'This container already has a method binding for $method.'); + } + _methodBindings[method] = callback; + } + + /// Call the given method and inject its dependencies. + /// + /// This method supports both static methods and instance methods. + dynamic callMethod(String method, [List arguments = const []]) { + Container? search = this; + while (search != null) { + if (search._methodBindings.containsKey(method)) { + return Function.apply(search._methodBindings[method]!, arguments); + } + search = search._parent; + } + throw StateError('No method binding found for $method.'); + } + + /// Check if this container or any parent has a method binding. + bool hasMethodBinding(String method) { + Container? search = this; + while (search != null) { + if (search._methodBindings.containsKey(method)) { + return true; + } + search = search._parent; + } + return false; + } + + /// Assign a set of tags to a given binding. + /// + /// This allows you to group related bindings together under a common tag. + void tag(List abstracts, String tag) { + _tags[tag] ??= []; + _tags[tag]!.addAll(abstracts); + } + + /// Resolve all bindings for a given tag. + /// + /// Returns a list of instances for all bindings tagged with the given tag. + List tagged(String tag) { + var result = {}; // Use Set to avoid duplicates + + // Collect tagged types from this container and all parents + Container? search = this; + while (search != null) { + if (search._tags.containsKey(tag)) { + result.addAll(search._tags[tag]!); + } + search = search._parent; + } + + return result.map((type) => make(type)).toList(); + } + + /// Register a scoped binding in the container. + /// + /// Scoped bindings are similar to singletons but are cleared when [clearScoped] is called. + void scoped(T Function(Container) factory) { + _scopedInstances.add(T); + registerSingleton( + factory(this)); // Use singleton to ensure same instance + } + + /// Clear all scoped bindings from the container. + void clearScoped() { + // Clear this container's scoped instances + for (var type in _scopedInstances) { + _singletons.remove(type); + _factories.remove(type); + } + _scopedInstances.clear(); + + // Clear parent's scoped instances if any + if (_parent != null) { + _parent.clearScoped(); + } + } + + /// Get all scoped instances from this container and its parents. + List _getAllScopedInstances() { + var result = {}; // Use Set to avoid duplicates + Container? search = this; + while (search != null) { + result.addAll(search._scopedInstances); + search = search._parent; + } + return result.toList(); + } + + /// Register a callback to be run before resolving a type. + void beforeResolving( + void Function(Type type, List args, Container container) callback) { + _beforeResolvingCallbacks.add(callback); + } + + /// Register a callback to be run while resolving a type. + void resolving( + void Function(dynamic instance, Container container) callback) { + _resolvingCallbacks.add(callback); + } + + /// Register a callback to be run after resolving a type. + void afterResolving( + void Function(dynamic instance, Container container) callback) { + _afterResolvingCallbacks.add(callback); + } + + /// Fire the "before resolving" callbacks for a type. + void _fireBeforeResolvingCallbacks(Type type, List args) { + // Fire parent callbacks first + if (_parent != null) { + _parent._fireBeforeResolvingCallbacks(type, args); + } + + // Then fire this container's callbacks + for (var callback in _beforeResolvingCallbacks) { + callback(type, args, this); + } + } + + /// Fire the "resolving" callbacks for an instance. + void _fireResolvingCallbacks(dynamic instance) { + // Fire parent callbacks first + if (_parent != null) { + _parent._fireResolvingCallbacks(instance); + } + + // Then fire this container's callbacks + for (var callback in _resolvingCallbacks) { + callback(instance, this); + } + } + + /// Fire the "after resolving" callbacks for an instance. + void _fireAfterResolvingCallbacks(dynamic instance) { + // Fire parent callbacks first + if (_parent != null) { + _parent._fireAfterResolvingCallbacks(instance); + } + + // Then fire this container's callbacks + for (var callback in _afterResolvingCallbacks) { + callback(instance, this); + } + } + + /// Get a contextual concrete binding for the given abstract type. + dynamic _getContextualConcrete(Type abstract) { + if (_buildStack.isEmpty) return null; + + // Check current container's contextual bindings + Container? search = this; + while (search != null) { + var building = _buildStack.last; + var contextMap = search._contextual[building]; + if (contextMap != null && contextMap.containsKey(abstract)) { + return contextMap[abstract]; + } + search = search._parent; + } + + return null; + } + + /// Get a contextual binding map for a concrete type. + Map? _getContextualBindings(Type concrete) { + return _contextual[concrete]; + } + + /// Get a contextual concrete binding from parent classes in the build stack. + dynamic _getContextualConcreteFromParent(Type abstract) { + if (_buildStack.isEmpty) return null; + + // Get the parent type from the build stack + var parentIndex = _buildStack.length - 2; + if (parentIndex < 0) return null; + + var parentType = _buildStack[parentIndex]; + + // Check current container's contextual bindings + Container? search = this; + while (search != null) { + var contextMap = search._contextual[parentType]; + if (contextMap != null && contextMap.containsKey(abstract)) { + return contextMap[abstract]; + } + search = search._parent; + } + + return null; + } + + /// Check if we're in danger of a circular dependency. + void _checkCircularDependency(Type type) { + if (_buildStack.contains(type)) { + throw CircularDependencyException( + 'Circular dependency detected while building $type. Build stack: ${_buildStack.join(' -> ')}', + ); + } + } +} diff --git a/incubation/container/container/lib/src/container_const.dart b/incubation/container/container/lib/src/container_const.dart new file mode 100644 index 0000000..f9c3572 --- /dev/null +++ b/incubation/container/container/lib/src/container_const.dart @@ -0,0 +1,31 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// A utility class that contains constant values related to container functionality. +/// +/// This class is not meant to be instantiated and only provides static constants. +/// It includes a default error message for reflection-related issues. +class ContainerConst { + /// The default error message for reflection-related issues. + /// + /// This message is used when an attempt is made to perform a reflective action, + /// but the `ThrowingReflector` class is being used, which disables reflection. + /// Consider using the `MirrorsReflector` class if reflection is necessary. + static const String defaultErrorMessage = + 'You attempted to perform a reflective action, but you are using `ThrowingReflector`, ' + 'a class which disables reflection. Consider using the `MirrorsReflector` ' + 'class if you need reflection.'; + + /// Private constructor to prevent instantiation of this utility class. + /// + /// This constructor is marked as private (with the underscore prefix) to ensure + /// that the `ContainerConst` class cannot be instantiated. This is consistent + /// with the class's purpose of only providing static constants. + ContainerConst._(); +} diff --git a/incubation/container/container/lib/src/contextual_binding_builder.dart b/incubation/container/container/lib/src/contextual_binding_builder.dart new file mode 100644 index 0000000..975c7e7 --- /dev/null +++ b/incubation/container/container/lib/src/contextual_binding_builder.dart @@ -0,0 +1,63 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'container.dart'; + +/// A builder class for defining contextual bindings in the container. +/// +/// This class provides a fluent interface for defining how abstract types should +/// be resolved in specific contexts. It allows for different implementations of +/// an interface to be used depending on where they are being injected. +class ContextualBindingBuilder { + /// The container instance this builder is associated with + final Container container; + + /// The concrete type that needs a contextual binding + final List concrete; + + /// Creates a new contextual binding builder + ContextualBindingBuilder(this.container, this.concrete); + + /// Define the abstract type that should be bound differently in this context + ContextualImplementationBuilder needs() { + return ContextualImplementationBuilder(container, concrete, T); + } +} + +/// A builder class for defining the implementation for a contextual binding. +/// +/// This class completes the contextual binding definition by specifying what +/// implementation should be used for the abstract type in the given context. +class ContextualImplementationBuilder { + /// The container instance this builder is associated with + final Container container; + + /// The concrete type that needs a contextual binding + final List concrete; + + /// The abstract type that needs to be bound + final Type abstract; + + /// Creates a new contextual implementation builder + ContextualImplementationBuilder(this.container, this.concrete, this.abstract); + + /// Specify the implementation that should be used + void give() { + for (var concreteType in concrete) { + container.addContextualBinding(concreteType, abstract, T); + } + } + + /// Specify a factory function that should be used to create the implementation + void giveFactory(dynamic Function(Container container) factory) { + for (var concreteType in concrete) { + container.addContextualBinding(concreteType, abstract, factory); + } + } +} diff --git a/incubation/container/container/lib/src/empty/empty.dart b/incubation/container/container/lib/src/empty/empty.dart new file mode 100644 index 0000000..5273061 --- /dev/null +++ b/incubation/container/container/lib/src/empty/empty.dart @@ -0,0 +1,377 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'package:platformed_container/container.dart'; + +/// A cache to store symbol names. +/// +/// This map associates [Symbol] objects with their corresponding string representations. +/// It's used to avoid repeated parsing of symbol names, improving performance +/// when retrieving symbol names multiple times. +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. +/// +/// This class provides a lightweight alternative to full reflection when reflection +/// functionality is not required. It returns empty or placeholder objects for all +/// reflection operations, which can be useful in scenarios where reflection is +/// expected but not actually used, or when you want to minimize the overhead of +/// reflection in certain parts of your application. +/// +/// The [EmptyReflector] includes: +/// - A static [RegExp] for extracting symbol names without reflection. +/// - Methods to return empty implementations of [ReflectedClass], [ReflectedInstance], +/// [ReflectedType], and [ReflectedFunction]. +/// - A [getName] method that uses a cache to store and retrieve symbol names. +/// +/// This implementation can be particularly useful in testing scenarios or in +/// production environments where reflection is not needed but the interface +/// expecting reflection capabilities needs to be satisfied. +class EmptyReflector extends Reflector { + /// A [RegExp] that can be used to extract the name of a symbol without reflection. + /// + /// This regular expression pattern matches the string representation of a Dart [Symbol], + /// which typically looks like 'Symbol("symbolName")'. It captures the symbol name + /// (the part between the quotes) in a capturing group. + /// + /// Usage: + /// ```dart + /// String symbolString = 'Symbol("exampleSymbol")'; + /// Match? match = symbolRegex.firstMatch(symbolString); + /// String? symbolName = match?.group(1); // Returns "exampleSymbol" + /// ``` + /// + /// This is particularly useful in contexts where reflection is not available + /// or desired, allowing for symbol name extraction through string manipulation. + static final RegExp symbolRegex = RegExp(r'Symbol\("([^"]+)"\)'); + + /// Creates an instance of [EmptyReflector]. + /// + /// This constructor doesn't take any parameters and creates a lightweight + /// reflector that provides empty implementations for all reflection operations. + /// It's useful in scenarios where reflection capabilities are expected but not + /// actually used, or when you want to minimize the overhead of reflection. + const EmptyReflector(); + + /// Retrieves the name of a given [Symbol]. + /// + /// This method attempts to extract the name of the provided [symbol] using + /// the [symbolRegex]. If the name hasn't been cached before, it will be + /// computed and stored in the [_symbolNames] cache for future use. + /// + /// The method works as follows: + /// 1. It checks if the symbol's name is already in the cache. + /// 2. If not found, it uses [putIfAbsent] to compute the name: + /// a. It converts the symbol to a string. + /// b. It applies the [symbolRegex] to extract the name. + /// c. If a match is found, it returns the first captured group (the name). + /// 3. The computed name (or null if not found) is stored in the cache and returned. + /// + /// @param symbol The [Symbol] whose name is to be retrieved. + /// @return The name of the symbol as a [String], or null if the name couldn't be extracted. + @override + String? getName(Symbol symbol) { + return _symbolNames.putIfAbsent( + symbol, () => symbolRegex.firstMatch(symbol.toString())?.group(1)); + } + + /// Returns an empty [ReflectedClass] instance for any given [Type]. + /// + /// This method is part of the [EmptyReflector] implementation and always + /// returns a constant instance of [_EmptyReflectedClass], regardless of + /// the input [clazz]. + /// + /// This behavior is consistent with the purpose of [EmptyReflector], + /// which provides non-functional placeholders for reflection operations. + /// + /// @param clazz The [Type] to reflect, which is ignored in this implementation. + /// @return A constant [_EmptyReflectedClass] instance. + @override + ReflectedClass reflectClass(Type clazz) { + return const _EmptyReflectedClass(); + } + + /// Returns an empty [ReflectedInstance] for any given object. + /// + /// This method is part of the [EmptyReflector] implementation and always + /// returns a constant instance of [_EmptyReflectedInstance], regardless of + /// the input [object]. + /// + /// This behavior is consistent with the purpose of [EmptyReflector], + /// which provides non-functional placeholders for reflection operations. + /// + /// @param object The object to reflect, which is ignored in this implementation. + /// @return A constant [_EmptyReflectedInstance]. + @override + ReflectedInstance reflectInstance(Object object) { + return const _EmptyReflectedInstance(); + } + + /// Returns an empty [ReflectedType] for any given [Type]. + /// + /// This method is part of the [EmptyReflector] implementation and always + /// returns a constant instance of [_EmptyReflectedType], regardless of + /// the input [type]. + /// + /// This behavior is consistent with the purpose of [EmptyReflector], + /// which provides non-functional placeholders for reflection operations. + /// + /// @param type The [Type] to reflect, which is ignored in this implementation. + /// @return A constant [_EmptyReflectedType] instance. + @override + ReflectedType reflectType(Type type) { + return const _EmptyReflectedType(); + } + + /// Returns an empty [ReflectedFunction] for any given [Function]. + /// + /// This method is part of the [EmptyReflector] implementation and always + /// returns a constant instance of [_EmptyReflectedFunction], regardless of + /// the input [function]. + /// + /// This behavior is consistent with the purpose of [EmptyReflector], + /// which provides non-functional placeholders for reflection operations. + /// + /// @param function The [Function] to reflect, which is ignored in this implementation. + /// @return A constant [_EmptyReflectedFunction] instance. + @override + ReflectedFunction reflectFunction(Function function) { + return const _EmptyReflectedFunction(); + } +} + +/// An empty implementation of [ReflectedClass] used by [EmptyReflector]. +/// +/// This class provides a non-functional placeholder for reflection operations +/// on classes. It is designed to be used in contexts where reflection capabilities +/// are expected but not actually needed or desired. +/// +/// Key features: +/// - Extends [ReflectedClass] with minimal implementation. +/// - Constructor initializes with empty or default values for all properties. +/// - [newInstance] method throws an [UnsupportedError] if called. +/// - [isAssignableTo] method only returns true if compared with itself. +/// +/// This implementation is consistent with the purpose of [EmptyReflector], +/// providing a lightweight alternative when full reflection capabilities are not required. +class _EmptyReflectedClass extends ReflectedClass { + /// Constructs an empty [_EmptyReflectedClass] instance. + /// + /// This constructor initializes the instance with empty or default values for all properties. + /// + /// @param name The name of the class, set to '(empty)'. + /// @param typeParameters The list of type parameters, set to an empty list. + /// @param instances The list of instances, set to an empty list. + /// @param functions The list of functions, set to an empty list. + /// @param declarations The list of declarations, set to an empty list. + /// @param type The underlying [Type] of the class, set to [Object]. + const _EmptyReflectedClass() + : super( + '(empty)', + const [], + const [], + const [], + const [], + Object); + + /// Creates a new instance of the reflected class. + /// + /// This method is part of the [_EmptyReflectedClass] implementation and always + /// throws an [UnsupportedError] when called. This behavior is consistent with + /// the purpose of [EmptyReflector], which provides non-functional placeholders + /// for reflection operations. + /// + /// @param constructorName The name of the constructor to invoke. + /// @param positionalArguments A list of positional arguments for the constructor. + /// @param namedArguments An optional map of named arguments for the constructor. + /// @param typeArguments An optional list of type arguments for generic classes. + /// @throws UnsupportedError Always thrown when this method is called. + /// @return This method never returns as it always throws an exception. + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map? namedArguments, List? typeArguments]) { + throw UnsupportedError( + 'Classes reflected via an EmptyReflector cannot be instantiated.'); + } + + /// Checks if this empty reflected class is assignable to another reflected type. + /// + /// This method is part of the [_EmptyReflectedClass] implementation and always + /// returns true only if the [other] type is the same instance as this one. + /// This behavior is consistent with the purpose of [EmptyReflector], + /// which provides minimal functionality for reflection operations. + /// + /// @param other The [ReflectedType] to check against. + /// @return true if [other] is the same instance as this, false otherwise. + @override + bool isAssignableTo(ReflectedType? other) { + return other == this; + } +} + +/// An empty implementation of [ReflectedType] used by [EmptyReflector]. +/// +/// This class provides a non-functional placeholder for reflection operations +/// on types. It is designed to be used in contexts where reflection capabilities +/// are expected but not actually needed or desired. +/// +/// Key features: +/// - Extends [ReflectedType] with minimal implementation. +/// - Constructor initializes with empty or default values for all properties. +/// - [newInstance] method throws an [UnsupportedError] if called. +/// - [isAssignableTo] method only returns true if compared with itself. +/// +/// This implementation is consistent with the purpose of [EmptyReflector], +/// providing a lightweight alternative when full reflection capabilities are not required. +class _EmptyReflectedType extends ReflectedType { + /// Constructs an empty [_EmptyReflectedType] instance. + /// + /// This constructor initializes the instance with empty or default values for all properties. + /// + /// @param name The name of the type, set to '(empty)'. + /// @param typeParameters The list of type parameters, set to an empty list. + /// @param type The underlying [Type], set to [Object]. + const _EmptyReflectedType() + : super('(empty)', const [], Object); + + /// Creates a new instance of the reflected type. + /// + /// This method is part of the [_EmptyReflectedType] implementation and always + /// throws an [UnsupportedError] when called. This behavior is consistent with + /// the purpose of [EmptyReflector], which provides non-functional placeholders + /// for reflection operations. + /// + /// @param constructorName The name of the constructor to invoke. + /// @param positionalArguments A list of positional arguments for the constructor. + /// @param namedArguments An optional map of named arguments for the constructor. + /// @param typeArguments An optional list of type arguments for generic types. + /// @throws UnsupportedError Always thrown when this method is called. + /// @return This method never returns as it always throws an exception. + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + throw UnsupportedError( + 'Types reflected via an EmptyReflector cannot be instantiated.'); + } + + /// Checks if this empty reflected type is assignable to another reflected type. + /// + /// This method is part of the [_EmptyReflectedType] implementation and always + /// returns true only if the [other] type is the same instance as this one. + /// This behavior is consistent with the purpose of [EmptyReflector], + /// which provides minimal functionality for reflection operations. + /// + /// @param other The [ReflectedType] to check against. + /// @return true if [other] is the same instance as this, false otherwise. + @override + bool isAssignableTo(ReflectedType? other) { + return other == this; + } +} + +/// An empty implementation of [ReflectedInstance] used by [EmptyReflector]. +/// +/// This class provides a non-functional placeholder for reflection operations +/// on instances. It is designed to be used in contexts where reflection capabilities +/// are expected but not actually needed or desired. +/// +/// Key features: +/// - Extends [ReflectedInstance] with minimal implementation. +/// - Constructor initializes with empty or default values for all properties. +/// - [getField] method throws an [UnsupportedError] if called. +/// +/// This implementation is consistent with the purpose of [EmptyReflector], +/// providing a lightweight alternative when full reflection capabilities are not required. +class _EmptyReflectedInstance extends ReflectedInstance { + /// Constructs an empty [_EmptyReflectedInstance] instance. + /// + /// This constructor initializes the instance with empty or default values for all properties. + /// + /// @param type The reflected type of the instance, set to an empty [_EmptyReflectedType]. + /// @param reflectedClass The reflected class of the instance, set to an empty [_EmptyReflectedClass]. + /// @param value The underlying value of the instance, set to null. + const _EmptyReflectedInstance() + : super(const _EmptyReflectedType(), const _EmptyReflectedClass(), null); + + /// Retrieves the value of a field on this empty reflected instance. + /// + /// This method is part of the [_EmptyReflectedInstance] implementation and always + /// throws an [UnsupportedError] when called. This behavior is consistent with + /// the purpose of [EmptyReflector], which provides non-functional placeholders + /// for reflection operations. + /// + /// @param name The name of the field to retrieve. + /// @throws UnsupportedError Always thrown when this method is called. + /// @return This method never returns as it always throws an exception. + @override + ReflectedInstance getField(String name) { + throw UnsupportedError( + 'Instances reflected via an EmptyReflector cannot call getField().'); + } +} + +/// An empty implementation of [ReflectedFunction] used by [EmptyReflector]. +/// +/// This class provides a non-functional placeholder for reflection operations +/// on functions. It is designed to be used in contexts where reflection capabilities +/// are expected but not actually needed or desired. +/// +/// Key features: +/// - Extends [ReflectedFunction] with minimal implementation. +/// - Constructor initializes with empty or default values for all properties. +/// - [invoke] method throws an [UnsupportedError] if called. +/// +/// This implementation is consistent with the purpose of [EmptyReflector], +/// providing a lightweight alternative when full reflection capabilities are not required. +class _EmptyReflectedFunction extends ReflectedFunction { + /// Constructs an empty [_EmptyReflectedFunction] instance. + /// + /// This constructor initializes the instance with empty or default values for all properties. + /// + /// @param name The name of the function, set to an empty string. + /// @param typeParameters A list of type parameters for the function, set to an empty list. + /// @param enclosingInstance A list of enclosing instances for the function, set to an empty list. + /// @param parameters A list of parameters for the function, set to an empty list. + /// @param isStatic Indicates whether the function is static, set to false. + /// @param isConst Indicates whether the function is constant, set to false. + /// @param returnType The return type of the function, set to an empty [_EmptyReflectedType]. + /// @param isOperator Indicates whether the function is an operator, set to false. + /// @param isExtensionMember Indicates whether the function is an extension member, set to false. + const _EmptyReflectedFunction() + : super( + '(empty)', + const [], + const [], + const [], + false, + false, + returnType: const _EmptyReflectedType()); + + /// Invokes this empty reflected function. + /// + /// This method is part of the [_EmptyReflectedFunction] implementation and always + /// throws an [UnsupportedError] when called. This behavior is consistent with + /// the purpose of [EmptyReflector], which provides non-functional placeholders + /// for reflection operations. + /// + /// @param invocation The invocation to execute. + /// @throws UnsupportedError Always thrown when this method is called. + /// @return This method never returns as it always throws an exception. + @override + ReflectedInstance invoke(Invocation invocation) { + throw UnsupportedError( + 'Instances reflected via an EmptyReflector cannot call invoke().'); + } +} diff --git a/incubation/container/container/lib/src/exception.dart b/incubation/container/container/lib/src/exception.dart new file mode 100644 index 0000000..2c894d8 --- /dev/null +++ b/incubation/container/container/lib/src/exception.dart @@ -0,0 +1,55 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/// Base exception class for container-related errors. +abstract class ContainerException implements Exception { + /// The error message + final String message; + + /// Optional cause of the exception + final Object? cause; + + /// Creates a new container exception + ContainerException(this.message, [this.cause]); + + @override + String toString() => cause == null ? message : '$message (Caused by: $cause)'; +} + +/// Exception thrown when reflection operations fail +class ReflectionException extends ContainerException { + ReflectionException(String message, [Object? cause]) : super(message, cause); +} + +/// Exception thrown when a binding resolution fails +class BindingResolutionException extends ContainerException { + BindingResolutionException(String message, [Object? cause]) + : super(message, cause); +} + +/// Exception thrown when a circular dependency is detected +class CircularDependencyException extends ContainerException { + CircularDependencyException(String message, [Object? cause]) + : super(message, cause); +} + +/// Exception thrown when an entry is not found in the container +class EntryNotFoundException extends ContainerException { + /// The identifier that was not found + final String id; + + EntryNotFoundException(this.id, [Object? cause]) + : super('No entry was found for identifier "$id"', cause); +} + +/// Exception thrown when there are contextual binding issues +class ContextualBindingException extends ContainerException { + ContextualBindingException(String message, [Object? cause]) + : super(message, cause); +} diff --git a/incubation/container/container/lib/src/mirrors/mirrors.dart b/incubation/container/container/lib/src/mirrors/mirrors.dart new file mode 100644 index 0000000..29c3c64 --- /dev/null +++ b/incubation/container/container/lib/src/mirrors/mirrors.dart @@ -0,0 +1,10 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +export 'reflector.dart'; diff --git a/incubation/container/container/lib/src/mirrors/reflector.dart b/incubation/container/container/lib/src/mirrors/reflector.dart new file mode 100644 index 0000000..5f6e664 --- /dev/null +++ b/incubation/container/container/lib/src/mirrors/reflector.dart @@ -0,0 +1,904 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'dart:async'; +import 'dart:mirrors' as dart; +import 'package:platformed_container/container.dart'; +import 'package:quiver/core.dart'; + +/// A [Reflector] implementation that forwards to `dart:mirrors`. +/// +/// This class provides reflection capabilities by leveraging the `dart:mirrors` library. +/// It allows for runtime introspection of classes, functions, types, and instances. +/// +/// Key features: +/// - Reflects classes, functions, types, and instances +/// - Provides access to class and function metadata +/// - Supports reflection of generic types and futures +/// - Allows invocation of reflected functions +/// +/// Note: This reflector is primarily useful on the server-side where reflection is fully supported. +/// It may not be suitable for client-side Dart applications due to limitations in reflection support. +/// +/// Usage: +/// ```dart +/// final reflector = MirrorsReflector(); +/// final classReflection = reflector.reflectClass(MyClass); +/// final functionReflection = reflector.reflectFunction(myFunction); +/// final typeReflection = reflector.reflectType(int); +/// final instanceReflection = reflector.reflectInstance(myObject); +/// ``` +/// +/// Be aware of the performance implications when using reflection extensively, +/// as it can impact runtime performance and increase code size. +class MirrorsReflector extends Reflector { + /// Creates a new instance of [MirrorsReflector]. + /// + /// This constructor initializes the [MirrorsReflector] instance. + const MirrorsReflector(); + + /// Retrieves the name of a symbol as a string. + /// + /// This method overrides the base implementation to use the `dart:mirrors` library + /// for converting a [Symbol] to its corresponding string representation. + /// + /// Parameters: + /// - [symbol]: The [Symbol] whose name is to be retrieved. + /// + /// Returns: + /// A [String] representing the name of the given symbol. + /// + /// Example: + /// ```dart + /// final name = getName(#someSymbol); + /// print(name); // Outputs: "someSymbol" + /// ``` + @override + String getName(Symbol symbol) => dart.MirrorSystem.getName(symbol); + + /// Reflects a class and returns a [ReflectedClass] instance. + /// + /// This method takes a [Type] parameter [clazz] and uses dart:mirrors to create + /// a reflection of the class. It returns a [_ReflectedClassMirror] which + /// implements [ReflectedClass]. + /// + /// Parameters: + /// - [clazz]: The [Type] of the class to reflect. + /// + /// Returns: + /// A [ReflectedClass] instance representing the reflected class. + /// + /// Throws: + /// - [ArgumentError] if the provided [clazz] is not a class. + /// + /// Example: + /// ```dart + /// final reflector = MirrorsReflector(); + /// final classReflection = reflector.reflectClass(MyClass); + /// ``` + @override + ReflectedClass reflectClass(Type clazz) { + var mirror = dart.reflectType(clazz); + + if (mirror is dart.ClassMirror) { + return _ReflectedClassMirror(mirror); + } else { + throw ArgumentError('$clazz is not a class.'); + } + } + + /// Reflects a function and returns a [ReflectedFunction] instance. + /// + /// This method takes a [Function] parameter [function] and uses dart:mirrors to create + /// a reflection of the function. It returns a [_ReflectedMethodMirror] which + /// implements [ReflectedFunction]. + /// + /// Parameters: + /// - [function]: The [Function] to reflect. + /// + /// Returns: + /// A [ReflectedFunction] instance representing the reflected function. + /// + /// Example: + /// ```dart + /// final reflector = MirrorsReflector(); + /// final functionReflection = reflector.reflectFunction(myFunction); + /// ``` + @override + ReflectedFunction reflectFunction(Function function) { + var closure = dart.reflect(function) as dart.ClosureMirror; + return _ReflectedMethodMirror(closure.function, closure); + } + + /// Reflects a given type and returns a [ReflectedType] instance. + /// + /// This method takes a [Type] parameter and uses dart:mirrors to create + /// a reflection of the type. It returns either a [_ReflectedClassMirror] + /// or a [_ReflectedTypeMirror] depending on whether the reflected type + /// is a class or not. + /// + /// Parameters: + /// - [type]: The [Type] to reflect. + /// + /// Returns: + /// A [ReflectedType] instance representing the reflected type. + /// + /// If the reflected type doesn't have a reflected type (i.e., [hasReflectedType] is false), + /// it returns a reflection of the `dynamic` type instead. + /// + /// Example: + /// ```dart + /// final reflector = MirrorsReflector(); + /// final typeReflection = reflector.reflectType(int); + /// ``` + @override + ReflectedType reflectType(Type type) { + var mirror = dart.reflectType(type); + + if (!mirror.hasReflectedType) { + return reflectType(dynamic); + } else { + if (mirror is dart.ClassMirror) { + return _ReflectedClassMirror(mirror); + } else { + return _ReflectedTypeMirror(mirror); + } + } + } + + /// Reflects a Future of a given type and returns a [ReflectedType] instance. + /// + /// This method takes a [Type] parameter and creates a reflection of a Future + /// that wraps that type. It first reflects the inner type, then constructs + /// a Future type with that inner type as its type argument. + /// + /// Parameters: + /// - [type]: The [Type] to be wrapped in a Future. + /// + /// Returns: + /// A [ReflectedType] instance representing the reflected Future. + /// + /// Throws: + /// - [ArgumentError] if the provided [type] is not a class or type. + /// + /// Example: + /// ```dart + /// final reflector = MirrorsReflector(); + /// final futureIntReflection = reflector.reflectFutureOf(int); + /// // This will reflect Future + /// ``` + @override + ReflectedType reflectFutureOf(Type type) { + var inner = reflectType(type); + dart.TypeMirror localMirror; + if (inner is _ReflectedClassMirror) { + localMirror = inner.mirror; + } else if (inner is _ReflectedTypeMirror) { + localMirror = inner.mirror; + } else { + throw ArgumentError('$type is not a class or type.'); + } + + var future = dart.reflectType(Future, [localMirror.reflectedType]); + return _ReflectedClassMirror(future as dart.ClassMirror); + } + + /// Reflects an instance of an object and returns a [ReflectedInstance]. + /// + /// This method takes an [Object] parameter and uses dart:mirrors to create + /// a reflection of the object instance. It returns a [_ReflectedInstanceMirror] + /// which implements [ReflectedInstance]. + /// + /// Parameters: + /// - [object]: The object instance to reflect. + /// + /// Returns: + /// A [ReflectedInstance] representing the reflected object instance. + /// + /// Example: + /// ```dart + /// final reflector = MirrorsReflector(); + /// final instanceReflection = reflector.reflectInstance(myObject); + /// ``` + @override + ReflectedInstance reflectInstance(Object object) { + return _ReflectedInstanceMirror(dart.reflect(object)); + } +} + +/// Represents a reflected type parameter using dart:mirrors. +/// +/// This class extends [ReflectedTypeParameter] and wraps a [dart.TypeVariableMirror] +/// to provide reflection capabilities for type parameters in Dart. +/// +/// The class extracts the name of the type parameter from the mirror and passes +/// it to the superclass constructor. +/// +/// This is typically used internally by the reflection system to represent +/// type parameters of generic classes or methods. +class _ReflectedTypeParameter extends ReflectedTypeParameter { + /// The [dart.TypeVariableMirror] instance representing the reflected type parameter. + /// + /// This mirror provides access to the details of the type parameter, such as its name, + /// bounds, and other metadata. It is used internally by the [_ReflectedTypeParameter] + /// class to implement reflection capabilities for type parameters. + final dart.TypeVariableMirror mirror; + + /// Constructs a [_ReflectedTypeParameter] instance. + /// + /// This constructor takes a [dart.TypeVariableMirror] and initializes the + /// [_ReflectedTypeParameter] with the name of the type parameter extracted + /// from the mirror. + /// + /// Parameters: + /// - [mirror]: A [dart.TypeVariableMirror] representing the type parameter. + /// + /// The constructor uses [dart.MirrorSystem.getName] to extract the name of the + /// type parameter from the mirror's [simpleName] and passes it to the superclass + /// constructor. + _ReflectedTypeParameter(this.mirror) + : super(dart.MirrorSystem.getName(mirror.simpleName)); +} + +/// Represents a reflected type using dart:mirrors. +/// +/// This class extends [ReflectedType] and wraps a [dart.TypeMirror] +/// to provide reflection capabilities for types in Dart. +/// +/// The class extracts the name and type variables from the mirror and passes +/// them to the superclass constructor. It also implements type comparison +/// through the [isAssignableTo] method. +/// +/// Note that this class represents types that are not classes, and therefore +/// cannot be instantiated. Attempting to call [newInstance] will throw a +/// [ReflectionException]. +/// +/// This is typically used internally by the reflection system to represent +/// non-class types like interfaces, mixins, or type aliases. +class _ReflectedTypeMirror extends ReflectedType { + /// The [dart.TypeMirror] instance representing the reflected type. + /// + /// This mirror provides access to the details of the type, such as its name, + /// type variables, and other metadata. It is used internally by the + /// [_ReflectedTypeMirror] class to implement reflection capabilities for types. + final dart.TypeMirror mirror; + + /// Constructs a [_ReflectedTypeMirror] instance. + /// + /// This constructor takes a [dart.TypeMirror] and initializes the + /// [_ReflectedTypeMirror] with the following: + /// - The name of the type extracted from the mirror's [simpleName]. + /// - A list of [_ReflectedTypeParameter] objects created from the mirror's type variables. + /// - The reflected type of the mirror. + /// + /// Parameters: + /// - [mirror]: A [dart.TypeMirror] representing the type to be reflected. + /// + /// The constructor uses [dart.MirrorSystem.getName] to extract the name of the + /// type from the mirror's [simpleName]. It also maps the mirror's type variables + /// to [_ReflectedTypeParameter] objects and passes them along with the reflected + /// type to the superclass constructor. + _ReflectedTypeMirror(this.mirror) + : super( + dart.MirrorSystem.getName(mirror.simpleName), + mirror.typeVariables.map((m) => _ReflectedTypeParameter(m)).toList(), + mirror.reflectedType, + ); + + /// Checks if this reflected class is assignable to another reflected type. + /// + /// This method determines whether an instance of this class can be assigned + /// to a variable of the type represented by [other]. + /// + /// Parameters: + /// - [other]: The [ReflectedType] to check against. + /// + /// Returns: + /// - `true` if this class is assignable to [other]. + /// - `false` otherwise, including when [other] is not a [_ReflectedClassMirror] + /// or [_ReflectedTypeMirror]. + /// + /// The method uses dart:mirrors' [isAssignableTo] to perform the actual check + /// when [other] is either a [_ReflectedClassMirror] or [_ReflectedTypeMirror]. + @override + bool isAssignableTo(ReflectedType? other) { + if (other is _ReflectedClassMirror) { + return mirror.isAssignableTo(other.mirror); + } else if (other is _ReflectedTypeMirror) { + return mirror.isAssignableTo(other.mirror); + } else { + return false; + } + } + + /// Throws a [ReflectionException] when attempting to create a new instance. + /// + /// This method is intended to be overridden by classes that represent + /// instantiable types. For non-instantiable types (like interfaces or + /// abstract classes), this method throws an exception. + /// + /// Parameters: + /// - [constructorName]: The name of the constructor to invoke. + /// - [positionalArguments]: A list of positional arguments for the constructor. + /// - [namedArguments]: An optional map of named arguments for the constructor. + /// - [typeArguments]: An optional list of type arguments for generic classes. + /// + /// Throws: + /// [ReflectionException]: Always thrown with a message indicating that + /// this type cannot be instantiated. + /// + /// Example: + /// ```dart + /// // This will always throw a ReflectionException + /// reflectedType.newInstance('defaultConstructor', []); + /// ``` + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map? namedArguments, List? typeArguments]) { + throw ReflectionException( + '$name is not a class, and therefore cannot be instantiated.'); + } +} + +/// Represents a reflected class using dart:mirrors. +/// +/// This class extends [ReflectedClass] and wraps a [dart.ClassMirror] +/// to provide reflection capabilities for Dart classes. +/// +/// Key features: +/// - Reflects class name, type parameters, constructors, and declarations +/// - Provides access to class metadata (annotations) +/// - Supports type comparison through [isAssignableTo] +/// - Allows creation of new instances of the reflected class +/// +/// This class is typically used internally by the reflection system to +/// represent classes and their members. +class _ReflectedClassMirror extends ReflectedClass { + /// The [dart.ClassMirror] representing the reflected class. + /// + /// This mirror is used to extract information about the class, such as + /// its name, type parameters, constructors, and declarations. + /// + /// See also: + /// - [dart.ClassMirror] for more details about the mirror system. + final dart.ClassMirror mirror; + + /// Constructs a [_ReflectedClassMirror] instance. + /// + /// This constructor takes a [dart.ClassMirror] and initializes the + /// [_ReflectedClassMirror] with the following: + /// - The name of the class extracted from the mirror's [simpleName]. + /// - A list of [_ReflectedTypeParameter] objects created from the mirror's type variables. + /// - Empty lists for constructors and annotations (these are populated elsewhere). + /// - A list of declarations obtained from the [_declarationsOf] method. + /// - The reflected type of the mirror. + /// + /// Parameters: + /// - [mirror]: A [dart.ClassMirror] representing the class to be reflected. + /// + /// The constructor uses [dart.MirrorSystem.getName] to extract the name of the + /// class from the mirror's [simpleName]. It also maps the mirror's type variables + /// to [_ReflectedTypeParameter] objects and uses [_declarationsOf] to get the + /// class declarations. These are then passed to the superclass constructor. + _ReflectedClassMirror(this.mirror) + : super( + dart.MirrorSystem.getName(mirror.simpleName), + mirror.typeVariables.map((m) => _ReflectedTypeParameter(m)).toList(), + [], + [], + _declarationsOf(mirror), + mirror.reflectedType, + ); + + /// Retrieves a list of reflected constructors from a given [dart.ClassMirror]. + /// + /// This static method iterates through the declarations of the provided [mirror], + /// identifies the constructor methods, and creates [ReflectedFunction] instances + /// for each constructor found. + /// + /// Parameters: + /// - [mirror]: A [dart.ClassMirror] representing the class to examine. + /// + /// Returns: + /// A [List] of [ReflectedFunction] objects, each representing a constructor + /// of the class. + /// + /// The method specifically looks for [dart.MethodMirror] instances that are + /// marked as constructors (i.e., [isConstructor] is true). Each identified + /// constructor is wrapped in a [_ReflectedMethodMirror] and added to the + /// returned list. + static List _constructorsOf(dart.ClassMirror mirror) { + var out = []; + + for (var key in mirror.declarations.keys) { + var value = mirror.declarations[key]; + + if (value is dart.MethodMirror && value.isConstructor) { + out.add(_ReflectedMethodMirror(value)); + } + } + + return out; + } + + /// Retrieves a list of reflected declarations from a given [dart.ClassMirror]. + /// + /// This static method iterates through the declarations of the provided [mirror], + /// identifies non-constructor methods, and creates [ReflectedDeclaration] instances + /// for each method found. + /// + /// Parameters: + /// - [mirror]: A [dart.ClassMirror] representing the class to examine. + /// + /// Returns: + /// A [List] of [ReflectedDeclaration] objects, each representing a non-constructor + /// method of the class. + /// + /// The method specifically looks for [dart.MethodMirror] instances that are + /// not constructors (i.e., [isConstructor] is false). Each identified + /// method is wrapped in a [_ReflectedDeclarationMirror] and added to the + /// returned list. + static List _declarationsOf(dart.ClassMirror mirror) { + var out = []; + + for (var key in mirror.declarations.keys) { + var value = mirror.declarations[key]; + + if (value is dart.MethodMirror && !value.isConstructor) { + out.add( + _ReflectedDeclarationMirror(dart.MirrorSystem.getName(key), value)); + } + } + + return out; + } + + /// Retrieves the annotations (metadata) associated with this reflected class. + /// + /// This getter method overrides the base implementation to provide access to + /// the class-level annotations using dart:mirrors. It maps each metadata mirror + /// to a [_ReflectedInstanceMirror] and returns them as a list. + /// + /// Returns: + /// A [List] of [ReflectedInstance] objects, each representing an annotation + /// applied to this class. + /// + /// Example: + /// ```dart + /// @MyAnnotation() + /// class MyClass {} + /// + /// // Assuming we have a reflection of MyClass + /// final classReflection = reflector.reflectClass(MyClass); + /// final annotations = classReflection.annotations; + /// // annotations will contain a ReflectedInstance of MyAnnotation + /// ``` + /// + /// Note: This method relies on the [dart.ClassMirror]'s metadata property + /// and creates a new [_ReflectedInstanceMirror] for each annotation. + @override + List get annotations => + mirror.metadata.map((m) => _ReflectedInstanceMirror(m)).toList(); + + /// Retrieves a list of reflected constructors for this class. + /// + /// This getter method overrides the base implementation to provide access to + /// the constructors of the reflected class using dart:mirrors. It uses the + /// static [_constructorsOf] method to extract and wrap each constructor + /// in a [ReflectedFunction] object. + /// + /// Returns: + /// A [List] of [ReflectedFunction] objects, each representing a constructor + /// of this class. + /// + /// Example: + /// ```dart + /// final classReflection = reflector.reflectClass(MyClass); + /// final constructors = classReflection.constructors; + /// // constructors will contain ReflectedFunction objects for each + /// // constructor in MyClass + /// ``` + /// + /// Note: This method relies on the [dart.ClassMirror]'s declarations and + /// the [_constructorsOf] method to identify and create reflections of + /// the class constructors. + @override + List get constructors => _constructorsOf(mirror); + + /// Checks if this reflected type is assignable to another reflected type. + /// + /// This method determines whether an instance of this type can be assigned + /// to a variable of the type represented by [other]. + /// + /// Parameters: + /// - [other]: The [ReflectedType] to check against. + /// + /// Returns: + /// - `true` if this type is assignable to [other]. + /// - `false` otherwise, including when [other] is not a [_ReflectedClassMirror] + /// or [_ReflectedTypeMirror]. + /// + /// The method uses dart:mirrors' [isAssignableTo] to perform the actual check + /// when [other] is either a [_ReflectedClassMirror] or [_ReflectedTypeMirror]. + @override + bool isAssignableTo(ReflectedType? other) { + if (other is _ReflectedClassMirror) { + return mirror.isAssignableTo(other.mirror); + } else if (other is _ReflectedTypeMirror) { + return mirror.isAssignableTo(other.mirror); + } else { + return false; + } + } + + /// Creates a new instance of the reflected class. + /// + /// This method instantiates a new object of the class represented by this + /// [_ReflectedClassMirror] using the specified constructor and arguments. + /// + /// Parameters: + /// - [constructorName]: The name of the constructor to invoke. Use an empty + /// string for the default constructor. + /// - [positionalArguments]: A list of positional arguments to pass to the constructor. + /// - [namedArguments]: An optional map of named arguments to pass to the constructor. + /// - [typeArguments]: An optional list of type arguments for generic classes. + /// + /// Returns: + /// A [ReflectedInstance] representing the newly created instance. + /// + /// Throws: + /// May throw exceptions if the constructor invocation fails, e.g., due to + /// invalid arguments or if the class cannot be instantiated. + /// + /// Note: + /// This implementation currently does not use the [namedArguments] or + /// [typeArguments] parameters. They are included for API compatibility. + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map? namedArguments, List? typeArguments]) { + return _ReflectedInstanceMirror( + mirror.newInstance(Symbol(constructorName), positionalArguments)); + } + + /// Checks if this [_ReflectedClassMirror] is equal to another object. + /// + /// This method overrides the default equality operator to provide a custom + /// equality check for [_ReflectedClassMirror] instances. + /// + /// Parameters: + /// - [other]: The object to compare with this [_ReflectedClassMirror]. + /// + /// Returns: + /// - `true` if [other] is also a [_ReflectedClassMirror] and has the same + /// [mirror] as this instance. + /// - `false` otherwise. + /// + /// This implementation ensures that two [_ReflectedClassMirror] instances + /// are considered equal if and only if they reflect the same class (i.e., + /// their underlying [dart.ClassMirror]s are the same). + @override + bool operator ==(other) { + return other is _ReflectedClassMirror && other.mirror == mirror; + } + + /// Generates a hash code for this [_ReflectedClassMirror]. + /// + /// This method overrides the default [hashCode] implementation to provide + /// a consistent hash code for [_ReflectedClassMirror] instances. + /// + /// The hash code is generated using the [hash2] function from the Quiver + /// library, combining the [mirror] object and an empty string. The empty + /// string is used as a second parameter to maintain compatibility with + /// the [hash2] function, which requires two arguments. + /// + /// Returns: + /// An [int] representing the hash code of this [_ReflectedClassMirror]. + /// + /// Note: + /// This hash code implementation ensures that two [_ReflectedClassMirror] + /// instances with the same [mirror] will have the same hash code, which + /// is consistent with the equality check implemented in the [operator ==]. + @override + int get hashCode => hash2(mirror, " "); +} + +/// Represents a reflected declaration using dart:mirrors. +/// +/// This class extends [ReflectedDeclaration] and wraps a [dart.MethodMirror] +/// to provide reflection capabilities for method declarations in Dart. +/// +/// Key features: +/// - Reflects the name and static nature of the declaration +/// - Provides access to the underlying method as a [ReflectedFunction] +/// +/// This class is typically used internally by the reflection system to +/// represent method declarations within a class. +class _ReflectedDeclarationMirror extends ReflectedDeclaration { + /// The [dart.MethodMirror] instance representing the reflected method. + /// + /// This mirror provides access to the details of the method, such as its name, + /// parameters, return type, and other metadata. It is used internally by the + /// [_ReflectedDeclarationMirror] class to implement reflection capabilities + /// for method declarations. + final dart.MethodMirror mirror; + + /// Constructs a [_ReflectedDeclarationMirror] instance. + /// + /// This constructor initializes a new [_ReflectedDeclarationMirror] with the given [name] + /// and [mirror]. It uses the [dart.MethodMirror]'s [isStatic] property to determine + /// if the declaration is static, and passes `null` as the initial value for the function. + /// + /// Parameters: + /// - [name]: A [String] representing the name of the declaration. + /// - [mirror]: A [dart.MethodMirror] representing the reflected method. + /// + /// The constructor calls the superclass constructor with the provided [name], + /// the [isStatic] property from the [mirror], and `null` for the function parameter. + _ReflectedDeclarationMirror(String name, this.mirror) + : super(name, mirror.isStatic, null); + + /// Determines if this declaration is static. + /// + /// This getter overrides the base implementation to provide information + /// about whether the reflected declaration is static or not. It directly + /// accesses the [isStatic] property of the underlying [dart.MethodMirror]. + /// + /// Returns: + /// A [bool] value: + /// - `true` if the declaration is static. + /// - `false` if the declaration is not static (i.e., it's an instance method). + /// + /// This property is useful for determining the nature of the reflected + /// declaration, particularly when working with class methods and properties. + @override + bool get isStatic => mirror.isStatic; + + /// Retrieves a [ReflectedFunction] representation of this declaration. + /// + /// This getter overrides the base implementation to provide a [ReflectedFunction] + /// that represents the method associated with this declaration. It creates a new + /// [_ReflectedMethodMirror] instance using the underlying [dart.MethodMirror]. + /// + /// Returns: + /// A [ReflectedFunction] object that represents the method of this declaration. + /// + /// This property is useful for accessing detailed information about the method, + /// such as its parameters, return type, and other attributes, in a way that's + /// consistent with the reflection API. + @override + ReflectedFunction get function => _ReflectedMethodMirror(mirror); +} + +/// Represents a reflected instance of an object using dart:mirrors. +/// +/// This class extends [ReflectedInstance] and wraps a [dart.InstanceMirror] +/// to provide reflection capabilities for object instances in Dart. +/// +/// Key features: +/// - Reflects the type and runtime type of the instance +/// - Provides access to the underlying object (reflectee) +/// - Allows retrieval of field values through reflection +/// +/// This class is typically used internally by the reflection system to +/// represent instances of objects and provide reflective access to their fields. +class _ReflectedInstanceMirror extends ReflectedInstance { + /// The [dart.InstanceMirror] representing the reflected instance. + /// + /// This mirror provides access to the details of the object instance, such as its type, + /// fields, and methods. It is used internally by the [_ReflectedInstanceMirror] class + /// to implement reflection capabilities for object instances. + /// + /// The mirror allows for dynamic inspection and manipulation of the object's state + /// and behavior at runtime, enabling powerful reflection features. + final dart.InstanceMirror mirror; + + /// Constructs a [_ReflectedInstanceMirror] instance. + /// + /// This constructor initializes a new [_ReflectedInstanceMirror] with the given [mirror]. + /// It uses the [dart.InstanceMirror]'s [type] property to create [_ReflectedClassMirror] + /// instances for both the type and runtime type of the reflected instance. + /// + /// Parameters: + /// - [mirror]: A [dart.InstanceMirror] representing the reflected instance. + /// + /// The constructor calls the superclass constructor with: + /// - A [_ReflectedClassMirror] of the instance's type + /// - A [_ReflectedClassMirror] of the instance's runtime type + /// - The [reflectee] of the mirror, which is the actual object being reflected + /// + /// This setup allows the [_ReflectedInstanceMirror] to provide access to both + /// the compile-time and runtime type information of the reflected instance, + /// as well as the underlying object itself. + _ReflectedInstanceMirror(this.mirror) + : super(_ReflectedClassMirror(mirror.type), + _ReflectedClassMirror(mirror.type), mirror.reflectee); + + /// Retrieves the value of a field from the reflected instance. + /// + /// This method allows access to field values of the object represented by this + /// [_ReflectedInstanceMirror] through reflection. + /// + /// Parameters: + /// - [name]: A [String] representing the name of the field to retrieve. + /// + /// Returns: + /// A [ReflectedInstance] representing the value of the specified field. + /// This returned instance is wrapped in a [_ReflectedInstanceMirror]. + /// + /// Throws: + /// May throw exceptions if the field does not exist or if access is not allowed. + /// + /// Example: + /// ```dart + /// var fieldValue = reflectedInstance.getField('myField'); + /// ``` + /// + /// Note: + /// This method uses the underlying [dart.InstanceMirror]'s [getField] method + /// to perform the actual field access. + @override + ReflectedInstance getField(String name) { + return _ReflectedInstanceMirror(mirror.getField(Symbol(name))); + } +} + +/// Represents a reflected method using dart:mirrors. +/// +/// This class extends [ReflectedFunction] and wraps a [dart.MethodMirror] +/// to provide reflection capabilities for methods in Dart. +/// +/// Key features: +/// - Reflects method name, parameters, and return type +/// - Provides access to method metadata (annotations) +/// - Supports invocation of the reflected method (if a ClosureMirror is available) +/// +/// The class uses both [dart.MethodMirror] and optionally [dart.ClosureMirror] +/// to represent and potentially invoke the reflected method. +/// +/// Usage: +/// - Created internally by the reflection system to represent methods +/// - Can be used to inspect method details or invoke the method if a ClosureMirror is provided +/// +/// Note: +/// - Invocation is only possible if a ClosureMirror is provided during construction +/// - Throws a StateError if invoke is called without a ClosureMirror +class _ReflectedMethodMirror extends ReflectedFunction { + /// The [dart.MethodMirror] instance representing the reflected method. + /// + /// This mirror provides access to the details of the method, such as its name, + /// parameters, return type, and other metadata. It is used internally by the + /// [_ReflectedMethodMirror] class to implement reflection capabilities + /// for methods. + /// + /// The [dart.MethodMirror] is a crucial component in the reflection process, + /// allowing for introspection of method properties and behavior at runtime. + final dart.MethodMirror mirror; + + /// An optional [dart.ClosureMirror] representing the closure of the reflected method. + /// + /// This field is used to store a [dart.ClosureMirror] when the reflected method + /// is associated with a callable object (i.e., a closure). The presence of this + /// mirror enables the [invoke] method to directly call the reflected method. + /// + /// If this field is null, it indicates that the reflected method cannot be + /// directly invoked through this [_ReflectedMethodMirror] instance. + /// + /// Note: + /// - This field is crucial for supporting method invocation via reflection. + /// - It's typically set when reflecting on instance methods or standalone functions. + /// - For class-level method declarations that aren't bound to an instance, + /// this field may be null. + final dart.ClosureMirror? closureMirror; + + /// Constructs a [_ReflectedMethodMirror] instance. + /// + /// This constructor initializes a new [_ReflectedMethodMirror] with the given [mirror] + /// and optional [closureMirror]. It extracts various properties from the [dart.MethodMirror] + /// to populate the superclass constructor. + /// + /// Parameters: + /// - [mirror]: A [dart.MethodMirror] representing the reflected method. + /// - [closureMirror]: An optional [dart.ClosureMirror] for method invocation. + /// + /// The constructor initializes the following: + /// - Method name from the mirror's [simpleName] + /// - An empty list of reflected type parameters + /// - Metadata (annotations) as [_ReflectedInstanceMirror] objects + /// - Reflected parameters using [_reflectParameter] + /// - Getter and setter flags from the mirror + /// - Return type, using [dynamic] if the mirror doesn't have a reflected type + /// + /// This setup allows the [_ReflectedMethodMirror] to provide comprehensive + /// reflection capabilities for the method, including its signature, metadata, + /// and potential invocation (if a [closureMirror] is provided). + _ReflectedMethodMirror(this.mirror, [this.closureMirror]) + : super( + dart.MirrorSystem.getName(mirror.simpleName), + [], + mirror.metadata + .map((mirror) => _ReflectedInstanceMirror(mirror)) + .toList(), + mirror.parameters.map(_reflectParameter).toList(), + mirror.isGetter, + mirror.isSetter, + returnType: !mirror.returnType.hasReflectedType + ? const MirrorsReflector().reflectType(dynamic) + : const MirrorsReflector() + .reflectType(mirror.returnType.reflectedType)); + + /// Reflects a parameter of a method using dart:mirrors. + /// + /// This static method creates a [ReflectedParameter] instance from a given [dart.ParameterMirror]. + /// It extracts various properties from the mirror to construct a comprehensive reflection of the parameter. + /// + /// Parameters: + /// - [mirror]: A [dart.ParameterMirror] representing the parameter to be reflected. + /// + /// Returns: + /// A [ReflectedParameter] instance containing the reflected information of the parameter. + /// + /// The method extracts the following information: + /// - Parameter name from the mirror's [simpleName] + /// - Metadata (annotations) as [_ReflectedInstanceMirror] objects + /// - Parameter type, reflected using [MirrorsReflector] + /// - Whether the parameter is required (not optional) + /// - Whether the parameter is named + /// + /// This method is typically used internally by the reflection system to create + /// parameter reflections for method signatures. + static ReflectedParameter _reflectParameter(dart.ParameterMirror mirror) { + return ReflectedParameter( + dart.MirrorSystem.getName(mirror.simpleName), + mirror.metadata + .map((mirror) => _ReflectedInstanceMirror(mirror)) + .toList(), + const MirrorsReflector().reflectType(mirror.type.reflectedType), + !mirror.isOptional, + mirror.isNamed); + } + + /// Invokes the reflected method with the given invocation details. + /// + /// This method allows for dynamic invocation of the reflected method using the + /// provided [Invocation] object. It requires that a [closureMirror] was provided + /// during the construction of this [_ReflectedMethodMirror]. + /// + /// Parameters: + /// - [invocation]: An [Invocation] object containing the details of the method call, + /// including the method name, positional arguments, and named arguments. + /// + /// Returns: + /// A [ReflectedInstance] representing the result of the method invocation. + /// + /// Throws: + /// - [StateError] if this [_ReflectedMethodMirror] was created without a [closureMirror], + /// indicating that direct invocation is not possible. + /// + /// Example: + /// ```dart + /// var result = reflectedMethod.invoke(Invocation.method(#methodName, [arg1, arg2])); + /// ``` + /// + /// Note: + /// This method relies on the presence of a [closureMirror] to perform the actual + /// invocation. If no [closureMirror] is available, it means the reflected method + /// cannot be directly invoked, and an error will be thrown. + @override + ReflectedInstance invoke(Invocation invocation) { + if (closureMirror == null) { + throw StateError( + 'This object was reflected without a ClosureMirror, and therefore cannot be directly invoked.'); + } + + return _ReflectedInstanceMirror(closureMirror!.invoke(invocation.memberName, + invocation.positionalArguments, invocation.namedArguments)); + } +} diff --git a/incubation/container/container/lib/src/reflectable/reflectable.dart b/incubation/container/container/lib/src/reflectable/reflectable.dart new file mode 100644 index 0000000..8e88713 --- /dev/null +++ b/incubation/container/container/lib/src/reflectable/reflectable.dart @@ -0,0 +1,8 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ diff --git a/incubation/container/container/lib/src/reflector.dart b/incubation/container/container/lib/src/reflector.dart new file mode 100644 index 0000000..3b72136 --- /dev/null +++ b/incubation/container/container/lib/src/reflector.dart @@ -0,0 +1,298 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'package:collection/collection.dart'; +import 'package:quiver/core.dart'; + +/// Abstract class representing a reflector for introspection of Dart types and instances. +/// +/// This class provides methods to reflect on various Dart constructs such as classes, +/// functions, types, and instances. It allows for runtime inspection and manipulation +/// of code elements. +/// +/// The methods in this class are designed to be implemented by concrete reflector +/// classes, potentially using different reflection mechanisms (e.g., mirrors, code +/// generation). +/// +/// Note: The `reflectFutureOf` method throws an `UnsupportedError` by default and +/// requires `dart:mirrors` for implementation. +abstract class Reflector { + /// Constructs a new [Reflector] instance. + /// + /// This constructor is declared as `const` to allow for compile-time constant creation + /// of [Reflector] instances. Subclasses of [Reflector] may override this constructor + /// to provide their own initialization logic if needed. + const Reflector(); + + String? getName(Symbol symbol); + + ReflectedClass? reflectClass(Type clazz); + + ReflectedFunction? reflectFunction(Function function); + + ReflectedType? reflectType(Type type); + + ReflectedInstance? reflectInstance(Object object); + + ReflectedType reflectFutureOf(Type type) { + throw UnsupportedError('`reflectFutureOf` requires `dart:mirrors`.'); + } +} + +/// Represents a reflected instance of an object. +/// +/// This abstract class provides a way to introspect and manipulate object instances +/// at runtime. It encapsulates information about the object's type, class, and the +/// actual object instance (reflectee). +/// +/// The [type] property represents the reflected type of the instance. +/// The [clazz] property represents the reflected class of the instance. +/// The [reflectee] property holds the actual object instance being reflected. +/// +/// This class also provides methods for comparing instances and accessing fields. +/// +/// Use the [getField] method to retrieve a reflected instance of a specific field. +abstract class ReflectedInstance { + final ReflectedType type; + final ReflectedClass clazz; + final Object? reflectee; + + const ReflectedInstance(this.type, this.clazz, this.reflectee); + + @override + int get hashCode => hash2(type, clazz); + + @override + bool operator ==(other) => + other is ReflectedInstance && other.type == type && other.clazz == clazz; + + ReflectedInstance getField(String name); +} + +/// Represents a reflected type in the Dart language. +/// +/// This abstract class encapsulates information about a Dart type, including its name, +/// type parameters, and the actual Dart [Type] it represents. +/// +/// The [name] property holds the name of the type. +/// The [typeParameters] list contains the type parameters if the type is generic. +/// The [reflectedType] property holds the actual Dart [Type] being reflected. +/// +/// This class provides methods for creating new instances of the type, comparing types, +/// and checking type assignability. +/// +/// The [newInstance] method allows for dynamic creation of new instances of the type. +/// The [isAssignableTo] method checks if this type is assignable to another type. +/// +/// This class also overrides [hashCode] and [operator ==] for proper equality comparisons. +abstract class ReflectedType { + final String name; + final List typeParameters; + final Type reflectedType; + + const ReflectedType(this.name, this.typeParameters, this.reflectedType); + + @override + int get hashCode => hash3(name, typeParameters, reflectedType); + + @override + bool operator ==(other) => + other is ReflectedType && + other.name == name && + const ListEquality() + .equals(other.typeParameters, typeParameters) && + other.reflectedType == reflectedType; + + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]); + + bool isAssignableTo(ReflectedType? other); +} + +/// Represents a reflected class in the Dart language. +/// +/// This abstract class extends [ReflectedType] and provides additional information +/// specific to classes, including annotations, constructors, and declarations. +/// +/// The [annotations] list contains reflected instances of annotations applied to the class. +/// The [constructors] list contains reflected functions representing the class constructors. +/// The [declarations] list contains reflected declarations (fields, methods, etc.) of the class. +/// +/// This class overrides [hashCode] and [operator ==] to include the additional properties +/// in equality comparisons and hash code calculations. +abstract class ReflectedClass extends ReflectedType { + final List annotations; + final List constructors; + final List declarations; + + const ReflectedClass( + String name, + List typeParameters, + this.annotations, + this.constructors, + this.declarations, + Type reflectedType) + : super(name, typeParameters, reflectedType); + + @override + int get hashCode => + hash4(super.hashCode, annotations, constructors, declarations); + + @override + bool operator ==(other) => + other is ReflectedClass && + super == other && + const ListEquality() + .equals(other.annotations, annotations) && + const ListEquality() + .equals(other.constructors, constructors) && + const ListEquality() + .equals(other.declarations, declarations); +} + +/// Represents a reflected declaration in the Dart language. +/// +/// This class encapsulates information about a declaration within a class or object, +/// such as a method, field, or property. +/// +/// The [name] property holds the name of the declaration. +/// The [isStatic] property indicates whether the declaration is static. +/// The [function] property, if non-null, represents the reflected function associated +/// with this declaration (applicable for methods and some properties). +/// +/// This class provides methods for comparing declarations and calculating hash codes. +/// It overrides [hashCode] and [operator ==] for proper equality comparisons. +class ReflectedDeclaration { + final String name; + final bool isStatic; + final ReflectedFunction? function; + + const ReflectedDeclaration(this.name, this.isStatic, this.function); + + @override + int get hashCode => hash3(name, isStatic, function); + + @override + bool operator ==(other) => + other is ReflectedDeclaration && + other.name == name && + other.isStatic == isStatic && + other.function == function; +} + +/// Represents a reflected function in the Dart language. +/// +/// This abstract class encapsulates information about a function, including its name, +/// type parameters, annotations, return type, parameters, and whether it's a getter or setter. +/// +/// The [name] property holds the name of the function. +/// The [typeParameters] list contains the type parameters if the function is generic. +/// The [annotations] list contains reflected instances of annotations applied to the function. +/// The [returnType] property represents the function's return type (if applicable). +/// The [parameters] list contains the function's parameters. +/// The [isGetter] and [isSetter] properties indicate if the function is a getter or setter. +/// +/// This class provides methods for comparing functions and calculating hash codes. +/// It also includes an [invoke] method for dynamically calling the function. +/// +/// This class overrides [hashCode] and [operator ==] for proper equality comparisons. +abstract class ReflectedFunction { + final String name; + final List typeParameters; + final List annotations; + final ReflectedType? returnType; + final List parameters; + final bool isGetter, isSetter; + + const ReflectedFunction(this.name, this.typeParameters, this.annotations, + this.parameters, this.isGetter, this.isSetter, + {this.returnType}); + + @override + int get hashCode => hashObjects([ + name, + typeParameters, + annotations, + returnType, + parameters, + isGetter, + isSetter + ]); + + @override + bool operator ==(other) => + other is ReflectedFunction && + other.name == name && + const ListEquality() + .equals(other.typeParameters, typeParameters) && + const ListEquality() + .equals(other.annotations, annotations) && + other.returnType == returnType && + const ListEquality() + .equals(other.parameters, other.parameters) && + other.isGetter == isGetter && + other.isSetter == isSetter; + + ReflectedInstance invoke(Invocation invocation); +} + +/// Represents a reflected parameter in the Dart language. +/// +/// This class encapsulates information about a function or method parameter, +/// including its name, annotations, type, and properties such as whether it's +/// required or named. +/// +/// Properties: +/// - [name]: The name of the parameter. +/// - [annotations]: A list of reflected instances of annotations applied to the parameter. +/// - [type]: The reflected type of the parameter. +/// - [isRequired]: Indicates whether the parameter is required. +/// - [isNamed]: Indicates whether the parameter is a named parameter. +/// +/// This class provides methods for comparing parameters and calculating hash codes. +/// It overrides [hashCode] and [operator ==] for proper equality comparisons. +class ReflectedParameter { + final String name; + final List annotations; + final ReflectedType type; + final bool isRequired; + final bool isNamed; + + const ReflectedParameter( + this.name, this.annotations, this.type, this.isRequired, this.isNamed); + + @override + int get hashCode => + hashObjects([name, annotations, type, isRequired, isNamed]); + + @override + bool operator ==(other) => + other is ReflectedParameter && + other.name == name && + const ListEquality() + .equals(other.annotations, annotations) && + other.type == type && + other.isRequired == isRequired && + other.isNamed == isNamed; +} + +class ReflectedTypeParameter { + final String name; + + const ReflectedTypeParameter(this.name); + + @override + int get hashCode => hashObjects([name]); + + @override + bool operator ==(other) => + other is ReflectedTypeParameter && other.name == name; +} diff --git a/incubation/container/container/lib/src/static/static.dart b/incubation/container/container/lib/src/static/static.dart new file mode 100644 index 0000000..0481dc1 --- /dev/null +++ b/incubation/container/container/lib/src/static/static.dart @@ -0,0 +1,179 @@ +/* + * This file is part of the Protevus Platform. + * + * (C) Protevus + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import 'package:platformed_container/container.dart'; + +/// A static implementation of the [Reflector] class that performs simple [Map] lookups. +/// +/// `package:platform_container_generator` uses this to create reflectors from analysis metadata. +class StaticReflector extends Reflector { + /// A map that associates [Symbol] objects with their corresponding string names. + /// + /// This map is used to store and retrieve the string representations of symbols, + /// which can be useful for reflection and debugging purposes. + final Map names; + + /// A map that associates [Type] objects with their corresponding [ReflectedType] objects. + /// + /// This map is used to store and retrieve reflection information for different types, + /// allowing for runtime introspection of type metadata and structure. + final Map types; + + /// A map that associates [Function] objects with their corresponding [ReflectedFunction] objects. + /// + /// This map is used to store and retrieve reflection information for functions, + /// enabling runtime introspection of function metadata, parameters, and return types. + final Map functions; + + /// A map that associates [Object] instances with their corresponding [ReflectedInstance] objects. + /// + /// This map is used to store and retrieve reflection information for specific object instances, + /// allowing for runtime introspection of object properties, methods, and metadata. + final Map instances; + + /// Creates a new [StaticReflector] instance with optional parameters. + /// + /// The [StaticReflector] constructor allows you to initialize the reflector + /// with pre-populated maps for names, types, functions, and instances. + /// + /// Parameters: + /// - [names]: A map of [Symbol] to [String] for symbol name lookups. Defaults to an empty map. + /// - [types]: A map of [Type] to [ReflectedType] for type reflection. Defaults to an empty map. + /// - [functions]: A map of [Function] to [ReflectedFunction] for function reflection. Defaults to an empty map. + /// - [instances]: A map of [Object] to [ReflectedInstance] for instance reflection. Defaults to an empty map. + /// + /// All parameters are optional and default to empty constant maps if not provided. + const StaticReflector( + {this.names = const {}, + this.types = const {}, + this.functions = const {}, + this.instances = const {}}); + + /// Returns the string name associated with the given [Symbol]. + /// + /// This method looks up the string representation of the provided [symbol] + /// in the [names] map. If the symbol is found, its corresponding string + /// name is returned. If the symbol is not found in the map, an [ArgumentError] + /// is thrown. + /// + /// Parameters: + /// - [symbol]: The [Symbol] for which to retrieve the string name. + /// + /// Returns: + /// The string name associated with the given [symbol], or null if not found. + /// + /// Throws: + /// - [ArgumentError]: If the provided [symbol] is not found in the [names] map. + @override + String? getName(Symbol symbol) { + if (!names.containsKey(symbol)) { + throw ArgumentError( + 'The value of $symbol is unknown - it was not generated.'); + } + + return names[symbol]; + } + + /// Reflects a class based on its [Type]. + /// + /// This method attempts to reflect the given class [Type] by calling [reflectType] + /// and casting the result to [ReflectedClass]. If the reflection is successful + /// and the result is a [ReflectedClass], it is returned. Otherwise, null is returned. + /// + /// Parameters: + /// - [clazz]: The [Type] of the class to reflect. + /// + /// Returns: + /// A [ReflectedClass] instance if the reflection is successful and the result + /// is a [ReflectedClass], or null otherwise. + @override + ReflectedClass? reflectClass(Type clazz) => + reflectType(clazz) as ReflectedClass?; + + /// Reflects a function based on its [Function] object. + /// + /// This method attempts to retrieve reflection information for the given [function] + /// from the [functions] map. If the function is found in the map, its corresponding + /// [ReflectedFunction] object is returned. If the function is not found, an + /// [ArgumentError] is thrown. + /// + /// Parameters: + /// - [function]: The [Function] object to reflect. + /// + /// Returns: + /// A [ReflectedFunction] object containing reflection information about the + /// given function, or null if not found. + /// + /// Throws: + /// - [ArgumentError]: If there is no reflection information available for + /// the given [function]. + @override + ReflectedFunction? reflectFunction(Function function) { + if (!functions.containsKey(function)) { + throw ArgumentError( + 'There is no reflection information available about $function.'); + } + + return functions[function]; + } + + /// Reflects an object instance to retrieve its reflection information. + /// + /// This method attempts to retrieve reflection information for the given [object] + /// from the [instances] map. If the object is found in the map, its corresponding + /// [ReflectedInstance] object is returned. If the object is not found, an + /// [ArgumentError] is thrown. + /// + /// Parameters: + /// - [object]: The object instance to reflect. + /// + /// Returns: + /// A [ReflectedInstance] object containing reflection information about the + /// given object instance, or null if not found. + /// + /// Throws: + /// - [ArgumentError]: If there is no reflection information available for + /// the given [object]. + @override + ReflectedInstance? reflectInstance(Object object) { + if (!instances.containsKey(object)) { + throw ArgumentError( + 'There is no reflection information available about $object.'); + } + + return instances[object]; + } + + /// Reflects a type to retrieve its reflection information. + /// + /// This method attempts to retrieve reflection information for the given [type] + /// from the [types] map. If the type is found in the map, its corresponding + /// [ReflectedType] object is returned. If the type is not found, an + /// [ArgumentError] is thrown. + /// + /// Parameters: + /// - [type]: The [Type] to reflect. + /// + /// Returns: + /// A [ReflectedType] object containing reflection information about the + /// given type, or null if not found. + /// + /// Throws: + /// - [ArgumentError]: If there is no reflection information available for + /// the given [type]. + @override + ReflectedType? reflectType(Type type) { + if (!types.containsKey(type)) { + throw ArgumentError( + 'There is no reflection information available about $type.'); + } + + return types[type]; + } +} diff --git a/incubation/container/container/lib/src/throwing.dart b/incubation/container/container/lib/src/throwing.dart new file mode 100644 index 0000000..4d4b9ee --- /dev/null +++ b/incubation/container/container/lib/src/throwing.dart @@ -0,0 +1,93 @@ +import 'package:platformed_container/src/container_const.dart'; +import 'empty/empty.dart'; +import 'reflector.dart'; + +/// A [Reflector] implementation that throws exceptions on all attempts +/// to perform reflection. +/// +/// Use this in contexts where you know you won't need any reflective capabilities. +class ThrowingReflector extends Reflector { + /// The error message to give the end user when an [UnsupportedError] is thrown. + final String errorMessage; + + /* + static const String defaultErrorMessage = + 'You attempted to perform a reflective action, but you are using `ThrowingReflector`, ' + 'a class which disables reflection. Consider using the `MirrorsReflector` ' + 'class if you need reflection.'; + */ + + /// Creates a [ThrowingReflector] instance. + /// + /// [errorMessage] is the message to be used when throwing an [UnsupportedError]. + /// If not provided, it defaults to [ContainerConst.defaultErrorMessage]. + const ThrowingReflector( + {this.errorMessage = ContainerConst.defaultErrorMessage}); + + /// Retrieves the name associated with the given [symbol]. + /// + /// This method delegates the task to an instance of [EmptyReflector]. + /// It returns the name as a [String] if found, or `null` if not found. + /// + /// [symbol] is the [Symbol] for which to retrieve the name. + /// + /// Returns a [String] representing the name of the symbol, or `null` if not found. + @override + String? getName(Symbol symbol) => const EmptyReflector().getName(symbol); + + /// Creates and returns an [UnsupportedError] with the specified [errorMessage]. + /// + /// This method is used internally to generate consistent error messages + /// when reflection operations are attempted on this [ThrowingReflector]. + /// + /// Returns an [UnsupportedError] instance with the configured error message. + UnsupportedError _error() => UnsupportedError(errorMessage); + + /// Reflects on a given class type and throws an [UnsupportedError]. + /// + /// This method is part of the [ThrowingReflector] implementation and is designed + /// to prevent reflective operations. When called, it throws an [UnsupportedError] + /// with the configured error message. + /// + /// [clazz] is the [Type] of the class to reflect on. + /// + /// Throws an [UnsupportedError] when invoked, as reflection is not supported. + @override + ReflectedClass reflectClass(Type clazz) => throw _error(); + + /// Reflects on a given object instance and throws an [UnsupportedError]. + /// + /// This method is part of the [ThrowingReflector] implementation and is designed + /// to prevent reflective operations on object instances. When called, it throws + /// an [UnsupportedError] with the configured error message. + /// + /// [object] is the object instance to reflect on. + /// + /// Throws an [UnsupportedError] when invoked, as reflection is not supported. + @override + ReflectedInstance reflectInstance(Object object) => throw _error(); + + /// Reflects on a given type and throws an [UnsupportedError]. + /// + /// This method is part of the [ThrowingReflector] implementation and is designed + /// to prevent reflective operations on types. When called, it throws an + /// [UnsupportedError] with the configured error message. + /// + /// [type] is the [Type] to reflect on. + /// + /// Throws an [UnsupportedError] when invoked, as reflection is not supported. + @override + ReflectedType reflectType(Type type) => throw _error(); + + /// Reflects on a given function and throws an [UnsupportedError]. + /// + /// This method is part of the [ThrowingReflector] implementation and is designed + /// to prevent reflective operations on functions. When called, it throws an + /// [UnsupportedError] with the configured error message. + /// + /// [function] is the [Function] to reflect on. + /// + /// Throws an [UnsupportedError] when invoked, as reflection is not supported. + @override + ReflectedFunction reflectFunction(Function function) => throw _error(); +} diff --git a/incubation/container/container/pubspec.yaml b/incubation/container/container/pubspec.yaml new file mode 100644 index 0000000..7a2cd84 --- /dev/null +++ b/incubation/container/container/pubspec.yaml @@ -0,0 +1,14 @@ +name: platformed_container +version: 9.0.0 +description: Protevus Platform hierarchical DI container, and pluggable backends for reflection. +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/container/container +environment: + sdk: '>=3.3.0 <4.0.0' +dependencies: + collection: ^1.19.1 + quiver: ^3.2.2 +dev_dependencies: + test: ^1.25.8 + lints: ^4.0.0 diff --git a/incubation/container/container/test/common.dart b/incubation/container/container/test/common.dart new file mode 100644 index 0000000..be66a88 --- /dev/null +++ b/incubation/container/container/test/common.dart @@ -0,0 +1,122 @@ +import 'dart:async'; + +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +void returnVoidFromAFunction(int x) {} + +void testReflector(Reflector reflector) { + var blaziken = Pokemon('Blaziken', PokemonType.fire); + late Container container; + + setUp(() { + container = Container(reflector); + container.registerSingleton(blaziken); + container.registerFactory>((_) async => 46); + }); + + test('get field', () { + var blazikenMirror = reflector.reflectInstance(blaziken)!; + expect(blazikenMirror.getField('type').reflectee, blaziken.type); + }); + + group('reflectFunction', () { + var mirror = reflector.reflectFunction(returnVoidFromAFunction); + + test('void return type returns dynamic', () { + expect(mirror!.returnType, reflector.reflectType(dynamic)); + }); + + test('counts parameters', () { + expect(mirror!.parameters, hasLength(1)); + }); + + test('counts types parameters', () { + expect(mirror!.typeParameters, isEmpty); + }); + + test('correctly reflects parameter types', () { + var p = mirror!.parameters[0]; + expect(p.name, 'x'); + expect(p.isRequired, true); + expect(p.isNamed, false); + expect(p.annotations, isEmpty); + expect(p.type, reflector.reflectType(int)); + }); + }); + + test('make on singleton type returns singleton', () { + expect(container.make(Pokemon), blaziken); + }); + + test('make with generic returns same as make with explicit type', () { + expect(container.make(), blaziken); + }); + + test('make async returns async object', () async { + expect(container.makeAsync(), completion(46)); + }); + + test('make async returns sync object', () async { + expect(container.makeAsync(), completion(blaziken)); + }); + + test('make on aliased singleton returns singleton', () { + container.registerSingleton(blaziken, as: StateError); + expect(container.make(StateError), blaziken); + }); + + test('constructor injects singleton', () { + var lower = container.make(); + expect(lower.lowercaseName, blaziken.name.toLowerCase()); + }); + + test('newInstance works', () { + var type = container.reflector.reflectType(Pokemon)!; + var instance = + type.newInstance('changeName', [blaziken, 'Charizard']).reflectee + as Pokemon; + print(instance); + expect(instance.name, 'Charizard'); + expect(instance.type, PokemonType.fire); + }); + + test('isAssignableTo', () { + var pokemonType = container.reflector.reflectType(Pokemon); + var kantoPokemonType = container.reflector.reflectType(KantoPokemon)!; + + expect(kantoPokemonType.isAssignableTo(pokemonType), true); + expect( + kantoPokemonType + .isAssignableTo(container.reflector.reflectType(String)), + false); + }); +} + +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 Pokemon(name, other.type); + } + + @override + String toString() => 'NAME: $name, TYPE: $type'; +} + +class KantoPokemon extends Pokemon { + KantoPokemon(super.name, super.type); +} + +enum PokemonType { water, fire, grass, ice, poison, flying } diff --git a/incubation/container/container/test/contextual_binding_test.dart b/incubation/container/container/test/contextual_binding_test.dart new file mode 100644 index 0000000..3881209 --- /dev/null +++ b/incubation/container/container/test/contextual_binding_test.dart @@ -0,0 +1,257 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +// Test interfaces and implementations +abstract class Logger { + void log(String message); +} + +class FileLogger implements Logger { + final String filename; + FileLogger(this.filename); + @override + void log(String message) => print('File($filename): $message'); +} + +class ConsoleLogger implements Logger { + @override + void log(String message) => print('Console: $message'); +} + +class LoggerClient { + final Logger logger; + LoggerClient(this.logger); +} + +class SpecialLoggerClient { + final Logger logger; + SpecialLoggerClient(this.logger); +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + }); + + group('Contextual Binding Tests', () { + test('basic contextual binding resolves correctly', () { + // Register default binding + container.registerSingleton(ConsoleLogger()); + + // Register contextual binding + container.when(LoggerClient).needs().give(); + + // The default binding should be used here + var logger = container.make(); + expect(logger, isA()); + + // The contextual binding should be used here + var client = container.make(); + expect(client.logger, isA()); + }); + + test('multiple contextual bindings work independently', () { + container.registerSingleton(ConsoleLogger()); + + container.when(LoggerClient).needs().give(); + container.when(SpecialLoggerClient).needs().give(); + + var client1 = container.make(); + var client2 = container.make(); + + expect(client1.logger, isA()); + expect(client2.logger, isA()); + }); + + test('contextual binding with factory function works', () { + container.registerSingleton(ConsoleLogger()); + + container + .when(LoggerClient) + .needs() + .giveFactory((container) => FileLogger('test.log')); + + var client = container.make(); + expect(client.logger, isA()); + expect((client.logger as FileLogger).filename, equals('test.log')); + }); + + test('contextual binding throws when implementation not found', () { + container.when(LoggerClient).needs().give(); + + expect( + () => container.make(), + throwsA(isA()), + ); + }); + + test('contextual bindings are inherited by child containers', () { + container.registerSingleton(ConsoleLogger()); + container.when(LoggerClient).needs().give(); + + var childContainer = container.createChild(); + var client = childContainer.make(); + + expect(client.logger, isA()); + }); + + test('child container can override parent contextual binding', () { + container.registerSingleton(ConsoleLogger()); + container.when(LoggerClient).needs().give(); + + var childContainer = container.createChild(); + childContainer + .when(LoggerClient) + .needs() + .giveFactory((container) => FileLogger('child.log')); + + var client = childContainer.make(); + expect(client.logger, isA()); + expect((client.logger as FileLogger).filename, equals('child.log')); + }); + }); +} + +// Mock reflector implementation for testing +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) => null; + + @override + ReflectedType? reflectType(Type type) { + if (type == LoggerClient) { + return MockReflectedClass( + 'LoggerClient', + [], + [], + [ + MockConstructor([MockParameter('logger', Logger)]) + ], + [], + type, + (name, positional, named, typeArgs) => LoggerClient(positional[0]), + ); + } else if (type == SpecialLoggerClient) { + return MockReflectedClass( + 'SpecialLoggerClient', + [], + [], + [ + MockConstructor([MockParameter('logger', Logger)]) + ], + [], + type, + (name, positional, named, typeArgs) => + SpecialLoggerClient(positional[0]), + ); + } else if (type == FileLogger) { + return MockReflectedClass( + 'FileLogger', + [], + [], + [ + MockConstructor([MockParameter('filename', String)]) + ], + [], + type, + (name, positional, named, typeArgs) => FileLogger(positional[0]), + ); + } + return null; + } + + @override + ReflectedInstance? reflectInstance(Object? instance) => null; + + @override + ReflectedFunction? reflectFunction(Function function) => null; + + @override + ReflectedType reflectFutureOf(Type type) => throw UnimplementedError(); +} + +class MockReflectedClass extends ReflectedClass { + final Function instanceBuilder; + + MockReflectedClass( + String name, + List typeParameters, + List annotations, + List constructors, + List declarations, + Type reflectedType, + this.instanceBuilder, + ) : super(name, typeParameters, annotations, constructors, declarations, + reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + var instance = instanceBuilder( + constructorName, positionalArguments, namedArguments, typeArguments); + return MockReflectedInstance(this, instance); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (other == null) return false; + return reflectedType == other.reflectedType; + } +} + +class MockReflectedInstance extends ReflectedInstance { + MockReflectedInstance(ReflectedClass clazz, Object? reflectee) + : super(clazz, clazz, reflectee); + + @override + ReflectedInstance getField(String name) { + throw UnimplementedError(); + } +} + +class MockConstructor extends ReflectedFunction { + final List params; + + MockConstructor(this.params) + : super('', [], [], params, false, false, + returnType: MockReflectedType('void', [], dynamic)); + + @override + ReflectedInstance invoke(Invocation invocation) { + throw UnimplementedError(); + } +} + +class MockParameter extends ReflectedParameter { + MockParameter(String name, Type type) + : super(name, [], MockReflectedType(type.toString(), [], type), true, + false); +} + +class MockReflectedType extends ReflectedType { + MockReflectedType(String name, List typeParameters, + Type reflectedType) + : super(name, typeParameters, reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + throw UnimplementedError(); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (other == null) return false; + return reflectedType == other.reflectedType; + } +} diff --git a/incubation/container/container/test/empty_reflector_test.dart b/incubation/container/container/test/empty_reflector_test.dart new file mode 100644 index 0000000..0ed9f14 --- /dev/null +++ b/incubation/container/container/test/empty_reflector_test.dart @@ -0,0 +1,138 @@ +import 'package:platformed_container/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 Object', () { + expect(mirror.reflectedType, Object); + }); + + 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 Object', () { + expect(mirror.reflectedType, Object); + }); + + 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 = Invocation.method(#drive, []); + expect(() => mirror.invoke(invocation), throwsUnsupportedError); + }); + }); + + group('reflectInstance', () { + var mirror = reflector.reflectInstance(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!!!'); + } +} diff --git a/incubation/container/container/test/has_test.dart b/incubation/container/container/test/has_test.dart new file mode 100644 index 0000000..44e39fa --- /dev/null +++ b/incubation/container/container/test/has_test.dart @@ -0,0 +1,51 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +void main() { + late Container container; + + setUp(() { + container = Container(const EmptyReflector()) + ..registerSingleton(Song(title: 'I Wish')) + ..registerNamedSingleton('foo', 1) + ..registerFactory((container) { + return Artist( + name: 'Stevie Wonder', + song: container.make(), + ); + }); + }); + + test('hasNamed', () { + var child = container.createChild()..registerNamedSingleton('bar', 2); + expect(child.hasNamed('foo'), true); + expect(child.hasNamed('bar'), true); + expect(child.hasNamed('baz'), false); + }); + + test('has on singleton', () { + var result = container.has(); + expect(result, true); + }); + + test('has on factory', () { + expect(container.has(), true); + }); + + test('false if neither', () { + expect(container.has(), false); + }); +} + +class Artist { + final String? name; + final Song? song; + + Artist({this.name, this.song}); +} + +class Song { + final String? title; + + Song({this.title}); +} diff --git a/incubation/container/container/test/lazy_test.dart b/incubation/container/container/test/lazy_test.dart new file mode 100644 index 0000000..93d8a64 --- /dev/null +++ b/incubation/container/container/test/lazy_test.dart @@ -0,0 +1,18 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +void main() { + test('returns the same instance', () { + var container = Container(const EmptyReflector()) + ..registerLazySingleton((_) => Dummy('a')); + + var first = container.make(); + expect(container.make(), first); + }); +} + +class Dummy { + final String s; + + Dummy(this.s); +} diff --git a/incubation/container/container/test/method_binding_test.dart b/incubation/container/container/test/method_binding_test.dart new file mode 100644 index 0000000..1c2d022 --- /dev/null +++ b/incubation/container/container/test/method_binding_test.dart @@ -0,0 +1,94 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +class Calculator { + int add(int a, int b) => a + b; + int multiply(int a, int b) => a * b; +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + }); + + group('Method Binding Tests', () { + test('can bind and call method', () { + var calculator = Calculator(); + container.bindMethod('add', calculator.add); + + var result = container.callMethod('add', [5, 3]); + expect(result, equals(8)); + }); + + test('can bind multiple methods', () { + var calculator = Calculator(); + container.bindMethod('add', calculator.add); + container.bindMethod('multiply', calculator.multiply); + + expect(container.callMethod('add', [5, 3]), equals(8)); + expect(container.callMethod('multiply', [5, 3]), equals(15)); + }); + + test('throws when method not found', () { + expect( + () => container.callMethod('nonexistent'), + throwsA(isA()), + ); + }); + + test('throws when binding duplicate method', () { + var calculator = Calculator(); + container.bindMethod('add', calculator.add); + + expect( + () => container.bindMethod('add', calculator.add), + throwsA(isA()), + ); + }); + + test('child container inherits parent methods', () { + var calculator = Calculator(); + container.bindMethod('add', calculator.add); + + var childContainer = container.createChild(); + expect(childContainer.callMethod('add', [5, 3]), equals(8)); + }); + + test('child container can override parent methods', () { + var calculator = Calculator(); + container.bindMethod('add', calculator.add); + + var childContainer = container.createChild(); + childContainer.bindMethod('add', (a, b) => a * b); // Override to multiply + + expect( + container.callMethod('add', [5, 3]), equals(8)); // Parent unchanged + expect(childContainer.callMethod('add', [5, 3]), + equals(15)); // Child overridden + }); + }); +} + +// Minimal mock reflector for method binding tests +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) => null; + + @override + ReflectedType? reflectType(Type type) => null; + + @override + ReflectedInstance? reflectInstance(Object? instance) => null; + + @override + ReflectedFunction? reflectFunction(Function function) => null; + + @override + ReflectedType reflectFutureOf(Type type) => throw UnimplementedError(); +} diff --git a/incubation/container/container/test/mirrors_test.dart b/incubation/container/container/test/mirrors_test.dart new file mode 100644 index 0000000..1470b52 --- /dev/null +++ b/incubation/container/container/test/mirrors_test.dart @@ -0,0 +1,26 @@ +import 'dart:async'; +import 'package:platformed_container/container.dart'; +import 'package:platformed_container/mirrors.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +void main() { + testReflector(const MirrorsReflector()); + + test('futureOf', () { + var r = MirrorsReflector(); + var fStr = r.reflectFutureOf(String); + expect(fStr.reflectedType.toString(), 'Future'); + // expect(fStr.reflectedType, Future.value(null).runtimeType); + }); + + test('concrete future make', () async { + var c = Container(MirrorsReflector()); + c.registerFactory>((_) async => 'hey'); + var fStr = c.reflector.reflectFutureOf(String); + var s1 = await c.make(fStr.reflectedType); + var s2 = await c.makeAsync(String); + print([s1, s2]); + expect(s1, s2); + }); +} diff --git a/incubation/container/container/test/named_test.dart b/incubation/container/container/test/named_test.dart new file mode 100644 index 0000000..bb94131 --- /dev/null +++ b/incubation/container/container/test/named_test.dart @@ -0,0 +1,34 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +void main() { + late Container container; + + setUp(() { + container = Container(const EmptyReflector()); + container.registerNamedSingleton('foo', Foo(bar: 'baz')); + }); + + test('fetch by name', () { + expect(container.findByName('foo').bar, 'baz'); + }); + + test('cannot redefine', () { + expect(() => container.registerNamedSingleton('foo', Foo(bar: 'quux')), + throwsStateError); + }); + + test('throws on unknown name', () { + expect(() => container.findByName('bar'), throwsStateError); + }); + + test('throws on incorrect type', () { + expect(() => container.findByName>('foo'), throwsA(anything)); + }); +} + +class Foo { + final String? bar; + + Foo({this.bar}); +} diff --git a/incubation/container/container/test/scoped_test.dart b/incubation/container/container/test/scoped_test.dart new file mode 100644 index 0000000..e68c303 --- /dev/null +++ b/incubation/container/container/test/scoped_test.dart @@ -0,0 +1,322 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +class RequestScope { + final String id; + RequestScope(this.id); +} + +class UserService { + final RequestScope scope; + UserService(this.scope); +} + +class OrderService { + final RequestScope scope; + OrderService(this.scope); +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + }); + + group('Scoped Instance Tests', () { + test('scoped instances are shared within scope', () { + container.scoped((c) => RequestScope('request-1')); + container.registerFactory( + (c) => UserService(c.make())); + container.registerFactory( + (c) => OrderService(c.make())); + + var userService = container.make(); + var orderService = container.make(); + + expect(userService.scope, same(orderService.scope)); + expect(userService.scope.id, equals('request-1')); + }); + + test('scoped instances are cleared after clearScoped', () { + container.scoped((c) => RequestScope('request-1')); + var scope1 = container.make(); + + container.clearScoped(); + container.scoped((c) => RequestScope('request-2')); + var scope2 = container.make(); + + expect(scope1.id, equals('request-1')); + expect(scope2.id, equals('request-2')); + expect(scope1, isNot(same(scope2))); + }); + + test('child container inherits parent scoped instances', () { + container.scoped((c) => RequestScope('request-1')); + var childContainer = container.createChild(); + + var parentScope = container.make(); + var childScope = childContainer.make(); + + expect(parentScope, same(childScope)); + }); + + test('child container can override parent scoped instances', () { + container.scoped((c) => RequestScope('parent-request')); + var childContainer = container.createChild(); + childContainer.scoped((c) => RequestScope('child-request')); + + var parentScope = container.make(); + var childScope = childContainer.make(); + + expect(parentScope.id, equals('parent-request')); + expect(childScope.id, equals('child-request')); + expect(parentScope, isNot(same(childScope))); + }); + + test('clearing parent scoped instances affects child containers', () { + container.scoped((c) => RequestScope('request-1')); + var childContainer = container.createChild(); + + var beforeClear = childContainer.make(); + container.clearScoped(); + container.scoped((c) => RequestScope('request-2')); + var afterClear = childContainer.make(); + + expect(beforeClear.id, equals('request-1')); + expect(afterClear.id, equals('request-2')); + expect(beforeClear, isNot(same(afterClear))); + }); + }); + + group('Resolution Lifecycle Tests', () { + test('before resolving callbacks are called', () { + var callLog = []; + container.beforeResolving((type, args, container) { + callLog.add('before:${type.toString()}'); + }); + + container.registerSingleton(RequestScope('test')); + container.make(); + + expect(callLog, contains('before:RequestScope')); + }); + + test('resolving callbacks are called', () { + var callLog = []; + container.resolving((instance, container) { + callLog.add('resolving:${(instance as RequestScope).id}'); + }); + + container.registerSingleton(RequestScope('test')); + container.make(); + + expect(callLog, contains('resolving:test')); + }); + + test('after resolving callbacks are called', () { + var callLog = []; + container.afterResolving((instance, container) { + callLog.add('after:${(instance as RequestScope).id}'); + }); + + container.registerSingleton(RequestScope('test')); + container.make(); + + expect(callLog, contains('after:test')); + }); + + test('callbacks are called in correct order', () { + var callOrder = []; + + container.beforeResolving((type, args, container) { + callOrder.add('before'); + }); + + container.resolving((instance, container) { + callOrder.add('resolving'); + }); + + container.afterResolving((instance, container) { + callOrder.add('after'); + }); + + container.registerSingleton(RequestScope('test')); + container.make(); + + expect(callOrder, orderedEquals(['before', 'resolving', 'after'])); + }); + + test('child container inherits parent callbacks', () { + var callLog = []; + container.beforeResolving((type, args, container) { + callLog.add('parent-before'); + }); + + var childContainer = container.createChild(); + childContainer.registerSingleton(RequestScope('test')); + childContainer.make(); + + expect(callLog, contains('parent-before')); + }); + + test('child container can add its own callbacks', () { + var callLog = []; + + container.beforeResolving((type, args, container) { + callLog.add('parent-before'); + }); + + var childContainer = container.createChild(); + childContainer.beforeResolving((type, args, container) { + callLog.add('child-before'); + }); + + childContainer.registerSingleton(RequestScope('test')); + childContainer.make(); + + expect(callLog, containsAll(['parent-before', 'child-before'])); + }); + }); +} + +// Minimal mock reflector for scoped and lifecycle tests +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) => null; + + @override + ReflectedType? reflectType(Type type) { + if (type == RequestScope) { + return MockReflectedClass( + 'RequestScope', + [], + [], + [ + MockConstructor([MockParameter('id', String)]) + ], + [], + type, + (name, positional, named, typeArgs) => RequestScope(positional[0]), + ); + } else if (type == UserService) { + return MockReflectedClass( + 'UserService', + [], + [], + [ + MockConstructor([MockParameter('scope', RequestScope)]) + ], + [], + type, + (name, positional, named, typeArgs) => UserService(positional[0]), + ); + } else if (type == OrderService) { + return MockReflectedClass( + 'OrderService', + [], + [], + [ + MockConstructor([MockParameter('scope', RequestScope)]) + ], + [], + type, + (name, positional, named, typeArgs) => OrderService(positional[0]), + ); + } + return null; + } + + @override + ReflectedInstance? reflectInstance(Object? instance) => null; + + @override + ReflectedFunction? reflectFunction(Function function) => null; + + @override + ReflectedType reflectFutureOf(Type type) => throw UnimplementedError(); +} + +class MockReflectedClass extends ReflectedClass { + final Function instanceBuilder; + + MockReflectedClass( + String name, + List typeParameters, + List annotations, + List constructors, + List declarations, + Type reflectedType, + this.instanceBuilder, + ) : super(name, typeParameters, annotations, constructors, declarations, + reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + var instance = instanceBuilder( + constructorName, positionalArguments, namedArguments, typeArguments); + return MockReflectedInstance(this, instance); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (other == null) return false; + return reflectedType == other.reflectedType; + } +} + +class MockReflectedInstance extends ReflectedInstance { + MockReflectedInstance(ReflectedClass clazz, Object? reflectee) + : super(clazz, clazz, reflectee); + + @override + ReflectedInstance getField(String name) { + throw UnimplementedError(); + } +} + +class MockConstructor extends ReflectedFunction { + final List params; + + MockConstructor(this.params) + : super('', [], [], params, false, false, + returnType: MockReflectedType('void', [], dynamic)); + + @override + ReflectedInstance invoke(Invocation invocation) { + throw UnimplementedError(); + } +} + +class MockParameter extends ReflectedParameter { + MockParameter(String name, Type type) + : super(name, [], MockReflectedType(type.toString(), [], type), true, + false); +} + +class MockReflectedType extends ReflectedType { + MockReflectedType(String name, List typeParameters, + Type reflectedType) + : super(name, typeParameters, reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + throw UnimplementedError(); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (other == null) return false; + return reflectedType == other.reflectedType; + } +} diff --git a/incubation/container/container/test/tag_test.dart b/incubation/container/container/test/tag_test.dart new file mode 100644 index 0000000..6f7ab68 --- /dev/null +++ b/incubation/container/container/test/tag_test.dart @@ -0,0 +1,241 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; +import 'common.dart'; + +// Test interfaces and implementations +abstract class Repository { + String getName(); +} + +class UserRepository implements Repository { + @override + String getName() => 'users'; +} + +class ProductRepository implements Repository { + @override + String getName() => 'products'; +} + +class OrderRepository implements Repository { + @override + String getName() => 'orders'; +} + +void main() { + late Container container; + + setUp(() { + container = Container(MockReflector()); + }); + + group('Tag Tests', () { + test('can tag and resolve multiple bindings', () { + container.registerSingleton(UserRepository(), + as: UserRepository); + container.registerSingleton(ProductRepository(), + as: ProductRepository); + container.registerSingleton(OrderRepository(), + as: OrderRepository); + + container.tag([UserRepository, ProductRepository], 'basic'); + container.tag([OrderRepository], 'advanced'); + container.tag([UserRepository, OrderRepository], 'critical'); + + var basicRepos = container.tagged('basic'); + expect(basicRepos, hasLength(2)); + expect(basicRepos.map((r) => r.getName()), + containsAll(['users', 'products'])); + + var advancedRepos = container.tagged('advanced'); + expect(advancedRepos, hasLength(1)); + expect(advancedRepos.first.getName(), equals('orders')); + + var criticalRepos = container.tagged('critical'); + expect(criticalRepos, hasLength(2)); + expect(criticalRepos.map((r) => r.getName()), + containsAll(['users', 'orders'])); + }); + + test('can tag same binding with multiple tags', () { + container.registerSingleton(UserRepository(), + as: UserRepository); + + container.tag([UserRepository], 'tag1'); + container.tag([UserRepository], 'tag2'); + + expect(container.tagged('tag1'), hasLength(1)); + expect(container.tagged('tag2'), hasLength(1)); + expect(container.tagged('tag1').first, isA()); + expect(container.tagged('tag2').first, isA()); + }); + + test('returns empty list for unknown tag', () { + var repos = container.tagged('nonexistent'); + expect(repos, isEmpty); + }); + + test('child container inherits parent tags', () { + container.registerSingleton(UserRepository(), + as: UserRepository); + container.registerSingleton(ProductRepository(), + as: ProductRepository); + container.tag([UserRepository, ProductRepository], 'basic'); + + var childContainer = container.createChild(); + var basicRepos = childContainer.tagged('basic'); + expect(basicRepos, hasLength(2)); + expect(basicRepos.map((r) => r.getName()), + containsAll(['users', 'products'])); + }); + + test('child container can add new tags', () { + container.registerSingleton(UserRepository(), + as: UserRepository); + container.tag([UserRepository], 'parent-tag'); + + var childContainer = container.createChild(); + childContainer.registerSingleton(ProductRepository(), + as: ProductRepository); + childContainer.tag([ProductRepository], 'child-tag'); + + expect(childContainer.tagged('parent-tag'), hasLength(1)); + expect(childContainer.tagged('child-tag'), hasLength(1)); + expect(childContainer.tagged('parent-tag').first, isA()); + expect( + childContainer.tagged('child-tag').first, isA()); + }); + + test('child container can extend parent tags', () { + container.registerSingleton(UserRepository(), + as: UserRepository); + container.tag([UserRepository], 'repositories'); + + var childContainer = container.createChild(); + childContainer.registerSingleton(ProductRepository(), + as: ProductRepository); + childContainer.tag([ProductRepository], 'repositories'); + + var repos = childContainer.tagged('repositories'); + expect(repos, hasLength(2)); + expect(repos.map((r) => r.getName()), containsAll(['users', 'products'])); + }); + }); +} + +// Minimal mock reflector for tag tests +class MockReflector extends Reflector { + @override + String? getName(Symbol symbol) => null; + + @override + ReflectedClass? reflectClass(Type clazz) => null; + + @override + ReflectedType? reflectType(Type type) { + if (type == UserRepository || + type == ProductRepository || + type == OrderRepository) { + return MockReflectedClass( + type.toString(), + [], + [], + [MockConstructor([])], + [], + type, + (name, positional, named, typeArgs) => _createInstance(type), + ); + } + return null; + } + + dynamic _createInstance(Type type) { + if (type == UserRepository) return UserRepository(); + if (type == ProductRepository) return ProductRepository(); + if (type == OrderRepository) return OrderRepository(); + throw StateError('Unknown type: $type'); + } + + @override + ReflectedInstance? reflectInstance(Object? instance) => null; + + @override + ReflectedFunction? reflectFunction(Function function) => null; + + @override + ReflectedType reflectFutureOf(Type type) => throw UnimplementedError(); +} + +class MockReflectedClass extends ReflectedClass { + final Function instanceBuilder; + + MockReflectedClass( + String name, + List typeParameters, + List annotations, + List constructors, + List declarations, + Type reflectedType, + this.instanceBuilder, + ) : super(name, typeParameters, annotations, constructors, declarations, + reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + var instance = instanceBuilder( + constructorName, positionalArguments, namedArguments, typeArguments); + return MockReflectedInstance(this, instance); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (other == null) return false; + return reflectedType == other.reflectedType; + } +} + +class MockReflectedInstance extends ReflectedInstance { + MockReflectedInstance(ReflectedClass clazz, Object? reflectee) + : super(clazz, clazz, reflectee); + + @override + ReflectedInstance getField(String name) { + throw UnimplementedError(); + } +} + +class MockConstructor extends ReflectedFunction { + final List params; + + MockConstructor(this.params) + : super('', [], [], params, false, false, + returnType: MockReflectedType('void', [], dynamic)); + + @override + ReflectedInstance invoke(Invocation invocation) { + throw UnimplementedError(); + } +} + +class MockReflectedType extends ReflectedType { + MockReflectedType(String name, List typeParameters, + Type reflectedType) + : super(name, typeParameters, reflectedType); + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + throw UnimplementedError(); + } + + @override + bool isAssignableTo(ReflectedType? other) { + if (other == null) return false; + return reflectedType == other.reflectedType; + } +} diff --git a/incubation/container/container/test/throwing_reflector_test.dart b/incubation/container/container/test/throwing_reflector_test.dart new file mode 100644 index 0000000..32cc7d3 --- /dev/null +++ b/incubation/container/container/test/throwing_reflector_test.dart @@ -0,0 +1,36 @@ +import 'package:platformed_container/container.dart'; +import 'package:test/test.dart'; + +void main() { + var reflector = const ThrowingReflector(); + + test('getName', () { + expect(reflector.getName(#foo), 'foo'); + expect(reflector.getName(#==), '=='); + }); + + test('reflectClass fails', () { + expect(() => reflector.reflectClass(Truck), throwsUnsupportedError); + }); + + test('reflectType fails', () { + expect(() => reflector.reflectType(Truck), throwsUnsupportedError); + }); + + test('reflectFunction throws', () { + void doIt(int x) {} + expect(() => reflector.reflectFunction(doIt), throwsUnsupportedError); + }); + + test('reflectInstance throws', () { + expect(() => reflector.reflectInstance(Truck()), throwsUnsupportedError); + }); +} + +class Truck { + int get wheelCount => 4; + + void drive() { + print('Vroom!!!'); + } +} diff --git a/incubation/container/container_generator/.gitignore b/incubation/container/container_generator/.gitignore new file mode 100644 index 0000000..4b16115 --- /dev/null +++ b/incubation/container/container_generator/.gitignore @@ -0,0 +1,16 @@ +# See https://www.dartlang.org/guides/libraries/private-files + +# Files and directories created by pub +.dart_tool/ +.packages +.pub/ +build/ +# If you're building an application, you may want to check-in your pubspec.lock +pubspec.lock + +# Directory created by dartdoc +# If you don't generate documentation locally you can remove this line. +doc/api/ + +test/*.reflectable.dart +example/*.reflectable.dart diff --git a/incubation/container/container_generator/CHANGELOG.md b/incubation/container/container_generator/CHANGELOG.md new file mode 100644 index 0000000..5fecd3e --- /dev/null +++ b/incubation/container/container_generator/CHANGELOG.md @@ -0,0 +1,58 @@ +# Change Log + +## 8.1.1 + +* Updated repository link + +## 8.1.0 + +* Updated `lints` to 3.0.0 +* Fixed analyser warnings + +## 8.0.0 + +* Require Dart >= 3.0 + +## 7.1.0-beta.1 + +* Require Dart >= 2.19 +* Upgraded `relectable` to 4.x.x + +## 7.0.0 + +* Require Dart >= 2.17 + +## 6.0.0 + +* Require Dart >= 2.16 + +## 5.0.0 + +* Skipped release + +## 4.0.0 + +* Skipped release + +## 3.0.1 + +* Updated `package:angel3_container` + +## 3.0.0 + +* Fixed NNBD issues +* All 9 test cases passed + +## 3.0.0-beta.1 + +* Migrated to support Dart >= 2.12 NNBD +* Updated linter to `package:lints` +* Updated to use `platform_` packages + +## 2.0.0 + +* Migrated to work with Dart >= 2.12 Non NNBD + +## 1.0.1 + +* Update for `pkg:angel_container@1.0.3`. diff --git a/incubation/container/container_generator/LICENSE b/incubation/container/container_generator/LICENSE new file mode 100644 index 0000000..df5e063 --- /dev/null +++ b/incubation/container/container_generator/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, dukefirehawk.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/incubation/container/container_generator/README.md b/incubation/container/container_generator/README.md new file mode 100644 index 0000000..432ce12 --- /dev/null +++ b/incubation/container/container_generator/README.md @@ -0,0 +1,32 @@ +# Protevus Container Generator + +![Pub Version (including pre-releases)](https://img.shields.io/pub/v/angel3_container_generator?include_prereleases) +[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) +[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) +[![License](https://img.shields.io/github/license/dart-backend/angel)](https://github.com/dart-backend/angel/tree/master/packages/container/angel3_container_generator/LICENSE) + +An alternative container for Protevus that uses `reflectable` package instead of `dart:mirrors` for reflection. However, `reflectable` has more limited relfection capabilities when compared to `dart:mirrors`. + +## Usage + +* Annotable the class with `@contained`. +* Run `dart run build_runner build ` +* Alternatively create a `build.xml` file with the following content + + ```yaml + targets: + $default: + builders: + reflectable: + generate_for: + - bin/**_controller.dart + options: + formatted: true + ``` + +## Known limitation + +* `analyser` 6.x is not supported due to `reflectable` +* Reflection on functions/closures is not supported +* Reflection on private declarations is not supported +* Reflection on generic type is not supported diff --git a/incubation/container/container_generator/analysis_options.yaml b/incubation/container/container_generator/analysis_options.yaml new file mode 100644 index 0000000..ea2c9e9 --- /dev/null +++ b/incubation/container/container_generator/analysis_options.yaml @@ -0,0 +1 @@ +include: package:lints/recommended.yaml \ No newline at end of file diff --git a/incubation/container/container_generator/doc/.gitkeep b/incubation/container/container_generator/doc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/incubation/container/container_generator/example/main.dart b/incubation/container/container_generator/example/main.dart new file mode 100644 index 0000000..bc24685 --- /dev/null +++ b/incubation/container/container_generator/example/main.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:platform_container/container.dart'; +import 'package:platform_container_generator/generator.dart'; + +Future main() async { + // Create a container instance. + Container container = Container(GeneratedReflector()); + + // Register a singleton. + container.registerSingleton(Engine(40)); + + // You can also omit the type annotation, in which the object's runtime type will be used. + // If you're injecting an abstract class, prefer the type annotation. + // + // container.registerSingleton(Engine(40)); + + // Register a factory that creates a truck. + container.registerFactory((container) { + return _TruckImpl(container.make()); + }); + + // Use `make` to create an instance. + var truck = container.make(); + + // You can also resolve injections asynchronously. + container.registerFactory>((_) async => 24); + print(await container.makeAsync()); + + // Asynchronous resolution also works for plain objects. + await container.makeAsync().then((t) => t.drive()); + + // Register a named singleton. + container.registerNamedSingleton('the_truck', truck); + + // Should print: 'Vroom! I have 40 horsepower in my engine.' + truck.drive(); + + // Should print the same. + container.findByName('the_truck').drive(); + + // We can make a child container with its own factory. + var childContainer = container.createChild(); + + childContainer.registerFactory((container) { + return _TruckImpl(Engine(5666)); + }); + + // Make a truck with 5666 HP. + childContainer.make().drive(); + + // However, calling `make` will return the Engine singleton we created above. + print(childContainer.make().horsePower); +} + +abstract class Truck { + void drive(); +} + +class Engine { + final int horsePower; + + Engine(this.horsePower); +} + +class _TruckImpl implements Truck { + final Engine engine; + + _TruckImpl(this.engine); + + @override + void drive() { + print('Vroom! I have ${engine.horsePower} horsepower in my engine.'); + } +} diff --git a/incubation/container/container_generator/lib/generator.dart b/incubation/container/container_generator/lib/generator.dart new file mode 100644 index 0000000..40dd09f --- /dev/null +++ b/incubation/container/container_generator/lib/generator.dart @@ -0,0 +1,255 @@ +import 'package:platform_container/container.dart'; +import 'package:reflectable/reflectable.dart'; + +/// A [Reflectable] instance that can be used as an annotation on types to generate metadata for them. +const Reflectable contained = ContainedReflectable(); + +@contained +class ContainedReflectable extends Reflectable { + const ContainedReflectable() + : super( + topLevelInvokeCapability, + typeAnnotationQuantifyCapability, + superclassQuantifyCapability, + libraryCapability, + invokingCapability, + metadataCapability, + reflectedTypeCapability, + typeCapability, + typingCapability); +} + +/// A [Reflector] instance that uses a [Reflectable] to reflect upon data. +class GeneratedReflector extends Reflector { + final Reflectable reflectable; + + const GeneratedReflector([this.reflectable = contained]); + + @override + String getName(Symbol symbol) { + return symbol.toString().substring(7); + } + + @override + ReflectedClass reflectClass(Type clazz) { + return reflectType(clazz) as ReflectedClass; + } + + @override + ReflectedFunction reflectFunction(Function function) { + if (!reflectable.canReflect(function)) { + throw UnsupportedError('Cannot reflect $function.'); + } + + var mirror = reflectable.reflect(function); + + if (mirror is ClosureMirror) { + return _GeneratedReflectedFunction(mirror.function, this, mirror); + } else { + throw ArgumentError('$function is not a Function.'); + } + } + + @override + ReflectedInstance reflectInstance(Object object) { + if (!reflectable.canReflect(object)) { + throw UnsupportedError('Cannot reflect $object.'); + } else { + var mirror = reflectable.reflect(object); + return _GeneratedReflectedInstance(mirror, this); + } + } + + @override + ReflectedType reflectType(Type type) { + if (!reflectable.canReflectType(type)) { + throw UnsupportedError('Cannot reflect $type.'); + } else { + var mirror = reflectable.reflectType(type); + return mirror is ClassMirror + ? _GeneratedReflectedClass(mirror, this) + : _GeneratedReflectedType(mirror); + } + } +} + +class _GeneratedReflectedInstance extends ReflectedInstance { + final InstanceMirror mirror; + final GeneratedReflector reflector; + + _GeneratedReflectedInstance(this.mirror, this.reflector) + : super(_GeneratedReflectedType(mirror.type), + _GeneratedReflectedClass(mirror.type, reflector), mirror.reflectee); + + @override + ReflectedType get type => clazz; + + @override + ReflectedInstance getField(String name) { + var result = mirror.invokeGetter(name)!; + var instance = reflector.reflectable.reflect(result); + return _GeneratedReflectedInstance(instance, reflector); + } +} + +class _GeneratedReflectedClass extends ReflectedClass { + final ClassMirror mirror; + final Reflector reflector; + + _GeneratedReflectedClass(this.mirror, this.reflector) + : super(mirror.simpleName, [], [], [], [], mirror.reflectedType); + + @override + List get typeParameters => + mirror.typeVariables.map(_convertTypeVariable).toList(); + + @override + List get constructors => + _constructorsOf(mirror.declarations, reflector); + + @override + List get declarations => + _declarationsOf(mirror.declarations, reflector); + + @override + List get annotations => mirror.metadata + .map(reflector.reflectInstance) + .whereType() + .toList(); + + @override + bool isAssignableTo(ReflectedType? other) { + if (other is _GeneratedReflectedClass) { + return mirror.isAssignableTo(other.mirror); + } else if (other is _GeneratedReflectedType) { + return mirror.isAssignableTo(other.mirror); + } else { + return false; + } + } + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map? namedArguments, List? typeArguments]) { + namedArguments ??= {}; + var result = mirror.newInstance(constructorName, positionalArguments, + namedArguments.map((k, v) => MapEntry(Symbol(k), v))); + return reflector.reflectInstance(result)!; + } +} + +class _GeneratedReflectedType extends ReflectedType { + final TypeMirror mirror; + + _GeneratedReflectedType(this.mirror) + : super(mirror.simpleName, [], mirror.reflectedType); + + @override + List get typeParameters => + mirror.typeVariables.map(_convertTypeVariable).toList(); + + @override + bool isAssignableTo(ReflectedType? other) { + if (other is _GeneratedReflectedClass) { + return mirror.isAssignableTo(other.mirror); + } else if (other is _GeneratedReflectedType) { + return mirror.isAssignableTo(other.mirror); + } else { + return false; + } + } + + @override + ReflectedInstance newInstance( + String constructorName, List positionalArguments, + [Map namedArguments = const {}, + List typeArguments = const []]) { + throw UnsupportedError('Cannot create a new instance of $reflectedType.'); + } +} + +class _GeneratedReflectedFunction extends ReflectedFunction { + final MethodMirror mirror; + final Reflector reflector; + final ClosureMirror? closure; + + _GeneratedReflectedFunction(this.mirror, this.reflector, [this.closure]) + : super( + mirror.simpleName, + [], + [], + mirror.parameters + .map((p) => _convertParameter(p, reflector)) + .toList(), + mirror.isGetter, + mirror.isSetter, + returnType: !mirror.isRegularMethod + ? null + : _GeneratedReflectedType(mirror.returnType)); + + @override + List get annotations => mirror.metadata + .map(reflector.reflectInstance) + .whereType() + .toList(); + + @override + ReflectedInstance invoke(Invocation invocation) { + if (closure != null) { + throw UnsupportedError('Only closures can be invoked directly.'); + } else { + var result = closure!.delegate(invocation)!; + return reflector.reflectInstance(result)!; + } + } +} + +List _constructorsOf( + Map map, Reflector reflector) { + return map.entries.fold>([], (out, entry) { + var v = entry.value; + + if (v is MethodMirror && v.isConstructor) { + return out..add(_GeneratedReflectedFunction(v, reflector)); + } else { + return out; + } + }); +} + +List _declarationsOf( + Map map, Reflector reflector) { + return map.entries.fold>([], (out, entry) { + var v = entry.value; + + if (v is VariableMirror) { + var decl = ReflectedDeclaration(v.simpleName, v.isStatic, null); + return out..add(decl); + } + if (v is MethodMirror) { + var decl = ReflectedDeclaration( + v.simpleName, v.isStatic, _GeneratedReflectedFunction(v, reflector)); + return out..add(decl); + } else { + return out; + } + }); +} + +ReflectedTypeParameter _convertTypeVariable(TypeVariableMirror mirror) { + return ReflectedTypeParameter(mirror.simpleName); +} + +ReflectedParameter _convertParameter( + ParameterMirror mirror, Reflector reflector) { + return ReflectedParameter( + mirror.simpleName, + mirror.metadata + .map(reflector.reflectInstance) + .whereType() + .toList(), + reflector.reflectType(mirror.type.reflectedType)!, + !mirror.isOptional, + mirror.isNamed); +} diff --git a/incubation/container/container_generator/pubspec.yaml b/incubation/container/container_generator/pubspec.yaml new file mode 100644 index 0000000..0a5eec8 --- /dev/null +++ b/incubation/container/container_generator/pubspec.yaml @@ -0,0 +1,19 @@ +name: platformed_container_generator +version: 9.0.0 +description: Protevus Platform Codegen support for using pkg:reflectable with pkg:platform_container. +homepage: https://protevus.com +documentation: https://docs.protevus.com +repository: https://git.protevus.com/protevus/platform/src/branch/main/packages/container/container_generator +environment: + sdk: '>=3.3.0 <4.0.0' +dependencies: + platform_container: ^9.0.0 + reflectable: ^4.0.12 +dev_dependencies: + build_runner: ^2.4.13 + build_test: ^2.2.2 + test: ^1.25.8 + lints: ^4.0.0 +# dependency_overrides: +# platform_container: +# path: ../platform_container diff --git a/incubation/container/container_generator/test/reflector_test.dart b/incubation/container/container_generator/test/reflector_test.dart new file mode 100644 index 0000000..e2bd7d9 --- /dev/null +++ b/incubation/container/container_generator/test/reflector_test.dart @@ -0,0 +1,179 @@ +import 'package:platform_container/container.dart'; +import 'package:platform_container_generator/generator.dart'; + +import 'package:test/test.dart'; +import 'reflector_test.reflectable.dart'; + +void main() { + initializeReflectable(); + + var reflector = const GeneratedReflector(); + late Container container; + + setUp(() { + container = Container(reflector); + container.registerSingleton(Artist(name: 'Stevie Wonder')); + }); + + group('reflectClass', () { + var mirror = reflector.reflectClass(Artist); + + test('name', () { + expect(mirror.name, 'Artist'); + }); + }); + + test('inject constructor parameters', () { + var album = container.make(); + print(album.title); + expect(album.title, 'flowers by stevie wonder'); + }); + + // Skip as pkg:reflectable cannot reflect on closures at all (yet) + //testReflector(reflector); +} + +@contained +void returnVoidFromAFunction(int x) {} + +void testReflector(Reflector reflector) { + var blaziken = Pokemon('Blaziken', PokemonType.fire); + late Container container; + + setUp(() { + container = Container(reflector); + container.registerSingleton(blaziken); + }); + + test('get field', () { + var blazikenMirror = reflector.reflectInstance(blaziken)!; + expect(blazikenMirror.getField('type').reflectee, blaziken.type); + }); + + group('reflectFunction', () { + var mirror = reflector.reflectFunction(returnVoidFromAFunction); + + test('void return type returns dynamic', () { + expect(mirror?.returnType, reflector.reflectType(dynamic)); + }); + + test('counts parameters', () { + expect(mirror?.parameters, hasLength(1)); + }); + + test('counts types parameters', () { + expect(mirror?.typeParameters, isEmpty); + }); + + test('correctly reflects parameter types', () { + var p = mirror?.parameters[0]; + expect(p?.name, 'x'); + expect(p?.isRequired, true); + expect(p?.isNamed, false); + expect(p?.annotations, isEmpty); + expect(p?.type, reflector.reflectType(int)); + }); + }, skip: 'pkg:reflectable cannot reflect on closures at all (yet)'); + + test('make on singleton type returns singleton', () { + expect(container.make(Pokemon), blaziken); + }); + + test('make with generic returns same as make with explicit type', () { + expect(container.make(), blaziken); + }); + + test('make on aliased singleton returns singleton', () { + container.registerSingleton(blaziken, as: StateError); + expect(container.make(StateError), blaziken); + }); + + test('constructor injects singleton', () { + var lower = container.make(); + expect(lower.lowercaseName, blaziken.name.toLowerCase()); + }); + + test('newInstance works', () { + var type = container.reflector.reflectType(Pokemon)!; + var instance = + type.newInstance('changeName', [blaziken, 'Charizard']).reflectee + as Pokemon; + print(instance); + expect(instance.name, 'Charizard'); + expect(instance.type, PokemonType.fire); + }); + + test('isAssignableTo', () { + var pokemonType = container.reflector.reflectType(Pokemon); + var kantoPokemonType = container.reflector.reflectType(KantoPokemon)!; + + expect(kantoPokemonType.isAssignableTo(pokemonType), true); + + expect( + kantoPokemonType + .isAssignableTo(container.reflector.reflectType(String)), + false); + }); +} + +@contained +class LowerPokemon { + final Pokemon pokemon; + + LowerPokemon(this.pokemon); + + String get lowercaseName => pokemon.name.toLowerCase(); +} + +@contained +class Pokemon { + final String name; + final PokemonType type; + + Pokemon(this.name, this.type); + + factory Pokemon.changeName(Pokemon other, String name) { + return Pokemon(name, other.type); + } + + @override + String toString() => 'NAME: $name, TYPE: $type'; +} + +@contained +class KantoPokemon extends Pokemon { + KantoPokemon(super.name, super.type); +} + +@contained +enum PokemonType { water, fire, grass, ice, poison, flying } + +@contained +class Artist { + final String name; + + Artist({required this.name}); + + String get lowerName { + return name.toLowerCase(); + } +} + +@contained +class Album { + final Artist artist; + + Album(this.artist); + + String get title => 'flowers by ${artist.lowerName}'; +} + +@contained +class AlbumLength { + final Artist artist; + final Album album; + + AlbumLength(this.artist, this.album); + + int get totalLength => artist.name.length + album.title.length; +}