mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
185 lines
6.1 KiB
Dart
185 lines
6.1 KiB
Dart
// Copyright 2016 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:async';
|
|
import 'dart:collection';
|
|
|
|
import 'package:meta/meta.dart';
|
|
|
|
/// Generates an [AppContext] value.
|
|
///
|
|
/// Generators are allowed to return `null`, in which case the context will
|
|
/// store the `null` value as the value for that type.
|
|
typedef dynamic Generator();
|
|
|
|
/// An exception thrown by [AppContext] when you try to get a [Type] value from
|
|
/// the context, and the instantiation of the value results in a dependency
|
|
/// cycle.
|
|
class ContextDependencyCycleException implements Exception {
|
|
ContextDependencyCycleException._(this.cycle);
|
|
|
|
/// The dependency cycle (last item depends on first item).
|
|
final List<Type> cycle;
|
|
|
|
@override
|
|
String toString() => 'Dependency cycle detected: ${cycle.join(' -> ')}';
|
|
}
|
|
|
|
/// The current [AppContext], as determined by the [Zone] hierarchy.
|
|
///
|
|
/// This will be the first context found as we scan up the zone hierarchy, or
|
|
/// the "root" context if a context cannot be found in the hierarchy. The root
|
|
/// context will not have any values associated with it.
|
|
///
|
|
/// This is guaranteed to never return `null`.
|
|
AppContext get context => Zone.current[_Key.key] ?? AppContext._root;
|
|
|
|
/// A lookup table (mapping types to values) and an implied scope, in which
|
|
/// code is run.
|
|
///
|
|
/// [AppContext] is used to define a singleton injection context for code that
|
|
/// is run within it. Each time you call [run], a child context (and a new
|
|
/// scope) is created.
|
|
///
|
|
/// Child contexts are created and run using zones. To read more about how
|
|
/// zones work, see https://www.dartlang.org/articles/libraries/zones.
|
|
class AppContext {
|
|
AppContext._(
|
|
this._parent,
|
|
this.name, [
|
|
this._overrides = const <Type, Generator>{},
|
|
this._fallbacks = const <Type, Generator>{},
|
|
]);
|
|
|
|
final String name;
|
|
final AppContext _parent;
|
|
final Map<Type, Generator> _overrides;
|
|
final Map<Type, Generator> _fallbacks;
|
|
final Map<Type, dynamic> _values = <Type, dynamic>{};
|
|
|
|
List<Type> _reentrantChecks;
|
|
|
|
/// Bootstrap context.
|
|
static final AppContext _root = new AppContext._(null, 'ROOT');
|
|
|
|
dynamic _boxNull(dynamic value) => value ?? _BoxedNull.instance;
|
|
|
|
dynamic _unboxNull(dynamic value) => value == _BoxedNull.instance ? null : value;
|
|
|
|
/// Returns the generated value for [type] if such a generator exists.
|
|
///
|
|
/// If [generators] does not contain a mapping for the specified [type], this
|
|
/// returns `null`.
|
|
///
|
|
/// If a generator existed and generated a `null` value, this will return a
|
|
/// boxed value indicating null.
|
|
///
|
|
/// If a value for [type] has already been generated by this context, the
|
|
/// existing value will be returned, and the generator will not be invoked.
|
|
///
|
|
/// If the generator ends up triggering a reentrant call, it signals a
|
|
/// dependency cycle, and a [ContextDependencyCycleException] will be thrown.
|
|
dynamic _generateIfNecessary(Type type, Map<Type, Generator> generators) {
|
|
if (!generators.containsKey(type))
|
|
return null;
|
|
|
|
return _values.putIfAbsent(type, () {
|
|
_reentrantChecks ??= <Type>[];
|
|
|
|
final int index = _reentrantChecks.indexOf(type);
|
|
if (index >= 0) {
|
|
// We're already in the process of trying to generate this type.
|
|
throw new ContextDependencyCycleException._(
|
|
new UnmodifiableListView<Type>(_reentrantChecks.sublist(index)));
|
|
}
|
|
|
|
_reentrantChecks.add(type);
|
|
try {
|
|
return _boxNull(generators[type]());
|
|
} finally {
|
|
_reentrantChecks.removeLast();
|
|
if (_reentrantChecks.isEmpty)
|
|
_reentrantChecks = null;
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Gets the value associated with the specified [type], or `null` if no
|
|
/// such value has been associated.
|
|
dynamic operator [](Type type) {
|
|
dynamic value = _generateIfNecessary(type, _overrides);
|
|
if (value == null && _parent != null)
|
|
value = _parent[type];
|
|
return _unboxNull(value ?? _generateIfNecessary(type, _fallbacks));
|
|
}
|
|
|
|
/// Runs [body] in a child context and returns the value returned by [body].
|
|
///
|
|
/// If [overrides] is specified, the child context will return corresponding
|
|
/// values when consulted via [operator[]].
|
|
///
|
|
/// If [fallbacks] is specified, the child context will return corresponding
|
|
/// values when consulted via [operator[]] only if its parent context didn't
|
|
/// return such a value.
|
|
///
|
|
/// If [name] is specified, the child context will be assigned the given
|
|
/// name. This is useful for debugging purposes and is analogous to naming a
|
|
/// thread in Java.
|
|
Future<V> run<V>({
|
|
@required FutureOr<V> body(),
|
|
String name,
|
|
Map<Type, Generator> overrides,
|
|
Map<Type, Generator> fallbacks,
|
|
}) async {
|
|
final AppContext child = new AppContext._(
|
|
this,
|
|
name,
|
|
new Map<Type, Generator>.unmodifiable(overrides ?? const <Type, Generator>{}),
|
|
new Map<Type, Generator>.unmodifiable(fallbacks ?? const <Type, Generator>{}),
|
|
);
|
|
return await runZoned<Future<V>>(
|
|
() async => await body(),
|
|
zoneValues: <_Key, AppContext>{_Key.key: child},
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
final StringBuffer buf = new StringBuffer();
|
|
String indent = '';
|
|
AppContext ctx = this;
|
|
while (ctx != null) {
|
|
buf.write('AppContext');
|
|
if (ctx.name != null)
|
|
buf.write('[${ctx.name}]');
|
|
if (ctx._overrides.isNotEmpty)
|
|
buf.write('\n$indent overrides: [${ctx._overrides.keys.join(', ')}]');
|
|
if (ctx._fallbacks.isNotEmpty)
|
|
buf.write('\n$indent fallbacks: [${ctx._fallbacks.keys.join(', ')}]');
|
|
if (ctx._parent != null)
|
|
buf.write('\n$indent parent: ');
|
|
ctx = ctx._parent;
|
|
indent += ' ';
|
|
}
|
|
return buf.toString();
|
|
}
|
|
}
|
|
|
|
/// Private key used to store the [AppContext] in the [Zone].
|
|
class _Key {
|
|
const _Key();
|
|
|
|
static const _Key key = _Key();
|
|
|
|
@override
|
|
String toString() => 'context';
|
|
}
|
|
|
|
/// Private object that denotes a generated `null` value.
|
|
class _BoxedNull {
|
|
const _BoxedNull();
|
|
|
|
static const _BoxedNull instance = _BoxedNull();
|
|
}
|