You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2013/11/11 17:41:27 UTC

svn commit: r1540764 - in /subversion/trunk/subversion/bindings/javahl/native: ./ jniwrapper/

Author: brane
Date: Mon Nov 11 16:41:27 2013
New Revision: 1540764

URL: http://svn.apache.org/r1540764
Log:
Add a proper channel wrapper to JavaHL, and use it to implement the
tunnel agent callbacks.

* subversion/bindings/javahl/native/jniwrapper/jni_channel.hpp,
  subversion/bindings/javahl/native/jniwrapper/jni_channel.cpp:
   New; channel wrapper implementation.

* subversion/bindings/javahl/native/jniwrapper/jni_env.hpp
  (Java::Env::GetDirectBufferAddress): New JNI function wrapper.
* subversion/bindings/javahl/native/jniwrapper/jni_array.hpp
  (Java::ByteArray::ByteArray): New constructor for uninitialized arrays.
* subversion/bindings/javahl/native/jniwrapper/jni_exception.hpp
  (Java::IOException): New exception generator.
* subversion/bindings/javahl/native/jniwrapper/jni_base.cpp
  (Java::IOException::m_class_name): Initialize.

* subversion/bindings/javahl/native/org_apache_subversion_javahl_util_TunnelChannel.cpp:
   Reimplement everything using the new channel wrapper and jniwrapper.

Added:
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.cpp
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.hpp
Modified:
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_array.hpp
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_base.cpp
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_env.hpp
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_exception.hpp
    subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_TunnelChannel.cpp

Modified: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_array.hpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_array.hpp?rev=1540764&r1=1540763&r2=1540764&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_array.hpp (original)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_array.hpp Mon Nov 11 16:41:27 2013
@@ -52,6 +52,15 @@ public:
     {}
 
   /**
+   * Constructs a new, uninitialized array of size @a length.
+   */
+  explicit ByteArray(Env env, jsize length)
+    : m_env(env),
+      m_length(length),
+      m_array(m_env.NewByteArray(m_length))
+    {}
+
+  /**
    * Constructs a new array and wrapper from @a text.
    */
   explicit ByteArray(Env env, const char* text)

Modified: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_base.cpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_base.cpp?rev=1540764&r1=1540763&r2=1540764&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_base.cpp (original)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_base.cpp Mon Nov 11 16:41:27 2013
@@ -205,6 +205,9 @@ const char* const NullPointerException::
 const char* const OutOfMemoryError::m_class_name =
   "java/lang/OutOfMemoryError";
 
+const char* const IOException::m_class_name =
+  "java/io/IOException";
+
 } // namespace Java
 
 

Added: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.cpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.cpp?rev=1540764&view=auto
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.cpp (added)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.cpp Mon Nov 11 16:41:27 2013
@@ -0,0 +1,254 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#include <stdexcept>
+
+#include "jni_array.hpp"
+#include "jni_channel.hpp"
+
+#include "svn_private_config.h"
+
+namespace Java {
+
+namespace {
+// Get the ByteBuffer's internal array.
+jbyteArray get_array(Env env, jclass cls, jobject buffer,
+                     MethodID& mid_has_array, MethodID& mid_get_array)
+{
+  if (!mid_has_array)
+    mid_has_array = env.GetMethodID(cls, "hasArray", "()Z");
+  if (!env.CallBooleanMethod(buffer, mid_has_array))
+    return NULL;
+
+  if (!mid_get_array)
+    mid_get_array = env.GetMethodID(cls, "array", "()[B");
+  return jbyteArray(env.CallObjectMethod(buffer, mid_get_array));
+}
+
+// Get the offset in the ByteBuffer's array. NEVER call this function
+// unless the buffer actually has an accessible array.
+jint get_array_offset(Env env, jclass cls, jobject buffer,
+                      MethodID& mid_get_array_offset)
+{
+  if (!mid_get_array_offset)
+    mid_get_array_offset = env.GetMethodID(cls, "arrayOffset", "()I");
+  return env.CallIntMethod(buffer, mid_get_array_offset);
+}
+
+// Get the remaining space in a ByteBuffer.
+jint get_remaining(Env env, jclass cls, jobject buffer,
+                   MethodID& mid_get_remaining)
+{
+  if (!mid_get_remaining)
+    mid_get_remaining = env.GetMethodID(cls, "remaining", "()I");
+  return env.CallIntMethod(buffer, mid_get_remaining);
+}
+
+// Get the current position of a ByteBuffer.
+jint get_position(Env env, jclass cls, jobject buffer,
+                  MethodID& mid_get_position)
+{
+  if (!mid_get_position)
+    mid_get_position = env.GetMethodID(cls, "position", "()I");
+  return env.CallIntMethod(buffer, mid_get_position);
+}
+
+// Set the new position of a ByteBuffer.
+void set_position(Env env, jclass cls, jobject buffer,
+                  MethodID& mid_set_position,
+                  jint new_position)
+{
+  if (!mid_set_position)
+    mid_set_position = env.GetMethodID(cls,  "position",
+                                       "(I)Ljava/nio/Buffer;");
+  env.CallObjectMethod(buffer, mid_set_position, new_position);
+}
+
+// Get byte array contents from a ByteBuffer.
+void get_bytearray(Env env, jclass cls, jobject buffer,
+                   MethodID& mid_get_bytearray,
+                   ByteArray& array, jint length = -1, jint offset = 0)
+{
+  if (!mid_get_bytearray)
+    mid_get_bytearray = env.GetMethodID(cls, "get",
+                                        "([BII)Ljava/nio/ByteBuffer;");
+  env.CallObjectMethod(
+      buffer, mid_get_bytearray, array.get(), offset,
+      (length >= 0 ? length : (array.length() - offset)));
+}
+
+// Put byte array contents into a ByteBuffer.
+void put_bytearray(Env env, jclass cls, jobject buffer,
+                   MethodID& mid_put_bytearray,
+                   ByteArray& array, jint length = -1, jint offset = 0)
+{
+  if (!mid_put_bytearray)
+    mid_put_bytearray = env.GetMethodID(cls, "put",
+                                        "([BII)Ljava/nio/ByteBuffer;");
+  env.CallObjectMethod(buffer, mid_put_bytearray,
+                       array.get(), offset,
+                       (length >= 0 ? length : (array.length() - offset)));
+}
+
+struct BadReaderWriter : public ChannelReader, ChannelWriter
+{
+  BadReaderWriter() {}
+
+  virtual jint operator()(Env, void*, jint)
+    {
+      throw std::logic_error(_("Reading from write-only channel"));
+    }
+
+  virtual jint operator()(Env, const void*, jint)
+    {
+      throw std::logic_error(_("Writing to read-only channel"));
+    }
+} bad_reader_writer;
+
+} // anonymous namespace
+
+
+ChannelReader& ByteChannel::m_null_reader = bad_reader_writer;
+ChannelWriter& ByteChannel::m_null_writer = bad_reader_writer;
+
+const char* const ByteChannel::m_byte_buffer_class_name =
+  "java/nio/ByteBuffer";
+
+jint ByteChannel::read(jobject destination)
+{
+  const jint remaining = get_remaining(m_env, m_cls_byte_buffer, destination,
+                                       m_mid_byte_buffer_get_remaining);
+  if (!remaining)
+    {
+      // No space in the buffer; don't try to read anything.
+      return 0;
+    }
+
+  const jint position = get_position(m_env, m_cls_byte_buffer, destination,
+                                     m_mid_byte_buffer_get_position);
+
+  jint bytes_read = 0;
+  void* data = m_env.GetDirectBufferAddress(destination);
+  if (data)
+    {
+      data = static_cast<char*>(data) + position;
+      bytes_read = m_reader(m_env, data, remaining);
+    }
+  else
+    {
+      // It was not a direct buffer ... see if it has an array.
+      jbyteArray raw_array = get_array(m_env, m_cls_byte_buffer, destination,
+                                       m_mid_byte_buffer_has_array,
+                                       m_mid_byte_buffer_get_array);
+      if (raw_array)
+        {
+          const jint array_offset = get_array_offset(
+              m_env, m_cls_byte_buffer, destination,
+              m_mid_byte_buffer_get_array_offset);
+          ByteArray array(m_env, raw_array);
+          ByteArray::MutableContents contents(array);
+          data = contents.data();
+          data = static_cast<char*>(data) + position + array_offset;
+          bytes_read = m_reader(m_env, data, remaining);
+        }
+    }
+  if (data)
+    {
+      if (bytes_read > 0)
+        set_position(m_env, m_cls_byte_buffer, destination,
+                     m_mid_byte_buffer_set_position,
+                     position + bytes_read);
+      return bytes_read;
+    }
+
+  // No accessible array, either. Oh well. Create a byte array and
+  // push it into the buffer.
+  ByteArray array(m_env, remaining);
+  ByteArray::MutableContents contents(array);
+  bytes_read = m_reader(m_env, contents.data(), contents.length());
+  if (bytes_read > 0)
+    put_bytearray(m_env, m_cls_byte_buffer, destination,
+                  m_mid_byte_buffer_put_bytearray,
+                  array, bytes_read);
+  return bytes_read;
+}
+
+jint ByteChannel::write(jobject source)
+{
+  const jint remaining = get_remaining(m_env, m_cls_byte_buffer, source,
+                                       m_mid_byte_buffer_get_remaining);
+  if (!remaining)
+    {
+      // No data in the buffer; don't try to write anything.
+      return 0;
+    }
+
+  const jint position = get_position(m_env, m_cls_byte_buffer, source,
+                                     m_mid_byte_buffer_get_position);
+
+  jint bytes_written = 0;
+  const void* data = m_env.GetDirectBufferAddress(source);
+  if (data)
+    {
+      data = static_cast<const char*>(data) + position;
+      bytes_written = m_writer(m_env, data, remaining);
+    }
+  else
+    {
+      // It was not a direct buffer ... see if it has an array.
+      jbyteArray raw_array = get_array(m_env, m_cls_byte_buffer, source,
+                                       m_mid_byte_buffer_has_array,
+                                       m_mid_byte_buffer_get_array);
+      if (raw_array)
+        {
+          const jint array_offset = get_array_offset(
+              m_env, m_cls_byte_buffer, source,
+              m_mid_byte_buffer_get_array_offset);
+          const ByteArray array(m_env, raw_array);
+          ByteArray::Contents contents(array);
+          data = contents.data();
+          data = static_cast<const char*>(data) + position + array_offset;
+          bytes_written = m_writer(m_env, data, remaining);
+        }
+    }
+  if (data)
+    {
+      if (bytes_written > 0)
+        set_position(m_env, m_cls_byte_buffer, source,
+                     m_mid_byte_buffer_set_position,
+                     position + bytes_written);
+      return bytes_written;
+    }
+
+  // No accessible array, either. Oh well. Get an array from the
+  // buffer and read data from that.
+  ByteArray array(m_env, remaining);
+  get_bytearray(m_env, m_cls_byte_buffer, source,
+                m_mid_byte_buffer_get_bytearray,
+                array);
+  ByteArray::Contents contents(array);
+  bytes_written = m_writer(m_env, contents.data(), contents.length());
+  return bytes_written;
+}
+
+} // namespace Java

Added: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.hpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.hpp?rev=1540764&view=auto
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.hpp (added)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_channel.hpp Mon Nov 11 16:41:27 2013
@@ -0,0 +1,202 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you under the Apache License, Version 2.0 (the
+ *    "License"); you may not use this file except in compliance
+ *    with the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing,
+ *    software distributed under the License is distributed on an
+ *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *    KIND, either express or implied.  See the License for the
+ *    specific language governing permissions and limitations
+ *    under the License.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#ifndef SVN_JAVAHL_JNIWRAPPER_CHANNEL_HPP
+#define SVN_JAVAHL_JNIWRAPPER_CHANNEL_HPP
+
+#include "jni_env.hpp"
+
+namespace Java {
+
+/**
+ * Abstract base class for implementing channel read method internals.
+ *
+ * @since New in 1.9.
+ */
+struct ChannelReader
+{
+  /**
+   * Reads at most @a length bytes into @a buffer, returning the
+   * number of bytes read (which may be zero) or -1 if at
+   * end-of-stream.
+   */
+  virtual jint operator()(Env env, void* buffer, jint length) = 0;
+};
+
+/**
+ * Abstract base class for implementing channel write method internals.
+ *
+ * @since New in 1.9.
+ */
+struct ChannelWriter
+{
+  /**
+   * Writes at exactly @a length bytes from @a buffer, returning the
+   * number of bytes written (which may be zero).
+   */
+  virtual jint operator()(Env env, const void* buffer, jint length) = 0;
+};
+
+
+/**
+ * Wrapper for @c java.nio.channels.ByteChannel. Unlike most wrappers,
+ * this one does not actually represent a ByteChannel object. The
+ * assumption is that the native implementation will want to implement
+ * the read and write methods, not invoke them.
+ *
+ * Also serves as the (protected) base of the Readable- and
+ * WritableByteChannel interfaces; this is for purposes of code
+ * sharing only. We're not interested in replicating Java's class
+ * hierarchy here.
+ *
+ * @since New in 1.9.
+ */
+class ByteChannel
+{
+public:
+  /**
+   * Constructs a wrapper for @a channel with @a reader and @a writer
+   * as the read and write method implementations.
+   */
+  explicit ByteChannel(Env env, ChannelReader& reader, ChannelWriter& writer)
+    : m_env(env),
+      m_reader(reader),
+      m_writer(writer),
+      m_cls_byte_buffer(env.FindClass(m_byte_buffer_class_name))
+    {}
+
+  /**
+   * Reads bytes into @a destination, which must be a
+   * @c java.nio.ByteBuffer instance, from #m_reader.
+   * @return the number of bytes read, or -1 if at end-of-stream.
+   */
+  jint read(jobject destination);
+
+  /**
+   * Writes bytes from @a source, which must be a
+   * @c java.nio.ByteBuffer instance, to #m_writer.
+   * @return the number of bytes written.
+   */
+  jint write(jobject source);
+
+protected:
+  /**
+   * Constructor used by read-only subclasses.
+   */
+  explicit ByteChannel(Env env, ChannelReader& reader)
+    : m_env(env),
+      m_reader(reader),
+      m_writer(m_null_writer),
+      m_cls_byte_buffer(env.FindClass(m_byte_buffer_class_name))
+    {}
+
+  /**
+   * Constructor used by write-only subclasses.
+   */
+  explicit ByteChannel(Env env, ChannelWriter& writer)
+    : m_env(env),
+      m_reader(m_null_reader),
+      m_writer(writer),
+      m_cls_byte_buffer(env.FindClass(m_byte_buffer_class_name))
+    {}
+
+private:
+  Env m_env;
+  ChannelReader& m_reader;
+  ChannelWriter& m_writer;
+
+  static ChannelReader& m_null_reader;
+  static ChannelWriter& m_null_writer;
+
+  // Private references for the java.nio.ByteBuffer class.
+  static const char* const m_byte_buffer_class_name;
+  const jclass m_cls_byte_buffer;
+  MethodID m_mid_byte_buffer_has_array;
+  MethodID m_mid_byte_buffer_get_array;
+  MethodID m_mid_byte_buffer_get_array_offset;
+  MethodID m_mid_byte_buffer_get_remaining;
+  MethodID m_mid_byte_buffer_get_position;
+  MethodID m_mid_byte_buffer_set_position;
+  MethodID m_mid_byte_buffer_get_bytearray;
+  MethodID m_mid_byte_buffer_put_bytearray;
+};
+
+
+/**
+ * Wrapper for @c java.nio.channels.ReadableByteChannel.
+ *
+ * @since New in 1.9.
+ */
+class ReadableByteChannel : protected ByteChannel
+{
+public:
+  /**
+   * Constructs a wrapper for @a channel with @a reader the read
+   * method implementation.
+   */
+  explicit ReadableByteChannel(Env env, ChannelReader& reader)
+    : ByteChannel(env, reader)
+    {}
+
+  /**
+   * Reads bytes into @a destination, which must be a
+   * @c java.nio.ByteBuffer instance, from #m_reader.
+   * @return the number of bytes read, or -1 if at end-of-stream.
+   */
+  jint read(jobject destination)
+    {
+      return ByteChannel::read(destination);
+    }
+};
+
+
+/**
+ * Wrapper @c java.nio.channels.WritableByteChannel.
+ *
+ * @since New in 1.9.
+ */
+class WritableByteChannel : protected ByteChannel
+{
+public:
+  /**
+   * Constructs a wrapper for @a channel with @a writer as the write
+   * method implementation.
+   */
+  explicit WritableByteChannel(Env env, ChannelWriter& writer)
+    : ByteChannel(env, writer)
+    {}
+
+  /**
+   * Writes bytes from @a source, which must be a
+   * @c java.nio.ByteBuffer instance, to #m_writer.
+   * @return the number of bytes written.
+   */
+  jint write(jobject source)
+    {
+      return ByteChannel::write(source);
+    }
+};
+
+} // namespace Java
+
+#endif // SVN_JAVAHL_JNIWRAPPER_CHANNEL_HPP

Modified: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_env.hpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_env.hpp?rev=1540764&r1=1540763&r2=1540764&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_env.hpp (original)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_env.hpp Mon Nov 11 16:41:27 2013
@@ -509,6 +509,14 @@ public:
   SVN_JAVAHL_JNIWRAPPER_PRIMITIVE_TYPE_ARRAY(jdouble, Double)
 #undef SVN_JAVAHL_JNIWRAPPER_PRIMITIVE_TYPE_ARRAY
 
+  /** Wrapped JNI function. */
+  void* GetDirectBufferAddress(jobject buffer) const
+    {
+      void* const addr = m_env->GetDirectBufferAddress(buffer);
+      check_java_exception();
+      return addr;
+    }
+
 private:
   ::JNIEnv* m_env;
   static ::JavaVM* m_jvm;

Modified: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_exception.hpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_exception.hpp?rev=1540764&r1=1540763&r2=1540764&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_exception.hpp (original)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_exception.hpp Mon Nov 11 16:41:27 2013
@@ -206,6 +206,25 @@ private:
   static const char* const m_class_name;
 };
 
+/**
+ * Generator class for exceptions of type @c java.io.IOException.
+ *
+ * @since New in 1.9.
+ */
+class IOException : public Exception
+{
+public:
+  /**
+   * Constructs an exception generator object.
+   */
+  explicit IOException(Env env)
+    : Exception(env, m_class_name)
+    {}
+
+private:
+  static const char* const m_class_name;
+};
+
 } // namespace Java
 
 #endif // SVN_JAVAHL_JNIWRAPPER_ENV_HPP

Modified: subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_TunnelChannel.cpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_TunnelChannel.cpp?rev=1540764&r1=1540763&r2=1540764&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_TunnelChannel.cpp (original)
+++ subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_TunnelChannel.cpp Mon Nov 11 16:41:27 2013
@@ -25,8 +25,6 @@
  *        TunnelChannel, RequestChannel and ResponseChannel
  */
 
-#include <assert.h>             // TEMPORARY until we handle weird byte arrays
-
 #include <string>
 
 #include <apr_file_io.h>
@@ -35,191 +33,142 @@
 #include "../include/org_apache_subversion_javahl_util_RequestChannel.h"
 #include "../include/org_apache_subversion_javahl_util_ResponseChannel.h"
 
-#include "JNIUtil.h"
-#include "JNIStackElement.h"
-#include "JNIByteArray.h"
+#include "jniwrapper/jni_exception.hpp"
+#include "jniwrapper/jni_channel.hpp"
+#include "jniwrapper/jni_stack.hpp"
 
 #include "svn_private_config.h"
 
 namespace {
-apr_file_t* get_file_descriptor(jlong jfd)
+apr_file_t* get_file_descriptor(Java::Env env, jlong jfd)
 {
   apr_file_t* fd = reinterpret_cast<apr_file_t*>(jfd);
   if (!fd)
-    {
-      JNIUtil::throwNullPointerException("nativeChannel");
-      return NULL;
-    }
+    Java::NullPointerException(env).raise("nativeChannel");
   return fd;
 }
 
-void throw_IOException(const char* message, apr_status_t status)
+void throw_IOException(Java::Env env, const char* message,
+                       apr_status_t status)
 {
+  char buf[1024];
   std::string msg(message);
-  if (status)
-    {
-      char buf[1024];
-      apr_strerror(status, buf, sizeof(buf) - 1);
-      msg += ": ";
-      msg += buf;
-    }
-  JNIUtil::raiseThrowable("java/io/IOException", msg.c_str());
+  apr_strerror(status, buf, sizeof(buf) - 1);
+  msg += buf;
+  Java::IOException(env).raise(msg.c_str());
 }
 
-class ByteBufferProxy
+class TunnelReader : public Java::ChannelReader
 {
 public:
-  ByteBufferProxy(jobject buf, JNIEnv* env)
-    : m_buf(buf),
-      m_direct(env->GetDirectBufferAddress(buf)),
-      m_array(m_direct ? NULL : get_array(buf, env)),
-      m_array_offset(m_array ? get_array_offset(buf, env) : 0),
-      m_offset(get_position(buf, env)),
-      m_size(get_remaining(buf, env))
+  explicit TunnelReader(Java::Env env, jlong jnative_channel)
+    : m_fd(get_file_descriptor(env, jnative_channel))
     {}
 
-  jint read(apr_file_t* fd, JNIEnv* env)
+  virtual jint operator()(Java::Env env, void* buffer, jint length)
     {
-      if (!m_size)
+      if (!length)
         return 0;
 
-      JNIByteArray arr(m_array, false, false);
-      apr_size_t bytes_read = m_size;
-      apr_status_t status = apr_file_read(
-          fd, get_base_address(arr), &bytes_read);
+      apr_size_t bytes_read = length;
+      const apr_status_t status = apr_file_read(m_fd, buffer, &bytes_read);
       if (status && !APR_STATUS_IS_EOF(status))
         {
-          throw_IOException(_("Error reading from native file handle"),
-                            status);
-          return 0;
+          throw_IOException(
+              env, _("Error reading from native file handle: "),
+              status);
+          return -1;
         }
-      update_position(bytes_read, env);
+      if (APR_STATUS_IS_EOF(status))
+        return -1;
       return jint(bytes_read);
     }
 
-  jint write(apr_file_t* fd, JNIEnv* env)
+private:
+  apr_file_t* const m_fd;
+};
+
+class TunnelWriter : public Java::ChannelWriter
+{
+public:
+  explicit TunnelWriter(Java::Env env, jlong jnative_channel)
+    : m_fd(get_file_descriptor(env, jnative_channel))
+    {}
+
+  virtual jint operator()(Java::Env env, const void* buffer, jint length)
     {
-      if (!m_size)
+      if (!length)
         return 0;
 
-      JNIByteArray arr(m_array);
       apr_size_t bytes_written;
-      apr_status_t status = apr_file_write_full(
-          fd, get_base_address(arr), m_size, &bytes_written);
+      const apr_status_t status =
+        apr_file_write_full(m_fd, buffer, length, &bytes_written);
       if (status)
         {
-          throw_IOException(_("Error writing to native file handle"),
-                            status);
-          return 0;
+          throw_IOException(
+              env, _("Error writing to native file handle: "),
+              status);
+          return -1;
         }
-      update_position(bytes_written, env);
       return jint(bytes_written);
     }
 
 private:
-  void *get_base_address(JNIByteArray& arr)
-    {
-      void* base = (m_direct ? m_direct
-                    : const_cast<signed char*>(arr.getBytes()));
-      // FIXME: We do not currently support buffers that are nether
-      // direct, nor have an accessible array.
-      assert(base != 0);
-      return static_cast<char*>(base) + m_offset + m_array_offset;
-    }
-
-  void update_position(apr_size_t amount, JNIEnv* env)
-    {
-      jmethodID mid = env->GetMethodID(
-          env->GetObjectClass(m_buf), "position", "(I)Ljava/nio/Buffer;");
-      if (mid)
-        env->CallObjectMethod(m_buf, mid, jint(amount));
-    }
-
-  static jbyteArray get_array(jobject buf, JNIEnv* env)
-    {
-      jclass cls = env->GetObjectClass(buf);
-      jmethodID mid = env->GetMethodID(cls, "hasArray", "()Z");
-      if (!mid)
-        return NULL;
-
-      jboolean has_array = env->CallBooleanMethod(buf, mid);
-      if (!has_array)
-        return NULL;
-
-      mid = env->GetMethodID(cls, "array", "()[B");
-      if (mid)
-        return jbyteArray(env->CallObjectMethod(buf, mid));
-      return NULL;
-    }
-
-  static apr_size_t get_array_offset(jobject buf, JNIEnv* env)
-    {
-      jmethodID mid = env->GetMethodID(
-          env->GetObjectClass(buf), "arrayOffset", "()I");
-      if (mid)
-        return env->CallIntMethod(buf, mid);
-      return 0;
-    }
-
-  static apr_size_t get_position(jobject buf, JNIEnv* env)
-    {
-      jmethodID mid = env->GetMethodID(
-          env->GetObjectClass(buf), "position", "()I");
-      if (mid)
-        return env->CallIntMethod(buf, mid);
-      return 0;
-    }
-
-  static apr_size_t get_remaining(jobject buf, JNIEnv* env)
-    {
-      jmethodID mid = env->GetMethodID(
-          env->GetObjectClass(buf), "remaining", "()I");
-      if (mid)
-        return env->CallIntMethod(buf, mid);
-      return 0;
-    }
-
-  jobject m_buf;
-  void *m_direct;
-  jbyteArray m_array;
-  apr_size_t m_array_offset;
-  apr_size_t m_offset;
-  apr_size_t m_size;
+  apr_file_t* const m_fd;
 };
+
 } // anonymous namespace
 
+
 JNIEXPORT void JNICALL
 Java_org_apache_subversion_javahl_util_TunnelChannel_nativeClose(
-    JNIEnv* env, jclass jclazz, jlong nativeChannel)
+    JNIEnv* jenv, jclass jclazz, jlong jnative_channel)
 {
-  JNIEntryStatic(TunnelChannel, close);
-  apr_file_t* fd = reinterpret_cast<apr_file_t*>(nativeChannel);
-  if (!fd)
-    return;
+  SVN_JAVAHL_JNI_TRY_STATIC(TunnelChannel, close)
+    {
+      const Java::Env env(jenv);
+
+      apr_file_t* const fd = get_file_descriptor(env, jnative_channel);
+      if (!fd)
+        return;
 
-  apr_status_t status = apr_file_close(fd);
-  if (status)
-    throw_IOException(_("Error closing native file handle"), status);
+      const apr_status_t status = apr_file_close(fd);
+      if (status)
+        throw_IOException(
+            env, _("Error closing native file handle: "),
+            status);
+    }
+  SVN_JAVAHL_JNI_CATCH;
 }
 
 JNIEXPORT jint JNICALL
 Java_org_apache_subversion_javahl_util_RequestChannel_nativeRead(
-    JNIEnv* env, jclass jclazz, jlong nativeChannel, jobject dst)
+    JNIEnv* jenv, jclass jclazz, jlong jnative_channel, jobject jdst_buffer)
 {
-  JNIEntryStatic(RequestChannel, read);
-  apr_file_t* fd = get_file_descriptor(nativeChannel);
-  if (fd)
-    return ByteBufferProxy(dst, env).read(fd, env);
+  SVN_JAVAHL_JNI_TRY_STATIC(RequestChannel, read)
+    {
+      const Java::Env env(jenv);
+
+      TunnelReader reader(env, jnative_channel);
+      Java::ReadableByteChannel channel(env, reader);
+      return channel.read(jdst_buffer);
+    }
+  SVN_JAVAHL_JNI_CATCH;
   return -1;
 }
 
 JNIEXPORT jint JNICALL
 Java_org_apache_subversion_javahl_util_ResponseChannel_nativeWrite(
-    JNIEnv* env, jclass jclazz, jlong nativeChannel, jobject src)
+    JNIEnv* jenv, jclass jclazz, jlong jnative_channel, jobject jsrc_buffer)
 {
-  JNIEntryStatic(ResponseChannel, write);
-  apr_file_t* fd = get_file_descriptor(nativeChannel);
-  if (fd)
-    return ByteBufferProxy(src, env).write(fd, env);
+  SVN_JAVAHL_JNI_TRY_STATIC(ResponseChannel, write)
+    {
+      const Java::Env env(jenv);
+
+      TunnelWriter writer(env, jnative_channel);
+      Java::WritableByteChannel channel(env, writer);
+      return channel.write(jsrc_buffer);
+    }
+  SVN_JAVAHL_JNI_CATCH;
   return -1;
 }