1/*
2 * Copyright (C) 2008 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
17package com.android.internal.os;
18
19import java.io.IOException;
20import java.io.OutputStream;
21import java.io.PrintStream;
22import java.nio.ByteBuffer;
23import java.nio.CharBuffer;
24import java.nio.charset.Charset;
25import java.nio.charset.CharsetDecoder;
26import java.nio.charset.CoderResult;
27import java.nio.charset.CodingErrorAction;
28import java.util.Formatter;
29import java.util.Locale;
30
31/**
32 * A print stream which logs output line by line.
33 *
34 * {@hide}
35 */
36abstract class LoggingPrintStream extends PrintStream {
37
38    private final StringBuilder builder = new StringBuilder();
39
40    /**
41     * A buffer that is initialized when raw bytes are first written to this
42     * stream. It may contain the leading bytes of multi-byte characters.
43     * Between writes this buffer is always ready to receive data; ie. the
44     * position is at the first unassigned byte and the limit is the capacity.
45     */
46    private ByteBuffer encodedBytes;
47
48    /**
49     * A buffer that is initialized when raw bytes are first written to this
50     * stream. Between writes this buffer is always clear; ie. the position is
51     * zero and the limit is the capacity.
52     */
53    private CharBuffer decodedChars;
54
55    /**
56     * Decodes bytes to characters using the system default charset. Initialized
57     * when raw bytes are first written to this stream.
58     */
59    private CharsetDecoder decoder;
60
61    protected LoggingPrintStream() {
62        super(new OutputStream() {
63            public void write(int oneByte) throws IOException {
64                throw new AssertionError();
65            }
66        });
67    }
68
69    /**
70     * Logs the given line.
71     */
72    protected abstract void log(String line);
73
74    @Override
75    public synchronized void flush() {
76        flush(true);
77    }
78
79    /**
80     * Searches buffer for line breaks and logs a message for each one.
81     *
82     * @param completely true if the ending chars should be treated as a line
83     *  even though they don't end in a line break
84     */
85    private void flush(boolean completely) {
86        int length = builder.length();
87
88        int start = 0;
89        int nextBreak;
90
91        // Log one line for each line break.
92        while (start < length
93                && (nextBreak = builder.indexOf("\n", start)) != -1) {
94            log(builder.substring(start, nextBreak));
95            start = nextBreak + 1;
96        }
97
98        if (completely) {
99            // Log the remainder of the buffer.
100            if (start < length) {
101                log(builder.substring(start));
102            }
103            builder.setLength(0);
104        } else {
105            // Delete characters leading up to the next starting point.
106            builder.delete(0, start);
107        }
108    }
109
110    public void write(int oneByte) {
111        write(new byte[] { (byte) oneByte }, 0, 1);
112    }
113
114    @Override
115    public void write(byte[] buffer) {
116        write(buffer, 0, buffer.length);
117    }
118
119    @Override
120    public synchronized void write(byte bytes[], int start, int count) {
121        if (decoder == null) {
122            encodedBytes = ByteBuffer.allocate(80);
123            decodedChars = CharBuffer.allocate(80);
124            decoder = Charset.defaultCharset().newDecoder()
125                    .onMalformedInput(CodingErrorAction.REPLACE)
126                    .onUnmappableCharacter(CodingErrorAction.REPLACE);
127        }
128
129        int end = start + count;
130        while (start < end) {
131            // copy some bytes from the array to the long-lived buffer. This
132            // way, if we end with a partial character we don't lose it.
133            int numBytes = Math.min(encodedBytes.remaining(), end - start);
134            encodedBytes.put(bytes, start, numBytes);
135            start += numBytes;
136
137            encodedBytes.flip();
138            CoderResult coderResult;
139            do {
140                // decode bytes from the byte buffer into the char buffer
141                coderResult = decoder.decode(encodedBytes, decodedChars, false);
142
143                // copy chars from the char buffer into our string builder
144                decodedChars.flip();
145                builder.append(decodedChars);
146                decodedChars.clear();
147            } while (coderResult.isOverflow());
148            encodedBytes.compact();
149        }
150        flush(false);
151    }
152
153    /** Always returns false. */
154    @Override
155    public boolean checkError() {
156        return false;
157    }
158
159    /** Ignored. */
160    @Override
161    protected void setError() { /* ignored */ }
162
163    /** Ignored. */
164    @Override
165    public void close() { /* ignored */ }
166
167    @Override
168    public PrintStream format(String format, Object... args) {
169        return format(Locale.getDefault(), format, args);
170    }
171
172    @Override
173    public PrintStream printf(String format, Object... args) {
174        return format(format, args);
175    }
176
177    @Override
178    public PrintStream printf(Locale l, String format, Object... args) {
179        return format(l, format, args);
180    }
181
182    private final Formatter formatter = new Formatter(builder, null);
183
184    @Override
185    public synchronized PrintStream format(
186            Locale l, String format, Object... args) {
187        if (format == null) {
188            throw new NullPointerException("format");
189        }
190
191        formatter.format(l, format, args);
192        flush(false);
193        return this;
194    }
195
196    @Override
197    public synchronized void print(char[] charArray) {
198        builder.append(charArray);
199        flush(false);
200    }
201
202    @Override
203    public synchronized void print(char ch) {
204        builder.append(ch);
205        if (ch == '\n') {
206            flush(false);
207        }
208    }
209
210    @Override
211    public synchronized void print(double dnum) {
212        builder.append(dnum);
213    }
214
215    @Override
216    public synchronized void print(float fnum) {
217        builder.append(fnum);
218    }
219
220    @Override
221    public synchronized void print(int inum) {
222        builder.append(inum);
223    }
224
225    @Override
226    public synchronized void print(long lnum) {
227        builder.append(lnum);
228    }
229
230    @Override
231    public synchronized void print(Object obj) {
232        builder.append(obj);
233        flush(false);
234    }
235
236    @Override
237    public synchronized void print(String str) {
238        builder.append(str);
239        flush(false);
240    }
241
242    @Override
243    public synchronized void print(boolean bool) {
244        builder.append(bool);
245    }
246
247    @Override
248    public synchronized void println() {
249        flush(true);
250    }
251
252    @Override
253    public synchronized void println(char[] charArray) {
254        builder.append(charArray);
255        flush(true);
256    }
257
258    @Override
259    public synchronized void println(char ch) {
260        builder.append(ch);
261        flush(true);
262    }
263
264    @Override
265    public synchronized void println(double dnum) {
266        builder.append(dnum);
267        flush(true);
268    }
269
270    @Override
271    public synchronized void println(float fnum) {
272        builder.append(fnum);
273        flush(true);
274    }
275
276    @Override
277    public synchronized void println(int inum) {
278        builder.append(inum);
279        flush(true);
280    }
281
282    @Override
283    public synchronized void println(long lnum) {
284        builder.append(lnum);
285        flush(true);
286    }
287
288    @Override
289    public synchronized void println(Object obj) {
290        builder.append(obj);
291        flush(true);
292    }
293
294    @Override
295    public synchronized void println(String s) {
296        if (builder.length() == 0 && s != null) {
297            // Optimization for a simple println.
298            int length = s.length();
299
300            int start = 0;
301            int nextBreak;
302
303            // Log one line for each line break.
304            while (start < length
305                    && (nextBreak = s.indexOf('\n', start)) != -1) {
306                log(s.substring(start, nextBreak));
307                start = nextBreak + 1;
308            }
309
310            if (start < length) {
311                log(s.substring(start));
312            }
313        } else {
314            builder.append(s);
315            flush(true);
316        }
317    }
318
319    @Override
320    public synchronized void println(boolean bool) {
321        builder.append(bool);
322        flush(true);
323    }
324
325    @Override
326    public synchronized PrintStream append(char c) {
327        print(c);
328        return this;
329    }
330
331    @Override
332    public synchronized PrintStream append(CharSequence csq) {
333        builder.append(csq);
334        flush(false);
335        return this;
336    }
337
338    @Override
339    public synchronized PrintStream append(
340            CharSequence csq, int start, int end) {
341        builder.append(csq, start, end);
342        flush(false);
343        return this;
344    }
345}
346