Index: third_party/protobuf/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java |
diff --git a/third_party/protobuf/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java b/third_party/protobuf/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java |
index 0cc38175f908cd41bb6b2c4e46723d676b373fcb..6157a52f50f58f9e0a28621f69332b56f0a9b1b4 100644 |
--- a/third_party/protobuf/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java |
+++ b/third_party/protobuf/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java |
@@ -33,11 +33,12 @@ package com.google.protobuf; |
import static java.lang.Math.max; |
import static java.lang.Math.min; |
-import java.io.FileOutputStream; |
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. |
@@ -75,6 +76,12 @@ final class ByteBufferWriter { |
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. |
*/ |
@@ -93,10 +100,7 @@ final class ByteBufferWriter { |
// 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 (output instanceof FileOutputStream) { |
- // Use a channel to write out the ByteBuffer. This will automatically empty the buffer. |
- ((FileOutputStream) output).getChannel().write(buffer); |
- } else { |
+ } 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()); |
@@ -142,4 +146,40 @@ final class ByteBufferWriter { |
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; |
+ } |
} |