// 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; } }