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