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