| // 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. |
| |
| // part of "core_patch.dart"; |
| |
| @patch |
| class StringBuffer { |
| static const int _BUFFER_SIZE = 64; |
| static const int _PARTS_TO_COMPACT = 128; |
| static const int _PARTS_TO_COMPACT_SIZE_LIMIT = _PARTS_TO_COMPACT * 8; |
| |
| /** |
| * When strings are written to the string buffer, we add them to a |
| * list of string parts. |
| */ |
| List<String> _parts; |
| |
| /** |
| * Total number of code units in the string parts. Does not include |
| * the code units added to the buffer. |
| */ |
| int _partsCodeUnits = 0; |
| |
| /** |
| * To preserve memory, we sometimes compact the parts. This combines |
| * several smaller parts into a single larger part to cut down on the |
| * cost that comes from the per-object memory overhead. We keep track |
| * of the last index where we ended our compaction and the number of |
| * code units added since the last compaction. |
| */ |
| int _partsCompactionIndex = 0; |
| int _partsCodeUnitsSinceCompaction = 0; |
| |
| /** |
| * The buffer is used to build up a string from code units. It is |
| * used when writing short strings or individual char codes to the |
| * buffer. The buffer is allocated on demand. |
| */ |
| Uint16List _buffer; |
| int _bufferPosition = 0; |
| |
| /** |
| * Collects the approximate maximal magnitude of the code units added |
| * to the buffer. |
| * |
| * The value of each added code unit is or'ed with this variable, so the |
| * most significant bit set in any code unit is also set in this value. |
| * If below 256, the string in the buffer is a Latin-1 string. |
| */ |
| int _bufferCodeUnitMagnitude = 0; |
| |
| /// Creates the string buffer with an initial content. |
| @patch |
| StringBuffer([Object content = ""]) { |
| write(content); |
| } |
| |
| @patch |
| int get length => _partsCodeUnits + _bufferPosition; |
| |
| @patch |
| void write(Object obj) { |
| String str = '$obj'; |
| if (str.isEmpty) return; |
| _consumeBuffer(); |
| _addPart(str); |
| } |
| |
| @patch |
| void writeCharCode(int charCode) { |
| if (charCode <= 0xFFFF) { |
| if (charCode < 0) { |
| throw new RangeError.range(charCode, 0, 0x10FFFF); |
| } |
| _ensureCapacity(1); |
| _buffer[_bufferPosition++] = charCode; |
| _bufferCodeUnitMagnitude |= charCode; |
| } else { |
| if (charCode > 0x10FFFF) { |
| throw new RangeError.range(charCode, 0, 0x10FFFF); |
| } |
| _ensureCapacity(2); |
| int bits = charCode - 0x10000; |
| _buffer[_bufferPosition++] = 0xD800 | (bits >> 10); |
| _buffer[_bufferPosition++] = 0xDC00 | (bits & 0x3FF); |
| _bufferCodeUnitMagnitude |= 0xFFFF; |
| } |
| } |
| |
| @patch |
| 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); |
| } |
| } |
| } |
| |
| @patch |
| void writeln([Object obj = ""]) { |
| write(obj); |
| write("\n"); |
| } |
| |
| /** Makes the buffer empty. */ |
| @patch |
| void clear() { |
| _parts = null; |
| _partsCodeUnits = _bufferPosition = _bufferCodeUnitMagnitude = 0; |
| } |
| |
| /** Returns the contents of buffer as a string. */ |
| @patch |
| String toString() { |
| _consumeBuffer(); |
| return (_partsCodeUnits == 0) |
| ? "" |
| : _StringBase._concatRange(_parts, 0, _parts.length); |
| } |
| |
| /** Ensures that the buffer has enough capacity to add n code units. */ |
| void _ensureCapacity(int n) { |
| if (_buffer == null) { |
| _buffer = new Uint16List(_BUFFER_SIZE); |
| } else if (_bufferPosition + n > _buffer.length) { |
| _consumeBuffer(); |
| } |
| } |
| |
| /** |
| * Consumes the content of the buffer by turning it into a string |
| * and adding it as a part. After calling this the buffer position |
| * will be reset to zero. |
| */ |
| void _consumeBuffer() { |
| if (_bufferPosition == 0) return; |
| bool isLatin1 = _bufferCodeUnitMagnitude <= 0xFF; |
| String str = _create(_buffer, _bufferPosition, isLatin1); |
| _bufferPosition = _bufferCodeUnitMagnitude = 0; |
| _addPart(str); |
| } |
| |
| /** |
| * Adds a new part to this string buffer and keeps track of how |
| * many code units are contained in the parts. |
| */ |
| void _addPart(String str) { |
| int length = str.length; |
| _partsCodeUnits += length; |
| _partsCodeUnitsSinceCompaction += length; |
| |
| if (_parts == null) { |
| // Empirically this is a good capacity to minimize total bytes allocated. |
| _parts = new _GrowableList.withCapacity(10)..add(str); |
| } else { |
| _parts.add(str); |
| int partsSinceCompaction = _parts.length - _partsCompactionIndex; |
| if (partsSinceCompaction == _PARTS_TO_COMPACT) { |
| _compact(); |
| } |
| } |
| } |
| |
| /** |
| * Compacts the last N parts if their average size allows us to save a |
| * lot of memory by turning them all into a single part. |
| */ |
| void _compact() { |
| if (_partsCodeUnitsSinceCompaction < _PARTS_TO_COMPACT_SIZE_LIMIT) { |
| String compacted = _StringBase._concatRange( |
| _parts, |
| _partsCompactionIndex, // Start |
| _partsCompactionIndex + _PARTS_TO_COMPACT // End |
| ); |
| _parts.length = _parts.length - _PARTS_TO_COMPACT; |
| _parts.add(compacted); |
| } |
| _partsCodeUnitsSinceCompaction = 0; |
| _partsCompactionIndex = _parts.length; |
| } |
| |
| /** |
| * Create a [String] from the UFT-16 code units in buffer. |
| */ |
| static String _create(Uint16List buffer, int length, bool isLatin1) |
| native "StringBuffer_createStringFromUint16Array"; |
| } |