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