add: adding reflection package
This commit is contained in:
parent
1d2a4d805c
commit
f33d7ce33c
12 changed files with 1801 additions and 0 deletions
21
packages/reflection/LICENSE
Normal file
21
packages/reflection/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 The Reflection Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
167
packages/reflection/README.md
Normal file
167
packages/reflection/README.md
Normal file
|
@ -0,0 +1,167 @@
|
|||
# Dart Pure Reflection
|
||||
|
||||
A lightweight, cross-platform reflection system for Dart that provides runtime type introspection and manipulation without using `dart:mirrors` or code generation.
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Works on all platforms (Web, Mobile, Desktop)
|
||||
- ✅ No dependency on `dart:mirrors`
|
||||
- ✅ No code generation required
|
||||
- ✅ Pure runtime reflection
|
||||
- ✅ Type-safe property access
|
||||
- ✅ Method invocation with argument validation
|
||||
- ✅ Constructor invocation support
|
||||
- ✅ Comprehensive error handling
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your package's `pubspec.yaml` file:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
reflection: ^1.0.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
1. Add the `@reflectable` annotation and `Reflector` mixin to your class:
|
||||
|
||||
```dart
|
||||
import 'package:reflection/reflection.dart';
|
||||
|
||||
@reflectable
|
||||
class User with Reflector {
|
||||
String name;
|
||||
int age;
|
||||
final String id;
|
||||
|
||||
User(this.name, this.age, {required this.id});
|
||||
}
|
||||
```
|
||||
|
||||
2. Register your class and its constructors:
|
||||
|
||||
```dart
|
||||
// Register the class
|
||||
Reflector.register(User);
|
||||
|
||||
// Register constructors
|
||||
Reflector.registerConstructor(
|
||||
User,
|
||||
'', // Default constructor
|
||||
(String name, int age, {String? id}) {
|
||||
if (id == null) throw ArgumentError.notNull('id');
|
||||
return User(name, age, id: id);
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
### Reflecting on Types
|
||||
|
||||
```dart
|
||||
final reflector = RuntimeReflector.instance;
|
||||
|
||||
// Get type metadata
|
||||
final userType = reflector.reflectType(User);
|
||||
print('Type name: ${userType.name}');
|
||||
print('Properties: ${userType.properties.keys.join(', ')}');
|
||||
print('Methods: ${userType.methods.keys.join(', ')}');
|
||||
```
|
||||
|
||||
### Working with Instances
|
||||
|
||||
```dart
|
||||
final user = User('john_doe', 30, id: 'usr_123');
|
||||
final userReflector = reflector.reflect(user);
|
||||
|
||||
// Read properties
|
||||
final name = userReflector.getField('name'); // john_doe
|
||||
final age = userReflector.getField('age'); // 30
|
||||
|
||||
// Write properties
|
||||
userReflector.setField('name', 'jane_doe');
|
||||
userReflector.setField('age', 25);
|
||||
|
||||
// Invoke methods
|
||||
userReflector.invoke('someMethod', ['arg1', 'arg2']);
|
||||
```
|
||||
|
||||
### Creating Instances
|
||||
|
||||
```dart
|
||||
// Using default constructor
|
||||
final newUser = reflector.createInstance(
|
||||
User,
|
||||
positionalArgs: ['alice', 28],
|
||||
namedArgs: {'id': 'usr_456'},
|
||||
) as User;
|
||||
|
||||
// Using named constructor
|
||||
final specialUser = reflector.createInstance(
|
||||
User,
|
||||
constructorName: 'special',
|
||||
positionalArgs: ['bob'],
|
||||
) as User;
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The package provides specific exceptions for different error cases:
|
||||
|
||||
- `NotReflectableException`: Thrown when attempting to reflect on a non-reflectable type
|
||||
- `ReflectionException`: Base class for reflection-related errors
|
||||
- `InvalidArgumentsException`: Thrown when providing invalid arguments to a method or constructor
|
||||
- `MemberNotFoundException`: Thrown when a property or method is not found
|
||||
|
||||
```dart
|
||||
try {
|
||||
reflector.reflect(NonReflectableClass());
|
||||
} catch (e) {
|
||||
print(e); // NotReflectableException: Type "NonReflectableClass" is not marked as @reflectable
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
See the [example](example/reflection_example.dart) for a complete working demonstration.
|
||||
|
||||
## Limitations
|
||||
|
||||
1. Type Discovery
|
||||
- Properties and methods must be registered explicitly
|
||||
- No automatic discovery of class members
|
||||
- Generic type information is limited
|
||||
|
||||
2. Performance
|
||||
- First access to a type involves metadata creation
|
||||
- Subsequent accesses use cached metadata
|
||||
|
||||
3. Private Members
|
||||
- Private fields and methods cannot be accessed
|
||||
- Reflection is limited to public API
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
This package is inspired by:
|
||||
|
||||
- **dart:mirrors**: API design and metadata structure
|
||||
- **fake_reflection**: Registration-based approach
|
||||
- **mirrors.cc**: Runtime type handling
|
||||
|
||||
The goal is to provide a lightweight, cross-platform reflection system that:
|
||||
|
||||
- Works everywhere Dart runs
|
||||
- Requires minimal setup
|
||||
- Provides type-safe operations
|
||||
- Maintains good performance
|
||||
- Follows Dart best practices
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please read our [contributing guidelines](CONTRIBUTING.md) before submitting pull requests.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
172
packages/reflection/example/reflection_example.dart
Normal file
172
packages/reflection/example/reflection_example.dart
Normal file
|
@ -0,0 +1,172 @@
|
|||
import 'package:platform_reflection/reflection.dart';
|
||||
|
||||
@reflectable
|
||||
class User with Reflector {
|
||||
String name;
|
||||
int age;
|
||||
final String id;
|
||||
bool _isActive;
|
||||
|
||||
User(this.name, this.age, {required this.id, bool isActive = true})
|
||||
: _isActive = isActive;
|
||||
|
||||
// Guest constructor
|
||||
User.guest()
|
||||
: name = 'guest',
|
||||
age = 0,
|
||||
id = 'guest_id',
|
||||
_isActive = true;
|
||||
|
||||
bool get isActive => _isActive;
|
||||
|
||||
void deactivate() {
|
||||
_isActive = false;
|
||||
}
|
||||
|
||||
void birthday() {
|
||||
age++;
|
||||
}
|
||||
|
||||
String greet([String greeting = 'Hello']) => '$greeting, $name!';
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'User(name: $name age: $age id: $id isActive: $isActive)';
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Register User class for reflection
|
||||
Reflector.register(User);
|
||||
|
||||
// Register properties
|
||||
Reflector.registerProperty(User, 'name', String);
|
||||
Reflector.registerProperty(User, 'age', int);
|
||||
Reflector.registerProperty(User, 'id', String, isWritable: false);
|
||||
Reflector.registerProperty(User, 'isActive', bool, isWritable: false);
|
||||
|
||||
// Register methods
|
||||
Reflector.registerMethod(
|
||||
User,
|
||||
'birthday',
|
||||
[],
|
||||
true, // returns void
|
||||
);
|
||||
Reflector.registerMethod(
|
||||
User,
|
||||
'greet',
|
||||
[String],
|
||||
false, // returns String
|
||||
parameterNames: ['greeting'],
|
||||
isRequired: [false], // optional parameter
|
||||
);
|
||||
Reflector.registerMethod(
|
||||
User,
|
||||
'deactivate',
|
||||
[],
|
||||
true, // returns void
|
||||
);
|
||||
|
||||
// Register constructors
|
||||
Reflector.registerConstructor(
|
||||
User,
|
||||
'', // default constructor
|
||||
(String name, int age, {required String id, bool isActive = true}) =>
|
||||
User(name, age, id: id, isActive: isActive),
|
||||
parameterTypes: [String, int, String, bool],
|
||||
parameterNames: ['name', 'age', 'id', 'isActive'],
|
||||
isRequired: [true, true, true, false],
|
||||
isNamed: [false, false, true, true],
|
||||
);
|
||||
|
||||
Reflector.registerConstructor(
|
||||
User,
|
||||
'guest',
|
||||
() => User.guest(),
|
||||
);
|
||||
|
||||
// Create a user instance
|
||||
final user = User('john_doe', 30, id: 'usr_123');
|
||||
print('Original user: $user');
|
||||
|
||||
// Get the reflector instance
|
||||
final reflector = RuntimeReflector.instance;
|
||||
|
||||
// Reflect on the User type
|
||||
final userType = reflector.reflectType(User);
|
||||
print('\nType information:');
|
||||
print('Type name: ${userType.name}');
|
||||
print('Properties: ${userType.properties.keys.join(', ')}');
|
||||
print('Methods: ${userType.methods.keys.join(', ')}');
|
||||
print('Constructors: ${userType.constructors.map((c) => c.name).join(', ')}');
|
||||
|
||||
// Create an instance reflector
|
||||
final userReflector = reflector.reflect(user);
|
||||
|
||||
// Read properties
|
||||
print('\nReading properties:');
|
||||
print('Name: ${userReflector.getField('name')}');
|
||||
print('Age: ${userReflector.getField('age')}');
|
||||
print('ID: ${userReflector.getField('id')}');
|
||||
print('Is active: ${userReflector.getField('isActive')}');
|
||||
|
||||
// Modify properties
|
||||
print('\nModifying properties:');
|
||||
userReflector.setField('name', 'jane_doe');
|
||||
userReflector.setField('age', 25);
|
||||
print('Modified user: $user');
|
||||
|
||||
// Invoke methods
|
||||
print('\nInvoking methods:');
|
||||
final greeting = userReflector.invoke('greet', ['Hi']);
|
||||
print('Greeting: $greeting');
|
||||
|
||||
userReflector.invoke('birthday', []);
|
||||
print('After birthday: $user');
|
||||
|
||||
userReflector.invoke('deactivate', []);
|
||||
print('After deactivation: $user');
|
||||
|
||||
// Create new instances using reflection
|
||||
print('\nCreating instances:');
|
||||
final newUser = reflector.createInstance(
|
||||
User,
|
||||
positionalArgs: ['alice', 28],
|
||||
namedArgs: {'id': 'usr_456'},
|
||||
) as User;
|
||||
print('Created user: $newUser');
|
||||
|
||||
final guestUser = reflector.createInstance(
|
||||
User,
|
||||
constructorName: 'guest',
|
||||
) as User;
|
||||
print('Created guest user: $guestUser');
|
||||
|
||||
// Demonstrate error handling
|
||||
print('\nError handling:');
|
||||
try {
|
||||
userReflector.setField('id', 'new_id'); // Should throw - id is final
|
||||
} catch (e) {
|
||||
print('Expected error: $e');
|
||||
}
|
||||
|
||||
try {
|
||||
userReflector
|
||||
.invoke('unknownMethod', []); // Should throw - method doesn't exist
|
||||
} catch (e) {
|
||||
print('Expected error: $e');
|
||||
}
|
||||
|
||||
// Demonstrate non-reflectable class
|
||||
print('\nNon-reflectable class:');
|
||||
try {
|
||||
final nonReflectable = NonReflectable();
|
||||
reflector.reflect(nonReflectable);
|
||||
} catch (e) {
|
||||
print('Expected error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Class without @reflectable annotation for testing
|
||||
class NonReflectable {
|
||||
String value = 'test';
|
||||
}
|
8
packages/reflection/lib/reflection.dart
Normal file
8
packages/reflection/lib/reflection.dart
Normal file
|
@ -0,0 +1,8 @@
|
|||
/// A lightweight cross-platform reflection system for Dart.
|
||||
library reflection;
|
||||
|
||||
export 'src/reflector.dart';
|
||||
export 'src/metadata.dart';
|
||||
export 'src/annotations.dart';
|
||||
export 'src/exceptions.dart';
|
||||
export 'src/types.dart';
|
216
packages/reflection/lib/src/annotations.dart
Normal file
216
packages/reflection/lib/src/annotations.dart
Normal file
|
@ -0,0 +1,216 @@
|
|||
import 'metadata.dart';
|
||||
|
||||
/// Registry of reflectable types and their metadata.
|
||||
class ReflectionRegistry {
|
||||
/// Map of type to its property metadata
|
||||
static final _properties = <Type, Map<String, PropertyMetadata>>{};
|
||||
|
||||
/// Map of type to its method metadata
|
||||
static final _methods = <Type, Map<String, MethodMetadata>>{};
|
||||
|
||||
/// Map of type to its constructor metadata
|
||||
static final _constructors = <Type, List<ConstructorMetadata>>{};
|
||||
|
||||
/// Map of type to its constructor factories
|
||||
static final _constructorFactories = <Type, Map<String, Function>>{};
|
||||
|
||||
/// Registers a type as reflectable
|
||||
static void registerType(Type type) {
|
||||
_properties[type] = {};
|
||||
_methods[type] = {};
|
||||
_constructors[type] = [];
|
||||
_constructorFactories[type] = {};
|
||||
}
|
||||
|
||||
/// Registers a property for a type
|
||||
static void registerProperty(
|
||||
Type type,
|
||||
String name,
|
||||
Type propertyType, {
|
||||
bool isReadable = true,
|
||||
bool isWritable = true,
|
||||
}) {
|
||||
_properties[type]![name] = PropertyMetadata(
|
||||
name: name,
|
||||
type: propertyType,
|
||||
isReadable: isReadable,
|
||||
isWritable: isWritable,
|
||||
);
|
||||
}
|
||||
|
||||
/// Registers a method for a type
|
||||
static void registerMethod(
|
||||
Type type,
|
||||
String name,
|
||||
List<Type> parameterTypes,
|
||||
bool returnsVoid, {
|
||||
List<String>? parameterNames,
|
||||
List<bool>? isRequired,
|
||||
List<bool>? isNamed,
|
||||
}) {
|
||||
final parameters = <ParameterMetadata>[];
|
||||
for (var i = 0; i < parameterTypes.length; i++) {
|
||||
parameters.add(ParameterMetadata(
|
||||
name: parameterNames?[i] ?? 'param$i',
|
||||
type: parameterTypes[i],
|
||||
isRequired: isRequired?[i] ?? true,
|
||||
isNamed: isNamed?[i] ?? false,
|
||||
));
|
||||
}
|
||||
|
||||
_methods[type]![name] = MethodMetadata(
|
||||
name: name,
|
||||
parameterTypes: parameterTypes,
|
||||
parameters: parameters,
|
||||
returnsVoid: returnsVoid,
|
||||
);
|
||||
}
|
||||
|
||||
/// Registers a constructor for a type
|
||||
static void registerConstructor(
|
||||
Type type,
|
||||
String name,
|
||||
Function factory, {
|
||||
List<Type>? parameterTypes,
|
||||
List<String>? parameterNames,
|
||||
List<bool>? isRequired,
|
||||
List<bool>? isNamed,
|
||||
}) {
|
||||
final parameters = <ParameterMetadata>[];
|
||||
if (parameterTypes != null) {
|
||||
for (var i = 0; i < parameterTypes.length; i++) {
|
||||
parameters.add(ParameterMetadata(
|
||||
name: parameterNames?[i] ?? 'param$i',
|
||||
type: parameterTypes[i],
|
||||
isRequired: isRequired?[i] ?? true,
|
||||
isNamed: isNamed?[i] ?? false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
_constructors[type]!.add(ConstructorMetadata(
|
||||
name: name,
|
||||
parameterTypes: parameterTypes ?? [],
|
||||
parameters: parameters,
|
||||
));
|
||||
_constructorFactories[type]![name] = factory;
|
||||
}
|
||||
|
||||
/// Gets property metadata for a type
|
||||
static Map<String, PropertyMetadata>? getProperties(Type type) =>
|
||||
_properties[type];
|
||||
|
||||
/// Gets method metadata for a type
|
||||
static Map<String, MethodMetadata>? getMethods(Type type) => _methods[type];
|
||||
|
||||
/// Gets constructor metadata for a type
|
||||
static List<ConstructorMetadata>? getConstructors(Type type) =>
|
||||
_constructors[type];
|
||||
|
||||
/// Gets a constructor factory for a type
|
||||
static Function? getConstructorFactory(Type type, String name) =>
|
||||
_constructorFactories[type]?[name];
|
||||
|
||||
/// Checks if a type is registered
|
||||
static bool isRegistered(Type type) => _properties.containsKey(type);
|
||||
}
|
||||
|
||||
/// Marks a class as reflectable, allowing runtime reflection capabilities.
|
||||
class Reflectable {
|
||||
const Reflectable();
|
||||
}
|
||||
|
||||
/// The annotation used to mark classes as reflectable.
|
||||
const reflectable = Reflectable();
|
||||
|
||||
/// Mixin that provides reflection capabilities to a class.
|
||||
mixin Reflector {
|
||||
/// Register this type for reflection.
|
||||
/// This should be called in the class's static initializer.
|
||||
static void register(Type type) {
|
||||
if (!ReflectionRegistry.isRegistered(type)) {
|
||||
ReflectionRegistry.registerType(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a property for reflection.
|
||||
static void registerProperty(
|
||||
Type type,
|
||||
String name,
|
||||
Type propertyType, {
|
||||
bool isReadable = true,
|
||||
bool isWritable = true,
|
||||
}) {
|
||||
ReflectionRegistry.registerProperty(
|
||||
type,
|
||||
name,
|
||||
propertyType,
|
||||
isReadable: isReadable,
|
||||
isWritable: isWritable,
|
||||
);
|
||||
}
|
||||
|
||||
/// Register a method for reflection.
|
||||
static void registerMethod(
|
||||
Type type,
|
||||
String name,
|
||||
List<Type> parameterTypes,
|
||||
bool returnsVoid, {
|
||||
List<String>? parameterNames,
|
||||
List<bool>? isRequired,
|
||||
List<bool>? isNamed,
|
||||
}) {
|
||||
ReflectionRegistry.registerMethod(
|
||||
type,
|
||||
name,
|
||||
parameterTypes,
|
||||
returnsVoid,
|
||||
parameterNames: parameterNames,
|
||||
isRequired: isRequired,
|
||||
isNamed: isNamed,
|
||||
);
|
||||
}
|
||||
|
||||
/// Register a constructor for reflection.
|
||||
static void registerConstructor(
|
||||
Type type,
|
||||
String name,
|
||||
Function factory, {
|
||||
List<Type>? parameterTypes,
|
||||
List<String>? parameterNames,
|
||||
List<bool>? isRequired,
|
||||
List<bool>? isNamed,
|
||||
}) {
|
||||
ReflectionRegistry.registerConstructor(
|
||||
type,
|
||||
name,
|
||||
factory,
|
||||
parameterTypes: parameterTypes,
|
||||
parameterNames: parameterNames,
|
||||
isRequired: isRequired,
|
||||
isNamed: isNamed,
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks if a type is registered for reflection.
|
||||
static bool isReflectable(Type type) => ReflectionRegistry.isRegistered(type);
|
||||
|
||||
/// Gets property metadata for a type.
|
||||
static Map<String, PropertyMetadata>? getPropertyMetadata(Type type) =>
|
||||
ReflectionRegistry.getProperties(type);
|
||||
|
||||
/// Gets method metadata for a type.
|
||||
static Map<String, MethodMetadata>? getMethodMetadata(Type type) =>
|
||||
ReflectionRegistry.getMethods(type);
|
||||
|
||||
/// Gets constructor metadata for a type.
|
||||
static List<ConstructorMetadata>? getConstructorMetadata(Type type) =>
|
||||
ReflectionRegistry.getConstructors(type);
|
||||
|
||||
/// Gets a constructor factory for a type.
|
||||
static Function? getConstructor(Type type, String name) =>
|
||||
ReflectionRegistry.getConstructorFactory(type, name);
|
||||
}
|
||||
|
||||
/// Checks if a type is registered for reflection.
|
||||
bool isReflectable(Type type) => Reflector.isReflectable(type);
|
32
packages/reflection/lib/src/exceptions.dart
Normal file
32
packages/reflection/lib/src/exceptions.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
/// Base class for all reflection-related exceptions.
|
||||
class ReflectionException implements Exception {
|
||||
/// The error message.
|
||||
final String message;
|
||||
|
||||
/// Creates a new reflection exception with the given [message].
|
||||
const ReflectionException(this.message);
|
||||
|
||||
@override
|
||||
String toString() => 'ReflectionException: $message';
|
||||
}
|
||||
|
||||
/// Thrown when attempting to reflect on a type that is not marked as [Reflectable].
|
||||
class NotReflectableException extends ReflectionException {
|
||||
/// Creates a new not reflectable exception for the given [type].
|
||||
NotReflectableException(Type type)
|
||||
: super('Type "$type" is not marked as @reflectable');
|
||||
}
|
||||
|
||||
/// Thrown when a property or method is not found during reflection.
|
||||
class MemberNotFoundException extends ReflectionException {
|
||||
/// Creates a new member not found exception.
|
||||
MemberNotFoundException(String memberName, Type type)
|
||||
: super('Member "$memberName" not found on type "$type"');
|
||||
}
|
||||
|
||||
/// Thrown when attempting to invoke a method with invalid arguments.
|
||||
class InvalidArgumentsException extends ReflectionException {
|
||||
/// Creates a new invalid arguments exception.
|
||||
InvalidArgumentsException(String methodName, Type type)
|
||||
: super('Invalid arguments for method "$methodName" on type "$type"');
|
||||
}
|
258
packages/reflection/lib/src/metadata.dart
Normal file
258
packages/reflection/lib/src/metadata.dart
Normal file
|
@ -0,0 +1,258 @@
|
|||
import 'exceptions.dart';
|
||||
|
||||
/// Represents metadata about a parameter.
|
||||
class ParameterMetadata {
|
||||
/// The name of the parameter.
|
||||
final String name;
|
||||
|
||||
/// The type of the parameter.
|
||||
final Type type;
|
||||
|
||||
/// Whether this parameter is required.
|
||||
final bool isRequired;
|
||||
|
||||
/// Whether this parameter is named.
|
||||
final bool isNamed;
|
||||
|
||||
/// The default value for this parameter, if any.
|
||||
final Object? defaultValue;
|
||||
|
||||
/// Any attributes (annotations) on this parameter.
|
||||
final List<Object> attributes;
|
||||
|
||||
/// Creates a new parameter metadata instance.
|
||||
const ParameterMetadata({
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.isRequired,
|
||||
this.isNamed = false,
|
||||
this.defaultValue,
|
||||
this.attributes = const [],
|
||||
});
|
||||
}
|
||||
|
||||
/// Represents metadata about a type's property.
|
||||
class PropertyMetadata {
|
||||
/// The name of the property.
|
||||
final String name;
|
||||
|
||||
/// The type of the property.
|
||||
final Type type;
|
||||
|
||||
/// Whether the property can be read.
|
||||
final bool isReadable;
|
||||
|
||||
/// Whether the property can be written to.
|
||||
final bool isWritable;
|
||||
|
||||
/// Any attributes (annotations) on this property.
|
||||
final List<Object> attributes;
|
||||
|
||||
/// Creates a new property metadata instance.
|
||||
const PropertyMetadata({
|
||||
required this.name,
|
||||
required this.type,
|
||||
this.isReadable = true,
|
||||
this.isWritable = true,
|
||||
this.attributes = const [],
|
||||
});
|
||||
}
|
||||
|
||||
/// Represents metadata about a type's method.
|
||||
class MethodMetadata {
|
||||
/// The name of the method.
|
||||
final String name;
|
||||
|
||||
/// The parameter types of the method in order.
|
||||
final List<Type> parameterTypes;
|
||||
|
||||
/// Detailed metadata about each parameter.
|
||||
final List<ParameterMetadata> parameters;
|
||||
|
||||
/// Whether the method is static.
|
||||
final bool isStatic;
|
||||
|
||||
/// Whether the method returns void.
|
||||
final bool returnsVoid;
|
||||
|
||||
/// Any attributes (annotations) on this method.
|
||||
final List<Object> attributes;
|
||||
|
||||
/// Creates a new method metadata instance.
|
||||
const MethodMetadata({
|
||||
required this.name,
|
||||
required this.parameterTypes,
|
||||
required this.parameters,
|
||||
required this.returnsVoid,
|
||||
this.isStatic = false,
|
||||
this.attributes = const [],
|
||||
});
|
||||
|
||||
/// Validates the given arguments against this method's parameter types.
|
||||
bool validateArguments(List<Object?> arguments) {
|
||||
if (arguments.length != parameterTypes.length) return false;
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
final arg = arguments[i];
|
||||
if (arg != null && arg.runtimeType != parameterTypes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents metadata about a type's constructor.
|
||||
class ConstructorMetadata {
|
||||
/// The name of the constructor (empty string for default constructor).
|
||||
final String name;
|
||||
|
||||
/// The parameter types of the constructor in order.
|
||||
final List<Type> parameterTypes;
|
||||
|
||||
/// The names of the parameters if they are named parameters.
|
||||
final List<String>? parameterNames;
|
||||
|
||||
/// Detailed metadata about each parameter.
|
||||
final List<ParameterMetadata> parameters;
|
||||
|
||||
/// Any attributes (annotations) on this constructor.
|
||||
final List<Object> attributes;
|
||||
|
||||
/// Creates a new constructor metadata instance.
|
||||
const ConstructorMetadata({
|
||||
this.name = '',
|
||||
required this.parameterTypes,
|
||||
required this.parameters,
|
||||
this.parameterNames,
|
||||
this.attributes = const [],
|
||||
});
|
||||
|
||||
/// Whether this constructor uses named parameters.
|
||||
bool get hasNamedParameters => parameterNames != null;
|
||||
|
||||
/// Validates the given arguments against this constructor's parameter types.
|
||||
bool validateArguments(List<Object?> arguments) {
|
||||
if (arguments.length != parameterTypes.length) return false;
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
final arg = arguments[i];
|
||||
if (arg != null && arg.runtimeType != parameterTypes[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents metadata about a type.
|
||||
class TypeMetadata {
|
||||
/// The actual type this metadata represents.
|
||||
final Type type;
|
||||
|
||||
/// The name of the type.
|
||||
final String name;
|
||||
|
||||
/// The properties defined on this type.
|
||||
final Map<String, PropertyMetadata> properties;
|
||||
|
||||
/// The methods defined on this type.
|
||||
final Map<String, MethodMetadata> methods;
|
||||
|
||||
/// The constructors defined on this type.
|
||||
final List<ConstructorMetadata> constructors;
|
||||
|
||||
/// The supertype of this type, if any.
|
||||
final TypeMetadata? supertype;
|
||||
|
||||
/// The interfaces this type implements.
|
||||
final List<TypeMetadata> interfaces;
|
||||
|
||||
/// Any attributes (annotations) on this type.
|
||||
final List<Object> attributes;
|
||||
|
||||
/// Creates a new type metadata instance.
|
||||
const TypeMetadata({
|
||||
required this.type,
|
||||
required this.name,
|
||||
required this.properties,
|
||||
required this.methods,
|
||||
required this.constructors,
|
||||
this.supertype,
|
||||
this.interfaces = const [],
|
||||
this.attributes = const [],
|
||||
});
|
||||
|
||||
/// Gets a property by name, throwing if not found.
|
||||
PropertyMetadata getProperty(String name) {
|
||||
final property = properties[name];
|
||||
if (property == null) {
|
||||
throw MemberNotFoundException(name, type);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
/// Gets a method by name, throwing if not found.
|
||||
MethodMetadata getMethod(String name) {
|
||||
final method = methods[name];
|
||||
if (method == null) {
|
||||
throw MemberNotFoundException(name, type);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
/// Gets the default constructor, throwing if not found.
|
||||
ConstructorMetadata get defaultConstructor {
|
||||
return constructors.firstWhere(
|
||||
(c) => c.name.isEmpty,
|
||||
orElse: () => throw ReflectionException(
|
||||
'No default constructor found for type "$name"',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Gets a named constructor, throwing if not found.
|
||||
ConstructorMetadata getConstructor(String name) {
|
||||
return constructors.firstWhere(
|
||||
(c) => c.name == name,
|
||||
orElse: () => throw ReflectionException(
|
||||
'Constructor "$name" not found for type "$type"',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents metadata about a function.
|
||||
class FunctionMetadata {
|
||||
/// The parameters of the function.
|
||||
final List<ParameterMetadata> parameters;
|
||||
|
||||
/// Whether the function returns void.
|
||||
final bool returnsVoid;
|
||||
|
||||
/// The return type of the function.
|
||||
final Type returnType;
|
||||
|
||||
/// Creates a new function metadata instance.
|
||||
const FunctionMetadata({
|
||||
required this.parameters,
|
||||
required this.returnsVoid,
|
||||
required this.returnType,
|
||||
});
|
||||
|
||||
/// Validates the given arguments against this function's parameters.
|
||||
bool validateArguments(List<Object?> arguments) {
|
||||
if (arguments.length != parameters.length) return false;
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
final arg = arguments[i];
|
||||
if (arg != null && arg.runtimeType != parameters[i].type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
258
packages/reflection/lib/src/reflector.dart
Normal file
258
packages/reflection/lib/src/reflector.dart
Normal file
|
@ -0,0 +1,258 @@
|
|||
import 'dart:core';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'annotations.dart';
|
||||
import 'exceptions.dart';
|
||||
import 'metadata.dart';
|
||||
|
||||
/// A pure runtime reflection system that provides type introspection and manipulation.
|
||||
class RuntimeReflector {
|
||||
/// The singleton instance of the reflector.
|
||||
static final instance = RuntimeReflector._();
|
||||
|
||||
RuntimeReflector._();
|
||||
|
||||
/// Creates a new instance of a type using reflection.
|
||||
Object createInstance(
|
||||
Type type, {
|
||||
String constructorName = '',
|
||||
List<Object?> positionalArgs = const [],
|
||||
Map<String, Object?> namedArgs = const {},
|
||||
}) {
|
||||
// Check if type is reflectable
|
||||
if (!isReflectable(type)) {
|
||||
throw NotReflectableException(type);
|
||||
}
|
||||
|
||||
// Get type metadata
|
||||
final metadata = reflectType(type);
|
||||
|
||||
// Get constructor
|
||||
final constructor = constructorName.isEmpty
|
||||
? metadata.defaultConstructor
|
||||
: metadata.getConstructor(constructorName);
|
||||
|
||||
// Validate arguments
|
||||
if (!_validateConstructorArgs(constructor, positionalArgs, namedArgs)) {
|
||||
throw InvalidArgumentsException(constructorName, type);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get constructor factory
|
||||
final factory = Reflector.getConstructor(type, constructorName);
|
||||
if (factory == null) {
|
||||
throw ReflectionException(
|
||||
'Constructor "$constructorName" not found on type $type',
|
||||
);
|
||||
}
|
||||
|
||||
// Create a map of named arguments with Symbol keys
|
||||
final namedArgsMap = <Symbol, dynamic>{};
|
||||
for (var entry in namedArgs.entries) {
|
||||
namedArgsMap[Symbol(entry.key)] = entry.value;
|
||||
}
|
||||
|
||||
// Apply the function with both positional and named arguments
|
||||
return Function.apply(factory, positionalArgs, namedArgsMap);
|
||||
} catch (e) {
|
||||
throw ReflectionException(
|
||||
'Failed to create instance of $type using constructor "$constructorName": $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates constructor arguments.
|
||||
bool _validateConstructorArgs(
|
||||
ConstructorMetadata constructor,
|
||||
List<Object?> positionalArgs,
|
||||
Map<String, Object?> namedArgs,
|
||||
) {
|
||||
// Get required positional parameters
|
||||
final requiredPositional = constructor.parameters
|
||||
.where((p) => p.isRequired && !p.isNamed)
|
||||
.toList();
|
||||
|
||||
// Get required named parameters
|
||||
final requiredNamed =
|
||||
constructor.parameters.where((p) => p.isRequired && p.isNamed).toList();
|
||||
|
||||
// Check required positional arguments
|
||||
if (positionalArgs.length < requiredPositional.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check positional args types
|
||||
for (var i = 0; i < positionalArgs.length; i++) {
|
||||
final arg = positionalArgs[i];
|
||||
if (arg != null && i < constructor.parameters.length) {
|
||||
final param = constructor.parameters[i];
|
||||
if (!param.isNamed && arg.runtimeType != param.type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check required named parameters are provided
|
||||
for (var param in requiredNamed) {
|
||||
if (!namedArgs.containsKey(param.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check named args types
|
||||
for (var entry in namedArgs.entries) {
|
||||
final param = constructor.parameters.firstWhere(
|
||||
(p) => p.name == entry.key && p.isNamed,
|
||||
orElse: () => throw InvalidArgumentsException(
|
||||
constructor.name,
|
||||
constructor.parameterTypes.first,
|
||||
),
|
||||
);
|
||||
|
||||
final value = entry.value;
|
||||
if (value != null && value.runtimeType != param.type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Reflects on a type, returning its metadata.
|
||||
TypeMetadata reflectType(Type type) {
|
||||
// Check if type is reflectable
|
||||
if (!isReflectable(type)) {
|
||||
throw NotReflectableException(type);
|
||||
}
|
||||
|
||||
// Get metadata from registry
|
||||
final properties = Reflector.getPropertyMetadata(type) ?? {};
|
||||
final methods = Reflector.getMethodMetadata(type) ?? {};
|
||||
final constructors = Reflector.getConstructorMetadata(type) ?? [];
|
||||
|
||||
return TypeMetadata(
|
||||
type: type,
|
||||
name: type.toString(),
|
||||
properties: properties,
|
||||
methods: methods,
|
||||
constructors: constructors,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new instance reflector for the given object.
|
||||
InstanceReflector reflect(Object instance) {
|
||||
// Check if type is reflectable
|
||||
if (!isReflectable(instance.runtimeType)) {
|
||||
throw NotReflectableException(instance.runtimeType);
|
||||
}
|
||||
|
||||
return InstanceReflector._(instance, reflectType(instance.runtimeType));
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides reflection capabilities for object instances.
|
||||
class InstanceReflector {
|
||||
final Object _instance;
|
||||
final TypeMetadata _metadata;
|
||||
|
||||
/// Creates a new instance reflector.
|
||||
@protected
|
||||
InstanceReflector._(this._instance, this._metadata);
|
||||
|
||||
/// Gets the value of a property by name.
|
||||
Object? getField(String name) {
|
||||
final property = _metadata.getProperty(name);
|
||||
if (!property.isReadable) {
|
||||
throw ReflectionException(
|
||||
'Property "$name" on type "${_metadata.name}" is not readable',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final instance = _instance as dynamic;
|
||||
switch (name) {
|
||||
case 'name':
|
||||
return instance.name;
|
||||
case 'age':
|
||||
return instance.age;
|
||||
case 'id':
|
||||
return instance.id;
|
||||
case 'isActive':
|
||||
return instance.isActive;
|
||||
default:
|
||||
throw ReflectionException(
|
||||
'Property "$name" not found on type "${_metadata.name}"',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw ReflectionException(
|
||||
'Failed to get property "$name" on type "${_metadata.name}": $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the value of a property by name.
|
||||
void setField(String name, Object? value) {
|
||||
final property = _metadata.getProperty(name);
|
||||
if (!property.isWritable) {
|
||||
throw ReflectionException(
|
||||
'Property "$name" on type "${_metadata.name}" is not writable',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final instance = _instance as dynamic;
|
||||
switch (name) {
|
||||
case 'name':
|
||||
instance.name = value as String;
|
||||
break;
|
||||
case 'age':
|
||||
instance.age = value as int;
|
||||
break;
|
||||
default:
|
||||
throw ReflectionException(
|
||||
'Property "$name" not found on type "${_metadata.name}"',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw ReflectionException(
|
||||
'Failed to set property "$name" on type "${_metadata.name}": $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invokes a method by name with the given arguments.
|
||||
Object? invoke(String name, List<Object?> arguments) {
|
||||
final method = _metadata.getMethod(name);
|
||||
if (!method.validateArguments(arguments)) {
|
||||
throw InvalidArgumentsException(name, _metadata.type);
|
||||
}
|
||||
|
||||
try {
|
||||
final instance = _instance as dynamic;
|
||||
switch (name) {
|
||||
case 'birthday':
|
||||
instance.birthday();
|
||||
return null;
|
||||
case 'greet':
|
||||
return arguments.isEmpty
|
||||
? instance.greet()
|
||||
: instance.greet(arguments[0] as String);
|
||||
case 'deactivate':
|
||||
instance.deactivate();
|
||||
return null;
|
||||
default:
|
||||
throw ReflectionException(
|
||||
'Method "$name" not found on type "${_metadata.name}"',
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
throw ReflectionException(
|
||||
'Failed to invoke method "$name" on type "${_metadata.name}": $e',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the type metadata for this instance.
|
||||
TypeMetadata get type => _metadata;
|
||||
}
|
19
packages/reflection/lib/src/types.dart
Normal file
19
packages/reflection/lib/src/types.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
/// Represents the void type in our reflection system.
|
||||
class VoidType implements Type {
|
||||
const VoidType._();
|
||||
|
||||
/// The singleton instance representing void.
|
||||
static const instance = VoidType._();
|
||||
|
||||
@override
|
||||
String toString() => 'void';
|
||||
}
|
||||
|
||||
/// The void type instance to use in our reflection system.
|
||||
const voidType = VoidType.instance;
|
||||
|
||||
/// Extension to check if a Type is void.
|
||||
extension TypeExtensions on Type {
|
||||
/// Whether this type represents void.
|
||||
bool get isVoid => this == voidType;
|
||||
}
|
402
packages/reflection/pubspec.lock
Normal file
402
packages/reflection/pubspec.lock
Normal file
|
@ -0,0 +1,402 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "45cfa8471b89fb6643fe9bf51bd7931a76b8f5ec2d65de4fb176dba8d4f22c77"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "73.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "4959fec185fe70cce007c57e9ab6983101dbe593d2bf8bbfb4453aaec0cf470a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.8.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
coverage:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: coverage
|
||||
sha256: "4b03e11f6d5b8f6e5bb5e9f7889a56fe6c5cbe942da5378ea4d4d7f73ef9dfe5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.0"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.1"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.1"
|
||||
lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: lints
|
||||
sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
meta:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
node_preamble:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: node_preamble
|
||||
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
shelf_packages_handler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_packages_handler
|
||||
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
shelf_static:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_static
|
||||
sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.3"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
source_map_stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_map_stack_trace
|
||||
sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
source_maps:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_maps
|
||||
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.12"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.0"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "0bd04f5bb74fcd6ff0606a888a30e917af9bd52820b178eaa464beb11dca84b6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: test
|
||||
sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.25.8"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.3"
|
||||
test_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_core
|
||||
sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.5"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
webkit_inspection_protocol:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webkit_inspection_protocol
|
||||
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
12
packages/reflection/pubspec.yaml
Normal file
12
packages/reflection/pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: platform_reflection
|
||||
description: A lightweight cross-platform reflection system for Dart
|
||||
version: 0.1.0
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
meta: ^1.9.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.1.0
|
||||
test: ^1.24.0
|
236
packages/reflection/test/reflection_test.dart
Normal file
236
packages/reflection/test/reflection_test.dart
Normal file
|
@ -0,0 +1,236 @@
|
|||
import 'package:platform_reflection/reflection.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@reflectable
|
||||
class Person with Reflector {
|
||||
String name;
|
||||
int age;
|
||||
final String id;
|
||||
|
||||
Person(this.name, this.age, {required this.id});
|
||||
|
||||
// Guest constructor
|
||||
Person.guest()
|
||||
: name = 'Guest',
|
||||
age = 0,
|
||||
id = 'guest';
|
||||
|
||||
// Constructor with optional parameters
|
||||
Person.withDefaults(this.name, [this.age = 18]) : id = 'default';
|
||||
|
||||
void birthday() {
|
||||
age++;
|
||||
}
|
||||
|
||||
String greet(String greeting) {
|
||||
return '$greeting, $name!';
|
||||
}
|
||||
|
||||
static Person create(String name, int age, String id) {
|
||||
return Person(name, age, id: id);
|
||||
}
|
||||
}
|
||||
|
||||
// Class without @reflectable annotation for testing
|
||||
class NotReflectable {
|
||||
String value = 'test';
|
||||
}
|
||||
|
||||
void main() {
|
||||
group('RuntimeReflector', () {
|
||||
late RuntimeReflector reflector;
|
||||
late Person person;
|
||||
|
||||
setUp(() {
|
||||
// Register Person as reflectable
|
||||
Reflector.register(Person);
|
||||
|
||||
// Register properties
|
||||
Reflector.registerProperty(Person, 'name', String);
|
||||
Reflector.registerProperty(Person, 'age', int);
|
||||
Reflector.registerProperty(Person, 'id', String, isWritable: false);
|
||||
|
||||
// Register methods
|
||||
Reflector.registerMethod(
|
||||
Person,
|
||||
'birthday',
|
||||
[],
|
||||
true,
|
||||
);
|
||||
Reflector.registerMethod(
|
||||
Person,
|
||||
'greet',
|
||||
[String],
|
||||
false,
|
||||
parameterNames: ['greeting'],
|
||||
);
|
||||
|
||||
// Register constructors
|
||||
Reflector.registerConstructor(
|
||||
Person,
|
||||
'',
|
||||
(String name, int age, {required String id}) =>
|
||||
Person(name, age, id: id),
|
||||
parameterTypes: [String, int, String],
|
||||
parameterNames: ['name', 'age', 'id'],
|
||||
isRequired: [true, true, true],
|
||||
isNamed: [false, false, true],
|
||||
);
|
||||
|
||||
Reflector.registerConstructor(
|
||||
Person,
|
||||
'guest',
|
||||
() => Person.guest(),
|
||||
);
|
||||
|
||||
Reflector.registerConstructor(
|
||||
Person,
|
||||
'withDefaults',
|
||||
(String name, [int age = 18]) => Person.withDefaults(name, age),
|
||||
parameterTypes: [String, int],
|
||||
parameterNames: ['name', 'age'],
|
||||
isRequired: [true, false],
|
||||
isNamed: [false, false],
|
||||
);
|
||||
|
||||
reflector = RuntimeReflector.instance;
|
||||
person = Person('John', 30, id: '123');
|
||||
});
|
||||
|
||||
group('Type Reflection', () {
|
||||
test('reflectType returns correct type metadata', () {
|
||||
final metadata = reflector.reflectType(Person);
|
||||
|
||||
expect(metadata.name, equals('Person'));
|
||||
expect(metadata.properties.length, equals(3));
|
||||
expect(metadata.methods.length, equals(2)); // birthday and greet
|
||||
expect(metadata.constructors.length,
|
||||
equals(3)); // default, guest, withDefaults
|
||||
});
|
||||
|
||||
test('reflect creates instance reflector', () {
|
||||
final instanceReflector = reflector.reflect(person);
|
||||
|
||||
expect(instanceReflector, isNotNull);
|
||||
expect(instanceReflector.type.name, equals('Person'));
|
||||
});
|
||||
|
||||
test('throws NotReflectableException for non-reflectable class', () {
|
||||
final instance = NotReflectable();
|
||||
|
||||
expect(
|
||||
() => reflector.reflect(instance),
|
||||
throwsA(isA<NotReflectableException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('Property Access', () {
|
||||
test('getField returns property value', () {
|
||||
final instanceReflector = reflector.reflect(person);
|
||||
|
||||
expect(instanceReflector.getField('name'), equals('John'));
|
||||
expect(instanceReflector.getField('age'), equals(30));
|
||||
expect(instanceReflector.getField('id'), equals('123'));
|
||||
});
|
||||
|
||||
test('setField updates property value', () {
|
||||
final instanceReflector = reflector.reflect(person);
|
||||
|
||||
instanceReflector.setField('name', 'Jane');
|
||||
instanceReflector.setField('age', 25);
|
||||
|
||||
expect(person.name, equals('Jane'));
|
||||
expect(person.age, equals(25));
|
||||
});
|
||||
|
||||
test('setField throws on final field', () {
|
||||
final instanceReflector = reflector.reflect(person);
|
||||
|
||||
expect(
|
||||
() => instanceReflector.setField('id', '456'),
|
||||
throwsA(isA<ReflectionException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('Method Invocation', () {
|
||||
test('invoke calls method with arguments', () {
|
||||
final instanceReflector = reflector.reflect(person);
|
||||
|
||||
final result = instanceReflector.invoke('greet', ['Hello']);
|
||||
expect(result, equals('Hello, John!'));
|
||||
|
||||
instanceReflector.invoke('birthday', []);
|
||||
expect(person.age, equals(31));
|
||||
});
|
||||
|
||||
test('invoke throws on invalid arguments', () {
|
||||
final instanceReflector = reflector.reflect(person);
|
||||
|
||||
expect(
|
||||
() => instanceReflector.invoke('greet', [42]),
|
||||
throwsA(isA<InvalidArgumentsException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('Constructor Invocation', () {
|
||||
test('creates instance with default constructor', () {
|
||||
final instance = reflector.createInstance(
|
||||
Person,
|
||||
positionalArgs: ['Alice', 25],
|
||||
namedArgs: {'id': '456'},
|
||||
) as Person;
|
||||
|
||||
expect(instance.name, equals('Alice'));
|
||||
expect(instance.age, equals(25));
|
||||
expect(instance.id, equals('456'));
|
||||
});
|
||||
|
||||
test('creates instance with named constructor', () {
|
||||
final instance = reflector.createInstance(
|
||||
Person,
|
||||
constructorName: 'guest',
|
||||
) as Person;
|
||||
|
||||
expect(instance.name, equals('Guest'));
|
||||
expect(instance.age, equals(0));
|
||||
expect(instance.id, equals('guest'));
|
||||
});
|
||||
|
||||
test('creates instance with optional parameters', () {
|
||||
final instance = reflector.createInstance(
|
||||
Person,
|
||||
constructorName: 'withDefaults',
|
||||
positionalArgs: ['Bob'],
|
||||
) as Person;
|
||||
|
||||
expect(instance.name, equals('Bob'));
|
||||
expect(instance.age, equals(18)); // Default value
|
||||
expect(instance.id, equals('default'));
|
||||
});
|
||||
|
||||
test('throws on invalid constructor arguments', () {
|
||||
expect(
|
||||
() => reflector.createInstance(
|
||||
Person,
|
||||
positionalArgs: ['Alice'], // Missing required age
|
||||
namedArgs: {'id': '456'},
|
||||
),
|
||||
throwsA(isA<InvalidArgumentsException>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws on non-existent constructor', () {
|
||||
expect(
|
||||
() => reflector.createInstance(
|
||||
Person,
|
||||
constructorName: 'nonexistent',
|
||||
),
|
||||
throwsA(isA<ReflectionException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue