1 // Protocol Buffers - Google's data interchange format 2 // Copyright 2008 Google Inc. All rights reserved. 3 // https://developers.google.com/protocol-buffers/ 4 // 5 // Redistribution and use in source and binary forms, with or without 6 // modification, are permitted provided that the following conditions are 7 // met: 8 // 9 // * Redistributions of source code must retain the above copyright 10 // notice, this list of conditions and the following disclaimer. 11 // * Redistributions in binary form must reproduce the above 12 // copyright notice, this list of conditions and the following disclaimer 13 // in the documentation and/or other materials provided with the 14 // distribution. 15 // * Neither the name of Google Inc. nor the names of its 16 // contributors may be used to endorse or promote products derived from 17 // this software without specific prior written permission. 18 // 19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 package com.google.protobuf; 32 33 import static java.lang.Math.max; 34 import static java.lang.Math.min; 35 36 import java.io.IOException; 37 import java.io.OutputStream; 38 import java.lang.ref.SoftReference; 39 import java.lang.reflect.Field; 40 import java.nio.ByteBuffer; 41 import java.nio.channels.WritableByteChannel; 42 43 /** Utility class to provide efficient writing of {@link ByteBuffer}s to {@link OutputStream}s. */ 44 final class ByteBufferWriter { ByteBufferWriter()45 private ByteBufferWriter() {} 46 47 /** 48 * Minimum size for a cached buffer. This prevents us from allocating buffers that are too small 49 * to be easily reused. 50 */ 51 // TODO(nathanmittler): tune this property or allow configuration? 52 private static final int MIN_CACHED_BUFFER_SIZE = 1024; 53 54 /** 55 * Maximum size for a cached buffer. If a larger buffer is required, it will be allocated but not 56 * cached. 57 */ 58 // TODO(nathanmittler): tune this property or allow configuration? 59 private static final int MAX_CACHED_BUFFER_SIZE = 16 * 1024; 60 61 /** The fraction of the requested buffer size under which the buffer will be reallocated. */ 62 // TODO(nathanmittler): tune this property or allow configuration? 63 private static final float BUFFER_REALLOCATION_THRESHOLD = 0.5f; 64 65 /** 66 * Keeping a soft reference to a thread-local buffer. This buffer is used for writing a {@link 67 * ByteBuffer} to an {@link OutputStream} when no zero-copy alternative was available. Using a 68 * "soft" reference since VMs may keep this reference around longer than "weak" (e.g. HotSpot will 69 * maintain soft references until memory pressure warrants collection). 70 */ 71 private static final ThreadLocal<SoftReference<byte[]>> BUFFER = 72 new ThreadLocal<SoftReference<byte[]>>(); 73 74 /** This is a hack for GAE, where {@code FileOutputStream} is unavailable. */ 75 private static final Class<?> FILE_OUTPUT_STREAM_CLASS = safeGetClass("java.io.FileOutputStream"); 76 77 private static final long CHANNEL_FIELD_OFFSET = getChannelFieldOffset(FILE_OUTPUT_STREAM_CLASS); 78 79 /** 80 * For testing purposes only. Clears the cached buffer to force a new allocation on the next 81 * invocation. 82 */ clearCachedBuffer()83 static void clearCachedBuffer() { 84 BUFFER.set(null); 85 } 86 87 /** 88 * Writes the remaining content of the buffer to the given stream. The buffer {@code position} 89 * will remain unchanged by this method. 90 */ write(ByteBuffer buffer, OutputStream output)91 static void write(ByteBuffer buffer, OutputStream output) throws IOException { 92 final int initialPos = buffer.position(); 93 try { 94 if (buffer.hasArray()) { 95 // Optimized write for array-backed buffers. 96 // Note that we're taking the risk that a malicious OutputStream could modify the array. 97 output.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining()); 98 } else if (!writeToChannel(buffer, output)) { 99 // Read all of the data from the buffer to an array. 100 // TODO(nathanmittler): Consider performance improvements for other "known" stream types. 101 final byte[] array = getOrCreateBuffer(buffer.remaining()); 102 while (buffer.hasRemaining()) { 103 int length = min(buffer.remaining(), array.length); 104 buffer.get(array, 0, length); 105 output.write(array, 0, length); 106 } 107 } 108 } finally { 109 // Restore the initial position. 110 buffer.position(initialPos); 111 } 112 } 113 getOrCreateBuffer(int requestedSize)114 private static byte[] getOrCreateBuffer(int requestedSize) { 115 requestedSize = max(requestedSize, MIN_CACHED_BUFFER_SIZE); 116 117 byte[] buffer = getBuffer(); 118 // Only allocate if we need to. 119 if (buffer == null || needToReallocate(requestedSize, buffer.length)) { 120 buffer = new byte[requestedSize]; 121 122 // Only cache the buffer if it's not too big. 123 if (requestedSize <= MAX_CACHED_BUFFER_SIZE) { 124 setBuffer(buffer); 125 } 126 } 127 return buffer; 128 } 129 needToReallocate(int requestedSize, int bufferLength)130 private static boolean needToReallocate(int requestedSize, int bufferLength) { 131 // First check against just the requested length to avoid the multiply. 132 return bufferLength < requestedSize 133 && bufferLength < requestedSize * BUFFER_REALLOCATION_THRESHOLD; 134 } 135 getBuffer()136 private static byte[] getBuffer() { 137 SoftReference<byte[]> sr = BUFFER.get(); 138 return sr == null ? null : sr.get(); 139 } 140 setBuffer(byte[] value)141 private static void setBuffer(byte[] value) { 142 BUFFER.set(new SoftReference<byte[]>(value)); 143 } 144 writeToChannel(ByteBuffer buffer, OutputStream output)145 private static boolean writeToChannel(ByteBuffer buffer, OutputStream output) throws IOException { 146 if (CHANNEL_FIELD_OFFSET >= 0 && FILE_OUTPUT_STREAM_CLASS.isInstance(output)) { 147 // Use a channel to write out the ByteBuffer. This will automatically empty the buffer. 148 WritableByteChannel channel = null; 149 try { 150 channel = (WritableByteChannel) UnsafeUtil.getObject(output, CHANNEL_FIELD_OFFSET); 151 } catch (ClassCastException e) { 152 // Absorb. 153 } 154 if (channel != null) { 155 channel.write(buffer); 156 return true; 157 } 158 } 159 return false; 160 } 161 safeGetClass(String className)162 private static Class<?> safeGetClass(String className) { 163 try { 164 return Class.forName(className); 165 } catch (ClassNotFoundException e) { 166 return null; 167 } 168 } 169 getChannelFieldOffset(Class<?> clazz)170 private static long getChannelFieldOffset(Class<?> clazz) { 171 try { 172 if (clazz != null && UnsafeUtil.hasUnsafeArrayOperations()) { 173 Field field = clazz.getDeclaredField("channel"); 174 return UnsafeUtil.objectFieldOffset(field); 175 } 176 } catch (Throwable e) { 177 // Absorb 178 } 179 return -1; 180 } 181 } 182