1 /*
2  * Copyright (C) 2015 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 
17 package com.android.internal.util;
18 
19 import java.io.PrintWriter;
20 import java.io.Writer;
21 import java.util.Arrays;
22 
23 /**
24  * A writer that breaks up its output into chunks before writing to its out writer,
25  * and which is linebreak aware, i.e., chunks will created along line breaks, if
26  * possible.
27  *
28  * Note: this class is not thread-safe.
29  */
30 public class LineBreakBufferedWriter extends PrintWriter {
31 
32     /**
33      * A buffer to collect data until the buffer size is reached.
34      *
35      * Note: we manage a char[] ourselves to avoid an allocation when printing to the
36      *       out writer. Otherwise a StringBuilder would have been simpler to use.
37      */
38     private char[] buffer;
39 
40     /**
41      * The index of the first free element in the buffer.
42      */
43     private int bufferIndex;
44 
45     /**
46      * The chunk size (=maximum buffer size) to use for this writer.
47      */
48     private final int bufferSize;
49 
50 
51     /**
52      * Index of the last newline character discovered in the buffer. The writer will try
53      * to split there.
54      */
55     private int lastNewline = -1;
56 
57     /**
58      * The line separator for println().
59      */
60     private final String lineSeparator;
61 
62     /**
63      * Create a new linebreak-aware buffered writer with the given output and buffer
64      * size. The initial capacity will be a default value.
65      * @param out The writer to write to.
66      * @param bufferSize The maximum buffer size.
67      */
LineBreakBufferedWriter(Writer out, int bufferSize)68     public LineBreakBufferedWriter(Writer out, int bufferSize) {
69         this(out, bufferSize, 16);  // 16 is the default size of a StringBuilder buffer.
70     }
71 
72     /**
73      * Create a new linebreak-aware buffered writer with the given output, buffer
74      * size and initial capacity.
75      * @param out The writer to write to.
76      * @param bufferSize The maximum buffer size.
77      * @param initialCapacity The initial capacity of the internal buffer.
78      */
LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity)79     public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) {
80         super(out);
81         this.buffer = new char[Math.min(initialCapacity, bufferSize)];
82         this.bufferIndex = 0;
83         this.bufferSize = bufferSize;
84         this.lineSeparator = System.getProperty("line.separator");
85     }
86 
87     /**
88      * Flush the current buffer. This will ignore line breaks.
89      */
90     @Override
flush()91     public void flush() {
92         writeBuffer(bufferIndex);
93         bufferIndex = 0;
94         super.flush();
95     }
96 
97     @Override
write(int c)98     public void write(int c) {
99         if (bufferIndex < buffer.length) {
100             buffer[bufferIndex] = (char)c;
101             bufferIndex++;
102             if ((char)c == '\n') {
103                 lastNewline = bufferIndex;
104             }
105         } else {
106             // This should be an uncommon case, we mostly expect char[] and String. So
107             // let the chunking be handled by the char[] case.
108             write(new char[] { (char)c }, 0 ,1);
109         }
110     }
111 
112     @Override
println()113     public void println() {
114         write(lineSeparator);
115     }
116 
117     @Override
write(char[] buf, int off, int len)118     public void write(char[] buf, int off, int len) {
119         while (bufferIndex + len > bufferSize) {
120             // Find the next newline in the buffer, see if that's below the limit.
121             // Repeat.
122             int nextNewLine = -1;
123             int maxLength = bufferSize - bufferIndex;
124             for (int i = 0; i < maxLength; i++) {
125                 if (buf[off + i] == '\n') {
126                     if (bufferIndex + i < bufferSize) {
127                         nextNewLine = i;
128                     } else {
129                         break;
130                     }
131                 }
132             }
133 
134             if (nextNewLine != -1) {
135                 // We can add some more data.
136                 appendToBuffer(buf, off, nextNewLine);
137                 writeBuffer(bufferIndex);
138                 bufferIndex = 0;
139                 lastNewline = -1;
140                 off += nextNewLine + 1;
141                 len -= nextNewLine + 1;
142             } else if (lastNewline != -1) {
143                 // Use the last newline.
144                 writeBuffer(lastNewline);
145                 removeFromBuffer(lastNewline + 1);
146                 lastNewline = -1;
147             } else {
148                 // OK, there was no newline, break at a full buffer.
149                 int rest = bufferSize - bufferIndex;
150                 appendToBuffer(buf, off, rest);
151                 writeBuffer(bufferIndex);
152                 bufferIndex = 0;
153                 off += rest;
154                 len -= rest;
155             }
156         }
157 
158         // Add to the buffer, this will fit.
159         if (len > 0) {
160             // Add the chars, find the last newline.
161             appendToBuffer(buf, off, len);
162             for (int i = len - 1; i >= 0; i--) {
163                 if (buf[off + i] == '\n') {
164                     lastNewline = bufferIndex - len + i;
165                     break;
166                 }
167             }
168         }
169     }
170 
171     @Override
write(String s, int off, int len)172     public void write(String s, int off, int len) {
173         while (bufferIndex + len > bufferSize) {
174             // Find the next newline in the buffer, see if that's below the limit.
175             // Repeat.
176             int nextNewLine = -1;
177             int maxLength = bufferSize - bufferIndex;
178             for (int i = 0; i < maxLength; i++) {
179                 if (s.charAt(off + i) == '\n') {
180                     if (bufferIndex + i < bufferSize) {
181                         nextNewLine = i;
182                     } else {
183                         break;
184                     }
185                 }
186             }
187 
188             if (nextNewLine != -1) {
189                 // We can add some more data.
190                 appendToBuffer(s, off, nextNewLine);
191                 writeBuffer(bufferIndex);
192                 bufferIndex = 0;
193                 lastNewline = -1;
194                 off += nextNewLine + 1;
195                 len -= nextNewLine + 1;
196             } else if (lastNewline != -1) {
197                 // Use the last newline.
198                 writeBuffer(lastNewline);
199                 removeFromBuffer(lastNewline + 1);
200                 lastNewline = -1;
201             } else {
202                 // OK, there was no newline, break at a full buffer.
203                 int rest = bufferSize - bufferIndex;
204                 appendToBuffer(s, off, rest);
205                 writeBuffer(bufferIndex);
206                 bufferIndex = 0;
207                 off += rest;
208                 len -= rest;
209             }
210         }
211 
212         // Add to the buffer, this will fit.
213         if (len > 0) {
214             // Add the chars, find the last newline.
215             appendToBuffer(s, off, len);
216             for (int i = len - 1; i >= 0; i--) {
217                 if (s.charAt(off + i) == '\n') {
218                     lastNewline = bufferIndex - len + i;
219                     break;
220                 }
221             }
222         }
223     }
224 
225     /**
226      * Append the characters to the buffer. This will potentially resize the buffer,
227      * and move the index along.
228      * @param buf The char[] containing the data.
229      * @param off The start index to copy from.
230      * @param len The number of characters to copy.
231      */
appendToBuffer(char[] buf, int off, int len)232     private void appendToBuffer(char[] buf, int off, int len) {
233         if (bufferIndex + len > buffer.length) {
234             ensureCapacity(bufferIndex + len);
235         }
236         System.arraycopy(buf, off, buffer, bufferIndex, len);
237         bufferIndex += len;
238     }
239 
240     /**
241      * Append the characters from the given string to the buffer. This will potentially
242      * resize the buffer, and move the index along.
243      * @param s The string supplying the characters.
244      * @param off The start index to copy from.
245      * @param len The number of characters to copy.
246      */
appendToBuffer(String s, int off, int len)247     private void appendToBuffer(String s, int off, int len) {
248         if (bufferIndex + len > buffer.length) {
249             ensureCapacity(bufferIndex + len);
250         }
251         s.getChars(off, off + len, buffer, bufferIndex);
252         bufferIndex += len;
253     }
254 
255     /**
256      * Resize the buffer. We use the usual double-the-size plus constant scheme for
257      * amortized O(1) insert. Note: we expect small buffers, so this won't check for
258      * overflow.
259      * @param capacity The size to be ensured.
260      */
ensureCapacity(int capacity)261     private void ensureCapacity(int capacity) {
262         int newSize = buffer.length * 2 + 2;
263         if (newSize < capacity) {
264             newSize = capacity;
265         }
266         buffer = Arrays.copyOf(buffer, newSize);
267     }
268 
269     /**
270      * Remove the characters up to (and excluding) index i from the buffer. This will
271      * not resize the buffer, but will update bufferIndex.
272      * @param i The number of characters to remove from the front.
273      */
removeFromBuffer(int i)274     private void removeFromBuffer(int i) {
275         int rest = bufferIndex - i;
276         if (rest > 0) {
277             System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest);
278             bufferIndex = rest;
279         } else {
280             bufferIndex = 0;
281         }
282     }
283 
284     /**
285      * Helper method, write the given part of the buffer, [start,length), to the output.
286      * @param length The number of characters to flush.
287      */
writeBuffer(int length)288     private void writeBuffer(int length) {
289         if (length > 0) {
290             super.write(buffer, 0, length);
291         }
292     }
293 }
294