Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(31)

Side by Side Diff: sdk/lib/io/string_stream.dart

Issue 12316036: Merge IO v2 branch to bleeding edge (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Rebased to r18818 Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/io/stream_util.dart ('k') | sdk/lib/io/websocket.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.io; 5 part of dart.io;
6
7 // Interface for decoders decoding binary data into string data. The
8 // decoder keeps track of line breaks during decoding.
9 abstract class _StringDecoder {
10 // Add more binary data to be decoded. The ownership of the buffer
11 // is transfered to the decoder and the caller most not modify it any more.
12 int write(List<int> buffer);
13
14 void done();
15
16 // Returns whether any decoded data is available.
17 bool get isEmpty;
18
19 // Returns the number of available decoded characters.
20 int available();
21
22 // Get the number of line breaks present in the current decoded
23 // data.
24 int get lineBreaks;
25
26 // Get up to [len] characters of string data decoded since the last
27 // call to [decode] or [decodeLine]. Returns null if no decoded data
28 // is available. If [len] is not specified all decoded characters
29 // are returned.
30 String decoded([int len]);
31
32 // Get the string data decoded since the last call to [decode] or
33 // [decodeLine] up to the next line break present. Returns null if
34 // no line break is present. The line break character sequence is
35 // discarded.
36 String get decodedLine;
37
38 // Set the handler that will be called if an error ocurs while decoding.
39 void set onError(Function callback);
40 }
41
42
43 class _StringDecoders {
44 static _StringDecoder decoder(Encoding encoding) {
45 if (encoding == Encoding.UTF_8) {
46 return new _UTF8Decoder();
47 } else if (encoding == Encoding.ISO_8859_1) {
48 return new _Latin1Decoder();
49 } else if (encoding == Encoding.ASCII) {
50 return new _AsciiDecoder();
51 } else if (encoding == Encoding.SYSTEM) {
52 if (Platform.operatingSystem == 'windows') {
53 return new _WindowsCodePageDecoder();
54 }
55 return new _UTF8Decoder();
56 } else {
57 if (encoding is Encoding) {
58 throw new StreamException("Unsupported encoding ${encoding.name}");
59 } else {
60 throw new StreamException("Unsupported encoding ${encoding}");
61 }
62 }
63 }
64 }
65
66
67 class DecoderException implements Exception {
68 const DecoderException([String this.message]);
69 String toString() => "DecoderException: $message";
70 final String message;
71 }
72
73
74 // Utility class for decoding UTF-8 from data delivered as a stream of
75 // bytes.
76 abstract class _StringDecoderBase implements _StringDecoder {
77 _StringDecoderBase()
78 : _bufferList = new _BufferList(),
79 _result = new List<int>(),
80 _lineBreakEnds = new Queue<int>();
81
82 int write(List<int> buffer) {
83 _bufferList.add(buffer);
84 // Decode as many bytes into characters as possible.
85 while (_bufferList.length > 0) {
86 if (!_processNext()) {
87 break;
88 }
89 }
90 return buffer.length;
91 }
92
93 void done() { }
94
95 bool get isEmpty => _result.isEmpty;
96
97 int get lineBreaks => _lineBreaks;
98
99 String decoded([int length]) {
100 if (isEmpty) return null;
101
102 List<int> result;
103 if (length != null && length < available()) {
104 result = _result.getRange(_resultOffset, length);
105 } else if (_resultOffset == 0) {
106 result = _result;
107 } else {
108 result = _result.getRange(_resultOffset, _result.length - _resultOffset);
109 }
110
111 _resultOffset += result.length;
112 while (!_lineBreakEnds.isEmpty &&
113 _lineBreakEnds.first < _charOffset + _resultOffset) {
114 _lineBreakEnds.removeFirst();
115 _lineBreaks--;
116 }
117 if (_result.length == _resultOffset) _resetResult();
118 return new String.fromCharCodes(result);
119 }
120
121 String get decodedLine {
122 if (_lineBreakEnds.isEmpty) return null;
123 int lineEnd = _lineBreakEnds.removeFirst();
124 int terminationSequenceLength = 1;
125 if (_result[lineEnd - _charOffset] == LF &&
126 lineEnd > _charOffset &&
127 _resultOffset < lineEnd &&
128 _result[lineEnd - _charOffset - 1] == CR) {
129 terminationSequenceLength = 2;
130 }
131 var lineLength =
132 lineEnd - _charOffset - _resultOffset - terminationSequenceLength + 1;
133 String result =
134 new String.fromCharCodes(_result.getRange(_resultOffset, lineLength));
135 _lineBreaks--;
136 _resultOffset += (lineLength + terminationSequenceLength);
137 if (_result.length == _resultOffset) _resetResult();
138 return result;
139 }
140
141 // Add another decoded character.
142 void addChar(int charCode) {
143 _result.add(charCode);
144 _charCount++;
145 // Check for line ends (\r, \n and \r\n).
146 if (charCode == LF) {
147 _recordLineBreakEnd(_charCount - 1);
148 } else if (_lastCharCode == CR) {
149 _recordLineBreakEnd(_charCount - 2);
150 }
151 _lastCharCode = charCode;
152 }
153
154 int available() => _result.length - _resultOffset;
155
156 void _recordLineBreakEnd(int charPos) {
157 _lineBreakEnds.add(charPos);
158 _lineBreaks++;
159 }
160
161 void _resetResult() {
162 _charOffset += _result.length;
163 _result = new List<int>();
164 _resultOffset = 0;
165 }
166
167 bool _processNext();
168
169 _BufferList _bufferList;
170 int _resultOffset = 0;
171 List<int> _result;
172 int _lineBreaks = 0; // Number of line breaks in the current list.
173 // The positions of the line breaks are tracked in terms of absolute
174 // character positions from the begining of the decoded data.
175 Queue<int> _lineBreakEnds; // Character position of known line breaks.
176 int _charOffset = 0; // Character number of the first character in the list.
177 int _charCount = 0; // Total number of characters decoded.
178 int _lastCharCode = -1;
179 Function onError;
180
181 final int LF = 10;
182 final int CR = 13;
183 }
184
185
186 // Utility class for decoding UTF-8 from data delivered as a stream of
187 // bytes.
188 class _UTF8Decoder extends _StringDecoderBase {
189 static const kMaxCodePoint = 0x10FFFF;
190 static const kReplacementCodePoint = 0x3f;
191
192 void done() {
193 if (!_bufferList.isEmpty) {
194 _reportError(new DecoderException("Illegal UTF-8"));
195 }
196 }
197
198 bool _reportError(error) {
199 if (onError != null) {
200 onError(error);
201 return false;
202 } else {
203 throw error;
204 }
205 }
206
207 // Process the next UTF-8 encoded character.
208 bool _processNext() {
209 // Peek the next byte to calculate the number of bytes required for
210 // the next character.
211 int value = _bufferList.peek() & 0xFF;
212 if ((value & 0x80) == 0x80) {
213 int additionalBytes;
214 if ((value & 0xe0) == 0xc0) { // 110xxxxx
215 value = value & 0x1F;
216 additionalBytes = 1;
217 } else if ((value & 0xf0) == 0xe0) { // 1110xxxx
218 value = value & 0x0F;
219 additionalBytes = 2;
220 } else if ((value & 0xf8) == 0xf0) { // 11110xxx
221 value = value & 0x07;
222 additionalBytes = 3;
223 } else if ((value & 0xfc) == 0xf8) { // 111110xx
224 value = value & 0x03;
225 additionalBytes = 4;
226 } else if ((value & 0xfe) == 0xfc) { // 1111110x
227 value = value & 0x01;
228 additionalBytes = 5;
229 } else {
230 return _reportError(new DecoderException("Illegal UTF-8"));
231 }
232 // Check if there are enough bytes to decode the character. Otherwise
233 // return false.
234 if (_bufferList.length < additionalBytes + 1) {
235 return false;
236 }
237 // Remove the value peeked from the buffer list.
238 _bufferList.next();
239 for (int i = 0; i < additionalBytes; i++) {
240 int byte = _bufferList.next();
241 if ((byte & 0xc0) != 0x80) {
242 return _reportError(new DecoderException("Illegal UTF-8"));
243 }
244 value = value << 6 | (byte & 0x3F);
245 }
246 } else {
247 // Remove the value peeked from the buffer list.
248 _bufferList.next();
249 }
250 if (value > kMaxCodePoint) {
251 addChar(kReplacementCodePoint);
252 } else {
253 addChar(value);
254 }
255 return true;
256 }
257 }
258
259
260 // Utility class for decoding ascii data delivered as a stream of
261 // bytes.
262 class _AsciiDecoder extends _StringDecoderBase {
263 // Process the next ascii encoded character.
264 bool _processNext() {
265 while (_bufferList.length > 0) {
266 int byte = _bufferList.next();
267 if (byte > 127) {
268 var error = new DecoderException("Illegal ASCII character $byte");
269 if (onError != null) {
270 onError(error);
271 return false;
272 } else {
273 throw error;
274 }
275 }
276 addChar(byte);
277 }
278 return true;
279 }
280 }
281
282
283 // Utility class for decoding Latin-1 data delivered as a stream of
284 // bytes.
285 class _Latin1Decoder extends _StringDecoderBase {
286 // Process the next Latin-1 encoded character.
287 bool _processNext() {
288 while (_bufferList.length > 0) {
289 int byte = _bufferList.next();
290 addChar(byte);
291 }
292 return true;
293 }
294 }
295
296
297 // Utility class for decoding Windows current code page data delivered
298 // as a stream of bytes.
299 class _WindowsCodePageDecoder extends _StringDecoderBase {
300 // Process the next chunk of data.
301 bool _processNext() {
302 List<int> bytes = _bufferList.readBytes(_bufferList.length);
303 for (var charCode in _decodeBytes(bytes).charCodes) {
304 addChar(charCode);
305 }
306 return true;
307 }
308
309 external static String _decodeBytes(List<int> bytes);
310 }
311
312
313 // Interface for encoders encoding string data into binary data.
314 abstract class _StringEncoder {
315 List<int> encodeString(String string);
316 }
317
318
319 // Utility class for encoding a string into UTF-8 byte stream.
320 class _UTF8Encoder implements _StringEncoder {
321 List<int> encodeString(String string) {
322 int size = _encodingSize(string);
323 List<int> result = new Uint8List(size);
324 _encodeString(string, result);
325 return result;
326 }
327
328 static int _encodingSize(String string) => _encodeString(string, null);
329
330 static int _encodeString(String string, List<int> buffer) {
331 List<int> utf8CodeUnits = encodeUtf8(string);
332 if (buffer != null) {
333 for (int i = 0; i < utf8CodeUnits.length; i++) {
334 buffer[i] = utf8CodeUnits[i];
335 }
336 }
337 return utf8CodeUnits.length;
338 }
339 }
340
341
342 // Utility class for encoding a string into a Latin1 byte stream.
343 class _Latin1Encoder implements _StringEncoder {
344 List<int> encodeString(String string) {
345 List<int> result = new Uint8List(string.length);
346 for (int i = 0; i < string.length; i++) {
347 int charCode = string.charCodeAt(i);
348 if (charCode > 255) {
349 throw new EncoderException(
350 "No ISO_8859_1 encoding for code point $charCode");
351 }
352 result[i] = charCode;
353 }
354 return result;
355 }
356 }
357
358
359 // Utility class for encoding a string into an ASCII byte stream.
360 class _AsciiEncoder implements _StringEncoder {
361 List<int> encodeString(String string) {
362 List<int> result = new Uint8List(string.length);
363 for (int i = 0; i < string.length; i++) {
364 int charCode = string.charCodeAt(i);
365 if (charCode > 127) {
366 throw new EncoderException(
367 "No ASCII encoding for code point $charCode");
368 }
369 result[i] = charCode;
370 }
371 return result;
372 }
373 }
374
375
376 // Utility class for encoding a string into a current windows
377 // code page byte list.
378 class _WindowsCodePageEncoder implements _StringEncoder {
379 List<int> encodeString(String string) {
380 return _encodeString(string);
381 }
382
383 external static List<int> _encodeString(String string);
384 }
385
386
387 class _StringEncoders {
388 static _StringEncoder encoder(Encoding encoding) {
389 if (encoding == Encoding.UTF_8) {
390 return new _UTF8Encoder();
391 } else if (encoding == Encoding.ISO_8859_1) {
392 return new _Latin1Encoder();
393 } else if (encoding == Encoding.ASCII) {
394 return new _AsciiEncoder();
395 } else if (encoding == Encoding.SYSTEM) {
396 if (Platform.operatingSystem == 'windows') {
397 return new _WindowsCodePageEncoder();
398 }
399 return new _UTF8Encoder();
400 } else {
401 throw new StreamException("Unsupported encoding ${encoding.name}");
402 }
403 }
404 }
405
406
407 class EncoderException implements Exception {
408 const EncoderException([String this.message]);
409 String toString() => "EncoderException: $message";
410 final String message;
411 }
412
413
414 class _StringInputStream implements StringInputStream {
415 _StringInputStream(InputStream this._input, Encoding this._encoding) {
416 _decoder = _StringDecoders.decoder(encoding);
417 _input.onData = _onData;
418 _input.onClosed = _onClosed;
419 }
420
421 String read([int len]) {
422 String result = _decoder.decoded(len);
423 _checkInstallDataHandler();
424 return result;
425 }
426
427 String readLine() {
428 String decodedLine = _decoder.decodedLine;
429 if (decodedLine == null) {
430 if (_inputClosed) {
431 // Last line might not have a line separator.
432 decodedLine = _decoder.decoded();
433 if (decodedLine != null &&
434 decodedLine[decodedLine.length - 1] == '\r') {
435 decodedLine = decodedLine.substring(0, decodedLine.length - 1);
436 }
437 }
438 }
439 _checkInstallDataHandler();
440 return decodedLine;
441 }
442
443 int available() => _decoder.available();
444
445 Encoding get encoding => _encoding;
446
447 bool get closed => _inputClosed && _decoder.isEmpty;
448
449 void set onData(void callback()) {
450 _clientDataHandler = callback;
451 _clientLineHandler = null;
452 _checkInstallDataHandler();
453 _checkScheduleCallback();
454 }
455
456 void set onLine(void callback()) {
457 _clientLineHandler = callback;
458 _clientDataHandler = null;
459 _checkInstallDataHandler();
460 _checkScheduleCallback();
461 }
462
463 void set onClosed(void callback()) {
464 _clientCloseHandler = callback;
465 }
466
467 void set onError(void callback(e)) {
468 _input.onError = callback;
469 _decoder.onError = (e) {
470 _clientCloseHandler = null;
471 _input.close();
472 callback(e);
473 };
474 }
475
476 void _onData() {
477 _readData();
478 if (!_decoder.isEmpty && _clientDataHandler != null) {
479 _clientDataHandler();
480 }
481 if (_decoder.lineBreaks > 0 && _clientLineHandler != null) {
482 _clientLineHandler();
483 }
484 _checkScheduleCallback();
485 _checkInstallDataHandler();
486 }
487
488 void _onClosed() {
489 _inputClosed = true;
490 _decoder.done();
491 if (_decoder.isEmpty && _clientCloseHandler != null) {
492 _clientCloseHandler();
493 } else {
494 _checkScheduleCallback();
495 }
496 }
497
498 void _readData() {
499 List<int> data = _input.read();
500 if (data != null) {
501 _decoder.write(data);
502 }
503 }
504
505 void _checkInstallDataHandler() {
506 if (_inputClosed ||
507 (_clientDataHandler == null && _clientLineHandler == null)) {
508 _input.onData = null;
509 } else if (_clientDataHandler != null) {
510 if (_decoder.isEmpty) {
511 _input.onData = _onData;
512 } else {
513 _input.onData = null;
514 }
515 } else {
516 assert(_clientLineHandler != null);
517 if (_decoder.lineBreaks == 0) {
518 _input.onData = _onData;
519 } else {
520 _input.onData = null;
521 }
522 }
523 }
524
525 // TODO(sgjesse): Find a better way of scheduling callbacks from
526 // the event loop.
527 void _checkScheduleCallback() {
528 void issueDataCallback() {
529 _scheduledDataCallback = null;
530 if (_clientDataHandler != null) {
531 _clientDataHandler();
532 _checkScheduleCallback();
533 }
534 }
535
536 void issueLineCallback() {
537 _scheduledLineCallback = null;
538 if (_clientLineHandler != null) {
539 _clientLineHandler();
540 _checkScheduleCallback();
541 }
542 }
543
544 void issueCloseCallback() {
545 _scheduledCloseCallback = null;
546 if (!_closed) {
547 if (_clientCloseHandler != null) _clientCloseHandler();
548 _closed = true;
549 }
550 }
551
552 if (!_closed) {
553 // Schedule data callback if string data available.
554 if (_clientDataHandler != null &&
555 !_decoder.isEmpty &&
556 _scheduledDataCallback == null) {
557 if (_scheduledLineCallback != null) {
558 _scheduledLineCallback.cancel();
559 }
560 _scheduledDataCallback = Timer.run(issueDataCallback);
561 }
562
563 // Schedule line callback if a line is available.
564 if (_clientLineHandler != null &&
565 (_decoder.lineBreaks > 0 || (!_decoder.isEmpty && _inputClosed)) &&
566 _scheduledLineCallback == null) {
567 if (_scheduledDataCallback != null) {
568 _scheduledDataCallback.cancel();
569 }
570 _scheduledLineCallback = Timer.run(issueLineCallback);
571 }
572
573 // Schedule close callback if no more data and input is closed.
574 if (_decoder.isEmpty &&
575 _inputClosed &&
576 _scheduledCloseCallback == null) {
577 _scheduledCloseCallback = Timer.run(issueCloseCallback);
578 }
579 }
580 }
581
582 InputStream _input;
583 Encoding _encoding;
584 _StringDecoder _decoder;
585 bool _inputClosed = false; // Is the underlying input stream closed?
586 bool _closed = false; // Is this stream closed.
587 bool _eof = false; // Has all data been read from the decoder?
588 Timer _scheduledDataCallback;
589 Timer _scheduledLineCallback;
590 Timer _scheduledCloseCallback;
591 Function _clientDataHandler;
592 Function _clientLineHandler;
593 Function _clientCloseHandler;
594 }
OLDNEW
« no previous file with comments | « sdk/lib/io/stream_util.dart ('k') | sdk/lib/io/websocket.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698