|  | // Copyright (c) 2012, 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. | 
|  |  | 
|  | library event_helper; | 
|  |  | 
|  | import 'dart:async'; | 
|  |  | 
|  | abstract class Event { | 
|  | void replay(EventSink sink); | 
|  | } | 
|  |  | 
|  | class DataEvent implements Event { | 
|  | final data; | 
|  |  | 
|  | DataEvent(this.data); | 
|  |  | 
|  | void replay(EventSink sink) { | 
|  | sink.add(data); | 
|  | } | 
|  |  | 
|  | int get hashCode => data.hashCode; | 
|  |  | 
|  | bool operator ==(Object other) { | 
|  | if (other is! DataEvent) return false; | 
|  | DataEvent otherEvent = other; | 
|  | return data == otherEvent.data; | 
|  | } | 
|  |  | 
|  | String toString() => "DataEvent: $data"; | 
|  | } | 
|  |  | 
|  | class ErrorEvent implements Event { | 
|  | final error; | 
|  |  | 
|  | ErrorEvent(this.error); | 
|  |  | 
|  | void replay(EventSink sink) { | 
|  | sink.addError(error); | 
|  | } | 
|  |  | 
|  | int get hashCode => error.error.hashCode; | 
|  |  | 
|  | bool operator ==(Object other) { | 
|  | if (other is! ErrorEvent) return false; | 
|  | ErrorEvent otherEvent = other; | 
|  | return error == otherEvent.error; | 
|  | } | 
|  |  | 
|  | String toString() => "ErrorEvent: ${error}"; | 
|  | } | 
|  |  | 
|  | class DoneEvent implements Event { | 
|  | const DoneEvent(); | 
|  |  | 
|  | void replay(EventSink sink) { | 
|  | sink.close(); | 
|  | } | 
|  |  | 
|  | int get hashCode => 42; | 
|  |  | 
|  | bool operator ==(Object other) => other is DoneEvent; | 
|  |  | 
|  | String toString() => "DoneEvent"; | 
|  | } | 
|  |  | 
|  | /** Collector of events. */ | 
|  | class Events implements EventSink { | 
|  | final List<Event> events = []; | 
|  | bool trace = false; | 
|  | Completer onDoneSignal = new Completer(); | 
|  |  | 
|  | Events(); | 
|  |  | 
|  | Events.fromIterable(Iterable iterable) { | 
|  | for (var value in iterable) add(value); | 
|  | close(); | 
|  | } | 
|  |  | 
|  | /** Capture events from a stream into a new [Events] object. */ | 
|  | factory Events.capture(Stream stream, {bool cancelOnError}) = CaptureEvents; | 
|  |  | 
|  | // EventSink interface. | 
|  | void add(var value) { | 
|  | if (trace) print("Events#$hashCode: add($value)"); | 
|  | events.add(new DataEvent(value)); | 
|  | } | 
|  |  | 
|  | void addError(error, [StackTrace stackTrace]) { | 
|  | if (trace) print("Events#$hashCode: addError($error)"); | 
|  | events.add(new ErrorEvent(error)); | 
|  | } | 
|  |  | 
|  | void close() { | 
|  | if (trace) print("Events#$hashCode: close()"); | 
|  | events.add(const DoneEvent()); | 
|  | onDoneSignal.complete(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Error shorthand, for writing events manually. | 
|  | */ | 
|  | void error(var value, [StackTrace stackTrace]) { | 
|  | addError(value, stackTrace); | 
|  | } | 
|  |  | 
|  | /** Replay the captured events on a sink. */ | 
|  | void replay(EventSink sink) { | 
|  | for (int i = 0; i < events.length; i++) { | 
|  | events[i].replay(sink); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Create a new [Events] with the same captured events. | 
|  | * | 
|  | * This does not copy a subscription. | 
|  | */ | 
|  | Events copy() { | 
|  | Events result = new Events(); | 
|  | replay(result); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Operations that only work when there is a subscription feeding the Events. | 
|  |  | 
|  | /** | 
|  | * Pauses the subscription that feeds this [Events]. | 
|  | * | 
|  | * Should only be used when there is a subscription. That is, after a | 
|  | * call to [subscribeTo]. | 
|  | */ | 
|  | void pause([Future resumeSignal]) { | 
|  | throw new StateError("Not capturing events."); | 
|  | } | 
|  |  | 
|  | /** Resumes after a call to [pause]. */ | 
|  | void resume() { | 
|  | throw new StateError("Not capturing events."); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Sets an action to be called when this [Events] receives a 'done' event. | 
|  | * | 
|  | * The action will also be called if capturing events from a stream with | 
|  | * `cancelOnError` set to true and receiving an error. | 
|  | */ | 
|  | void onDone(void action()) { | 
|  | onDoneSignal.future.whenComplete(action); | 
|  | } | 
|  | } | 
|  |  | 
|  | class CaptureEvents extends Events { | 
|  | StreamSubscription subscription; | 
|  | bool cancelOnError = false; | 
|  |  | 
|  | CaptureEvents(Stream stream, {bool cancelOnError: false}) { | 
|  | this.cancelOnError = cancelOnError; | 
|  | subscription = stream.listen(add, | 
|  | onError: addError, onDone: close, cancelOnError: cancelOnError); | 
|  | } | 
|  |  | 
|  | void addError(error, [stackTrace]) { | 
|  | super.addError(error, stackTrace); | 
|  | if (cancelOnError) { | 
|  | onDoneSignal.complete(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void pause([Future resumeSignal]) { | 
|  | if (trace) print("Events#$hashCode: pause"); | 
|  | subscription.pause(resumeSignal); | 
|  | } | 
|  |  | 
|  | void resume() { | 
|  | if (trace) print("Events#$hashCode: resume"); | 
|  | subscription.resume(); | 
|  | } | 
|  |  | 
|  | void onDone(void action()) { | 
|  | if (trace) print("Events#$hashCode: onDone"); | 
|  | super.onDone(action); | 
|  | } | 
|  | } |