content: // Protocol Buffers - Google's data interchange format
  // Copyright 2008 Google Inc.  All rights reserved.
  // https://developers.google.com/protocol-buffers/
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are
  // met:
  //
  //     * Redistributions of source code must retain the above copyright
  // notice, this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above
  // copyright notice, this list of conditions and the following disclaimer
  // in the documentation and/or other materials provided with the
  // distribution.
  //     * Neither the name of Google Inc. nor the names of its
  // contributors may be used to endorse or promote products derived from
  // this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  package com.google.protobuf;
  
  import static java.lang.Math.max;
  import static java.lang.Math.min;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.lang.ref.SoftReference;
  import java.lang.reflect.Field;
  import java.nio.ByteBuffer;
  import java.nio.channels.WritableByteChannel;
  
  /**
   * Utility class to provide efficient writing of {@link ByteBuffer}s to {@link OutputStream}s.
   */
  final class ByteBufferWriter {
    private ByteBufferWriter() {}
  
    /**
     * Minimum size for a cached buffer. This prevents us from allocating buffers that are too
     * small to be easily reused.
     */
    // TODO(nathanmittler): tune this property or allow configuration?
    private static final int MIN_CACHED_BUFFER_SIZE = 1024;
  
    /**
     * Maximum size for a cached buffer. If a larger buffer is required, it will be allocated
     * but not cached.
     */
    // TODO(nathanmittler): tune this property or allow configuration?
    private static final int MAX_CACHED_BUFFER_SIZE = 16 * 1024;
  
    /**
     * The fraction of the requested buffer size under which the buffer will be reallocated.
     */
    // TODO(nathanmittler): tune this property or allow configuration?
    private static final float BUFFER_REALLOCATION_THRESHOLD = 0.5f;
  
    /**
     * Keeping a soft reference to a thread-local buffer. This buffer is used for writing a
     * {@link ByteBuffer} to an {@link OutputStream} when no zero-copy alternative was available.
     * Using a "soft" reference since VMs may keep this reference around longer than "weak"
     * (e.g. HotSpot will maintain soft references until memory pressure warrants collection).
     */
    private static final ThreadLocal<SoftReference<byte[]>> BUFFER =
        new ThreadLocal<SoftReference<byte[]>>();
  
    /**
     * This is a hack for GAE, where {@code FileOutputStream} is unavailable.
     */
    private static final Class<?> FILE_OUTPUT_STREAM_CLASS = safeGetClass("java.io.FileOutputStream");
    private static final long CHANNEL_FIELD_OFFSET = getChannelFieldOffset(FILE_OUTPUT_STREAM_CLASS);
  
    /**
     * For testing purposes only. Clears the cached buffer to force a new allocation on the next
     * invocation.
     */
    static void clearCachedBuffer() {
      BUFFER.set(null);
    }
  
    /**
     * Writes the remaining content of the buffer to the given stream. The buffer {@code position}
     * will remain unchanged by this method.
     */
    static void write(ByteBuffer buffer, OutputStream output) throws IOException {
      final int initialPos = buffer.position();
      try {
        if (buffer.hasArray()) {
          // Optimized write for array-backed buffers.
          // Note that we're taking the risk that a malicious OutputStream could modify the array.
          output.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
        } else if (!writeToChannel(buffer, output)){
          // Read all of the data from the buffer to an array.
          // TODO(nathanmittler): Consider performance improvements for other "known" stream types.
          final byte[] array = getOrCreateBuffer(buffer.remaining());
          while (buffer.hasRemaining()) {
            int length = min(buffer.remaining(), array.length);
            buffer.get(array, 0, length);
            output.write(array, 0, length);
          }
        }
      } finally {
        // Restore the initial position.
        buffer.position(initialPos);
      }
    }
  
    private static byte[] getOrCreateBuffer(int requestedSize) {
      requestedSize = max(requestedSize, MIN_CACHED_BUFFER_SIZE);
  
      byte[] buffer = getBuffer();
      // Only allocate if we need to.
      if (buffer == null || needToReallocate(requestedSize, buffer.length)) {
        buffer = new byte[requestedSize];
  
        // Only cache the buffer if it's not too big.
        if (requestedSize <= MAX_CACHED_BUFFER_SIZE) {
          setBuffer(buffer);
        }
      }
      return buffer;
    }
  
    private static boolean needToReallocate(int requestedSize, int bufferLength) {
      // First check against just the requested length to avoid the multiply.
      return bufferLength < requestedSize
          && bufferLength < requestedSize * BUFFER_REALLOCATION_THRESHOLD;
    }
  
    private static byte[] getBuffer() {
      SoftReference<byte[]> sr = BUFFER.get();
      return sr == null ? null : sr.get();
    }
  
    private static void setBuffer(byte[] value) {
      BUFFER.set(new SoftReference<byte[]>(value));
    }
  
    private static boolean writeToChannel(ByteBuffer buffer, OutputStream output) throws IOException {
      if (CHANNEL_FIELD_OFFSET >= 0 && FILE_OUTPUT_STREAM_CLASS.isInstance(output)) {
        // Use a channel to write out the ByteBuffer. This will automatically empty the buffer.
        WritableByteChannel channel = null;
        try {
          channel = (WritableByteChannel) UnsafeUtil.getObject(output, CHANNEL_FIELD_OFFSET);
        } catch (ClassCastException e) {
          // Absorb.
        }
        if (channel != null) {
          channel.write(buffer);
          return true;
        }
      }
      return false;
    }
  
    private static Class<?> safeGetClass(String className) {
      try {
        return Class.forName(className);
      } catch (ClassNotFoundException e) {
        return null;
      }
    }
    private static long getChannelFieldOffset(Class<?> clazz) {
      try {
        if (clazz != null && UnsafeUtil.hasUnsafeArrayOperations()) {
          Field field = clazz.getDeclaredField("channel");
          return UnsafeUtil.objectFieldOffset(field);
        }
      } catch (Throwable e) {
        // Absorb
      }
      return -1;
    }
  }
  