• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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