/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.util; import android.util.Log; import android.util.Printer; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; public class FastPrintWriter extends PrintWriter { private static class DummyWriter extends Writer { @Override public void close() throws IOException { UnsupportedOperationException ex = new UnsupportedOperationException("Shouldn't be here"); throw ex; } @Override public void flush() throws IOException { close(); } @Override public void write(char[] buf, int offset, int count) throws IOException { close(); } }; private final int mBufferLen; private final char[] mText; private int mPos; final private OutputStream mOutputStream; final private boolean mAutoFlush; final private String mSeparator; final private Writer mWriter; final private Printer mPrinter; private CharsetEncoder mCharset; final private ByteBuffer mBytes; private boolean mIoError; /** * Constructs a new {@code PrintWriter} with {@code out} as its target * stream. By default, the new print writer does not automatically flush its * contents to the target stream when a newline is encountered. * * @param out * the target output stream. * @throws NullPointerException * if {@code out} is {@code null}. */ public FastPrintWriter(OutputStream out) { this(out, false, 8192); } /** * Constructs a new {@code PrintWriter} with {@code out} as its target * stream. The parameter {@code autoFlush} determines if the print writer * automatically flushes its contents to the target stream when a newline is * encountered. * * @param out * the target output stream. * @param autoFlush * indicates whether contents are flushed upon encountering a * newline sequence. * @throws NullPointerException * if {@code out} is {@code null}. */ public FastPrintWriter(OutputStream out, boolean autoFlush) { this(out, autoFlush, 8192); } /** * Constructs a new {@code PrintWriter} with {@code out} as its target * stream and a custom buffer size. The parameter {@code autoFlush} determines * if the print writer automatically flushes its contents to the target stream * when a newline is encountered. * * @param out * the target output stream. * @param autoFlush * indicates whether contents are flushed upon encountering a * newline sequence. * @param bufferLen * specifies the size of the FastPrintWriter's internal buffer; the * default is 8192. * @throws NullPointerException * if {@code out} is {@code null}. */ public FastPrintWriter(OutputStream out, boolean autoFlush, int bufferLen) { super(new DummyWriter(), autoFlush); if (out == null) { throw new NullPointerException("out is null"); } mBufferLen = bufferLen; mText = new char[bufferLen]; mBytes = ByteBuffer.allocate(mBufferLen); mOutputStream = out; mWriter = null; mPrinter = null; mAutoFlush = autoFlush; mSeparator = System.lineSeparator(); initDefaultEncoder(); } /** * Constructs a new {@code PrintWriter} with {@code wr} as its target * writer. By default, the new print writer does not automatically flush its * contents to the target writer when a newline is encountered. * *
NOTE: Unlike PrintWriter, this version will still do buffering inside of * FastPrintWriter before sending data to the Writer. This means you must call * flush() before retrieving any data from the Writer.
* * @param wr * the target writer. * @throws NullPointerException * if {@code wr} is {@code null}. */ public FastPrintWriter(Writer wr) { this(wr, false, 8192); } /** * Constructs a new {@code PrintWriter} with {@code wr} as its target * writer. The parameter {@code autoFlush} determines if the print writer * automatically flushes its contents to the target writer when a newline is * encountered. * * @param wr * the target writer. * @param autoFlush * indicates whether to flush contents upon encountering a * newline sequence. * @throws NullPointerException * if {@code out} is {@code null}. */ public FastPrintWriter(Writer wr, boolean autoFlush) { this(wr, autoFlush, 8192); } /** * Constructs a new {@code PrintWriter} with {@code wr} as its target * writer and a custom buffer size. The parameter {@code autoFlush} determines * if the print writer automatically flushes its contents to the target writer * when a newline is encountered. * * @param wr * the target writer. * @param autoFlush * indicates whether to flush contents upon encountering a * newline sequence. * @param bufferLen * specifies the size of the FastPrintWriter's internal buffer; the * default is 8192. * @throws NullPointerException * if {@code wr} is {@code null}. */ public FastPrintWriter(Writer wr, boolean autoFlush, int bufferLen) { super(new DummyWriter(), autoFlush); if (wr == null) { throw new NullPointerException("wr is null"); } mBufferLen = bufferLen; mText = new char[bufferLen]; mBytes = null; mOutputStream = null; mWriter = wr; mPrinter = null; mAutoFlush = autoFlush; mSeparator = System.lineSeparator(); initDefaultEncoder(); } /** * Constructs a new {@code PrintWriter} with {@code pr} as its target * printer and the default buffer size. Because a {@link Printer} is line-base, * autoflush is always enabled. * * @param pr * the target writer. * @throws NullPointerException * if {@code pr} is {@code null}. */ public FastPrintWriter(Printer pr) { this(pr, 512); } /** * Constructs a new {@code PrintWriter} with {@code pr} as its target * printer and a custom buffer size. Because a {@link Printer} is line-base, * autoflush is always enabled. * * @param pr * the target writer. * @param bufferLen * specifies the size of the FastPrintWriter's internal buffer; the * default is 512. * @throws NullPointerException * if {@code pr} is {@code null}. */ public FastPrintWriter(Printer pr, int bufferLen) { super(new DummyWriter(), true); if (pr == null) { throw new NullPointerException("pr is null"); } mBufferLen = bufferLen; mText = new char[bufferLen]; mBytes = null; mOutputStream = null; mWriter = null; mPrinter = pr; mAutoFlush = true; mSeparator = System.lineSeparator(); initDefaultEncoder(); } private final void initEncoder(String csn) throws UnsupportedEncodingException { try { mCharset = Charset.forName(csn).newEncoder(); } catch (Exception e) { throw new UnsupportedEncodingException(csn); } mCharset.onMalformedInput(CodingErrorAction.REPLACE); mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE); } /** * Flushes this writer and returns the value of the error flag. * * @return {@code true} if either an {@code IOException} has been thrown * previously or if {@code setError()} has been called; * {@code false} otherwise. * @see #setError() */ public boolean checkError() { flush(); synchronized (lock) { return mIoError; } } /** * Sets the error state of the stream to false. * @since 1.6 */ protected void clearError() { synchronized (lock) { mIoError = false; } } /** * Sets the error flag of this writer to true. */ protected void setError() { synchronized (lock) { mIoError = true; } } private final void initDefaultEncoder() { mCharset = Charset.defaultCharset().newEncoder(); mCharset.onMalformedInput(CodingErrorAction.REPLACE); mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE); } private void appendLocked(char c) throws IOException { int pos = mPos; if (pos >= (mBufferLen-1)) { flushLocked(); pos = mPos; } mText[pos] = c; mPos = pos+1; } private void appendLocked(String str, int i, final int length) throws IOException { final int BUFFER_LEN = mBufferLen; if (length > BUFFER_LEN) { final int end = i + length; while (i < end) { int next = i + BUFFER_LEN; appendLocked(str, i, next < end ? BUFFER_LEN : (end - i)); i = next; } return; } int pos = mPos; if ((pos+length) > BUFFER_LEN) { flushLocked(); pos = mPos; } str.getChars(i, i + length, mText, pos); mPos = pos + length; } private void appendLocked(char[] buf, int i, final int length) throws IOException { final int BUFFER_LEN = mBufferLen; if (length > BUFFER_LEN) { final int end = i + length; while (i < end) { int next = i + BUFFER_LEN; appendLocked(buf, i, next < end ? BUFFER_LEN : (end - i)); i = next; } return; } int pos = mPos; if ((pos+length) > BUFFER_LEN) { flushLocked(); pos = mPos; } System.arraycopy(buf, i, mText, pos, length); mPos = pos + length; } private void flushBytesLocked() throws IOException { if (!mIoError) { int position; if ((position = mBytes.position()) > 0) { mBytes.flip(); mOutputStream.write(mBytes.array(), 0, position); mBytes.clear(); } } } private void flushLocked() throws IOException { //Log.i("PackageManager", "flush mPos=" + mPos); if (mPos > 0) { if (mOutputStream != null) { CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos); CoderResult result = mCharset.encode(charBuffer, mBytes, true); while (!mIoError) { if (result.isError()) { throw new IOException(result.toString()); } else if (result.isOverflow()) { flushBytesLocked(); result = mCharset.encode(charBuffer, mBytes, true); continue; } break; } if (!mIoError) { flushBytesLocked(); mOutputStream.flush(); } } else if (mWriter != null) { if (!mIoError) { mWriter.write(mText, 0, mPos); mWriter.flush(); } } else { int nonEolOff = 0; final int sepLen = mSeparator.length(); final int len = sepLen < mPos ? sepLen : mPos; while (nonEolOff < len && mText[mPos-1-nonEolOff] == mSeparator.charAt(mSeparator.length()-1-nonEolOff)) { nonEolOff++; } if (nonEolOff >= mPos) { mPrinter.println(""); } else { mPrinter.println(new String(mText, 0, mPos-nonEolOff)); } } mPos = 0; } } /** * Ensures that all pending data is sent out to the target. It also * flushes the target. If an I/O error occurs, this writer's error * state is set to {@code true}. */ @Override public void flush() { synchronized (lock) { try { flushLocked(); if (!mIoError) { if (mOutputStream != null) { mOutputStream.flush(); } else if (mWriter != null) { mWriter.flush(); } } } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } @Override public void close() { synchronized (lock) { try { flushLocked(); if (mOutputStream != null) { mOutputStream.close(); } else if (mWriter != null) { mWriter.close(); } } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Prints the string representation of the specified character array * to the target. * * @param charArray * the character array to print to the target. * @see #print(String) */ public void print(char[] charArray) { synchronized (lock) { try { appendLocked(charArray, 0, charArray.length); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Prints the string representation of the specified character to the * target. * * @param ch * the character to print to the target. * @see #print(String) */ public void print(char ch) { synchronized (lock) { try { appendLocked(ch); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Prints a string to the target. The string is converted to an array of * bytes using the encoding chosen during the construction of this writer. * The bytes are then written to the target with {@code write(int)}. ** If an I/O error occurs, this writer's error flag is set to {@code true}. * * @param str * the string to print to the target. * @see #write(int) */ public void print(String str) { if (str == null) { str = String.valueOf((Object) null); } synchronized (lock) { try { appendLocked(str, 0, str.length()); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } @Override public void print(int inum) { if (inum == 0) { print("0"); } else { super.print(inum); } } @Override public void print(long lnum) { if (lnum == 0) { print("0"); } else { super.print(lnum); } } /** * Prints a newline. Flushes this writer if the autoFlush flag is set to {@code true}. */ public void println() { synchronized (lock) { try { appendLocked(mSeparator, 0, mSeparator.length()); if (mAutoFlush) { flushLocked(); } } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } @Override public void println(int inum) { if (inum == 0) { println("0"); } else { super.println(inum); } } @Override public void println(long lnum) { if (lnum == 0) { println("0"); } else { super.println(lnum); } } /** * Prints the string representation of the character array {@code chars} followed by a newline. * Flushes this writer if the autoFlush flag is set to {@code true}. */ public void println(char[] chars) { print(chars); println(); } /** * Prints the string representation of the char {@code c} followed by a newline. * Flushes this writer if the autoFlush flag is set to {@code true}. */ public void println(char c) { print(c); println(); } /** * Writes {@code count} characters from {@code buffer} starting at {@code * offset} to the target. *
* This writer's error flag is set to {@code true} if this writer is closed * or an I/O error occurs. * * @param buf * the buffer to write to the target. * @param offset * the index of the first character in {@code buffer} to write. * @param count * the number of characters in {@code buffer} to write. * @throws IndexOutOfBoundsException * if {@code offset < 0} or {@code count < 0}, or if {@code * offset + count} is greater than the length of {@code buf}. */ @Override public void write(char[] buf, int offset, int count) { synchronized (lock) { try { appendLocked(buf, offset, count); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Writes one character to the target. Only the two least significant bytes * of the integer {@code oneChar} are written. *
* This writer's error flag is set to {@code true} if this writer is closed * or an I/O error occurs. * * @param oneChar * the character to write to the target. */ @Override public void write(int oneChar) { synchronized (lock) { try { appendLocked((char) oneChar); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Writes the characters from the specified string to the target. * * @param str * the non-null string containing the characters to write. */ @Override public void write(String str) { synchronized (lock) { try { appendLocked(str, 0, str.length()); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Writes {@code count} characters from {@code str} starting at {@code * offset} to the target. * * @param str * the non-null string containing the characters to write. * @param offset * the index of the first character in {@code str} to write. * @param count * the number of characters from {@code str} to write. * @throws IndexOutOfBoundsException * if {@code offset < 0} or {@code count < 0}, or if {@code * offset + count} is greater than the length of {@code str}. */ @Override public void write(String str, int offset, int count) { synchronized (lock) { try { appendLocked(str, offset, count); } catch (IOException e) { Log.w("FastPrintWriter", "Write failure", e); setError(); } } } /** * Appends a subsequence of the character sequence {@code csq} to the * target. This method works the same way as {@code * PrintWriter.print(csq.subsequence(start, end).toString())}. If {@code * csq} is {@code null}, then the specified subsequence of the string "null" * will be written to the target. * * @param csq * the character sequence appended to the target. * @param start * the index of the first char in the character sequence appended * to the target. * @param end * the index of the character following the last character of the * subsequence appended to the target. * @return this writer. * @throws StringIndexOutOfBoundsException * if {@code start > end}, {@code start < 0}, {@code end < 0} or * either {@code start} or {@code end} are greater or equal than * the length of {@code csq}. */ @Override public PrintWriter append(CharSequence csq, int start, int end) { if (csq == null) { csq = "null"; } String output = csq.subSequence(start, end).toString(); write(output, 0, output.length()); return this; } }