|  | // Copyright (c) 2013, 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. | 
|  |  | 
|  | part of dart.io; | 
|  |  | 
|  | /** | 
|  | * A combined byte and text output. | 
|  | * | 
|  | * An [IOSink] combines a [StreamSink] of bytes with a [StringSink], | 
|  | * and allows easy output of both bytes and text. | 
|  | * | 
|  | * Writing text ([write]) and adding bytes ([add]) may be interleaved freely. | 
|  | * | 
|  | * While a stream is being added using [addStream], any further attempts | 
|  | * to add or write to the [IOSink] will fail until the [addStream] completes. | 
|  | * | 
|  | * It is an error to add data to the [IOSink] after the sink is closed. | 
|  | */ | 
|  | abstract class IOSink implements StreamSink<List<int>>, StringSink { | 
|  | /** | 
|  | * Create an [IOSink] that outputs to a [target] [StreamConsumer] of bytes. | 
|  | * | 
|  | * Text written to [StreamSink] methods is encoded to bytes using [encoding] | 
|  | * before being output on [target]. | 
|  | */ | 
|  | factory IOSink(StreamConsumer<List<int>> target, {Encoding encoding: utf8}) => | 
|  | new _IOSinkImpl(target, encoding); | 
|  |  | 
|  | /** | 
|  | * The [Encoding] used when writing strings. Depending on the | 
|  | * underlying consumer this property might be mutable. | 
|  | */ | 
|  | late Encoding encoding; | 
|  |  | 
|  | /** | 
|  | * Adds byte [data] to the target consumer, ignoring [encoding]. | 
|  | * | 
|  | * The [encoding] does not apply to this method, and the `data` list is passed | 
|  | * directly to the target consumer as a stream event. | 
|  | * | 
|  | * This function must not be called when a stream is currently being added | 
|  | * using [addStream]. | 
|  | * | 
|  | * This operation is non-blocking. See [flush] or [done] for how to get any | 
|  | * errors generated by this call. | 
|  | * | 
|  | * The data list should not be modified after it has been passed to `add`. | 
|  | */ | 
|  | void add(List<int> data); | 
|  |  | 
|  | /** | 
|  | * Converts [obj] to a String by invoking [Object.toString] and | 
|  | * [add]s the encoding of the result to the target consumer. | 
|  | * | 
|  | * This operation is non-blocking. See [flush] or [done] for how to get any | 
|  | * errors generated by this call. | 
|  | */ | 
|  | void write(Object? obj); | 
|  |  | 
|  | /** | 
|  | * Iterates over the given [objects] and [write]s them in sequence. | 
|  | * | 
|  | * If [separator] is provided, a `write` with the `separator` is performed | 
|  | * between any two elements of objects. | 
|  | * | 
|  | * This operation is non-blocking. See [flush] or [done] for how to get any | 
|  | * errors generated by this call. | 
|  | */ | 
|  | void writeAll(Iterable objects, [String separator = ""]); | 
|  |  | 
|  | /** | 
|  | * Converts [obj] to a String by invoking [Object.toString] and | 
|  | * writes the result to `this`, followed by a newline. | 
|  | * | 
|  | * This operation is non-blocking. See [flush] or [done] for how to get any | 
|  | * errors generated by this call. | 
|  | */ | 
|  | void writeln([Object? obj = ""]); | 
|  |  | 
|  | /** | 
|  | * Writes the character of [charCode]. | 
|  | * | 
|  | * This method is equivalent to `write(new String.fromCharCode(charCode))`. | 
|  | * | 
|  | * This operation is non-blocking. See [flush] or [done] for how to get any | 
|  | * errors generated by this call. | 
|  | */ | 
|  | void writeCharCode(int charCode); | 
|  |  | 
|  | /** | 
|  | * Passes the error to the target consumer as an error event. | 
|  | * | 
|  | * This function must not be called when a stream is currently being added | 
|  | * using [addStream]. | 
|  | * | 
|  | * This operation is non-blocking. See [flush] or [done] for how to get any | 
|  | * errors generated by this call. | 
|  | */ | 
|  | void addError(error, [StackTrace? stackTrace]); | 
|  |  | 
|  | /** | 
|  | * Adds all elements of the given [stream] to `this`. | 
|  | * | 
|  | * Returns a [Future] that completes when | 
|  | * all elements of the given [stream] are added to `this`. | 
|  | * | 
|  | * This function must not be called when a stream is currently being added | 
|  | * using this function. | 
|  | */ | 
|  | Future addStream(Stream<List<int>> stream); | 
|  |  | 
|  | /** | 
|  | * Returns a [Future] that completes once all buffered data is accepted by the | 
|  | * underlying [StreamConsumer]. | 
|  | * | 
|  | * This method must not be called while an [addStream] is incomplete. | 
|  | * | 
|  | * NOTE: This is not necessarily the same as the data being flushed by the | 
|  | * operating system. | 
|  | */ | 
|  | Future flush(); | 
|  |  | 
|  | /** | 
|  | * Close the target consumer. | 
|  | * | 
|  | * NOTE: Writes to the [IOSink] may be buffered, and may not be flushed by | 
|  | * a call to `close()`. To flush all buffered writes, call `flush()` before | 
|  | * calling `close()`. | 
|  | */ | 
|  | Future close(); | 
|  |  | 
|  | /** | 
|  | * Get a future that will complete when the consumer closes, or when an | 
|  | * error occurs. This future is identical to the future returned by | 
|  | * [close]. | 
|  | */ | 
|  | Future get done; | 
|  | } | 
|  |  | 
|  | class _StreamSinkImpl<T> implements StreamSink<T> { | 
|  | final StreamConsumer<T> _target; | 
|  | final Completer _doneCompleter = new Completer(); | 
|  | StreamController<T>? _controllerInstance; | 
|  | Completer? _controllerCompleter; | 
|  | bool _isClosed = false; | 
|  | bool _isBound = false; | 
|  | bool _hasError = false; | 
|  |  | 
|  | _StreamSinkImpl(this._target); | 
|  |  | 
|  | void add(T data) { | 
|  | if (_isClosed) { | 
|  | throw StateError("StreamSink is closed"); | 
|  | } | 
|  | _controller.add(data); | 
|  | } | 
|  |  | 
|  | void addError(error, [StackTrace? stackTrace]) { | 
|  | if (_isClosed) { | 
|  | throw StateError("StreamSink is closed"); | 
|  | } | 
|  | _controller.addError(error, stackTrace); | 
|  | } | 
|  |  | 
|  | Future addStream(Stream<T> stream) { | 
|  | if (_isBound) { | 
|  | throw new StateError("StreamSink is already bound to a stream"); | 
|  | } | 
|  | if (_hasError) return done; | 
|  |  | 
|  | _isBound = true; | 
|  | var future = _controllerCompleter == null | 
|  | ? _target.addStream(stream) | 
|  | : _controllerCompleter!.future.then((_) => _target.addStream(stream)); | 
|  | _controllerInstance?.close(); | 
|  |  | 
|  | // Wait for any pending events in [_controller] to be dispatched before | 
|  | // adding [stream]. | 
|  | return future.whenComplete(() { | 
|  | _isBound = false; | 
|  | }); | 
|  | } | 
|  |  | 
|  | Future flush() { | 
|  | if (_isBound) { | 
|  | throw new StateError("StreamSink is bound to a stream"); | 
|  | } | 
|  | if (_controllerInstance == null) return new Future.value(this); | 
|  | // Adding an empty stream-controller will return a future that will complete | 
|  | // when all data is done. | 
|  | _isBound = true; | 
|  | var future = _controllerCompleter!.future; | 
|  | _controllerInstance!.close(); | 
|  | return future.whenComplete(() { | 
|  | _isBound = false; | 
|  | }); | 
|  | } | 
|  |  | 
|  | Future close() { | 
|  | if (_isBound) { | 
|  | throw new StateError("StreamSink is bound to a stream"); | 
|  | } | 
|  | if (!_isClosed) { | 
|  | _isClosed = true; | 
|  | if (_controllerInstance != null) { | 
|  | _controllerInstance!.close(); | 
|  | } else { | 
|  | _closeTarget(); | 
|  | } | 
|  | } | 
|  | return done; | 
|  | } | 
|  |  | 
|  | void _closeTarget() { | 
|  | _target.close().then(_completeDoneValue, onError: _completeDoneError); | 
|  | } | 
|  |  | 
|  | Future get done => _doneCompleter.future; | 
|  |  | 
|  | void _completeDoneValue(value) { | 
|  | if (!_doneCompleter.isCompleted) { | 
|  | _doneCompleter.complete(value); | 
|  | } | 
|  | } | 
|  |  | 
|  | void _completeDoneError(error, StackTrace? stackTrace) { | 
|  | if (!_doneCompleter.isCompleted) { | 
|  | _hasError = true; | 
|  | _doneCompleter.completeError(error, stackTrace); | 
|  | } | 
|  | } | 
|  |  | 
|  | StreamController<T> get _controller { | 
|  | if (_isBound) { | 
|  | throw new StateError("StreamSink is bound to a stream"); | 
|  | } | 
|  | if (_isClosed) { | 
|  | throw new StateError("StreamSink is closed"); | 
|  | } | 
|  | if (_controllerInstance == null) { | 
|  | _controllerInstance = new StreamController<T>(sync: true); | 
|  | _controllerCompleter = new Completer(); | 
|  | _target.addStream(_controller.stream).then((_) { | 
|  | if (_isBound) { | 
|  | // A new stream takes over - forward values to that stream. | 
|  | _controllerCompleter!.complete(this); | 
|  | _controllerCompleter = null; | 
|  | _controllerInstance = null; | 
|  | } else { | 
|  | // No new stream, .close was called. Close _target. | 
|  | _closeTarget(); | 
|  | } | 
|  | }, onError: (error, stackTrace) { | 
|  | if (_isBound) { | 
|  | // A new stream takes over - forward errors to that stream. | 
|  | _controllerCompleter!.completeError(error, stackTrace); | 
|  | _controllerCompleter = null; | 
|  | _controllerInstance = null; | 
|  | } else { | 
|  | // No new stream. No need to close target, as it has already | 
|  | // failed. | 
|  | _completeDoneError(error, stackTrace); | 
|  | } | 
|  | }); | 
|  | } | 
|  | return _controllerInstance!; | 
|  | } | 
|  | } | 
|  |  | 
|  | class _IOSinkImpl extends _StreamSinkImpl<List<int>> implements IOSink { | 
|  | Encoding _encoding; | 
|  | bool _encodingMutable = true; | 
|  |  | 
|  | _IOSinkImpl(StreamConsumer<List<int>> target, this._encoding) : super(target); | 
|  |  | 
|  | Encoding get encoding => _encoding; | 
|  |  | 
|  | void set encoding(Encoding value) { | 
|  | if (!_encodingMutable) { | 
|  | throw new StateError("IOSink encoding is not mutable"); | 
|  | } | 
|  | _encoding = value; | 
|  | } | 
|  |  | 
|  | void write(Object? obj) { | 
|  | String string = '$obj'; | 
|  | if (string.isEmpty) return; | 
|  | add(_encoding.encode(string)); | 
|  | } | 
|  |  | 
|  | void writeAll(Iterable objects, [String separator = ""]) { | 
|  | Iterator iterator = objects.iterator; | 
|  | if (!iterator.moveNext()) return; | 
|  | if (separator.isEmpty) { | 
|  | do { | 
|  | write(iterator.current); | 
|  | } while (iterator.moveNext()); | 
|  | } else { | 
|  | write(iterator.current); | 
|  | while (iterator.moveNext()) { | 
|  | write(separator); | 
|  | write(iterator.current); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void writeln([Object? object = ""]) { | 
|  | write(object); | 
|  | write("\n"); | 
|  | } | 
|  |  | 
|  | void writeCharCode(int charCode) { | 
|  | write(new String.fromCharCode(charCode)); | 
|  | } | 
|  | } |