|
|
|
@ -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. |
|
|
|
@ -74,6 +75,12 @@ final class ByteBufferWriter { |
|
|
|
|
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. |
|
|
|
@ -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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|