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