1package com.android.internal.util;
2
3import android.util.Printer;
4
5import java.io.IOException;
6import java.io.OutputStream;
7import java.io.PrintWriter;
8import java.io.UnsupportedEncodingException;
9import java.io.Writer;
10import java.nio.ByteBuffer;
11import java.nio.CharBuffer;
12import java.nio.charset.Charset;
13import java.nio.charset.CharsetEncoder;
14import java.nio.charset.CoderResult;
15import java.nio.charset.CodingErrorAction;
16
17public class FastPrintWriter extends PrintWriter {
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 int mBufferLen;
38    private final char[] mText;
39    private int mPos;
40
41    final private OutputStream mOutputStream;
42    final private boolean mAutoFlush;
43    final private String mSeparator;
44
45    final private Writer mWriter;
46    final private Printer mPrinter;
47
48    private CharsetEncoder mCharset;
49    final private ByteBuffer mBytes;
50
51    private boolean mIoError;
52
53    /**
54     * Constructs a new {@code PrintWriter} with {@code out} as its target
55     * stream. By default, the new print writer does not automatically flush its
56     * contents to the target stream when a newline is encountered.
57     *
58     * @param out
59     *            the target output stream.
60     * @throws NullPointerException
61     *             if {@code out} is {@code null}.
62     */
63    public FastPrintWriter(OutputStream out) {
64        this(out, false, 8192);
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        this(out, autoFlush, 8192);
83    }
84
85    /**
86     * Constructs a new {@code PrintWriter} with {@code out} as its target
87     * stream and a custom buffer size. The parameter {@code autoFlush} determines
88     * if the print writer automatically flushes its contents to the target stream
89     * when a newline is encountered.
90     *
91     * @param out
92     *            the target output stream.
93     * @param autoFlush
94     *            indicates whether contents are flushed upon encountering a
95     *            newline sequence.
96     * @param bufferLen
97     *            specifies the size of the FastPrintWriter's internal buffer; the
98     *            default is 8192.
99     * @throws NullPointerException
100     *             if {@code out} is {@code null}.
101     */
102    public FastPrintWriter(OutputStream out, boolean autoFlush, int bufferLen) {
103        super(sDummyWriter, autoFlush);
104        if (out == null) {
105            throw new NullPointerException("out is null");
106        }
107        mBufferLen = bufferLen;
108        mText = new char[bufferLen];
109        mBytes = ByteBuffer.allocate(mBufferLen);
110        mOutputStream = out;
111        mWriter = null;
112        mPrinter = null;
113        mAutoFlush = autoFlush;
114        mSeparator = System.lineSeparator();
115        initDefaultEncoder();
116    }
117
118    /**
119     * Constructs a new {@code PrintWriter} with {@code wr} as its target
120     * writer. By default, the new print writer does not automatically flush its
121     * contents to the target writer when a newline is encountered.
122     *
123     * <p>NOTE: Unlike PrintWriter, this version will still do buffering inside of
124     * FastPrintWriter before sending data to the Writer.  This means you must call
125     * flush() before retrieving any data from the Writer.</p>
126     *
127     * @param wr
128     *            the target writer.
129     * @throws NullPointerException
130     *             if {@code wr} is {@code null}.
131     */
132    public FastPrintWriter(Writer wr) {
133        this(wr, false, 8192);
134    }
135
136    /**
137     * Constructs a new {@code PrintWriter} with {@code wr} as its target
138     * writer. The parameter {@code autoFlush} determines if the print writer
139     * automatically flushes its contents to the target writer when a newline is
140     * encountered.
141     *
142     * @param wr
143     *            the target writer.
144     * @param autoFlush
145     *            indicates whether to flush contents upon encountering a
146     *            newline sequence.
147     * @throws NullPointerException
148     *             if {@code out} is {@code null}.
149     */
150    public FastPrintWriter(Writer wr, boolean autoFlush) {
151        this(wr, autoFlush, 8192);
152    }
153
154    /**
155     * Constructs a new {@code PrintWriter} with {@code wr} as its target
156     * writer and a custom buffer size. The parameter {@code autoFlush} determines
157     * if the print writer automatically flushes its contents to the target writer
158     * when a newline is encountered.
159     *
160     * @param wr
161     *            the target writer.
162     * @param autoFlush
163     *            indicates whether to flush contents upon encountering a
164     *            newline sequence.
165     * @param bufferLen
166     *            specifies the size of the FastPrintWriter's internal buffer; the
167     *            default is 8192.
168     * @throws NullPointerException
169     *             if {@code wr} is {@code null}.
170     */
171    public FastPrintWriter(Writer wr, boolean autoFlush, int bufferLen) {
172        super(sDummyWriter, autoFlush);
173        if (wr == null) {
174            throw new NullPointerException("wr is null");
175        }
176        mBufferLen = bufferLen;
177        mText = new char[bufferLen];
178        mBytes = null;
179        mOutputStream = null;
180        mWriter = wr;
181        mPrinter = null;
182        mAutoFlush = autoFlush;
183        mSeparator = System.lineSeparator();
184        initDefaultEncoder();
185    }
186
187    /**
188     * Constructs a new {@code PrintWriter} with {@code pr} as its target
189     * printer and the default buffer size.  Because a {@link Printer} is line-base,
190     * autoflush is always enabled.
191     *
192     * @param pr
193     *            the target writer.
194     * @throws NullPointerException
195     *             if {@code pr} is {@code null}.
196     */
197    public FastPrintWriter(Printer pr) {
198        this(pr, 512);
199    }
200
201    /**
202     * Constructs a new {@code PrintWriter} with {@code pr} as its target
203     * printer and a custom buffer size.  Because a {@link Printer} is line-base,
204     * autoflush is always enabled.
205     *
206     * @param pr
207     *            the target writer.
208     * @param bufferLen
209     *            specifies the size of the FastPrintWriter's internal buffer; the
210     *            default is 512.
211     * @throws NullPointerException
212     *             if {@code pr} is {@code null}.
213     */
214    public FastPrintWriter(Printer pr, int bufferLen) {
215        super(sDummyWriter, true);
216        if (pr == null) {
217            throw new NullPointerException("pr is null");
218        }
219        mBufferLen = bufferLen;
220        mText = new char[bufferLen];
221        mBytes = null;
222        mOutputStream = null;
223        mWriter = null;
224        mPrinter = pr;
225        mAutoFlush = true;
226        mSeparator = System.lineSeparator();
227        initDefaultEncoder();
228    }
229
230    private final void initEncoder(String csn) throws UnsupportedEncodingException {
231        try {
232            mCharset = Charset.forName(csn).newEncoder();
233        } catch (Exception e) {
234            throw new UnsupportedEncodingException(csn);
235        }
236        mCharset.onMalformedInput(CodingErrorAction.REPLACE);
237        mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
238    }
239
240    /**
241     * Flushes this writer and returns the value of the error flag.
242     *
243     * @return {@code true} if either an {@code IOException} has been thrown
244     *         previously or if {@code setError()} has been called;
245     *         {@code false} otherwise.
246     * @see #setError()
247     */
248    public boolean checkError() {
249        flush();
250        synchronized (lock) {
251            return mIoError;
252        }
253    }
254
255    /**
256     * Sets the error state of the stream to false.
257     * @since 1.6
258     */
259    protected void clearError() {
260        synchronized (lock) {
261            mIoError = false;
262        }
263    }
264
265    /**
266     * Sets the error flag of this writer to true.
267     */
268    protected void setError() {
269        synchronized (lock) {
270            mIoError = true;
271        }
272    }
273
274    private final void initDefaultEncoder() {
275        mCharset = Charset.defaultCharset().newEncoder();
276        mCharset.onMalformedInput(CodingErrorAction.REPLACE);
277        mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
278    }
279
280    private void appendLocked(char c) throws IOException {
281        int pos = mPos;
282        if (pos >= (mBufferLen-1)) {
283            flushLocked();
284            pos = mPos;
285        }
286        mText[pos] = c;
287        mPos = pos+1;
288    }
289
290    private void appendLocked(String str, int i, final int length) throws IOException {
291        final int BUFFER_LEN = mBufferLen;
292        if (length > BUFFER_LEN) {
293            final int end = i + length;
294            while (i < end) {
295                int next = i + BUFFER_LEN;
296                appendLocked(str, i, next < end ? BUFFER_LEN : (end - i));
297                i = next;
298            }
299            return;
300        }
301        int pos = mPos;
302        if ((pos+length) > BUFFER_LEN) {
303            flushLocked();
304            pos = mPos;
305        }
306        str.getChars(i, i + length, mText, pos);
307        mPos = pos + length;
308    }
309
310    private void appendLocked(char[] buf, int i, final int length) throws IOException {
311        final int BUFFER_LEN = mBufferLen;
312        if (length > BUFFER_LEN) {
313            final int end = i + length;
314            while (i < end) {
315                int next = i + BUFFER_LEN;
316                appendLocked(buf, i, next < end ? BUFFER_LEN : (end - i));
317                i = next;
318            }
319            return;
320        }
321        int pos = mPos;
322        if ((pos+length) > BUFFER_LEN) {
323            flushLocked();
324            pos = mPos;
325        }
326        System.arraycopy(buf, i, mText, pos, length);
327        mPos = pos + length;
328    }
329
330    private void flushBytesLocked() throws IOException {
331        int position;
332        if ((position = mBytes.position()) > 0) {
333            mBytes.flip();
334            mOutputStream.write(mBytes.array(), 0, position);
335            mBytes.clear();
336        }
337    }
338
339    private void flushLocked() throws IOException {
340        //Log.i("PackageManager", "flush mPos=" + mPos);
341        if (mPos > 0) {
342            if (mOutputStream != null) {
343                CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
344                CoderResult result = mCharset.encode(charBuffer, mBytes, true);
345                while (true) {
346                    if (result.isError()) {
347                        throw new IOException(result.toString());
348                    } else if (result.isOverflow()) {
349                        flushBytesLocked();
350                        result = mCharset.encode(charBuffer, mBytes, true);
351                        continue;
352                    }
353                    break;
354                }
355                flushBytesLocked();
356                mOutputStream.flush();
357            } else if (mWriter != null) {
358                mWriter.write(mText, 0, mPos);
359                mWriter.flush();
360            } else {
361                int nonEolOff = 0;
362                final int sepLen = mSeparator.length();
363                final int len = sepLen < mPos ? sepLen : mPos;
364                while (nonEolOff < len && mText[mPos-1-nonEolOff]
365                        == mSeparator.charAt(mSeparator.length()-1-nonEolOff)) {
366                    nonEolOff++;
367                }
368                if (nonEolOff >= mPos) {
369                    mPrinter.println("");
370                } else {
371                    mPrinter.println(new String(mText, 0, mPos-nonEolOff));
372                }
373            }
374            mPos = 0;
375        }
376    }
377
378    /**
379     * Ensures that all pending data is sent out to the target. It also
380     * flushes the target. If an I/O error occurs, this writer's error
381     * state is set to {@code true}.
382     */
383    @Override
384    public void flush() {
385        synchronized (lock) {
386            try {
387                flushLocked();
388                if (mOutputStream != null) {
389                    mOutputStream.flush();
390                } else if (mWriter != null) {
391                    mWriter.flush();
392                }
393            } catch (IOException e) {
394                setError();
395            }
396        }
397    }
398
399    @Override
400    public void close() {
401        synchronized (lock) {
402            try {
403                flushLocked();
404                if (mOutputStream != null) {
405                    mOutputStream.close();
406                } else if (mWriter != null) {
407                    mWriter.close();
408                }
409            } catch (IOException e) {
410                setError();
411            }
412        }
413    }
414
415    /**
416     * Prints the string representation of the specified character array
417     * to the target.
418     *
419     * @param charArray
420     *            the character array to print to the target.
421     * @see #print(String)
422     */
423    public void print(char[] charArray) {
424        synchronized (lock) {
425            try {
426                appendLocked(charArray, 0, charArray.length);
427            } catch (IOException e) {
428            }
429        }
430    }
431
432    /**
433     * Prints the string representation of the specified character to the
434     * target.
435     *
436     * @param ch
437     *            the character to print to the target.
438     * @see #print(String)
439     */
440    public void print(char ch) {
441        synchronized (lock) {
442            try {
443                appendLocked(ch);
444            } catch (IOException e) {
445            }
446        }
447    }
448
449    /**
450     * Prints a string to the target. The string is converted to an array of
451     * bytes using the encoding chosen during the construction of this writer.
452     * The bytes are then written to the target with {@code write(int)}.
453     * <p>
454     * If an I/O error occurs, this writer's error flag is set to {@code true}.
455     *
456     * @param str
457     *            the string to print to the target.
458     * @see #write(int)
459     */
460    public void print(String str) {
461        if (str == null) {
462            str = String.valueOf((Object) null);
463        }
464        synchronized (lock) {
465            try {
466                appendLocked(str, 0, str.length());
467            } catch (IOException e) {
468                setError();
469            }
470        }
471    }
472
473
474    @Override
475    public void print(int inum) {
476        if (inum == 0) {
477            print("0");
478        } else {
479            super.print(inum);
480        }
481    }
482
483    @Override
484    public void print(long lnum) {
485        if (lnum == 0) {
486            print("0");
487        } else {
488            super.print(lnum);
489        }
490    }
491
492    /**
493     * Prints a newline. Flushes this writer if the autoFlush flag is set to {@code true}.
494     */
495    public void println() {
496        synchronized (lock) {
497            try {
498                appendLocked(mSeparator, 0, mSeparator.length());
499                if (mAutoFlush) {
500                    flushLocked();
501                }
502            } catch (IOException e) {
503                setError();
504            }
505        }
506    }
507
508    @Override
509    public void println(int inum) {
510        if (inum == 0) {
511            println("0");
512        } else {
513            super.println(inum);
514        }
515    }
516
517    @Override
518    public void println(long lnum) {
519        if (lnum == 0) {
520            println("0");
521        } else {
522            super.println(lnum);
523        }
524    }
525
526    /**
527     * Prints the string representation of the character array {@code chars} followed by a newline.
528     * Flushes this writer if the autoFlush flag is set to {@code true}.
529     */
530    public void println(char[] chars) {
531        print(chars);
532        println();
533    }
534
535    /**
536     * Prints the string representation of the char {@code c} followed by a newline.
537     * Flushes this writer if the autoFlush flag is set to {@code true}.
538     */
539    public void println(char c) {
540        print(c);
541        println();
542    }
543
544    /**
545     * Writes {@code count} characters from {@code buffer} starting at {@code
546     * offset} to the target.
547     * <p>
548     * This writer's error flag is set to {@code true} if this writer is closed
549     * or an I/O error occurs.
550     *
551     * @param buf
552     *            the buffer to write to the target.
553     * @param offset
554     *            the index of the first character in {@code buffer} to write.
555     * @param count
556     *            the number of characters in {@code buffer} to write.
557     * @throws IndexOutOfBoundsException
558     *             if {@code offset < 0} or {@code count < 0}, or if {@code
559     *             offset + count} is greater than the length of {@code buf}.
560     */
561    @Override
562    public void write(char[] buf, int offset, int count) {
563        synchronized (lock) {
564            try {
565                appendLocked(buf, offset, count);
566            } catch (IOException e) {
567            }
568        }
569    }
570
571    /**
572     * Writes one character to the target. Only the two least significant bytes
573     * of the integer {@code oneChar} are written.
574     * <p>
575     * This writer's error flag is set to {@code true} if this writer is closed
576     * or an I/O error occurs.
577     *
578     * @param oneChar
579     *            the character to write to the target.
580     */
581    @Override
582    public void write(int oneChar) {
583        synchronized (lock) {
584            try {
585                appendLocked((char) oneChar);
586            } catch (IOException e) {
587            }
588        }
589    }
590
591    /**
592     * Writes the characters from the specified string to the target.
593     *
594     * @param str
595     *            the non-null string containing the characters to write.
596     */
597    @Override
598    public void write(String str) {
599        synchronized (lock) {
600            try {
601                appendLocked(str, 0, str.length());
602            } catch (IOException e) {
603            }
604        }
605    }
606
607    /**
608     * Writes {@code count} characters from {@code str} starting at {@code
609     * offset} to the target.
610     *
611     * @param str
612     *            the non-null string containing the characters to write.
613     * @param offset
614     *            the index of the first character in {@code str} to write.
615     * @param count
616     *            the number of characters from {@code str} to write.
617     * @throws IndexOutOfBoundsException
618     *             if {@code offset < 0} or {@code count < 0}, or if {@code
619     *             offset + count} is greater than the length of {@code str}.
620     */
621    @Override
622    public void write(String str, int offset, int count) {
623        synchronized (lock) {
624            try {
625                appendLocked(str, offset, count);
626            } catch (IOException e) {
627            }
628        }
629    }
630
631    /**
632     * Appends a subsequence of the character sequence {@code csq} to the
633     * target. This method works the same way as {@code
634     * PrintWriter.print(csq.subsequence(start, end).toString())}. If {@code
635     * csq} is {@code null}, then the specified subsequence of the string "null"
636     * will be written to the target.
637     *
638     * @param csq
639     *            the character sequence appended to the target.
640     * @param start
641     *            the index of the first char in the character sequence appended
642     *            to the target.
643     * @param end
644     *            the index of the character following the last character of the
645     *            subsequence appended to the target.
646     * @return this writer.
647     * @throws StringIndexOutOfBoundsException
648     *             if {@code start > end}, {@code start < 0}, {@code end < 0} or
649     *             either {@code start} or {@code end} are greater or equal than
650     *             the length of {@code csq}.
651     */
652    @Override
653    public PrintWriter append(CharSequence csq, int start, int end) {
654        if (csq == null) {
655            csq = "null";
656        }
657        String output = csq.subSequence(start, end).toString();
658        write(output, 0, output.length());
659        return this;
660    }
661}
662