Add public-facing method and class that allows intercepting stream events.

This allows intercepting events at the subscription level instead of creating
a new full stream and adding the events to that.

Also fix a number of typos and bugs detected by analyzer.

Review URL: https://codereview.chromium.org//11953103

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17746 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
lrn@google.com
2013-01-29 08:43:15 +00:00
parent 396edf6273
commit 7fc981b3a6
7 changed files with 461 additions and 62 deletions
+1 -1
View File
@@ -113,7 +113,7 @@ abstract class Future<T> {
* See [Completer]s, for futures with values that are computed asynchronously.
*/
factory Future.delayed(int milliseconds, T value()) {
_FutureImpl<T> future = new _ThenFuture<dynamic, T>((_) => value());
_ThenFuture<dynamic, T> future = new _ThenFuture<dynamic, T>((_) => value());
new Timer(milliseconds, (_) => future._sendValue(null));
return future;
}
+8 -3
View File
@@ -159,29 +159,34 @@ class _CycleEntry<T> {
Stream source;
/** The active subscription, if any. */
StreamSubscription subscription = null;
/** Whether the subscription is currently paused. */
bool isPaused = false;
/** Next entry in a linked list of entries. */
_CycleEntry next;
_CycleEntry(this.stream, this.source);
void cancel() {
// This method may be called event if this entry has never been activated.
// This method may be called even if this entry has never been activated.
if (subscription != null) {
subscription.cancel();
subscription = null;
isPaused = false;
}
}
void pause() {
ensureSubscribed();
if (!subscription.isPaused) {
if (!isPaused) {
subscription.pause();
isPaused = true;
}
}
void activate() {
ensureSubscribed();
if (subscription.isPaused) {
if (isPaused) {
isPaused = false;
subscription.resume();
}
}
+202 -8
View File
@@ -40,12 +40,11 @@ part of dart.async;
* A broadcast stream allows any number of listeners, and it fires
* its events when they are ready, whether there are listeners or not.
*
* Braodcast streams are used for independent events/observers.
* Broadcast streams are used for independent events/observers.
*
* The default implementation of [isBroadcast] and
* [asBroadcastStream] are assuming this is a single-subscription stream
* and a broadcast stream inheriting from [Stream] must override these
* to return [:true:] and [:this:] respectively.
* The default implementation of [isBroadcast] returns false.
* A broadcast stream inheriting from [Stream] must override [isBroadcast]
* to return [:true:].
*/
abstract class Stream<T> {
Stream();
@@ -93,6 +92,7 @@ abstract class Stream<T> {
* If this stream is already a broadcast stream, it is returned unmodified.
*/
Stream<T> asBroadcastStream() {
if (isBroadcast) return this;
return new _SingleStreamMultiplexer<T>(this);
}
@@ -172,8 +172,11 @@ abstract class Stream<T> {
* If the error is intercepted, the [handle] function can decide what to do
* with it. It can throw if it wants to raise a new (or the same) error,
* or simply return to make the stream forget the error.
*
* If you need to transform an error into a data event, use the more generic
* [Stream.transformEvent] to handle the event by writing a data event to
* the output sink
*/
// TODO(lrn): Say what to do if you want to convert the error to a value.
Stream<T> handleError(void handle(AsyncError error), { bool test(error) }) {
return new _HandleErrorStream<T>(this, handle, test);
}
@@ -206,6 +209,26 @@ abstract class Stream<T> {
return streamTransformer.bind(this);
}
/**
* Create a new stream from this by modifying events.
*
* Subscribing on the returned stream is the same as subscribing on
* this stream, except that events are passed through the [transformer]
* before being emitted. The transformer may generate any number and
* types of events for each incoming event. Pauses on the returned
* subscription are pauses on this stream.
*
* An example that duplicates all data events:
*
* someStream.transformEvents(new StreamEventTransformer.from(
* handleData: (var value, StreamSink sink) {
* sink.add(value);
* sink.add(value);
* }));
*/
Stream transformEvents(StreamEventTransformer<T, dynamic> transformer) {
return new EventTransformStream<T, dynamic>(this, transformer);
}
/** Reduces a sequence of values by repeatedly applying [combine]. */
Future reduce(var initialValue, combine(var previous, T element)) {
@@ -263,11 +286,11 @@ abstract class Stream<T> {
// checked mode. http://dartbug.com/7733
(/*T*/ element) {
_runUserCode(
() => match(element),
() => (element == match),
(bool isMatch) {
if (isMatch) {
subscription.cancel();
future._setValue(element);
future._setValue(true);
}
},
_cancelAndError(subscription, future)
@@ -934,3 +957,174 @@ abstract class StreamTransformer<S, T> {
Stream<T> bind(Stream<S> stream);
}
/**
* A transformer of stream events.
*
* A [StreamEventTransformer] transforms incoming Stream
* events of one kind into outgoing events of another kind.
*
* The default implementations of the "handle" methods forward
* the events unmodified. In that case the generic type [T] needs to be
* assignable to [S].
*
* You can use a [StreamEventTransformer] to modify a Stream's events using
* the [Stream.transformEvents] method.
*/
abstract class StreamEventTransformer<S, T> {
const StreamEventTransformer();
/**
* Create a [StreamEventTransformer] that delegates to the provided methods.
*
* The created transformer acts as if the provided functions were the
* methods of the same name.
*/
factory StreamEventTransformer.from({
void handleData(S data, StreamSink<T> sink),
void handleError(AsyncError error, StreamSink<T> sink),
void handleDone(StreamSink<T> sink)
}) {
return new _StreamEventTransformerImpl<S, T>(handleData,
handleError,
handleDone);
}
/**
* Act on incoming data event.
*
* The method may generate any number of events on the sink, but should
* not throw.
*/
void handleData(S event, StreamSink<T> sink) {
var data = event;
sink.add(data);
}
/**
* Act on incoming error event.
*
* The method may generate any number of events on the sink, but should
* not throw.
*/
void handleError(AsyncError error, StreamSink<T> sink) {
sink.signalError(error);
}
/**
* Act on incoming done event.
*
* The method may generate any number of events on the sink, but should
* not throw.
*/
void handleDone(StreamSink<T> sink){
sink.close();
}
}
/**
* Stream that transforms another stream by intercepting and replacing events.
*
* This [Stream] is a transformation of a source stream. Listening on this
* stream is the same as listening on the source stream, except that events
* are intercepted and modified by a [StreamEventTransformer] before becoming
* events on this stream.
*/
class EventTransformStream<S, T> extends Stream<T> {
Stream<S> _source;
StreamEventTransformer _transformer;
EventTransformStream(Stream<S> source,
StreamEventTransformer<S, T> transformer)
: _source = source, _transformer = transformer;
StreamSubscription<T> listen(void onData(T data),
{ void onError(AsyncError error),
void onDone(),
bool unsubscribeOnError }) {
return new _EventTransformStreamSubscription(_source, _transformer,
onData, onError, onDone,
unsubscribeOnError);
}
}
class _EventTransformStreamSubscription<S, T>
extends _BaseStreamSubscription<T>
implements _StreamOutputSink<T> {
/** The transformer used to transform events. */
final StreamEventTransformer<S, T> _transformer;
/** Whether to unsubscribe when emitting an error. */
final bool _unsubscribeOnError;
/** Source of incoming events. */
StreamSubscription<S> _subscription;
/** Cached StreamSink wrapper for this class. */
StreamSink<T> _sink;
_EventTransformStreamSubscription(Stream<S> source,
this._transformer,
void onData(T data),
void onError(AsyncError error),
void onDone(),
this._unsubscribeOnError)
: super(onData, onError, onDone) {
_sink = new _StreamOutputSinkWrapper<T>(this);
_subscription = source.listen(_handleData,
onError: _handleError,
onDone: _handleDone);
}
void pause([Future pauseSignal]) {
if (_subscription != null) _subscription.pause(pauseSignal);
}
void resume() {
if (_subscription != null) _subscription.resume();
}
void cancel() {
if (_subscription != null) {
_subscription.cancel();
_subscription = null;
}
}
void _handleData(S data) {
_transformer.handleData(data, _sink);
}
void _handleError(AsyncError error) {
_transformer.handleError(error, _sink);
}
void _handleDone() {
_transformer.handleDone(_sink);
}
// StreamOutputSink interface.
void _sendData(T data) {
_onData(data);
}
void _sendError(AsyncError error) {
_onError(error);
if (_unsubscribeOnError) {
cancel();
}
}
void _sendDone() {
// It's ok to cancel even if we have been unsubscribed already.
cancel();
_onDone();
}
}
class _StreamOutputSinkWrapper<T> implements StreamSink<T> {
_StreamOutputSink _sink;
_StreamOutputSinkWrapper(this._sink);
void add(T data) => _sink._sendData(data);
void signalError(AsyncError error) => _sink._sendError(error);
void close() => _sink._sendDone();
}
+9 -7
View File
@@ -62,10 +62,10 @@ abstract class _StreamImpl<T> extends Stream<T> {
// ------------------------------------------------------------------
// Stream interface.
StreamSubscription listen(void onData(T data),
{ void onError(AsyncError error),
void onDone(),
bool unsubscribeOnError }) {
StreamSubscription<T> listen(void onData(T data),
{ void onError(AsyncError error),
void onDone(),
bool unsubscribeOnError }) {
if (_isComplete) {
return new _DoneSubscription(onDone);
}
@@ -73,7 +73,7 @@ abstract class _StreamImpl<T> extends Stream<T> {
if (onError == null) onError = _nullErrorHandler;
if (onDone == null) onDone = _nullDoneHandler;
unsubscribeOnError = identical(true, unsubscribeOnError);
_StreamListener subscription =
_StreamSubscriptionImpl subscription =
_createSubscription(onData, onError, onDone, unsubscribeOnError);
_addListener(subscription);
return subscription;
@@ -1092,8 +1092,10 @@ class _DoneSubscription<T> implements StreamSubscription<T> {
bool get _isComplete => _timer == null && _pauseCount == 0;
void onData(void handleAction(T value)) {}
void onError(void handleError(StateError error)) {}
void onDone(void handleDone(T value)) {
void onError(void handleError(AsyncError error)) {}
void onDone(void handleDone()) {
_handler = handleDone;
}
+128 -43
View File
@@ -53,20 +53,23 @@ abstract class _ForwardingStream<S, T> extends Stream<T> {
bool get isBroadcast => _source.isBroadcast;
bool asBroadcastStream() => _source.asBroadcastStream;
StreamSubscription listen(void onData(T value),
{ void onError(AsyncError error),
void onDone(),
bool unsubscribeOnError }) {
StreamSubscription<T> listen(void onData(T value),
{ void onError(AsyncError error),
void onDone(),
bool unsubscribeOnError }) {
if (onData == null) onData = _nullDataHandler;
if (onError == null) onError = _nullErrorHandler;
if (onDone == null) onDone = _nullDoneHandler;
unsubscribeOnError = identical(true, unsubscribeOnError);
StreamSubscription subscription =
new _ForwardingStreamSubscription<S, T>(
this, onData, onError, onDone, unsubscribeOnError);
return subscription;
return _createSubscription(onData, onError, onDone, unsubscribeOnError);
}
StreamSubscription<T> _createSubscription(void onData(T value),
void onError(AsyncError error),
void onDone(),
bool unsubscribeOnError) {
return new _ForwardingStreamSubscription<S, T>(
this, onData, onError, onDone, unsubscribeOnError);
}
// Override the following methods in subclasses to change the behavior.
@@ -86,33 +89,26 @@ abstract class _ForwardingStream<S, T> extends Stream<T> {
}
/**
* Abstract superclass for subscriptions that forward to other subscriptions.
* Common behavior of [StreamSubscription] classes.
*
* Stores and allows updating of the event handlers of a [StreamSubscription].
*/
class _ForwardingStreamSubscription<S, T>
implements StreamSubscription<T>, _StreamOutputSink<T> {
final _ForwardingStream<S, T> _stream;
abstract class _BaseStreamSubscription<T> implements StreamSubscription<T> {
// TODO(ahe): Restore type when feature is implemented in dart2js
// checked mode. http://dartbug.com/7733
var /* _DataHandler<T> */ _onData;
_ErrorHandler _onError;
_DoneHandler _onDone;
StreamSubscription<S> _subscription;
_ForwardingStreamSubscription(this._stream,
this._onData,
this._onError,
this._onDone,
bool unsubscribeOnError) {
_subscription =
_stream._source.listen(_handleData,
onError: _handleError,
onDone: _handleDone,
unsubscribeOnError: unsubscribeOnError);
_BaseStreamSubscription(this._onData,
this._onError,
this._onDone) {
if (_onData == null) _onData = _nullDataHandler;
if (_onError == null) _onError = _nullErrorHandler;
if (_onDone == null) _onDone = _nullDoneHandler;
}
// StreamSubscription interface.
void onData(void handleData(T event)) {
if (handleData == null) handleData = _nullDataHandler;
_onData = handleData;
@@ -128,6 +124,39 @@ class _ForwardingStreamSubscription<S, T>
_onDone = handleDone;
}
void pause([Future resumeSignal]);
void resume();
void cancel();
}
/**
* Abstract superclass for subscriptions that forward to other subscriptions.
*/
class _ForwardingStreamSubscription<S, T>
extends _BaseStreamSubscription<T> implements _StreamOutputSink<T> {
final _ForwardingStream<S, T> _stream;
final bool _unsubscribeOnError;
StreamSubscription<S> _subscription;
_ForwardingStreamSubscription(this._stream,
void onData(T data),
void onError(AsyncError error),
void onDone(),
this._unsubscribeOnError)
: super(onData, onError, onDone) {
// Don't unsubscribe on incoming error, only if we send an error forwards.
_subscription =
_stream._source.listen(_handleData,
onError: _handleError,
onDone: _handleDone);
}
// StreamSubscription interface.
void pause([Future resumeSignal]) {
if (_subscription == null) {
throw new StateError("Subscription has been unsubscribed");
@@ -158,6 +187,10 @@ class _ForwardingStreamSubscription<S, T>
void _sendError(AsyncError error) {
_onError(error);
if (_unsubscribeOnError) {
_subscription.cancel();
_subscription = null;
}
}
void _sendDone() {
@@ -428,11 +461,28 @@ class _DistinctStream<T> extends _ForwardingStream<T, T> {
}
}
// Stream transformations and event transformations.
typedef void _TransformDataHandler<S, T>(S data, StreamSink<T> sink);
typedef void _TransformErrorHandler<T>(AsyncError data, StreamSink<T> sink);
typedef void _TransformDoneHandler<T>(StreamSink<T> sink);
/** Default data handler forwards all data. */
void _defaultHandleData(var data, StreamSink sink) {
sink.add(data);
}
/** Default error handler forwards all errors. */
void _defaultHandleError(AsyncError error, StreamSink sink) {
sink.signalError(error);
}
/** Default done handler forwards done. */
void _defaultHandleDone(StreamSink sink) {
sink.close();
}
/**
* A stream transformer that intercepts all events and can generate any event as
* output.
@@ -467,7 +517,7 @@ class _StreamTransformerImpl<S, T> implements StreamTransformer<S, T> {
try {
_onData(data, _sink);
} catch (e, s) {
_stream._signalError(_asyncError(e, s));
_sink.signalError(_asyncError(e, s));
}
}
@@ -475,7 +525,7 @@ class _StreamTransformerImpl<S, T> implements StreamTransformer<S, T> {
try {
_onError(error, _sink);
} catch (e, s) {
_stream._signalError(_asyncError(e, s, error));
_sink.signalError(_asyncError(e, s, error));
}
}
@@ -483,22 +533,9 @@ class _StreamTransformerImpl<S, T> implements StreamTransformer<S, T> {
try {
_onDone(_sink);
} catch (e, s) {
_stream._signalError(_asyncError(e, s));
_sink.signalError(_asyncError(e, s));
}
}
/** Default data handler forwards all data. */
static void _defaultHandleData(var data, StreamSink sink) {
sink.add(data);
}
/** Default error handler forwards all errors. */
static void _defaultHandleError(AsyncError error, StreamSink sink) {
sink.signalError(error);
}
/** Default done handler forwards done. */
static void _defaultHandleDone(StreamSink sink) {
sink.close();
}
}
/** Creates a [StreamSink] from a [_StreamImpl]'s input methods. */
@@ -509,3 +546,51 @@ class _StreamImplSink<T> implements StreamSink<T> {
void signalError(AsyncError error) { _target._signalError(error); }
void close() { _target._close(); }
}
/**
* A stream transformer that intercepts all events and can generate any event as
* output.
*
* Each incoming event on the source stream is passed to the corresponding
* provided event handler, along with a [StreamSink] linked to the output
* Stream.
* The handler can then decide exactly which events to send to the output.
*/
class _StreamEventTransformerImpl<S, T>
implements StreamEventTransformer<S, T> {
final _TransformDataHandler<S, T> _handleData;
final _TransformErrorHandler<T> _handleError;
final _TransformDoneHandler<T> _handleDone;
_StreamEventTransformerImpl(void onData(S data, StreamSink<T> sink),
void onError(AsyncError data, StreamSink<T> sink),
void onDone(StreamSink<T> sink))
: this._handleData = (onData == null ? _defaultHandleData : onData),
this._handleError = (onError == null ? _defaultHandleError : onError),
this._handleDone = (onDone == null ? _defaultHandleDone : onDone);
void handleData(S data, StreamSink<T> sink) {
try {
_handleData(data, sink);
} catch (e, s) {
sink.signalError(_asyncError(e, s));
}
}
void handleError(AsyncError error, StreamSink<T> sink) {
try {
_handleError(error, sink);
} catch (e, s) {
sink.signalError(_asyncError(e, s, error));
}
}
void handleDone(StreamSink<T> sink) {
try {
_handleDone(sink);
} catch (e, s) {
sink.signalError(_asyncError(e, s));
}
}
}
@@ -240,6 +240,42 @@ testSingleController() {
c.add(9);
c.close();
// test contains.
{
c = new StreamController();
// Error after match is not important.
sentEvents = new Events()..add("a")..add("x")..error("FAIL")..close();
Future<bool> contains = c.stream.contains("x");
contains.then((var c) {
Expect.isTrue(c);
});
sentEvents.replay(c);
}
{
c = new StreamController();
// Not matching is ok.
sentEvents = new Events()..add("a")..add("x")..add("b")..close();
Future<bool> contains = c.stream.contains("y");
contains.then((var c) {
Expect.isFalse(c);
});
sentEvents.replay(c);
}
{
c = new StreamController();
// Error before match makes future err.
sentEvents = new Events()..add("a")..error("FAIL")..add("b")..close();
Future<bool> contains = c.stream.contains("b");
contains.then((var c) {
Expect.fail("no value expected");
}).catchError((AsyncError e) {
Expect.equals("FAIL", e.error);
});
sentEvents.replay(c);
}
// Test transform.
c = new StreamController();
sentEvents = new Events()..add("a")..error(42)..add("b")..close();
@@ -0,0 +1,77 @@
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. 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 '../../../pkg/unittest/lib/unittest.dart';
import 'event_helper.dart';
void handleData(int data, StreamSink<int> sink) {
sink.signalError(new AsyncError("$data"));
sink.add(data + 1);
}
void handleError(AsyncError e, StreamSink<int> sink) {
String value = e.error;
int data = int.parse(value);
sink.add(data);
sink.signalError(new AsyncError("${data + 1}"));
}
void handleDone(StreamSink<int> sink) {
sink.add(99);
sink.close();
}
class EventTransformer extends StreamEventTransformer<int,int> {
void handleData(int data, StreamSink<int> sink) {
sink.signalError(new AsyncError("$data"));
sink.add(data + 1);
}
void handleError(AsyncError e, StreamSink<int> sink) {
String value = e.error;
int data = int.parse(value);
sink.add(data);
sink.signalError(new AsyncError("${data + 1}"));
}
void handleDone(StreamSink<int> sink) {
sink.add(99);
sink.close();
}
}
main() {
{
StreamController c = new StreamController();
Events expected = new Events()..error("0")..add(1)
..error("1")..add(2)
..add(3)..error("4")
..add(99)..close();
Events input = new Events()..add(0)..add(1)..error("3")..close();
Events actual = new Events.capture(
c.stream.transformEvents(new EventTransformer()));
actual.onDone(() {
Expect.listEquals(expected.events, actual.events);
});
input.replay(c);
}
{
StreamController c = new StreamController();
Events expected = new Events()..error("0")..add(1)
..error("1")..add(2)
..add(3)..error("4")
..add(99)..close();
Events input = new Events()..add(0)..add(1)..error("3")..close();
Events actual = new Events.capture(
c.stream.transformEvents(new StreamEventTransformer.from(
handleData: handleData,
handleError: handleError,
handleDone: handleDone
)));
actual.onDone(() {
Expect.listEquals(expected.events, actual.events);
});
input.replay(c);
}
}