Home | History | Annotate | Download | only in util
      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      */
     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      */
     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
     91     public void flush() {
     92         writeBuffer(bufferIndex);
     93         bufferIndex = 0;
     94         super.flush();
     95     }
     96 
     97     @Override
     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
    113     public void println() {
    114         write(lineSeparator);
    115     }
    116 
    117     @Override
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    288     private void writeBuffer(int length) {
    289         if (length > 0) {
    290             super.write(buffer, 0, length);
    291         }
    292     }
    293 }
    294