Skip to content

Commit

Permalink
Hacking ByteBufferWriter to work with GAE
Browse files Browse the repository at this point in the history
  • Loading branch information
nmittler committed Oct 20, 2016
1 parent 9c6940f commit c0f95c6
Showing 1 changed file with 45 additions and 5 deletions.
50 changes: 45 additions & 5 deletions java/core/src/main/java/com/google/protobuf/ByteBufferWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@
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.
Expand Down Expand Up @@ -74,6 +75,12 @@ private 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.
Expand All @@ -93,10 +100,7 @@ static void write(ByteBuffer buffer, OutputStream output) throws IOException {
// 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());
Expand Down Expand Up @@ -142,4 +146,40 @@ private static byte[] getBuffer() {
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;
}
}

0 comments on commit c0f95c6

Please sign in to comment.