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