content: /*
   * Copyright (C) 2015 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.internal.util;
  
  import java.io.PrintWriter;
  import java.io.Writer;
  import java.util.Arrays;
  
  /**
   * A writer that breaks up its output into chunks before writing to its out writer,
   * and which is linebreak aware, i.e., chunks will created along line breaks, if
   * possible.
   *
   * Note: this class is not thread-safe.
   */
  public class LineBreakBufferedWriter extends PrintWriter {
  
      /**
       * A buffer to collect data until the buffer size is reached.
       *
       * Note: we manage a char[] ourselves to avoid an allocation when printing to the
       *       out writer. Otherwise a StringBuilder would have been simpler to use.
       */
      private char[] buffer;
  
      /**
       * The index of the first free element in the buffer.
       */
      private int bufferIndex;
  
      /**
       * The chunk size (=maximum buffer size) to use for this writer.
       */
      private final int bufferSize;
  
  
      /**
       * Index of the last newline character discovered in the buffer. The writer will try
       * to split there.
       */
      private int lastNewline = -1;
  
      /**
       * The line separator for println().
       */
      private final String lineSeparator;
  
      /**
       * Create a new linebreak-aware buffered writer with the given output and buffer
       * size. The initial capacity will be a default value.
       * @param out The writer to write to.
       * @param bufferSize The maximum buffer size.
       */
      public LineBreakBufferedWriter(Writer out, int bufferSize) {
          this(out, bufferSize, 16);  // 16 is the default size of a StringBuilder buffer.
      }
  
      /**
       * Create a new linebreak-aware buffered writer with the given output, buffer
       * size and initial capacity.
       * @param out The writer to write to.
       * @param bufferSize The maximum buffer size.
       * @param initialCapacity The initial capacity of the internal buffer.
       */
      public LineBreakBufferedWriter(Writer out, int bufferSize, int initialCapacity) {
          super(out);
          this.buffer = new char[Math.min(initialCapacity, bufferSize)];
          this.bufferIndex = 0;
          this.bufferSize = bufferSize;
          this.lineSeparator = System.getProperty("line.separator");
      }
  
      /**
       * Flush the current buffer. This will ignore line breaks.
       */
      @Override
      public void flush() {
          writeBuffer(bufferIndex);
          bufferIndex = 0;
          super.flush();
      }
  
      @Override
      public void write(int c) {
          if (bufferIndex < buffer.length) {
              buffer[bufferIndex] = (char)c;
              bufferIndex++;
              if ((char)c == '\n') {
                  lastNewline = bufferIndex;
              }
          } else {
              // This should be an uncommon case, we mostly expect char[] and String. So
              // let the chunking be handled by the char[] case.
              write(new char[] { (char)c }, 0 ,1);
          }
      }
  
      @Override
      public void println() {
          write(lineSeparator);
      }
  
      @Override
      public void write(char[] buf, int off, int len) {
          while (bufferIndex + len > bufferSize) {
              // Find the next newline in the buffer, see if that's below the limit.
              // Repeat.
              int nextNewLine = -1;
              int maxLength = bufferSize - bufferIndex;
              for (int i = 0; i < maxLength; i++) {
                  if (buf[off + i] == '\n') {
                      if (bufferIndex + i < bufferSize) {
                          nextNewLine = i;
                      } else {
                          break;
                      }
                  }
              }
  
              if (nextNewLine != -1) {
                  // We can add some more data.
                  appendToBuffer(buf, off, nextNewLine);
                  writeBuffer(bufferIndex);
                  bufferIndex = 0;
                  lastNewline = -1;
                  off += nextNewLine + 1;
                  len -= nextNewLine + 1;
              } else if (lastNewline != -1) {
                  // Use the last newline.
                  writeBuffer(lastNewline);
                  removeFromBuffer(lastNewline + 1);
                  lastNewline = -1;
              } else {
                  // OK, there was no newline, break at a full buffer.
                  int rest = bufferSize - bufferIndex;
                  appendToBuffer(buf, off, rest);
                  writeBuffer(bufferIndex);
                  bufferIndex = 0;
                  off += rest;
                  len -= rest;
              }
          }
  
          // Add to the buffer, this will fit.
          if (len > 0) {
              // Add the chars, find the last newline.
              appendToBuffer(buf, off, len);
              for (int i = len - 1; i >= 0; i--) {
                  if (buf[off + i] == '\n') {
                      lastNewline = bufferIndex - len + i;
                      break;
                  }
              }
          }
      }
  
      @Override
      public void write(String s, int off, int len) {
          while (bufferIndex + len > bufferSize) {
              // Find the next newline in the buffer, see if that's below the limit.
              // Repeat.
              int nextNewLine = -1;
              int maxLength = bufferSize - bufferIndex;
              for (int i = 0; i < maxLength; i++) {
                  if (s.charAt(off + i) == '\n') {
                      if (bufferIndex + i < bufferSize) {
                          nextNewLine = i;
                      } else {
                          break;
                      }
                  }
              }
  
              if (nextNewLine != -1) {
                  // We can add some more data.
                  appendToBuffer(s, off, nextNewLine);
                  writeBuffer(bufferIndex);
                  bufferIndex = 0;
                  lastNewline = -1;
                  off += nextNewLine + 1;
                  len -= nextNewLine + 1;
              } else if (lastNewline != -1) {
                  // Use the last newline.
                  writeBuffer(lastNewline);
                  removeFromBuffer(lastNewline + 1);
                  lastNewline = -1;
              } else {
                  // OK, there was no newline, break at a full buffer.
                  int rest = bufferSize - bufferIndex;
                  appendToBuffer(s, off, rest);
                  writeBuffer(bufferIndex);
                  bufferIndex = 0;
                  off += rest;
                  len -= rest;
              }
          }
  
          // Add to the buffer, this will fit.
          if (len > 0) {
              // Add the chars, find the last newline.
              appendToBuffer(s, off, len);
              for (int i = len - 1; i >= 0; i--) {
                  if (s.charAt(off + i) == '\n') {
                      lastNewline = bufferIndex - len + i;
                      break;
                  }
              }
          }
      }
  
      /**
       * Append the characters to the buffer. This will potentially resize the buffer,
       * and move the index along.
       * @param buf The char[] containing the data.
       * @param off The start index to copy from.
       * @param len The number of characters to copy.
       */
      private void appendToBuffer(char[] buf, int off, int len) {
          if (bufferIndex + len > buffer.length) {
              ensureCapacity(bufferIndex + len);
          }
          System.arraycopy(buf, off, buffer, bufferIndex, len);
          bufferIndex += len;
      }
  
      /**
       * Append the characters from the given string to the buffer. This will potentially
       * resize the buffer, and move the index along.
       * @param s The string supplying the characters.
       * @param off The start index to copy from.
       * @param len The number of characters to copy.
       */
      private void appendToBuffer(String s, int off, int len) {
          if (bufferIndex + len > buffer.length) {
              ensureCapacity(bufferIndex + len);
          }
          s.getChars(off, off + len, buffer, bufferIndex);
          bufferIndex += len;
      }
  
      /**
       * Resize the buffer. We use the usual double-the-size plus constant scheme for
       * amortized O(1) insert. Note: we expect small buffers, so this won't check for
       * overflow.
       * @param capacity The size to be ensured.
       */
      private void ensureCapacity(int capacity) {
          int newSize = buffer.length * 2 + 2;
          if (newSize < capacity) {
              newSize = capacity;
          }
          buffer = Arrays.copyOf(buffer, newSize);
      }
  
      /**
       * Remove the characters up to (and excluding) index i from the buffer. This will
       * not resize the buffer, but will update bufferIndex.
       * @param i The number of characters to remove from the front.
       */
      private void removeFromBuffer(int i) {
          int rest = bufferIndex - i;
          if (rest > 0) {
              System.arraycopy(buffer, bufferIndex - rest, buffer, 0, rest);
              bufferIndex = rest;
          } else {
              bufferIndex = 0;
          }
      }
  
      /**
       * Helper method, write the given part of the buffer, [start,length), to the output.
       * @param length The number of characters to flush.
       */
      private void writeBuffer(int length) {
          if (length > 0) {
              super.write(buffer, 0, length);
          }
      }
  }
  