content: /*
   * Copyright (C) 2013 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 android.annotation.UnsupportedAppUsage;
  import android.util.Log;
  import android.util.Printer;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.PrintWriter;
  import java.io.UnsupportedEncodingException;
  import java.io.Writer;
  import java.nio.ByteBuffer;
  import java.nio.CharBuffer;
  import java.nio.charset.Charset;
  import java.nio.charset.CharsetEncoder;
  import java.nio.charset.CoderResult;
  import java.nio.charset.CodingErrorAction;
  
  public class FastPrintWriter extends PrintWriter {
      private static class DummyWriter extends Writer {
          @Override
          public void close() throws IOException {
              UnsupportedOperationException ex
                      = new UnsupportedOperationException("Shouldn't be here");
              throw ex;
          }
  
          @Override
          public void flush() throws IOException {
              close();
          }
  
          @Override
          public void write(char[] buf, int offset, int count) throws IOException {
              close();
          }
      };
  
      private final int mBufferLen;
      private final char[] mText;
      private int mPos;
  
      final private OutputStream mOutputStream;
      final private boolean mAutoFlush;
      final private String mSeparator;
  
      final private Writer mWriter;
      final private Printer mPrinter;
  
      private CharsetEncoder mCharset;
      final private ByteBuffer mBytes;
  
      private boolean mIoError;
  
      /**
       * Constructs a new {@code PrintWriter} with {@code out} as its target
       * stream. By default, the new print writer does not automatically flush its
       * contents to the target stream when a newline is encountered.
       *
       * @param out
       *            the target output stream.
       * @throws NullPointerException
       *             if {@code out} is {@code null}.
       */
      @UnsupportedAppUsage
      public FastPrintWriter(OutputStream out) {
          this(out, false, 8192);
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code out} as its target
       * stream. The parameter {@code autoFlush} determines if the print writer
       * automatically flushes its contents to the target stream when a newline is
       * encountered.
       *
       * @param out
       *            the target output stream.
       * @param autoFlush
       *            indicates whether contents are flushed upon encountering a
       *            newline sequence.
       * @throws NullPointerException
       *             if {@code out} is {@code null}.
       */
      public FastPrintWriter(OutputStream out, boolean autoFlush) {
          this(out, autoFlush, 8192);
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code out} as its target
       * stream and a custom buffer size. The parameter {@code autoFlush} determines
       * if the print writer automatically flushes its contents to the target stream
       * when a newline is encountered.
       *
       * @param out
       *            the target output stream.
       * @param autoFlush
       *            indicates whether contents are flushed upon encountering a
       *            newline sequence.
       * @param bufferLen
       *            specifies the size of the FastPrintWriter's internal buffer; the
       *            default is 8192.
       * @throws NullPointerException
       *             if {@code out} is {@code null}.
       */
      public FastPrintWriter(OutputStream out, boolean autoFlush, int bufferLen) {
          super(new DummyWriter(), autoFlush);
          if (out == null) {
              throw new NullPointerException("out is null");
          }
          mBufferLen = bufferLen;
          mText = new char[bufferLen];
          mBytes = ByteBuffer.allocate(mBufferLen);
          mOutputStream = out;
          mWriter = null;
          mPrinter = null;
          mAutoFlush = autoFlush;
          mSeparator = System.lineSeparator();
          initDefaultEncoder();
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code wr} as its target
       * writer. By default, the new print writer does not automatically flush its
       * contents to the target writer when a newline is encountered.
       *
       * <p>NOTE: Unlike PrintWriter, this version will still do buffering inside of
       * FastPrintWriter before sending data to the Writer.  This means you must call
       * flush() before retrieving any data from the Writer.</p>
       *
       * @param wr
       *            the target writer.
       * @throws NullPointerException
       *             if {@code wr} is {@code null}.
       */
      public FastPrintWriter(Writer wr) {
          this(wr, false, 8192);
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code wr} as its target
       * writer. The parameter {@code autoFlush} determines if the print writer
       * automatically flushes its contents to the target writer when a newline is
       * encountered.
       *
       * @param wr
       *            the target writer.
       * @param autoFlush
       *            indicates whether to flush contents upon encountering a
       *            newline sequence.
       * @throws NullPointerException
       *             if {@code out} is {@code null}.
       */
      public FastPrintWriter(Writer wr, boolean autoFlush) {
          this(wr, autoFlush, 8192);
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code wr} as its target
       * writer and a custom buffer size. The parameter {@code autoFlush} determines
       * if the print writer automatically flushes its contents to the target writer
       * when a newline is encountered.
       *
       * @param wr
       *            the target writer.
       * @param autoFlush
       *            indicates whether to flush contents upon encountering a
       *            newline sequence.
       * @param bufferLen
       *            specifies the size of the FastPrintWriter's internal buffer; the
       *            default is 8192.
       * @throws NullPointerException
       *             if {@code wr} is {@code null}.
       */
      public FastPrintWriter(Writer wr, boolean autoFlush, int bufferLen) {
          super(new DummyWriter(), autoFlush);
          if (wr == null) {
              throw new NullPointerException("wr is null");
          }
          mBufferLen = bufferLen;
          mText = new char[bufferLen];
          mBytes = null;
          mOutputStream = null;
          mWriter = wr;
          mPrinter = null;
          mAutoFlush = autoFlush;
          mSeparator = System.lineSeparator();
          initDefaultEncoder();
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code pr} as its target
       * printer and the default buffer size.  Because a {@link Printer} is line-base,
       * autoflush is always enabled.
       *
       * @param pr
       *            the target writer.
       * @throws NullPointerException
       *             if {@code pr} is {@code null}.
       */
      public FastPrintWriter(Printer pr) {
          this(pr, 512);
      }
  
      /**
       * Constructs a new {@code PrintWriter} with {@code pr} as its target
       * printer and a custom buffer size.  Because a {@link Printer} is line-base,
       * autoflush is always enabled.
       *
       * @param pr
       *            the target writer.
       * @param bufferLen
       *            specifies the size of the FastPrintWriter's internal buffer; the
       *            default is 512.
       * @throws NullPointerException
       *             if {@code pr} is {@code null}.
       */
      public FastPrintWriter(Printer pr, int bufferLen) {
          super(new DummyWriter(), true);
          if (pr == null) {
              throw new NullPointerException("pr is null");
          }
          mBufferLen = bufferLen;
          mText = new char[bufferLen];
          mBytes = null;
          mOutputStream = null;
          mWriter = null;
          mPrinter = pr;
          mAutoFlush = true;
          mSeparator = System.lineSeparator();
          initDefaultEncoder();
      }
  
      private final void initEncoder(String csn) throws UnsupportedEncodingException {
          try {
              mCharset = Charset.forName(csn).newEncoder();
          } catch (Exception e) {
              throw new UnsupportedEncodingException(csn);
          }
          mCharset.onMalformedInput(CodingErrorAction.REPLACE);
          mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
      }
  
      /**
       * Flushes this writer and returns the value of the error flag.
       *
       * @return {@code true} if either an {@code IOException} has been thrown
       *         previously or if {@code setError()} has been called;
       *         {@code false} otherwise.
       * @see #setError()
       */
      public boolean checkError() {
          flush();
          synchronized (lock) {
              return mIoError;
          }
      }
  
      /**
       * Sets the error state of the stream to false.
       * @since 1.6
       */
      protected void clearError() {
          synchronized (lock) {
              mIoError = false;
          }
      }
  
      /**
       * Sets the error flag of this writer to true.
       */
      protected void setError() {
          synchronized (lock) {
              mIoError = true;
          }
      }
  
      private final void initDefaultEncoder() {
          mCharset = Charset.defaultCharset().newEncoder();
          mCharset.onMalformedInput(CodingErrorAction.REPLACE);
          mCharset.onUnmappableCharacter(CodingErrorAction.REPLACE);
      }
  
      private void appendLocked(char c) throws IOException {
          int pos = mPos;
          if (pos >= (mBufferLen-1)) {
              flushLocked();
              pos = mPos;
          }
          mText[pos] = c;
          mPos = pos+1;
      }
  
      private void appendLocked(String str, int i, final int length) throws IOException {
          final int BUFFER_LEN = mBufferLen;
          if (length > BUFFER_LEN) {
              final int end = i + length;
              while (i < end) {
                  int next = i + BUFFER_LEN;
                  appendLocked(str, i, next < end ? BUFFER_LEN : (end - i));
                  i = next;
              }
              return;
          }
          int pos = mPos;
          if ((pos+length) > BUFFER_LEN) {
              flushLocked();
              pos = mPos;
          }
          str.getChars(i, i + length, mText, pos);
          mPos = pos + length;
      }
  
      private void appendLocked(char[] buf, int i, final int length) throws IOException {
          final int BUFFER_LEN = mBufferLen;
          if (length > BUFFER_LEN) {
              final int end = i + length;
              while (i < end) {
                  int next = i + BUFFER_LEN;
                  appendLocked(buf, i, next < end ? BUFFER_LEN : (end - i));
                  i = next;
              }
              return;
          }
          int pos = mPos;
          if ((pos+length) > BUFFER_LEN) {
              flushLocked();
              pos = mPos;
          }
          System.arraycopy(buf, i, mText, pos, length);
          mPos = pos + length;
      }
  
      private void flushBytesLocked() throws IOException {
          if (!mIoError) {
              int position;
              if ((position = mBytes.position()) > 0) {
                  mBytes.flip();
                  mOutputStream.write(mBytes.array(), 0, position);
                  mBytes.clear();
              }
          }
      }
  
      private void flushLocked() throws IOException {
          //Log.i("PackageManager", "flush mPos=" + mPos);
          if (mPos > 0) {
              if (mOutputStream != null) {
                  CharBuffer charBuffer = CharBuffer.wrap(mText, 0, mPos);
                  CoderResult result = mCharset.encode(charBuffer, mBytes, true);
                  while (!mIoError) {
                      if (result.isError()) {
                          throw new IOException(result.toString());
                      } else if (result.isOverflow()) {
                          flushBytesLocked();
                          result = mCharset.encode(charBuffer, mBytes, true);
                          continue;
                      }
                      break;
                  }
                  if (!mIoError) {
                      flushBytesLocked();
                      mOutputStream.flush();
                  }
              } else if (mWriter != null) {
                  if (!mIoError) {
                      mWriter.write(mText, 0, mPos);
                      mWriter.flush();
                  }
              } else {
                  int nonEolOff = 0;
                  final int sepLen = mSeparator.length();
                  final int len = sepLen < mPos ? sepLen : mPos;
                  while (nonEolOff < len && mText[mPos-1-nonEolOff]
                          == mSeparator.charAt(mSeparator.length()-1-nonEolOff)) {
                      nonEolOff++;
                  }
                  if (nonEolOff >= mPos) {
                      mPrinter.println("");
                  } else {
                      mPrinter.println(new String(mText, 0, mPos-nonEolOff));
                  }
              }
              mPos = 0;
          }
      }
  
      /**
       * Ensures that all pending data is sent out to the target. It also
       * flushes the target. If an I/O error occurs, this writer's error
       * state is set to {@code true}.
       */
      @Override
      public void flush() {
          synchronized (lock) {
              try {
                  flushLocked();
                  if (!mIoError) {
                      if (mOutputStream != null) {
                          mOutputStream.flush();
                      } else if (mWriter != null) {
                          mWriter.flush();
                      }
                  }
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      @Override
      public void close() {
          synchronized (lock) {
              try {
                  flushLocked();
                  if (mOutputStream != null) {
                      mOutputStream.close();
                  } else if (mWriter != null) {
                      mWriter.close();
                  }
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Prints the string representation of the specified character array
       * to the target.
       *
       * @param charArray
       *            the character array to print to the target.
       * @see #print(String)
       */
      public void print(char[] charArray) {
          synchronized (lock) {
              try {
                  appendLocked(charArray, 0, charArray.length);
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Prints the string representation of the specified character to the
       * target.
       *
       * @param ch
       *            the character to print to the target.
       * @see #print(String)
       */
      public void print(char ch) {
          synchronized (lock) {
              try {
                  appendLocked(ch);
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Prints a string to the target. The string is converted to an array of
       * bytes using the encoding chosen during the construction of this writer.
       * The bytes are then written to the target with {@code write(int)}.
       * <p>
       * If an I/O error occurs, this writer's error flag is set to {@code true}.
       *
       * @param str
       *            the string to print to the target.
       * @see #write(int)
       */
      public void print(String str) {
          if (str == null) {
              str = String.valueOf((Object) null);
          }
          synchronized (lock) {
              try {
                  appendLocked(str, 0, str.length());
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
  
      @Override
      public void print(int inum) {
          if (inum == 0) {
              print("0");
          } else {
              super.print(inum);
          }
      }
  
      @Override
      public void print(long lnum) {
          if (lnum == 0) {
              print("0");
          } else {
              super.print(lnum);
          }
      }
  
      /**
       * Prints a newline. Flushes this writer if the autoFlush flag is set to {@code true}.
       */
      public void println() {
          synchronized (lock) {
              try {
                  appendLocked(mSeparator, 0, mSeparator.length());
                  if (mAutoFlush) {
                      flushLocked();
                  }
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      @Override
      public void println(int inum) {
          if (inum == 0) {
              println("0");
          } else {
              super.println(inum);
          }
      }
  
      @Override
      public void println(long lnum) {
          if (lnum == 0) {
              println("0");
          } else {
              super.println(lnum);
          }
      }
  
      /**
       * Prints the string representation of the character array {@code chars} followed by a newline.
       * Flushes this writer if the autoFlush flag is set to {@code true}.
       */
      public void println(char[] chars) {
          print(chars);
          println();
      }
  
      /**
       * Prints the string representation of the char {@code c} followed by a newline.
       * Flushes this writer if the autoFlush flag is set to {@code true}.
       */
      public void println(char c) {
          print(c);
          println();
      }
  
      /**
       * Writes {@code count} characters from {@code buffer} starting at {@code
       * offset} to the target.
       * <p>
       * This writer's error flag is set to {@code true} if this writer is closed
       * or an I/O error occurs.
       *
       * @param buf
       *            the buffer to write to the target.
       * @param offset
       *            the index of the first character in {@code buffer} to write.
       * @param count
       *            the number of characters in {@code buffer} to write.
       * @throws IndexOutOfBoundsException
       *             if {@code offset < 0} or {@code count < 0}, or if {@code
       *             offset + count} is greater than the length of {@code buf}.
       */
      @Override
      public void write(char[] buf, int offset, int count) {
          synchronized (lock) {
              try {
                  appendLocked(buf, offset, count);
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Writes one character to the target. Only the two least significant bytes
       * of the integer {@code oneChar} are written.
       * <p>
       * This writer's error flag is set to {@code true} if this writer is closed
       * or an I/O error occurs.
       *
       * @param oneChar
       *            the character to write to the target.
       */
      @Override
      public void write(int oneChar) {
          synchronized (lock) {
              try {
                  appendLocked((char) oneChar);
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Writes the characters from the specified string to the target.
       *
       * @param str
       *            the non-null string containing the characters to write.
       */
      @Override
      public void write(String str) {
          synchronized (lock) {
              try {
                  appendLocked(str, 0, str.length());
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Writes {@code count} characters from {@code str} starting at {@code
       * offset} to the target.
       *
       * @param str
       *            the non-null string containing the characters to write.
       * @param offset
       *            the index of the first character in {@code str} to write.
       * @param count
       *            the number of characters from {@code str} to write.
       * @throws IndexOutOfBoundsException
       *             if {@code offset < 0} or {@code count < 0}, or if {@code
       *             offset + count} is greater than the length of {@code str}.
       */
      @Override
      public void write(String str, int offset, int count) {
          synchronized (lock) {
              try {
                  appendLocked(str, offset, count);
              } catch (IOException e) {
                  Log.w("FastPrintWriter", "Write failure", e);
                  setError();
              }
          }
      }
  
      /**
       * Appends a subsequence of the character sequence {@code csq} to the
       * target. This method works the same way as {@code
       * PrintWriter.print(csq.subsequence(start, end).toString())}. If {@code
       * csq} is {@code null}, then the specified subsequence of the string "null"
       * will be written to the target.
       *
       * @param csq
       *            the character sequence appended to the target.
       * @param start
       *            the index of the first char in the character sequence appended
       *            to the target.
       * @param end
       *            the index of the character following the last character of the
       *            subsequence appended to the target.
       * @return this writer.
       * @throws StringIndexOutOfBoundsException
       *             if {@code start > end}, {@code start < 0}, {@code end < 0} or
       *             either {@code start} or {@code end} are greater or equal than
       *             the length of {@code csq}.
       */
      @Override
      public PrintWriter append(CharSequence csq, int start, int end) {
          if (csq == null) {
              csq = "null";
          }
          String output = csq.subSequence(start, end).toString();
          write(output, 0, output.length());
          return this;
      }
  }
  