content: // 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.convert;
  
  /**
   * This class provides an interface for converters to
   * efficiently transmit String data.
   *
   * Instead of limiting the interface to one non-chunked String it accepts
   * partial strings or can be transformed into a byte sink that
   * accepts UTF-8 code units.
   */
  abstract class StringConversionSink
      extends ChunkedConversionSink<String> {
    StringConversionSink();
    factory StringConversionSink.withCallback(void callback(String accumulated))
        = _StringCallbackSink;
    factory StringConversionSink.from(ChunkedConversionSink<String> sink)
        = _StringAdapterSink;
  
    /**
     * Creates a new instance wrapping the given [sink].
     *
     * Every string that is added to the returned instance is forwarded to
     * the [sink]. The instance is allowed to buffer and is not required to
     * forward immediately.
     */
    factory StringConversionSink.fromStringSink(StringSink sink) =
        _StringSinkConversionSink;
  
    /**
     * Adds the next [chunk] to `this`.
     *
     * Adds the substring defined by [start] and [end]-exclusive to `this`.
     *
     * If [isLast] is `true` closes `this`.
     */
    void addSlice(String chunk, int start, int end, bool isLast);
  
    /**
     * Returns `this` as a sink that accepts UTF-8 input.
     *
     * If used, this method must be the first and only call to `this`. It
     * invalidates `this`. All further operations must be performed on the result.
     */
    ByteConversionSink asUtf8Sink(bool allowMalformed);
    // - asRuneSink
    // - asCodeUnitsSink
  
    /**
     * Returns `this` as a [ClosableStringSink].
     *
     * If used, this method must be the first and only call to `this`. It
     * invalidates `this`. All further operations must be performed on the result.
     */
    ClosableStringSink asStringSink();
  }
  
  /**
   * A [ClosableStringSink] extends the [StringSink] interface by adding a
   * `close` method.
   */
  abstract class ClosableStringSink extends StringSink {
    /**
     * Creates a new instance combining a [StringSink] [sink] and a callback
     * [onClose] which is invoked when the returned instance is closed.
     */
    factory ClosableStringSink.fromStringSink(StringSink sink, void onClose())
        = _ClosableStringSink;
  
    /**
     * Closes `this` and flushes any outstanding data.
     */
    void close();
  }
  
  typedef void _StringSinkCloseCallback();
  
  /**
   * This class wraps an existing [StringSink] and invokes a
   * closure when [close] is invoked.
   */
  class _ClosableStringSink implements ClosableStringSink {
    final _StringSinkCloseCallback _callback;
    final StringSink _sink;
  
    _ClosableStringSink(this._sink, this._callback);
  
    void close() => _callback();
  
    void writeCharCode(int charCode) => _sink.writeCharCode(charCode);
    void write(Object o) => _sink.write(o);
    void writeln([Object o = ""]) => _sink.writeln(o);
    void writeAll(Iterable objects, [String separator = ""])
        => _sink.writeAll(objects, separator);
  }
  
  /**
   * This class wraps an existing [StringConversionSink] and exposes a
   * [ClosableStringSink] interface. The wrapped sink only needs to implement
   * `add` and `close`.
   */
  // TODO(floitsch): make this class public?
  class _StringConversionSinkAsStringSinkAdapter implements ClosableStringSink {
    static const _MIN_STRING_SIZE = 16;
  
    StringBuffer _buffer;
    StringConversionSink _chunkedSink;
  
    _StringConversionSinkAsStringSinkAdapter(this._chunkedSink)
        : _buffer = new StringBuffer();
  
    void close() {
      if (_buffer.isNotEmpty) _flush();
      _chunkedSink.close();
    }
  
    void writeCharCode(int charCode) {
      _buffer.writeCharCode(charCode);
      if (_buffer.length > _MIN_STRING_SIZE) _flush();
    }
  
    void write(Object o) {
      if (_buffer.isNotEmpty) _flush();
      String str = o.toString();
      _chunkedSink.add(o.toString());
    }
  
    void writeln([Object o = ""]) {
      _buffer.writeln(o);
      if (_buffer.length > _MIN_STRING_SIZE) _flush();
    }
  
    void writeAll(Iterable objects, [String separator = ""]) {
      if (_buffer.isNotEmpty) _flush();
      Iterator iterator = objects.iterator;
      if (!iterator.moveNext()) return;
      if (separator.isEmpty) {
        do {
          _chunkedSink.add(iterator.current.toString());
        } while (iterator.moveNext());
      } else {
        _chunkedSink.add(iterator.current.toString());
        while (iterator.moveNext()) {
          write(separator);
          _chunkedSink.add(iterator.current.toString());
        }
      }
    }
  
    void _flush() {
      String accumulated = _buffer.toString();
      _buffer.clear();
      _chunkedSink.add(accumulated);
    }
  }
  
  /**
   * This class provides a base-class for converters that need to accept String
   * inputs.
   */
  abstract class StringConversionSinkBase extends StringConversionSinkMixin {
  }
  
  /**
   * This class provides a mixin for converters that need to accept String
   * inputs.
   */
  abstract class StringConversionSinkMixin implements StringConversionSink {
  
    void addSlice(String str, int start, int end, bool isLast);
    void close();
  
    void add(String str) => addSlice(str, 0, str.length, false);
  
    ByteConversionSink asUtf8Sink(bool allowMalformed) {
      return new _Utf8ConversionSink(this, allowMalformed);
    }
  
    ClosableStringSink asStringSink() {
      return new _StringConversionSinkAsStringSinkAdapter(this);
    }
  }
  
  /**
   * This class is a [StringConversionSink] that wraps a [StringSink].
   */
  class _StringSinkConversionSink extends StringConversionSinkBase {
    StringSink _stringSink;
    _StringSinkConversionSink(StringSink this._stringSink);
  
    void close() {}
    void addSlice(String str, int start, int end, bool isLast) {
      if (start != 0 || end != str.length) {
        for (int i = start; i < end; i++) {
          _stringSink.writeCharCode(str.codeUnitAt(i));
        }
      } else {
        _stringSink.write(str);
      }
      if (isLast) close();
    }
  
    void add(String str) => _stringSink.write(str);
  
    ByteConversionSink asUtf8Sink(bool allowMalformed) {
      return new _Utf8StringSinkAdapter(this, _stringSink, allowMalformed);
    }
  
    ClosableStringSink asStringSink() {
      return new ClosableStringSink.fromStringSink(_stringSink, this.close);
    }
  }
  
  /**
   * This class accumulates all chunks into one string
   * and invokes a callback when the sink is closed.
   *
   * This class can be used to terminate a chunked conversion.
   */
  class _StringCallbackSink extends _StringSinkConversionSink {
    final _ChunkedConversionCallback<String> _callback;
    _StringCallbackSink(this._callback) : super(new StringBuffer());
  
    void close() {
      StringBuffer buffer = _stringSink;
      String accumulated = buffer.toString();
      buffer.clear();
      _callback(accumulated);
    }
  
    ByteConversionSink asUtf8Sink(bool allowMalformed) {
      return new _Utf8StringSinkAdapter(
          this, _stringSink, allowMalformed);
    }
  }
  
  /**
   * This class adapts a simple [ChunkedConversionSink] to a
   * [StringConversionSink].
   *
   * All additional methods of the [StringConversionSink] (compared to the
   * ChunkedConversionSink) are redirected to the `add` method.
   */
  class _StringAdapterSink extends StringConversionSinkBase {
    final ChunkedConversionSink<String> _sink;
  
    _StringAdapterSink(this._sink);
  
    void add(String str) => _sink.add(str);
  
    void addSlice(String str, int start, int end, bool isLast) {
      if (start == 0 && end == str.length) {
        add(str);
      } else {
        add(str.substring(start, end));
      }
      if (isLast) close();
    }
  
    void close() => _sink.close();
  }
  
  
  /**
   * Decodes UTF-8 code units and stores them in a [StringSink].
   */
  class _Utf8StringSinkAdapter extends ByteConversionSink {
    final _Utf8Decoder _decoder;
    final ChunkedConversionSink _chunkedSink;
  
    _Utf8StringSinkAdapter(ChunkedConversionSink chunkedSink,
                           StringSink sink, bool allowMalformed)
        : _chunkedSink = chunkedSink,
          _decoder = new _Utf8Decoder(sink, allowMalformed);
  
    void close() {
      _decoder.close();
      if(_chunkedSink != null) _chunkedSink.close();
    }
  
    void add(List<int> chunk) {
      addSlice(chunk, 0, chunk.length, false);
    }
  
    void addSlice(List<int> codeUnits, int startIndex, int endIndex,
                  bool isLast) {
      _decoder.convert(codeUnits, startIndex, endIndex);
      if (isLast) close();
    }
  }
  
  /**
   * Decodes UTF-8 code units.
   *
   * Forwards the decoded strings to the given [StringConversionSink].
   */
  // TODO(floitsch): make this class public?
  class _Utf8ConversionSink extends ByteConversionSink {
  
    final _Utf8Decoder _decoder;
    final StringConversionSink _chunkedSink;
    final StringBuffer _buffer;
    _Utf8ConversionSink(StringConversionSink sink, bool allowMalformed)
        : this._(sink, new StringBuffer(), allowMalformed);
  
    _Utf8ConversionSink._(this._chunkedSink, StringBuffer stringBuffer,
                         bool allowMalformed)
        : _decoder = new _Utf8Decoder(stringBuffer, allowMalformed),
          _buffer = stringBuffer;
  
    void close() {
      _decoder.close();
      if (_buffer.isNotEmpty) {
        String accumulated = _buffer.toString();
        _buffer.clear();
        _chunkedSink.addSlice(accumulated, 0, accumulated.length, true);
      } else  {
        _chunkedSink.close();
      }
    }
  
    void add(List<int> chunk) {
      addSlice(chunk, 0, chunk.length, false);
    }
  
    void addSlice(List<int> chunk, int startIndex, int endIndex, bool isLast) {
      _decoder.convert(chunk, startIndex, endIndex);
      if (_buffer.isNotEmpty) {
        String accumulated = _buffer.toString();
        _chunkedSink.addSlice(accumulated, 0, accumulated.length, isLast);
        _buffer.clear();
        return;
      }
      if (isLast) close();
    }
  }
  