OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of dart.convert; |
| 6 |
| 7 /** |
| 8 * This class provides an interface for converters to |
| 9 * efficiently transmit String data. |
| 10 * |
| 11 * Instead of limiting the interface to one non-chunked String it accepts |
| 12 * partial strings or can be transformed into a byte sink that |
| 13 * accepts UTF-8 code units. |
| 14 */ |
| 15 abstract class StringConversionSink |
| 16 extends ChunkedConversionSink<String> { |
| 17 StringConversionSink(); |
| 18 factory StringConversionSink.withCallback(void callback(String accumulated)) |
| 19 = _StringCallbackSink; |
| 20 factory StringConversionSink.from(ChunkedConversionSink<String> sink) |
| 21 = _StringAdapterSink; |
| 22 |
| 23 /** |
| 24 * Creates a new instance wrapping the given [sink]. |
| 25 * |
| 26 * Every string that is added to the returned instance is forwarded to |
| 27 * the [sink]. The instance is allowed to buffer and is not required to |
| 28 * forward immediately. |
| 29 */ |
| 30 factory StringConversionSink.fromStringSink(StringSink sink) = |
| 31 _StringSinkConversionSink; |
| 32 |
| 33 /** |
| 34 * Adds the next [chunk] to `this`. |
| 35 * |
| 36 * Adds the substring defined by [start] and [end]-exclusive to `this`. |
| 37 * |
| 38 * If [isLast] is `true` closes `this`. |
| 39 */ |
| 40 void addSlice(String chunk, int start, int end, bool isLast); |
| 41 |
| 42 /** |
| 43 * Returns `this` as a sink that accepts UTF-8 input. |
| 44 * |
| 45 * If used, this method must be the first and only call to `this`. It |
| 46 * invalidates `this`. All further operations must be performed on the result. |
| 47 */ |
| 48 ByteConversionSink asUtf8Sink(bool allowMalformed); |
| 49 // - asRuneSink |
| 50 // - asCodeUnitsSink |
| 51 |
| 52 /** |
| 53 * Returns `this` as a [ClosableStringSink]. |
| 54 * |
| 55 * If used, this method must be the first and only call to `this`. It |
| 56 * invalidates `this`. All further operations must be performed on the result. |
| 57 */ |
| 58 ClosableStringSink asStringSink(); |
| 59 } |
| 60 |
| 61 /** |
| 62 * A [ClosableStringSink] extends the [StringSink] interface by adding a |
| 63 * `close` method. |
| 64 */ |
| 65 abstract class ClosableStringSink extends StringSink { |
| 66 /** |
| 67 * Creates a new instance combining a [StringSink] [sink] and a callback |
| 68 * [onClose] which is invoked when the returned instance is closed. |
| 69 */ |
| 70 factory ClosableStringSink.fromStringSink(StringSink sink, void onClose()) |
| 71 = _ClosableStringSink; |
| 72 |
| 73 /** |
| 74 * Closes `this` and flushes any outstanding data. |
| 75 */ |
| 76 void close(); |
| 77 } |
| 78 |
| 79 typedef void _StringSinkCloseCallback(); |
| 80 |
| 81 /** |
| 82 * This class wraps an existing [StringSink] and invokes a |
| 83 * closure when [close] is invoked. |
| 84 */ |
| 85 class _ClosableStringSink implements ClosableStringSink { |
| 86 final _StringSinkCloseCallback _callback; |
| 87 final StringSink _sink; |
| 88 |
| 89 _ClosableStringSink(this._sink, this._callback); |
| 90 |
| 91 void close() => _callback(); |
| 92 |
| 93 void writeCharCode(int charCode) => _sink.writeCharCode(charCode); |
| 94 void write(Object o) => _sink.write(o); |
| 95 void writeln([Object o]) => _sink.writeln(o); |
| 96 void writeAll(Iterable objects, [String separator]) |
| 97 => _sink.writeAll(objects, separator); |
| 98 } |
| 99 |
| 100 /** |
| 101 * This class wraps an existing [StringConversionSink] and exposes a |
| 102 * [ClosableStringSink] interface. The wrapped sink only needs to implement |
| 103 * `add` and `close`. |
| 104 */ |
| 105 // TODO(floitsch): make this class public? |
| 106 class _StringConversionSinkAsStringSinkAdapter implements ClosableStringSink { |
| 107 static const _MIN_STRING_SIZE = 16; |
| 108 |
| 109 StringBuffer _buffer; |
| 110 StringConversionSink _chunkedSink; |
| 111 |
| 112 _StringConversionSinkAsStringSinkAdapter(this._chunkedSink) |
| 113 : _buffer = new StringBuffer(); |
| 114 |
| 115 void close() { |
| 116 if (_buffer.isNotEmpty) _flush(); |
| 117 _chunkedSink.close(); |
| 118 } |
| 119 |
| 120 void writeCharCode(int charCode) { |
| 121 _buffer.writeCharCode(charCode); |
| 122 if (_buffer.length > _MIN_STRING_SIZE) _flush(); |
| 123 } |
| 124 |
| 125 void write(Object o) { |
| 126 if (_buffer.isNotEmpty) _flush(); |
| 127 String str = o.toString(); |
| 128 _chunkedSink.add(o.toString()); |
| 129 } |
| 130 |
| 131 void writeln([Object o]) { |
| 132 _buffer.writeln(o); |
| 133 if (_buffer.length > _MIN_STRING_SIZE) _flush(); |
| 134 } |
| 135 |
| 136 void writeAll(Iterable objects, [String separator]) { |
| 137 if (_buffer.isNotEmpty) _flush(); |
| 138 Iterator iterator = objects.iterator; |
| 139 if (!iterator.moveNext()) return; |
| 140 if (separator.isEmpty) { |
| 141 do { |
| 142 _chunkedSink.add(iterator.current.toString()); |
| 143 } while (iterator.moveNext()); |
| 144 } else { |
| 145 _chunkedSink.add(iterator.current.toString()); |
| 146 while (iterator.moveNext()) { |
| 147 write(separator); |
| 148 _chunkedSink.add(iterator.current.toString()); |
| 149 } |
| 150 } |
| 151 } |
| 152 |
| 153 void _flush() { |
| 154 String accumulated = _buffer.toString(); |
| 155 _buffer.clear(); |
| 156 _chunkedSink.add(accumulated); |
| 157 } |
| 158 } |
| 159 |
| 160 /** |
| 161 * This class provides a base-class for converters that need to accept String |
| 162 * inputs. |
| 163 */ |
| 164 abstract class StringConversionSinkBase extends StringConversionSinkMixin { |
| 165 } |
| 166 |
| 167 /** |
| 168 * This class provides a mixin for converters that need to accept String |
| 169 * inputs. |
| 170 */ |
| 171 abstract class StringConversionSinkMixin implements StringConversionSink { |
| 172 |
| 173 void addSlice(String str, int start, int end, bool isLast); |
| 174 void close(); |
| 175 |
| 176 void add(String str) => addSlice(str, 0, str.length, false); |
| 177 |
| 178 ByteConversionSink asUtf8Sink(bool allowMalformed) { |
| 179 return new _Utf8ConversionSink(this, allowMalformed); |
| 180 } |
| 181 |
| 182 ClosableStringSink asStringSink() { |
| 183 return new _StringConversionSinkAsStringSinkAdapter(this); |
| 184 } |
| 185 } |
| 186 |
| 187 /** |
| 188 * This class is a [StringConversionSink] that wraps a [StringSink]. |
| 189 */ |
| 190 class _StringSinkConversionSink extends StringConversionSinkBase { |
| 191 StringSink _stringSink; |
| 192 _StringSinkConversionSink(StringSink this._stringSink); |
| 193 |
| 194 void close() {} |
| 195 void addSlice(String str, int start, int end, bool isLast) { |
| 196 if (start != 0 || end != str.length) { |
| 197 for (int i = start; i < end; i++) { |
| 198 _stringSink.writeCharCode(str.codeUnitAt(i)); |
| 199 } |
| 200 } else { |
| 201 _stringSink.write(str); |
| 202 } |
| 203 if (isLast) close(); |
| 204 } |
| 205 |
| 206 void add(String str) => _stringSink.write(str); |
| 207 |
| 208 ByteConversionSink asUtf8Sink(bool allowMalformed) { |
| 209 return new _Utf8StringSinkAdapter(this, _stringSink, allowMalformed); |
| 210 } |
| 211 |
| 212 ClosableStringSink asStringSink() { |
| 213 return new ClosableStringSink.fromStringSink(_stringSink, this.close); |
| 214 } |
| 215 } |
| 216 |
| 217 /** |
| 218 * This class accumulates all chunks into one string |
| 219 * and invokes a callback when the sink is closed. |
| 220 * |
| 221 * This class can be used to terminate a chunked conversion. |
| 222 */ |
| 223 class _StringCallbackSink extends _StringSinkConversionSink { |
| 224 final _ChunkedConversionCallback<String> _callback; |
| 225 _StringCallbackSink(this._callback) : super(new StringBuffer()); |
| 226 |
| 227 void close() { |
| 228 StringBuffer buffer = _stringSink; |
| 229 String accumulated = buffer.toString(); |
| 230 buffer.clear(); |
| 231 _callback(accumulated); |
| 232 } |
| 233 |
| 234 ByteConversionSink asUtf8Sink(bool allowMalformed) { |
| 235 return new _Utf8StringSinkAdapter( |
| 236 this, _stringSink, allowMalformed); |
| 237 } |
| 238 } |
| 239 |
| 240 /** |
| 241 * This class adapts a simple [ChunkedConversionSink] to a |
| 242 * [StringConversionSink]. |
| 243 * |
| 244 * All additional methods of the [StringConversionSink] (compared to the |
| 245 * ChunkedConversionSink) are redirected to the `add` method. |
| 246 */ |
| 247 class _StringAdapterSink extends StringConversionSinkBase { |
| 248 final ChunkedConversionSink<String> _sink; |
| 249 |
| 250 _StringAdapterSink(this._sink); |
| 251 |
| 252 void add(String str) => _sink.add(str); |
| 253 |
| 254 void addSlice(String str, int start, int end, bool isLast) { |
| 255 if (start == 0 && end == str.length) { |
| 256 add(str); |
| 257 } else { |
| 258 add(str.substring(start, end)); |
| 259 } |
| 260 if (isLast) close(); |
| 261 } |
| 262 |
| 263 void close() => _sink.close(); |
| 264 } |
| 265 |
| 266 |
| 267 /** |
| 268 * Decodes UTF-8 code units and stores them in a [StringSink]. |
| 269 */ |
| 270 class _Utf8StringSinkAdapter extends ByteConversionSink { |
| 271 final _Utf8Decoder _decoder; |
| 272 final ChunkedConversionSink _chunkedSink; |
| 273 |
| 274 _Utf8StringSinkAdapter(ChunkedConversionSink chunkedSink, |
| 275 StringSink sink, bool allowMalformed) |
| 276 : _chunkedSink = chunkedSink, |
| 277 _decoder = new _Utf8Decoder(sink, allowMalformed); |
| 278 |
| 279 void close() { |
| 280 _decoder.close(); |
| 281 if(_chunkedSink != null) _chunkedSink.close(); |
| 282 } |
| 283 |
| 284 void add(List<int> chunk) { |
| 285 addSlice(chunk, 0, chunk.length, false); |
| 286 } |
| 287 |
| 288 void addSlice(List<int> codeUnits, int startIndex, int endIndex, |
| 289 bool isLast) { |
| 290 _decoder.convert(codeUnits, startIndex, endIndex); |
| 291 if (isLast) close(); |
| 292 } |
| 293 } |
| 294 |
| 295 /** |
| 296 * Decodes UTF-8 code units. |
| 297 * |
| 298 * Forwards the decoded strings to the given [StringConversionSink]. |
| 299 */ |
| 300 // TODO(floitsch): make this class public? |
| 301 class _Utf8ConversionSink extends ByteConversionSink { |
| 302 static const _MIN_STRING_SIZE = 16; |
| 303 |
| 304 final _Utf8Decoder _decoder; |
| 305 final StringConversionSink _chunkedSink; |
| 306 final StringBuffer _buffer; |
| 307 _Utf8ConversionSink(StringConversionSink sink, bool allowMalformed) |
| 308 : this._(sink, new StringBuffer(), allowMalformed); |
| 309 |
| 310 _Utf8ConversionSink._(this._chunkedSink, StringBuffer stringBuffer, |
| 311 bool allowMalformed) |
| 312 : _decoder = new _Utf8Decoder(stringBuffer, allowMalformed), |
| 313 _buffer = stringBuffer; |
| 314 |
| 315 void close() { |
| 316 _decoder.close(); |
| 317 if (_buffer.isNotEmpty) { |
| 318 String accumulated = _buffer.toString(); |
| 319 _buffer.clear(); |
| 320 _chunkedSink.addSlice(accumulated, 0, accumulated.length, true); |
| 321 } else { |
| 322 _chunkedSink.close(); |
| 323 } |
| 324 } |
| 325 |
| 326 void add(List<int> chunk) { |
| 327 addSlice(chunk, 0, chunk.length, false); |
| 328 } |
| 329 |
| 330 void addSlice(List<int> chunk, int startIndex, int endIndex, |
| 331 bool isLast) { |
| 332 _decoder.convert(chunk, startIndex, endIndex); |
| 333 if (_buffer.length > _MIN_STRING_SIZE) { |
| 334 String accumulated = _buffer.toString(); |
| 335 _chunkedSink.addSlice(accumulated, 0, accumulated.length, isLast); |
| 336 _buffer.clear(); |
| 337 return; |
| 338 } |
| 339 if (isLast) close(); |
| 340 } |
| 341 } |
OLD | NEW |