diff options
author | nmittler <nathanmittler@google.com> | 2016-10-20 10:11:44 -0700 |
---|---|---|
committer | nmittler <nathanmittler@google.com> | 2016-10-20 11:25:11 -0700 |
commit | c0f95c6f942a8d269c9d432956ad9babb900df7e (patch) | |
tree | b08cf8027d4fde833307c95091dd718617620d37 | |
parent | 9c6940f3bb4d7893b4d453870acd374c1ae43a58 (diff) |
Hacking ByteBufferWriter to work with GAE
Fixes #2269
-rw-r--r-- | java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java | 50 |
1 files changed, 45 insertions, 5 deletions
diff --git a/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java b/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java index 0cc38175..6157a52f 100644 --- a/java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java +++ b/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; + } } |