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 
17 package com.android.internal.util;
18 
19 import android.util.Log;
20 import android.util.Printer;
21 
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.PrintWriter;
25 import java.io.UnsupportedEncodingException;
26 import java.io.Writer;
27 import java.nio.ByteBuffer;
28 import java.nio.CharBuffer;
29 import java.nio.charset.Charset;
30 import java.nio.charset.CharsetEncoder;
31 import java.nio.charset.CoderResult;
32 import java.nio.charset.CodingErrorAction;
33 
34 public class FastPrintWriter extends PrintWriter {
35     private static class DummyWriter extends Writer {
36         @Override
close()37         public void close() throws IOException {
38             UnsupportedOperationException ex
39                     = new UnsupportedOperationException("Shouldn't be here");
40             throw ex;
41         }
42 
43         @Override
flush()44         public void flush() throws IOException {
45             close();
46         }
47 
48         @Override
write(char[] buf, int offset, int count)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      */
FastPrintWriter(OutputStream out)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      */
FastPrintWriter(OutputStream out, boolean autoFlush)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      */
FastPrintWriter(OutputStream out, boolean autoFlush, int bufferLen)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      */
FastPrintWriter(Writer wr)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      */
FastPrintWriter(Writer wr, boolean autoFlush)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      */
FastPrintWriter(Writer wr, boolean autoFlush, int bufferLen)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      */
FastPrintWriter(Printer pr)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      */
FastPrintWriter(Printer pr, int bufferLen)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 
initEncoder(String csn)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      */
checkError()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      */
clearError()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      */
setError()285     protected void setError() {
286         synchronized (lock) {
287             mIoError = true;
288         }
289     }
290 
initDefaultEncoder()291     private final void initDefaultEncoder() {
292         mCharset = Charset.defaultCharset().newEncoder();
293         mCharset.onMalformedInput(CodingErrorAction.REPLACE);
294         mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
295     }
296 
appendLocked(char c)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 
appendLocked(String str, int i, final int length)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 
appendLocked(char[] buf, int i, final int length)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 
flushBytesLocked()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 
flushLocked()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