FastPrintWriter.java revision e5a9c92377e035b24f50f9f66f4cdfd9cf79c2dd
1package com.android.internal.util;
2
3import java.io.IOException;
4import java.io.OutputStream;
5import java.io.PrintWriter;
6import java.io.UnsupportedEncodingException;
7import java.io.Writer;
8import java.nio.ByteBuffer;
9import java.nio.CharBuffer;
10import java.nio.charset.Charset;
11import java.nio.charset.CharsetEncoder;
12import java.nio.charset.CoderResult;
13import java.nio.charset.CodingErrorAction;
14
15public class FastPrintWriter extends PrintWriter {
16    private static final int BUFFER_LEN = 8192;
17
18    private static Writer sDummyWriter = new Writer() {
19        @Override
20        public void close() throws IOException {
21            UnsupportedOperationException ex
22                    = new UnsupportedOperationException("Shouldn't be here");
23            throw ex;
24        }
25
26        @Override
27        public void flush() throws IOException {
28            close();
29        }
30
31        @Override
32        public void write(char[] buf, int offset, int count) throws IOException {
33            close();
34        }
35    };
36
37    private final char[] mText = new char[BUFFER_LEN];
38    private int mPos;
39
40    final private OutputStream mOutputStream;
41    final private Writer mWriter;
42    final private boolean mAutoFlush;
43    final private String mSeparator;
44    private CharsetEncoder mCharset;
45    final private ByteBuffer mBytes = ByteBuffer.allocate(BUFFER_LEN);
46    private boolean mIoError;
47
48    /**
49     * Constructs a new {@code PrintWriter} with {@code out} as its target
50     * stream. By default, the new print writer does not automatically flush its
51     * contents to the target stream when a newline is encountered.
52     *
53     * @param out
54     *            the target output stream.
55     * @throws NullPointerException
56     *             if {@code out} is {@code null}.
57     */
58    public FastPrintWriter(OutputStream out) {
59        super(sDummyWriter);
60        mOutputStream = out;
61        mWriter = null;
62        mAutoFlush = false;
63        mSeparator = System.lineSeparator();
64        initDefaultEncoder();
65    }
66
67    /**
68     * Constructs a new {@code PrintWriter} with {@code out} as its target
69     * stream. The parameter {@code autoFlush} determines if the print writer
70     * automatically flushes its contents to the target stream when a newline is
71     * encountered.
72     *
73     * @param out
74     *            the target output stream.
75     * @param autoFlush
76     *            indicates whether contents are flushed upon encountering a
77     *            newline sequence.
78     * @throws NullPointerException
79     *             if {@code out} is {@code null}.
80     */
81    public FastPrintWriter(OutputStream out, boolean autoFlush) {
82        super(sDummyWriter, autoFlush);
83        mOutputStream = out;
84        mWriter = null;
85        mAutoFlush = autoFlush;
86        mSeparator = System.lineSeparator();
87        initDefaultEncoder();
88    }
89
90    /**
91     * Constructs a new {@code PrintWriter} with {@code wr} as its target
92     * writer. By default, the new print writer does not automatically flush its
93     * contents to the target writer when a newline is encountered.
94     *
95     * @param wr
96     *            the target writer.
97     * @throws NullPointerException
98     *             if {@code wr} is {@code null}.
99     */
100    public FastPrintWriter(Writer wr) {
101        super(sDummyWriter);
102        mOutputStream = null;
103        mWriter = wr;
104        mAutoFlush = false;
105        mSeparator = System.lineSeparator();
106        initDefaultEncoder();
107    }
108
109    /**
110     * Constructs a new {@code PrintWriter} with {@code out} as its target
111     * writer. The parameter {@code autoFlush} determines if the print writer
112     * automatically flushes its contents to the target writer when a newline is
113     * encountered.
114     *
115     * @param wr
116     *            the target writer.
117     * @param autoFlush
118     *            indicates whether to flush contents upon encountering a
119     *            newline sequence.
120     * @throws NullPointerException
121     *             if {@code out} is {@code null}.
122     */
123    public FastPrintWriter(Writer wr, boolean autoFlush) {
124        super(sDummyWriter, autoFlush);
125        mOutputStream = null;
126        mWriter = wr;
127        mAutoFlush = autoFlush;
128        mSeparator = System.lineSeparator();
129        initDefaultEncoder();
130    }
131
132    private final void initEncoder(String csn) throws UnsupportedEncodingException {
133        try {
134            mCharset = Charset.forName(csn).newEncoder();
135        } catch (Exception e) {
136            throw new UnsupportedEncodingException(csn);
137        }
138        mCharset.onMalformedInput(CodingErrorAction.REPLACE);
139        mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
140    }
141
142    /**
143     * Flushes this writer and returns the value of the error flag.
144     *
145     * @return {@code true} if either an {@code IOException} has been thrown
146     *         previously or if {@code setError()} has been called;
147     *         {@code false} otherwise.
148     * @see #setError()
149     */
150    public boolean checkError() {
151        flush();
152        synchronized (lock) {
153            return mIoError;
154        }
155    }
156
157    /**
158     * Sets the error state of the stream to false.
159     * @since 1.6
160     */
161    protected void clearError() {
162        synchronized (lock) {
163            mIoError = false;
164        }
165    }
166
167    /**
168     * Sets the error flag of this writer to true.
169     */
170    protected void setError() {
171        synchronized (lock) {
172            mIoError = true;
173        }
174    }
175
176    private final void initDefaultEncoder() {
177        mCharset = Charset.defaultCharset().newEncoder();
178        mCharset.onMalformedInput(CodingErrorAction.REPLACE);
179        mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
180    }
181
182    private void appendLocked(char c) throws IOException {
183        int pos = mPos;
184        if (pos >= (BUFFER_LEN-1)) {
185            flushLocked();
186            pos = mPos;
187        }
188        mText[pos] = c;
189        mPos = pos+1;
190    }
191
192    private void appendLocked(String str, int i, final int length) throws IOException {
193        if (length > BUFFER_LEN) {
194            final int end = i + length;
195            while (i < end) {
196                int next = i + BUFFER_LEN;
197                appendLocked(str, i, next < end ? BUFFER_LEN : (end - i));
198                i = next;
199            }
200            return;
201        }
202        int pos = mPos;
203        if ((pos+length) > BUFFER_LEN) {
204            flushLocked();
205            pos = mPos;
206        }
207        str.getChars(i, i + length, mText, pos);
208        mPos = pos + length;
209    }
210
211    private void appendLocked(char[] buf, int i, final int length) throws IOException {
212        if (length > BUFFER_LEN) {
213            final int end = i + length;
214            while (i < end) {
215                int next = i + BUFFER_LEN;
216                appendLocked(buf, i, next < end ? BUFFER_LEN : (end - i));
217                i = next;
218            }
219            return;
220        }
221        int pos = mPos;
222        if ((pos+length) > BUFFER_LEN) {
223            flushLocked();
224            pos = mPos;
225        }
226        System.arraycopy(buf, i, mText, pos, length);
227        mPos = pos + length;
228    }
229
230    private void flushBytesLocked() throws IOException {
231        int position;
232        if ((position = mBytes.position()) > 0) {
233            mBytes.flip();
234            mOutputStream.write(mBytes.array(), 0, position);
235            mBytes.clear();
236        }
237    }
238
239    private void flushLocked() throws IOException {
240        //Log.i("PackageManager", "flush mPos=" + mPos);
241        if (mPos > 0) {
242            if (mOutputStream != null) {
243                CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
244                CoderResult result = mCharset.encode(charBuffer, mBytes, true);
245                while (true) {
246                    if (result.isError()) {
247                        throw new IOException(result.toString());
248                    } else if (result.isOverflow()) {
249                        flushBytesLocked();
250                        result = mCharset.encode(charBuffer, mBytes, true);
251                        continue;
252                    }
253                    break;
254                }
255                flushBytesLocked();
256                mOutputStream.flush();
257            } else {
258                mWriter.write(mText, 0, mPos);
259                mWriter.flush();
260            }
261            mPos = 0;
262        }
263    }
264
265    /**
266     * Ensures that all pending data is sent out to the target. It also
267     * flushes the target. If an I/O error occurs, this writer's error
268     * state is set to {@code true}.
269     */
270    @Override
271    public void flush() {
272        synchronized (lock) {
273            try {
274                flushLocked();
275                if (mOutputStream != null) {
276                    mOutputStream.flush();
277                } else {
278                    mWriter.flush();
279                }
280            } catch (IOException e) {
281                setError();
282            }
283        }
284    }
285
286    @Override
287    public void close() {
288        synchronized (lock) {
289            try {
290                flushLocked();
291                if (mOutputStream != null) {
292                    mOutputStream.close();
293                } else {
294                    mWriter.close();
295                }
296            } catch (IOException e) {
297                setError();
298            }
299        }
300    }
301
302    /**
303     * Prints the string representation of the specified character array
304     * to the target.
305     *
306     * @param charArray
307     *            the character array to print to the target.
308     * @see #print(String)
309     */
310    public void print(char[] charArray) {
311        synchronized (lock) {
312            try {
313                appendLocked(charArray, 0, charArray.length);
314            } catch (IOException e) {
315            }
316        }
317    }
318
319    /**
320     * Prints the string representation of the specified character to the
321     * target.
322     *
323     * @param ch
324     *            the character to print to the target.
325     * @see #print(String)
326     */
327    public void print(char ch) {
328        synchronized (lock) {
329            try {
330                appendLocked(ch);
331            } catch (IOException e) {
332            }
333        }
334    }
335
336    /**
337     * Prints a string to the target. The string is converted to an array of
338     * bytes using the encoding chosen during the construction of this writer.
339     * The bytes are then written to the target with {@code write(int)}.
340     * <p>
341     * If an I/O error occurs, this writer's error flag is set to {@code true}.
342     *
343     * @param str
344     *            the string to print to the target.
345     * @see #write(int)
346     */
347    public void print(String str) {
348        if (str == null) {
349            str = String.valueOf((Object) null);
350        }
351        synchronized (lock) {
352            try {
353                appendLocked(str, 0, str.length());
354            } catch (IOException e) {
355                setError();
356            }
357        }
358    }
359
360
361    @Override
362    public void print(int inum) {
363        if (inum == 0) {
364            print("0");
365        } else {
366            super.print(inum);
367        }
368    }
369
370    @Override
371    public void print(long lnum) {
372        if (lnum == 0) {
373            print("0");
374        } else {
375            super.print(lnum);
376        }
377    }
378
379    /**
380     * Prints a newline. Flushes this writer if the autoFlush flag is set to {@code true}.
381     */
382    public void println() {
383        synchronized (lock) {
384            try {
385                appendLocked(mSeparator, 0, mSeparator.length());
386                if (mAutoFlush) {
387                    flushLocked();
388                }
389            } catch (IOException e) {
390                setError();
391            }
392        }
393    }
394
395    @Override
396    public void println(int inum) {
397        if (inum == 0) {
398            println("0");
399        } else {
400            super.println(inum);
401        }
402    }
403
404    @Override
405    public void println(long lnum) {
406        if (lnum == 0) {
407            println("0");
408        } else {
409            super.print(lnum);
410        }
411    }
412
413    /**
414     * Prints the string representation of the character array {@code chars} followed by a newline.
415     * Flushes this writer if the autoFlush flag is set to {@code true}.
416     */
417    public void println(char[] chars) {
418        print(chars);
419        println();
420    }
421
422    /**
423     * Prints the string representation of the char {@code c} followed by a newline.
424     * Flushes this writer if the autoFlush flag is set to {@code true}.
425     */
426    public void println(char c) {
427        print(c);
428        println();
429    }
430
431    /**
432     * Writes {@code count} characters from {@code buffer} starting at {@code
433     * offset} to the target.
434     * <p>
435     * This writer's error flag is set to {@code true} if this writer is closed
436     * or an I/O error occurs.
437     *
438     * @param buf
439     *            the buffer to write to the target.
440     * @param offset
441     *            the index of the first character in {@code buffer} to write.
442     * @param count
443     *            the number of characters in {@code buffer} to write.
444     * @throws IndexOutOfBoundsException
445     *             if {@code offset < 0} or {@code count < 0}, or if {@code
446     *             offset + count} is greater than the length of {@code buf}.
447     */
448    @Override
449    public void write(char[] buf, int offset, int count) {
450        synchronized (lock) {
451            try {
452                appendLocked(buf, offset, count);
453            } catch (IOException e) {
454            }
455        }
456    }
457
458    /**
459     * Writes one character to the target. Only the two least significant bytes
460     * of the integer {@code oneChar} are written.
461     * <p>
462     * This writer's error flag is set to {@code true} if this writer is closed
463     * or an I/O error occurs.
464     *
465     * @param oneChar
466     *            the character to write to the target.
467     */
468    @Override
469    public void write(int oneChar) {
470        synchronized (lock) {
471            try {
472                appendLocked((char) oneChar);
473            } catch (IOException e) {
474            }
475        }
476    }
477
478    /**
479     * Writes the characters from the specified string to the target.
480     *
481     * @param str
482     *            the non-null string containing the characters to write.
483     */
484    @Override
485    public void write(String str) {
486        synchronized (lock) {
487            try {
488                appendLocked(str, 0, str.length());
489            } catch (IOException e) {
490            }
491        }
492    }
493
494    /**
495     * Writes {@code count} characters from {@code str} starting at {@code
496     * offset} to the target.
497     *
498     * @param str
499     *            the non-null string containing the characters to write.
500     * @param offset
501     *            the index of the first character in {@code str} to write.
502     * @param count
503     *            the number of characters from {@code str} to write.
504     * @throws IndexOutOfBoundsException
505     *             if {@code offset < 0} or {@code count < 0}, or if {@code
506     *             offset + count} is greater than the length of {@code str}.
507     */
508    @Override
509    public void write(String str, int offset, int count) {
510        synchronized (lock) {
511            try {
512                appendLocked(str, offset, count);
513            } catch (IOException e) {
514            }
515        }
516    }
517
518    /**
519     * Appends a subsequence of the character sequence {@code csq} to the
520     * target. This method works the same way as {@code
521     * PrintWriter.print(csq.subsequence(start, end).toString())}. If {@code
522     * csq} is {@code null}, then the specified subsequence of the string "null"
523     * will be written to the target.
524     *
525     * @param csq
526     *            the character sequence appended to the target.
527     * @param start
528     *            the index of the first char in the character sequence appended
529     *            to the target.
530     * @param end
531     *            the index of the character following the last character of the
532     *            subsequence appended to the target.
533     * @return this writer.
534     * @throws StringIndexOutOfBoundsException
535     *             if {@code start > end}, {@code start < 0}, {@code end < 0} or
536     *             either {@code start} or {@code end} are greater or equal than
537     *             the length of {@code csq}.
538     */
539    @Override
540    public PrintWriter append(CharSequence csq, int start, int end) {
541        if (csq == null) {
542            csq = "null";
543        }
544        String output = csq.subSequence(start, end).toString();
545        write(output, 0, output.length());
546        return this;
547    }
548}
549