706 lines
22 KiB
Java
706 lines
22 KiB
Java
/*
|
|
* 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;
|
|
|
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
|
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;
|
|
}
|
|
}
|