blob: 63124def6d2f329f75ca014028c1a9243d16737e [file] [log] [blame]
/*
* 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.compat.annotation.UnsupportedAppUsage;
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}.
*/
@UnsupportedAppUsage
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.
*
* <p>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.</p>
*
* @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)}.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}