350 lines
9.2 KiB
Java
350 lines
9.2 KiB
Java
/*
|
|
* Copyright (C) 2008 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.os;
|
|
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.io.PrintStream;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.CharBuffer;
|
|
import java.nio.charset.Charset;
|
|
import java.nio.charset.CharsetDecoder;
|
|
import java.nio.charset.CoderResult;
|
|
import java.nio.charset.CodingErrorAction;
|
|
import java.util.Formatter;
|
|
import java.util.Locale;
|
|
|
|
import com.android.internal.annotations.VisibleForTesting;
|
|
|
|
/**
|
|
* A print stream which logs output line by line.
|
|
*
|
|
* {@hide}
|
|
*/
|
|
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
|
public abstract class LoggingPrintStream extends PrintStream {
|
|
|
|
private final StringBuilder builder = new StringBuilder();
|
|
|
|
/**
|
|
* A buffer that is initialized when raw bytes are first written to this
|
|
* stream. It may contain the leading bytes of multi-byte characters.
|
|
* Between writes this buffer is always ready to receive data; ie. the
|
|
* position is at the first unassigned byte and the limit is the capacity.
|
|
*/
|
|
private ByteBuffer encodedBytes;
|
|
|
|
/**
|
|
* A buffer that is initialized when raw bytes are first written to this
|
|
* stream. Between writes this buffer is always clear; ie. the position is
|
|
* zero and the limit is the capacity.
|
|
*/
|
|
private CharBuffer decodedChars;
|
|
|
|
/**
|
|
* Decodes bytes to characters using the system default charset. Initialized
|
|
* when raw bytes are first written to this stream.
|
|
*/
|
|
private CharsetDecoder decoder;
|
|
|
|
protected LoggingPrintStream() {
|
|
super(new OutputStream() {
|
|
public void write(int oneByte) throws IOException {
|
|
throw new AssertionError();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Logs the given line.
|
|
*/
|
|
protected abstract void log(String line);
|
|
|
|
@Override
|
|
public synchronized void flush() {
|
|
flush(true);
|
|
}
|
|
|
|
/**
|
|
* Searches buffer for line breaks and logs a message for each one.
|
|
*
|
|
* @param completely true if the ending chars should be treated as a line
|
|
* even though they don't end in a line break
|
|
*/
|
|
private void flush(boolean completely) {
|
|
int length = builder.length();
|
|
|
|
int start = 0;
|
|
int nextBreak;
|
|
|
|
// Log one line for each line break.
|
|
while (start < length
|
|
&& (nextBreak = builder.indexOf("\n", start)) != -1) {
|
|
log(builder.substring(start, nextBreak));
|
|
start = nextBreak + 1;
|
|
}
|
|
|
|
if (completely) {
|
|
// Log the remainder of the buffer.
|
|
if (start < length) {
|
|
log(builder.substring(start));
|
|
}
|
|
builder.setLength(0);
|
|
} else {
|
|
// Delete characters leading up to the next starting point.
|
|
builder.delete(0, start);
|
|
}
|
|
}
|
|
|
|
public void write(int oneByte) {
|
|
write(new byte[] { (byte) oneByte }, 0, 1);
|
|
}
|
|
|
|
@Override
|
|
public void write(byte[] buffer) {
|
|
write(buffer, 0, buffer.length);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void write(byte bytes[], int start, int count) {
|
|
if (decoder == null) {
|
|
encodedBytes = ByteBuffer.allocate(80);
|
|
decodedChars = CharBuffer.allocate(80);
|
|
decoder = Charset.defaultCharset().newDecoder()
|
|
.onMalformedInput(CodingErrorAction.REPLACE)
|
|
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
|
}
|
|
|
|
int end = start + count;
|
|
while (start < end) {
|
|
// copy some bytes from the array to the long-lived buffer. This
|
|
// way, if we end with a partial character we don't lose it.
|
|
int numBytes = Math.min(encodedBytes.remaining(), end - start);
|
|
encodedBytes.put(bytes, start, numBytes);
|
|
start += numBytes;
|
|
|
|
encodedBytes.flip();
|
|
CoderResult coderResult;
|
|
do {
|
|
// decode bytes from the byte buffer into the char buffer
|
|
coderResult = decoder.decode(encodedBytes, decodedChars, false);
|
|
|
|
// copy chars from the char buffer into our string builder
|
|
decodedChars.flip();
|
|
builder.append(decodedChars);
|
|
decodedChars.clear();
|
|
} while (coderResult.isOverflow());
|
|
encodedBytes.compact();
|
|
}
|
|
flush(false);
|
|
}
|
|
|
|
/** Always returns false. */
|
|
@Override
|
|
public boolean checkError() {
|
|
return false;
|
|
}
|
|
|
|
/** Ignored. */
|
|
@Override
|
|
protected void setError() { /* ignored */ }
|
|
|
|
/** Ignored. */
|
|
@Override
|
|
public void close() { /* ignored */ }
|
|
|
|
@Override
|
|
public PrintStream format(String format, Object... args) {
|
|
return format(Locale.getDefault(), format, args);
|
|
}
|
|
|
|
@Override
|
|
public PrintStream printf(String format, Object... args) {
|
|
return format(format, args);
|
|
}
|
|
|
|
@Override
|
|
public PrintStream printf(Locale l, String format, Object... args) {
|
|
return format(l, format, args);
|
|
}
|
|
|
|
private final Formatter formatter = new Formatter(builder, null);
|
|
|
|
@Override
|
|
public synchronized PrintStream format(
|
|
Locale l, String format, Object... args) {
|
|
if (format == null) {
|
|
throw new NullPointerException("format");
|
|
}
|
|
|
|
formatter.format(l, format, args);
|
|
flush(false);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(char[] charArray) {
|
|
builder.append(charArray);
|
|
flush(false);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(char ch) {
|
|
builder.append(ch);
|
|
if (ch == '\n') {
|
|
flush(false);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(double dnum) {
|
|
builder.append(dnum);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(float fnum) {
|
|
builder.append(fnum);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(int inum) {
|
|
builder.append(inum);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(long lnum) {
|
|
builder.append(lnum);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(Object obj) {
|
|
builder.append(obj);
|
|
flush(false);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(String str) {
|
|
builder.append(str);
|
|
flush(false);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void print(boolean bool) {
|
|
builder.append(bool);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println() {
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(char[] charArray) {
|
|
builder.append(charArray);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(char ch) {
|
|
builder.append(ch);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(double dnum) {
|
|
builder.append(dnum);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(float fnum) {
|
|
builder.append(fnum);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(int inum) {
|
|
builder.append(inum);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(long lnum) {
|
|
builder.append(lnum);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(Object obj) {
|
|
builder.append(obj);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(String s) {
|
|
if (builder.length() == 0 && s != null) {
|
|
// Optimization for a simple println.
|
|
int length = s.length();
|
|
|
|
int start = 0;
|
|
int nextBreak;
|
|
|
|
// Log one line for each line break.
|
|
while (start < length
|
|
&& (nextBreak = s.indexOf('\n', start)) != -1) {
|
|
log(s.substring(start, nextBreak));
|
|
start = nextBreak + 1;
|
|
}
|
|
|
|
if (start < length) {
|
|
log(s.substring(start));
|
|
}
|
|
} else {
|
|
builder.append(s);
|
|
flush(true);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public synchronized void println(boolean bool) {
|
|
builder.append(bool);
|
|
flush(true);
|
|
}
|
|
|
|
@Override
|
|
public synchronized PrintStream append(char c) {
|
|
print(c);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public synchronized PrintStream append(CharSequence csq) {
|
|
builder.append(csq);
|
|
flush(false);
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public synchronized PrintStream append(
|
|
CharSequence csq, int start, int end) {
|
|
builder.append(csq, start, end);
|
|
flush(false);
|
|
return this;
|
|
}
|
|
}
|