add: adding reflection package

This commit is contained in:
Patrick Stewart 2024-11-21 20:04:34 -07:00
parent 1d2a4d805c
commit f33d7ce33c
12 changed files with 1801 additions and 0 deletions

View 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.

View 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.

View 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';
}

View 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';

View 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);

View 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"');
}

View 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;
}
}

View 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;
}

View 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;
}

View 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"

View 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

View 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>()),
);
});
});
});
}