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;
  
  /**
   * An instance of the default implementation of the [AsciiCodec].
   *
   * This instance provides a convenient access to the most common ASCII
   * use cases.
   *
   * Examples:
   *
   *     var encoded = ASCII.encode("This is ASCII!");
   *     var decoded = ASCII.decode([0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73,
   *                                 0x20, 0x41, 0x53, 0x43, 0x49, 0x49, 0x21]);
   */
  const AsciiCodec ASCII = const AsciiCodec();
  
  const int _ASCII_MASK = 0x7F;
  
  /**
   * An [AsciiCodec] allows encoding strings as ASCII bytes
   * and decoding ASCII bytes to strings.
   */
  class AsciiCodec extends Encoding {
    final bool _allowInvalid;
    /**
     * Instantiates a new [AsciiCodec].
     *
     * If [allowInvalid] is true, the [decode] method and the converter
     * returned by [decoder] will default to allowing invalid values.
     * If allowing invalid values, the values will be decoded into the Unicode
     * Replacement character (U+FFFD). If not, an exception will be thrown.
     * Calls to the [decode] method can choose to override this default.
     *
     * Encoders will not accept invalid (non Latin-1) characters.
     */
    const AsciiCodec({bool allowInvalid: false}) : _allowInvalid = allowInvalid;
  
    String get name => "us-ascii";
  
    /**
     * Decodes the ASCII [bytes] (a list of unsigned 7-bit integers) to the
     * corresponding string.
     *
     * If [bytes] contains values that are not in the range 0 .. 127, the decoder
     * will eventually throw a [FormatException].
     *
     * If [allowInvalid] is not provided, it defaults to the value used to create
     * this [AsciiCodec].
     */
    String decode(List<int> bytes, { bool allowInvalid }) {
      if (allowInvalid == null) allowInvalid = _allowInvalid;
      if (allowInvalid) {
        return const AsciiDecoder(allowInvalid: true).convert(bytes);
      } else {
        return const AsciiDecoder(allowInvalid: false).convert(bytes);
      }
    }
  
    Converter<String, List<int>> get encoder => const AsciiEncoder();
  
    Converter<List<int>, String> get decoder =>
        _allowInvalid ? const AsciiDecoder(allowInvalid: true)
                      : const AsciiDecoder(allowInvalid: false);
  }
  
  // Superclass for [AsciiEncoder] and [Latin1Encoder].
  // Generalizes common operations that only differ by a mask;
  class _UnicodeSubsetEncoder extends Converter<String, List<int>> {
    final int _subsetMask;
  
    const _UnicodeSubsetEncoder(this._subsetMask);
  
    List<int> convert(String string) {
      // TODO(11971): Use Uint8List when possible.
      List result = new List<int>(string.length);
      for (int i = 0; i < string.length; i++) {
        var codeUnit = string.codeUnitAt(i);
        if ((codeUnit & ~_subsetMask) != 0) {
          throw new ArgumentError("String contains invalid characters.");
        }
        result[i] = codeUnit;
      }
      return result;
    }
  
    /**
     * Starts a chunked conversion.
     *
     * The converter works more efficiently if the given [sink] is a
     * [ByteConversionSink].
     */
    StringConversionSink startChunkedConversion(
        ChunkedConversionSink<List<int>> sink) {
      if (sink is! ByteConversionSink) {
        sink = new ByteConversionSink.from(sink);
      }
      return new _UnicodeSubsetEncoderSink(_subsetMask, sink);
    }
  
    // Override the base-class' bind, to provide a better type.
    Stream<List<int>> bind(Stream<String> stream) => super.bind(stream);
  }
  
  /**
   * This class converts strings of only ASCII characters to bytes.
   */
  class AsciiEncoder extends _UnicodeSubsetEncoder {
    const AsciiEncoder() : super(_ASCII_MASK);
  }
  
  /**
   * This class encodes chunked strings to bytes (unsigned 8-bit
   * integers).
   */
  class _UnicodeSubsetEncoderSink extends StringConversionSinkBase {
    final ByteConversionSink _sink;
    final int _subsetMask;
  
    _UnicodeSubsetEncoderSink(this._subsetMask, this._sink);
  
    void close() {
      _sink.close();
    }
  
    void addSlice(String source, int start, int end, bool isLast) {
      if (start < 0 || start > source.length) {
        throw new RangeError.range(start, 0, source.length);
      }
      if (end < start || end > source.length) {
        throw new RangeError.range(end, start, source.length);
      }
      for (int i = start; i < end; i++) {
        int codeUnit = source.codeUnitAt(i);
        if ((codeUnit & ~_subsetMask) != 0) {
          throw new ArgumentError(
              "Source contains invalid character with code point: $codeUnit.");
        }
      }
      _sink.add(source.codeUnits.sublist(start, end));
      if (isLast) {
        close();
      }
    }
  }
  
  /**
   * This class converts Latin-1 bytes (lists of unsigned 8-bit integers)
   * to a string.
   */
  abstract class _UnicodeSubsetDecoder extends Converter<List<int>, String> {
    final bool _allowInvalid;
    final int _subsetMask;
  
    /**
     * Instantiates a new decoder.
     *
     * The [_allowInvalid] argument defines how [convert] deals
     * with invalid bytes.
     *
     * The [_subsetMask] argument is a bit mask used to define the subset
     * of Unicode being decoded. Use [_LATIN1_MASK] for Latin-1 (8-bit) or
     * [_ASCII_MASK] for ASCII (7-bit).
     *
     * If [_allowInvalid] is `true`, [convert] replaces invalid bytes with the
     * Unicode Replacement character `U+FFFD` (�).
     * Otherwise it throws a [FormatException].
     */
    const _UnicodeSubsetDecoder(this._allowInvalid, this._subsetMask);
  
    /**
     * Converts the [bytes] (a list of unsigned 7- or 8-bit integers) to the
     * corresponding string.
     */
    String convert(List<int> bytes) {
      for (int i = 0; i < bytes.length; i++) {
        int byte = bytes[i];
        if ((byte & ~_subsetMask) != 0) {
          if (!_allowInvalid) {
            throw new FormatException("Invalid value in input: $byte");
          }
          return _convertInvalid(bytes);
        }
      }
      return new String.fromCharCodes(bytes);
    }
  
    String _convertInvalid(List<int> bytes) {
      StringBuffer buffer = new StringBuffer();
      for (int i = 0; i < bytes.length; i++) {
        int value = bytes[i];
        if ((value & ~_subsetMask) != 0) value = 0xFFFD;
        buffer.writeCharCode(value);
      }
      return buffer.toString();
    }
  
    /**
     * Starts a chunked conversion.
     *
     * The converter works more efficiently if the given [sink] is a
     * [StringConversionSink].
     */
    ByteConversionSink startChunkedConversion(
        ChunkedConversionSink<String> sink) {
      StringConversionSink stringSink;
      if (sink is StringConversionSink) {
        stringSink = sink;
      } else {
        stringSink = new StringConversionSink.from(sink);
      }
      // TODO(lrn): Use stringSink.asUtf16Sink() if it becomes available.
      return new _Latin1DecoderSink(_allowInvalid, stringSink);
    }
  
    // Override the base-class's bind, to provide a better type.
    Stream<String> bind(Stream<List<int>> stream) => super.bind(stream);
  }
  
  class AsciiDecoder extends _UnicodeSubsetDecoder {
    const AsciiDecoder({bool allowInvalid: false})
        : super(allowInvalid, _ASCII_MASK);
  
    /**
     * Starts a chunked conversion.
     *
     * The converter works more efficiently if the given [sink] is a
     * [StringConversionSink].
     */
    ByteConversionSink startChunkedConversion(
        ChunkedConversionSink<String> sink) {
      StringConversionSink stringSink;
      if (sink is StringConversionSink) {
        stringSink = sink;
      } else {
        stringSink = new StringConversionSink.from(sink);
      }
      // TODO(lrn): Use asUtf16Sink when it becomes available. It
      // works just as well, is likely to have less decoding overhead,
      // and make adding U+FFFD easier.
      // At that time, merge this with _Latin1DecoderSink;
      return new _AsciiDecoderSink(_allowInvalid, stringSink.asUtf8Sink(false));
    }
  }
  
  class _AsciiDecoderSink extends ByteConversionSinkBase {
    final bool _allowInvalid;
    ByteConversionSink _utf8Sink;
    _AsciiDecoderSink(this._allowInvalid, this._utf8Sink);
  
    void close() {
      _utf8Sink.close();
    }
  
    void add(List<int> source) {
      addSlice(source, 0, source.length, false);
    }
  
    void addSlice(List<int> source, int start, int end, bool isLast) {
      if (start < 0 || start > source.length) {
        throw new RangeError.range(start, 0, source.length);
      }
      if (end < start || end > source.length) {
        throw new RangeError.range(end, start, source.length);
      }
      for (int i = start; i < end; i++) {
        if ((source[i] & ~_ASCII_MASK) != 0) {
          if (_allowInvalid) {
            if (i > start) _utf8Sink.addSlice(source, start, i, false);
            // Add UTF-8 encoding of U+FFFD.
            _utf8Sink.add(const<int>[0xEF, 0xBF, 0xBD]);
            start = i + 1;
          } else {
            throw new FormatException("Source contains non-ASCII bytes.");
          }
        }
      }
      if (start < end) {
        _utf8Sink.addSlice(source, start, end, isLast);
      } else if (isLast) {
        close();
      }
    }
  }
  