[Jython-checkins] jython: Refactor array-based PyBuffer implementations.

jeff.allen jython-checkins at python.org
Sat Aug 27 09:12:24 EDT 2016


https://hg.python.org/jython/rev/1d7c5ac12419
changeset:   7941:1d7c5ac12419
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Thu Jun 09 23:05:09 2016 +0100
summary:
  Refactor array-based PyBuffer implementations.

Quite substantial change to the class hierarchy for the array-based
buffers to permit sharing with a parallel one using ByteBuffer. The
ByteBuffer implementation is not in this commit (see later). There is
a bug in copyFrom, when copying between buffers exported by the same
object, that causes test_memoryview to fail.

files:
  src/org/python/core/PyBUF.java                          |   20 +-
  src/org/python/core/PyBuffer.java                       |   81 +-
  src/org/python/core/buffer/BaseArrayBuffer.java         |  279 ++++
  src/org/python/core/buffer/BaseBuffer.java              |  608 ++++++---
  src/org/python/core/buffer/SimpleBuffer.java            |  104 +-
  src/org/python/core/buffer/SimpleStringBuffer.java      |   57 +-
  src/org/python/core/buffer/SimpleWritableBuffer.java    |   72 +-
  src/org/python/core/buffer/Strided1DBuffer.java         |  134 +-
  src/org/python/core/buffer/Strided1DWritableBuffer.java |   59 +-
  src/org/python/core/buffer/ZeroByteBuffer.java          |   54 +-
  tests/java/org/python/core/PyBufferTest.java            |  185 ++-
  tests/java/org/python/core/PyBufferTestSupport.java     |    2 +-
  12 files changed, 1021 insertions(+), 634 deletions(-)


diff --git a/src/org/python/core/PyBUF.java b/src/org/python/core/PyBUF.java
--- a/src/org/python/core/PyBUF.java
+++ b/src/org/python/core/PyBUF.java
@@ -51,17 +51,17 @@
     int[] getShape();
 
     /**
-     * The number of units (bytes) stored in each indexable item.
+     * The number of bytes stored in each indexable item.
      *
-     * @return the number of units (bytes) comprising each item.
+     * @return the number of bytes comprising each item.
      */
     int getItemsize();
 
     /**
-     * The total number of units (bytes) stored, which will be the product of the elements of the
-     * <code>shape</code> array, and the item size in units.
+     * The total number of bytes represented by the view, which will be the product of the elements of the
+     * <code>shape</code> array, and the item size in bytes.
      *
-     * @return the total number of units stored.
+     * @return the total number of bytes represented.
      */
     int getLen();
 
@@ -143,7 +143,7 @@
     static final int STRIDES = 0x0010 | ND;
     /**
      * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
-     * specify that it will assume C-order organisation of the units. <code>getBuffer</code> will
+     * specify that it will assume C-order organisation of the items. <code>getBuffer</code> will
      * raise an exception if the exporter's buffer is not C-ordered. <code>C_CONTIGUOUS</code>
      * implies <code>STRIDES</code>.
      */
@@ -152,14 +152,14 @@
     static final int C_CONTIGUOUS = 0x0020 | STRIDES;
     /**
      * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
-     * specify that it will assume Fortran-order organisation of the units. <code>getBuffer</code>
+     * specify that it will assume Fortran-order organisation of the items. <code>getBuffer</code>
      * will raise an exception if the exporter's buffer is not Fortran-ordered.
      * <code>F_CONTIGUOUS</code> implies <code>STRIDES</code>.
      */
     static final int F_CONTIGUOUS = 0x0040 | STRIDES;
     /**
      * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
-     * specify that it will assume a contiguous organisation of the units, but will enquire which
+     * specify that it will assume a contiguous organisation of the items, but will enquire which
      * organisation it actually is.
      *
      * <code>getBuffer</code> will raise an exception if the exporter's buffer is not contiguous.
@@ -244,13 +244,13 @@
     static final int NAVIGATION = SIMPLE | ND | STRIDES | INDIRECT;
     /**
      * A constant used by the exporter in processing {@link BufferProtocol#getBuffer(int)} to check
-     * for assumed C-order organisation of the units.
+     * for assumed C-order organisation of the items.
      * <code>C_CONTIGUOUS = IS_C_CONTIGUOUS | STRIDES</code>.
      */
     static final int IS_C_CONTIGUOUS = C_CONTIGUOUS & ~STRIDES;
     /**
      * A constant used by the exporter in processing {@link BufferProtocol#getBuffer(int)} to check
-     * for assumed C-order Fortran-order organisation of the units.
+     * for assumed C-order Fortran-order organisation of the items.
      * <code>F_CONTIGUOUS = IS_F_CONTIGUOUS | STRIDES</code>.
      */
     static final int IS_F_CONTIGUOUS = F_CONTIGUOUS & ~STRIDES;
diff --git a/src/org/python/core/PyBuffer.java b/src/org/python/core/PyBuffer.java
--- a/src/org/python/core/PyBuffer.java
+++ b/src/org/python/core/PyBuffer.java
@@ -106,41 +106,40 @@
      * further study.)
      *
      * @param dest destination byte array
-     * @param destPos index in the destination array of the byte [0]
+     * @param destPos byte-index in the destination array of the byte [0]
      * @throws IndexOutOfBoundsException if the destination cannot hold it
      */
     void copyTo(byte[] dest, int destPos) throws IndexOutOfBoundsException, PyException;
 
     /**
-     * Copy a simple slice of the buffer to the destination byte array, defined by a starting index
-     * and length in the source buffer. This may validly be done only for a one-dimensional buffer,
-     * as the meaning of the starting index is otherwise not defined. The length (like the source
-     * index) is in source buffer <b>items</b>: <code>length*itemsize</code> bytes will be occupied
-     * in the destination.
+     * Copy a simple slice of the buffer-view to the destination byte array, defined by a starting
+     * item-index in the source buffer and the <code>count</code> of items to copy. This may validly
+     * be done only for a one-dimensional buffer, as the meaning of the starting item-index is
+     * otherwise not defined. <code>count*itemsize</code> bytes will be occupied in the destination.
      *
-     * @param srcIndex starting index in the source buffer
+     * @param srcIndex starting item-index in the source buffer
      * @param dest destination byte array
-     * @param destPos index in the destination array of the item [0,...]
-     * @param length number of items to copy
+     * @param destPos byte-index in the destination array of the source item [0,...]
+     * @param count number of items to copy
      * @throws IndexOutOfBoundsException if access out of bounds in source or destination
      */
-    void copyTo(int srcIndex, byte[] dest, int destPos, int length)     // mimic arraycopy args
+    void copyTo(int srcIndex, byte[] dest, int destPos, int count)     // mimic arraycopy args
             throws IndexOutOfBoundsException, PyException;
 
     /**
-     * Copy bytes from a slice of a (Java) byte array into the buffer. This may validly be done only
-     * for a one-dimensional buffer, as the meaning of the starting index is not otherwise defined.
-     * The length (like the destination index) is in buffer <b>items</b>:
-     * <code>length*itemsize</code> bytes will be read from the source.
+     * Copy from a slice of a (Java) byte array into the buffer starting at a given destination
+     * item-index. This may validly be done only for a one-dimensional buffer, as the meaning of the
+     * destination index is not otherwise defined. <code>count*itemsize</code> bytes will be read
+     * from the source.
      *
      * @param src source byte array
      * @param srcPos location in source of first byte to copy
-     * @param destIndex starting index in the destination (i.e. <code>this</code>)
-     * @param length number of bytes to copy in
+     * @param destIndex starting item-index in the destination (i.e. <code>this</code>)
+     * @param count number of items to copy in
      * @throws IndexOutOfBoundsException if access out of bounds in source or destination
      * @throws PyException (TypeError) if read-only buffer
      */
-    void copyFrom(byte[] src, int srcPos, int destIndex, int length)    // mimic arraycopy args
+    void copyFrom(byte[] src, int srcPos, int destIndex, int count)    // mimic arraycopy args
             throws IndexOutOfBoundsException, PyException;
 
     /**
@@ -202,10 +201,10 @@
      *
      * @param flags specifying features demanded and the navigational capabilities of the consumer
      * @param start index in the current buffer
-     * @param length number of items in the required slice
+     * @param count number of items in the required slice
      * @return a buffer representing the slice
      */
-    public PyBuffer getBufferSlice(int flags, int start, int length);
+    public PyBuffer getBufferSlice(int flags, int start, int count);
 
     /**
      * Get a <code>PyBuffer</code> that represents a slice of the current one described in terms of
@@ -217,7 +216,7 @@
      * Suppose that <i>x(i)</i> denotes the <i>i</i>th element of the current buffer, that is, the
      * byte retrieved by <code>this.byteAt(i)</code> or the unit indicated by
      * <code>this.getPointer(i)</code>. A request for a slice where <code>start</code> <i>= s</i>,
-     * <code>length</code> <i>= N</i> and <code>stride</code> <i>= m</i>, results in a buffer
+     * <code>count</code> <i>= N</i> and <code>stride</code> <i>= m</i>, results in a buffer
      * <i>y</i> such that <i>y(k) = x(s+km)</i> where <i>k=0..(N-1)</i>. In Python terms, this is
      * the slice <i>x[s : s+(N-1)m+1 : m]</i> (if <i>m>0</i>) or the slice <i>x[s : s+(N-1)m-1 :
      * m]</i> (if <i>m<0</i>). Implementations should check that this range is entirely within
@@ -226,29 +225,31 @@
      * In a simple buffer backed by a contiguous byte array, the result is a strided PyBuffer on the
      * same storage but where the offset is adjusted by <i>s</i> and the stride is as supplied. If
      * the current buffer is already strided and/or has an item size larger than single bytes, the
-     * new <code>start</code> index, <code>length</code> and <code>stride</code> will be translated
+     * new <code>start</code> index, <code>count</code> and <code>stride</code> will be translated
      * from the arguments given, through this buffer's stride and item size. The caller always
      * expresses <code>start</code> and <code>strides</code> in terms of the abstract view of this
      * buffer.
      *
      * @param flags specifying features demanded and the navigational capabilities of the consumer
      * @param start index in the current buffer
-     * @param length number of items in the required slice
+     * @param count number of items in the required slice
      * @param stride index-distance in the current buffer between consecutive items in the slice
      * @return a buffer representing the slice
      */
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride);
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride);
 
     // java.nio access to actual storage
     //
 
     /**
      * Obtain a {@link java.nio.ByteBuffer} giving access to the bytes that hold the data being
-     * exported to the consumer. The position of the buffer is at the item with zero index, the
-     * limit of the buffer is one beyond the largest valid index, and the mark is undefined.
+     * exported by the original object. The position of the buffer is at the first byte of the item
+     * with zero index (quite possibly not the lowest valid byte-index), the limit of the buffer is
+     * beyond the largest valid byte index, and the mark is undefined.
      * <p>
-     * For a one-dimensional contiguous buffer, assuming the following client code where
-     * <code>obj</code> has type <code>BufferProtocol</code>:
+     * For a one-dimensional contiguous buffer, the limit is one byte beyond the last item, so that
+     * consecutive reads from the <code>ByteBuffer</code> return the data in order. Assuming the
+     * following client code where <code>obj</code> has type <code>BufferProtocol</code>:
      *
      * <pre>
      * PyBuffer a = obj.getBuffer(PyBUF.SIMPLE);
@@ -259,18 +260,13 @@
      * the item with index <code>k</code> is in <code>bb</code> at positions
      * <code>bb.pos()+k*itemsize</code> to <code>bb.pos()+(k+1)*itemsize - 1</code> inclusive. And
      * if <code>itemsize==1</code>, the item is simply the byte at position <code>bb.pos()+k</code>.
-     * The buffer limit is set to the first byte beyond the valid data. A block read or write will
-     * therefore access the contents sequentially. In a one-dimensional contiguous buffer (only) it
-     * is safe to rely on <code>bb.remaining()</code> to obtain the number of bytes representing the
-     * object state.
      * <p>
      * If the buffer is multidimensional or non-contiguous (strided), the buffer position is still
-     * the (first byte of) the item at index <code>[0]</code> or <code>[0,...,0]</code>, and the
-     * limit is one item beyond the valid data. However, it is necessary to navigate <code>bb</code>
-     * using the <code>shape</code>, <code>strides</code> and maybe <code>suboffsets</code> provided
-     * by the API.
+     * the (first byte of) the item at index <code>[0]</code> or <code>[0,...,0]</code>. However, it
+     * is necessary to navigate <code>bb</code> using the <code>shape</code>, <code>strides</code>
+     * and maybe <code>suboffsets</code> provided by the API.
      *
-     * @return a ByteBuffer onto the exported data contents.
+     * @return a <code>ByteBuffer</code> onto the exported data contents.
      */
     ByteBuffer getNIOByteBuffer();
 
@@ -298,19 +294,18 @@
      */
     ByteBuffer getNIOByteBuffer(int... indices);
 
-    // Direct access to actual storage (deprecated)
-    //
-
     /**
-     * Determine whether the exporter is able to offer direct access to the exported storage as a
-     * Java byte array (through the API that involves class {@link Pointer}), or only supports the
+     * Report whether the exporter is able to offer direct access to the exported storage as a Java
+     * byte array (through the API that involves class {@link Pointer}), or only supports the
      * abstract API. See also {@link PyBUF#AS_ARRAY}.
      *
      * @return true if array access is supported, false if it is not.
      */
-    // XXX Pending: @Deprecated
     boolean hasArray();
 
+    // Direct access to actual storage (deprecated)
+    //
+
     /**
      * A class that references a <code>byte[]</code> array and a particular offset within it, as the
      * return type for methods that give direct access to byte-oriented data exported by a Python
@@ -410,7 +405,7 @@
      * free to navigate the underlying buffer <code>b.storage</code> without respecting these
      * boundaries. If the buffer is non-contiguous, the above description is still valid (since a
      * multi-byte item must itself be contiguously stored), but in any additional navigation of
-     * <code>b.storage[]</code> to other units, the client must use the shape, strides and
+     * <code>b.storage[]</code> to other items, the client must use the shape, strides and
      * sub-offsets provided by the API. Normally one starts <code>b = a.getBuf()</code> in order to
      * establish the offset of index [0,...,0].
      *
diff --git a/src/org/python/core/buffer/BaseArrayBuffer.java b/src/org/python/core/buffer/BaseArrayBuffer.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/core/buffer/BaseArrayBuffer.java
@@ -0,0 +1,279 @@
+package org.python.core.buffer;
+
+import java.nio.ByteBuffer;
+
+import org.python.core.PyBUF;
+import org.python.core.PyBuffer;
+import org.python.core.PyException;
+
+/**
+ * Base implementation of the Buffer API for when the storage implementation is <code>byte[]</code>.
+ * The description of {@link BaseBuffer} mostly applies. Methods provided or overridden here are
+ * appropriate to 1-dimensional arrays backed by <code>byte[]</code>.
+ *
+ */
+public abstract class BaseArrayBuffer extends BaseBuffer implements PyBuffer {
+
+    /**
+     * Reference to the underlying <code>byte[]</code> storage that the exporter is sharing with the
+     * consumer. The data need not occupy the whole array: in the constructor of a particular type
+     * of buffer, the exporter usually indicates an offset to the first significant byte and length
+     * (contiguous cases) or the index in <code>storage</code> that should be treated as the item
+     * with index zero (retrieved say by <code>buf.byteAt(0)</code>).
+     */
+    protected byte[] storage;
+
+    /**
+     * Construct an instance of <code>BaseArrayBuffer</code> in support of a sub-class, specifying
+     * the 'feature flags', or at least a starting set to be adjusted later. These are the features
+     * of the buffer exported, not the flags that form the consumer's request. The buffer will be
+     * read-only unless {@link PyBUF#WRITABLE} is set in the feature flags. {@link PyBUF#FORMAT} and
+     * {@link PyBUF#AS_ARRAY} are implicitly added to the feature flags. The navigation arrays are
+     * all null, awaiting action by the sub-class constructor. To complete initialisation, the
+     * sub-class normally must assign: the buffer ( {@link #storage}, {@link #index0}), and the
+     * navigation arrays ({@link #shape}, {@link #strides}), and call
+     * {@link #checkRequestFlags(int)} passing the consumer's request flags.
+     *
+     * @param featureFlags bit pattern that specifies the actual features allowed/required
+     */
+    protected BaseArrayBuffer(int featureFlags) {
+        super(featureFlags | AS_ARRAY);
+    }
+
+    @Override
+    protected int getSize() {
+        return shape[0];
+    }
+
+    @Override
+    public int getLen() {
+        return shape[0] * getItemsize();
+    }
+
+    @Override
+    protected byte byteAtImpl(int byteIndex) throws IndexOutOfBoundsException {
+        return storage[byteIndex];
+    }
+
+    @Override
+    protected void storeAtImpl(byte value, int byteIndex) throws IndexOutOfBoundsException,
+            PyException {
+        checkWritable();
+        storage[byteIndex] = value;
+    }
+
+    @Override
+    protected int byteIndex(int... indices) throws IndexOutOfBoundsException {
+        // BaseBuffer implementation can be simplified since if indices.length!=1 we error.
+        checkDimension(indices.length); // throws if != 1
+        return byteIndex(indices[0]);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Specialised to one-dimensional, possibly strided buffer.
+     */
+    @Override
+    protected int calcGreatestIndex() {
+        int stride = strides[0];
+        if (stride == 1) {
+            return index0 + shape[0] - 1;
+        } else if (stride > 0) {
+            return index0 + (shape[0] - 1) * stride;
+        } else {
+            return index0 - 1;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Specialised to one-dimensional, possibly strided buffer.
+     */
+    @Override
+    protected int calcLeastIndex() {
+        int stride = strides[0];
+        if (stride < 0) {
+            return index0 + (shape[0] - 1) * stride;
+        } else {
+            return index0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The implementation in <code>BaseArrayBuffer</code> deals with the general one-dimensional
+     * case of arbitrary item size and stride.
+     */
+    @Override
+    public void copyTo(int srcIndex, byte[] dest, int destPos, int count)
+            throws IndexOutOfBoundsException {
+
+        if (count > 0) {
+            // Pick up attributes necessary to choose an efficient copy strategy
+            int itemsize = getItemsize();
+            int stride = getStrides()[0];
+            int skip = stride - itemsize;
+            int s = byteIndex(srcIndex);
+
+            // Strategy depends on whether items are laid end-to-end contiguously or there are gaps
+            if (skip == 0) {
+                // stride == itemsize: straight copy of contiguous bytes
+                System.arraycopy(storage, s, dest, destPos, count * itemsize);
+            } else {
+                int limit = s + count * stride, d = destPos;
+                if (itemsize == 1) {
+                    // Non-contiguous copy: single byte items
+                    for (; s != limit; s += stride) {
+                        dest[d++] = storage[s];
+                    }
+                } else {
+                    // Non-contiguous copy: each time, copy itemsize bytes then skip
+                    for (; s != limit; s += skip) {
+                        int t = s + itemsize;
+                        while (s < t) {
+                            dest[d++] = storage[s++];
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default implementation in <code>BaseArrayBuffer</code> deals with the general
+     * one-dimensional case of arbitrary item size and stride.
+     */
+    @Override
+    public void copyFrom(byte[] src, int srcPos, int destIndex, int count)
+            throws IndexOutOfBoundsException, PyException {
+
+        checkWritable();
+
+        if (count > 0) {
+            // Pick up attributes necessary to choose an efficient copy strategy
+            int itemsize = getItemsize();
+            int stride = getStrides()[0];
+            int skip = stride - itemsize;
+            int d = byteIndex(destIndex);
+
+            // Strategy depends on whether items are laid end-to-end or there are gaps
+            if (skip == 0) {
+                // Straight copy of contiguous bytes
+                System.arraycopy(src, srcPos, storage, d, count * itemsize);
+            } else {
+                // Non-contiguous copy: single byte items
+                int limit = d + count * stride, s = srcPos;
+                if (itemsize == 1) {
+                    for (; d != limit; d += stride) {
+                        storage[d] = src[s++];
+                    }
+                } else {
+                    // Non-contiguous copy: itemsize bytes then skip to next item
+                    for (; d != limit; d += skip) {
+                        int t = d + itemsize;
+                        while (d < t) {
+                            storage[d++] = src[s++];
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
+        if (src instanceof BaseArrayBuffer) {
+            copyFromArrayBuffer((BaseArrayBuffer)src);
+        } else {
+            super.copyFrom(src);
+        }
+    }
+
+    private void copyFromArrayBuffer(BaseArrayBuffer src) throws IndexOutOfBoundsException,
+            PyException {
+
+        checkWritable();
+        src.checkDimension(1);
+
+        int itemsize = getItemsize();
+        int count = getSize();
+
+        // Block operation if different item or overall size (permit reshape)
+        if (src.getItemsize() != itemsize || src.getSize() != count) {
+            throw differentStructure();
+        }
+
+        for (int i = 0; i < count; i++) {
+            int s = src.byteIndex(i), d = byteIndex(i);
+            for (int j = 0; j < itemsize; j++) {
+                storage[d++] = src.byteAtImpl(s++);
+            }
+        }
+    }
+
+    /**
+     * Copy blocks of bytes, equally spaced in the source array, to locations equally spaced in the
+     * destination array, which may be the same array. The byte at
+     * <code>src[srcPos+k*srcStride+j]</code> will be copied to
+     * <code>dst[dstPos+k*dstStride+j]</code> for <code>0≤k<count</code> and
+     * <code>0≤j<size</code>. When the source and destination are the same array, the method
+     * deals correctly with the risk that a byte gets written under the alias <code>dst[x]</code>
+     * before it should have been copied referenced as <code>src[y]</code>.
+     *
+     * @param size of the blocks of bytes
+     * @param src the source array
+     * @param srcPos the position of the first block in the source
+     * @param srcStride the interval between the start of each block in the source
+     * @param dst the destination array
+     * @param dstPos the position of the first block in the destination
+     * @param dstStride the interval between the start of each block in the destination
+     * @param count the number of blocks to copy
+     */
+    private static void slicedArrayCopy(int size, byte[] src, int srcPos, int srcStride,
+            byte[] dst, int dstPos, int dstStride, int count) {}
+
+    @Override
+    protected ByteBuffer getNIOByteBufferImpl() {
+        // The buffer spans the whole storage, which may include data not in the view
+        ByteBuffer b = ByteBuffer.wrap(storage);
+        // Return as read-only if it is.
+        return isReadonly() ? b.asReadOnlyBuffer() : b;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * <code>BaseArrayBuffer</code> provides a reference to the storage array even when the buffer
+     * is intended not to be writable. There can be no enforcement of read-only character once a
+     * reference to the byte array has been handed out.
+     */
+    @SuppressWarnings("deprecation")
+    @Override
+    public Pointer getBuf() {
+        return new Pointer(storage, index0);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Specialised in <code>BaseArrayBuffer</code> to one dimension.
+     */
+    @Override
+    public boolean isContiguous(char order) {
+        if ("CFA".indexOf(order) < 0) {
+            return false;
+        } else {
+            if (getShape()[0] < 2) {
+                return true;
+            } else {
+                return getStrides()[0] == getItemsize();
+            }
+        }
+    }
+
+}
diff --git a/src/org/python/core/buffer/BaseBuffer.java b/src/org/python/core/buffer/BaseBuffer.java
--- a/src/org/python/core/buffer/BaseBuffer.java
+++ b/src/org/python/core/buffer/BaseBuffer.java
@@ -9,34 +9,35 @@
 import org.python.core.PyException;
 
 /**
- * Base implementation of the Buffer API providing variables and accessors for the navigational
+ * Base implementation of the Buffer API providing variables and accessors for the navigation
  * arrays, methods for expressing and checking the buffer request flags, methods and mechanism for
  * get-release counting, boilerplate error checks and their associated exceptions, and default
  * implementations of some methods for access to the buffer content. The design aim is to ensure
  * unglamorous common code need only be implemented once.
  * <p>
- * Where provided, the buffer access methods are appropriate to 1-dimensional arrays where the units
- * are single bytes, stored contiguously. Sub-classes that deal with N-dimensional arrays,
- * non-contiguous storage and items that are not single bytes must override the default
- * implementations.
+ * This class leaves undefined the storage mechanism for the bytes (typically <code>byte[]</code> or
+ * <code>java.nio.ByteBuffer</code>). A concrete class that extends this one must provide elementary
+ * accessors {@link #byteAtImpl(int)}, {@link #storeAtImpl(byte, int)} that abstract this storage, a
+ * factory {@link #getNIOByteBufferImpl()} for <code>ByteBuffer</code>s that wrap the storage, and a
+ * factory for slices {@link #getBufferSlice(int, int, int, int)}. The constructor must specify the
+ * feature flags (see {@link #BaseBuffer(int)}), set {@link #index0}, {@link #shape} and
+ * {@link #strides}, and finally check the client capabilities with {@link #checkRequestFlags(int)}.
+ * Sub-classes intended to represent slices of exporters that must count their exports as part of a
+ * locking protocol, as does <code>bytearray</code>, must override {@link #getRoot()} so that a
+ * buffer view {@link #release()} on a slice, propagates to the buffer view that provided it.
+ * <p>
+ * Access methods provided here necessarily work with the abstracted {@link #byteAtImpl(int)},
+ * {@link #storeAtImpl(byte, int)} interface, but subclasses are able to override them with more
+ * efficient versions that employ knowledge of the particular storage type used. The provided buffer
+ * access methods may be restricted to 1-dimensional arrays where the units are single bytes, stored
+ * contiguously. Sub-classes that deal with N-dimensional arrays, non-contiguous storage and items
+ * that are not single bytes must sometimes override the default implementations.
  * <p>
  * This base implementation is writable only if {@link PyBUF#WRITABLE} is in the feature flags
- * passed to the constructor. Otherwise, all methods for write access raise a
- * <code>BufferError</code> read-only exception and {@link #isReadonly()} returns <code>true</code>.
- * Sub-classes can follow the same pattern, setting {@link PyBUF#WRITABLE} in the constructor and,
- * if they have to, overriding the operations that write (<code>storeAt</code> and
- * <code>copyFrom</code>). The recommended pattern is:
- *
- * <pre>
- * if (isReadonly()) {
- *     throw notWritable();
- * }
- * // ... implementation of the write operation
- * </pre>
- * Another approach, used in the standard library, is to have distinct classes for the writable and
- * read-only variants. The implementors of simple buffers will find it efficient to override the
- * generic access methods to which performance might be sensitive, with a calculation specific to
- * their actual type.
+ * passed to the constructor. Otherwise, all methods for write access raise a <code>TypeError</code>
+ * and {@link #isReadonly()} returns <code>true</code>. However, a client intending to write should
+ * have presented {@link PyBUF#WRITABLE} in its client request flags when getting the buffer, and
+ * been prevented by a <code>BufferError</code> exception at that point.
  * <p>
  * At the time of writing, only one-dimensional buffers of item size one are used in the Jython
  * core.
@@ -59,20 +60,16 @@
     protected int[] strides;
 
     /**
-     * Reference to the underlying <code>byte[]</code> storage that the exporter is sharing with the
-     * consumer. The data need not occupy the whole array: in the constructor of a particular type
-     * of buffer, the exporter usually indicates an offset to the first significant byte and length
-     * (contiguous cases) or the index in <code>storage</code> that should be treated as the item
-     * with index zero (retrieved say by {@link #byteAt(int)}).
+     * The strides array for a contiguous byte buffer..
      */
-    // XXX Pending change of implementation to ByteBuffer
-    protected byte[] storage;
+    protected static final int[] CONTIG_STRIDES = {1};
 
     /**
-     * Absolute index in <code>storage</code> of <code>item[0]</code>. In one dimension, for a
-     * positive <code>stride</code> this is equal to the offset of the first byte used in
-     * {@link #storage}, and for a negative <code>stride</code> it is the last. In an N-dimensional
-     * buffer with strides of mixed sign, it could be anywhere in the data.
+     * Absolute byte-index in the storage of <code>item[0]</code>. In one dimension, for a positive
+     * <code>stride</code> this is equal to the offset of the first byte used in whatever
+     * byte-storage is provided, and for a negative <code>stride</code> it is the first byte of the
+     * last item. In an N-dimensional buffer with strides of mixed sign, it could be anywhere in the
+     * data.
      */
     protected int index0;
 
@@ -92,8 +89,8 @@
      * the consumer does not specify that it will use a navigation array the buffer requires.
      * <p>
      * In order to support efficient checking with {@link #checkRequestFlags(int)} we store a
-     * mutilated version of the apparent <code>featureFlags</code> in which the non-navigational
-     * flags are inverted. The syndrome <code>S</code> of the error is computed as follows. Let
+     * mutilated version of the apparent <code>featureFlags</code> in which the non-navigation flags
+     * are inverted. The syndrome <code>S</code> of the error is computed as follows. Let
      * <code>N=1</code> where we are dealing with a navigation flag, let <code>F</code> be a buffer
      * feature flag, and let <code>X</code> be the consumer request flags.
      *
@@ -126,20 +123,20 @@
     private int gFeatureFlags = ~NAVIGATION; // featureFlags = 0
 
     /**
-     * Construct an instance of BaseBuffer in support of a sub-class, specifying the 'feature
-     * flags', or at least a starting set to be adjusted later. These are the features of the buffer
-     * exported, not the flags that form the consumer's request. The buffer will be read-only unless
-     * {@link PyBUF#WRITABLE} is set in the feature flags. {@link PyBUF#FORMAT} and
-     * {@link PyBUF#AS_ARRAY} are implicitly added to the feature flags. The navigation arrays are
-     * all null, awaiting action by the sub-class constructor. To complete initialisation, the
-     * sub-class normally must assign: the buffer ( {@link #storage}, {@link #index0}), and the
-     * navigation arrays ({@link #shape}, {@link #strides}), and call
-     * {@link #checkRequestFlags(int)} passing the consumer's request flags.
+     * Construct an instance of <code>BaseBuffer</code> in support of a sub-class, specifying the
+     * 'feature flags', or at least a starting set to be adjusted later. These are the features of
+     * the buffer exported, not the flags that form the consumer's request. The buffer will be
+     * read-only unless {@link PyBUF#WRITABLE} is set in the feature flags. {@link PyBUF#FORMAT} is
+     * implicitly added to the feature flags. The navigation arrays are all null, awaiting action by
+     * the sub-class constructor. To complete initialisation, the sub-class normally must create its
+     * own wrapped byte-storage, assign {@link #index0}) and the navigation arrays ( {@link #shape},
+     * {@link #strides}), and call {@link #checkRequestFlags(int)} passing the consumer's request
+     * flags.
      *
      * @param featureFlags bit pattern that specifies the actual features allowed/required
      */
     protected BaseBuffer(int featureFlags) {
-        setFeatureFlags(featureFlags | FORMAT | AS_ARRAY);
+        setFeatureFlags(featureFlags | FORMAT);
     }
 
     /**
@@ -176,6 +173,17 @@
     }
 
     /**
+     * Remove features from this buffer expressed using the constants defined in {@link PyBUF},
+     * clearing individual flags specified while leaving others already set. Equivalent to
+     * <code>setFeatureFlags(~flags & getFeatureFlags())</code>.
+     *
+     * @param flags to clear within the feature flags
+     */
+    protected final void removeFeatureFlags(int flags) {
+        setFeatureFlags(~flags & getFeatureFlags());
+    }
+
+    /**
      * General purpose method to check the consumer request flags (typically the argument to
      * {@link BufferProtocol#getBuffer(int)}) against the feature flags (see
      * {@link #getFeatureFlags()}) that characterise the features of the buffer, and to raise an
@@ -188,7 +196,7 @@
      * capabilities this type (or instance) buffer actually has. It is an error, for the consumer to
      * specify in its request a feature that the buffer does not offer.
      * <p>
-     * In a subset of the flags, the consumer specifies the set of navigational arrays (
+     * In a subset of the flags, the consumer specifies the set of navigation arrays (
      * <code>shape</code>, <code>strides</code>, and <code>suboffsets</code>) it intends to use in
      * navigating the buffer. When the buffer implementation calls this check method, it has already
      * specified in {@link #setFeatureFlags(int)} what navigation is necessary for the consumer to
@@ -211,17 +219,11 @@
 
     @Override
     public boolean isReadonly() {
-        // WRITABLE is a non-navigational flag, so is inverted in gFeatureFlags
+        // WRITABLE is a non-navigation flag, so is inverted in gFeatureFlags
         return (gFeatureFlags & WRITABLE) != 0; // i.e. featureFlags & WRITABLE is false
     }
 
     @Override
-    public boolean hasArray() {
-        // AS_ARRAY is a non-navigational flag, so is inverted in gFeatureFlags
-        return (gFeatureFlags & AS_ARRAY) == 0; // i.e. featureFlags & AS_ARRAY is true
-    }
-
-    @Override
     public int getNdim() {
         return shape.length;
     }
@@ -232,64 +234,137 @@
         return shape;
     }
 
+    // XXX Consider making this part of the PyBUF interface
+    protected int getSize() {
+        final int N = shape.length;
+        int size = shape[0];
+        for (int k = 1; k < N; k++) {
+            size *= shape[k];
+        }
+        return size;
+    }
+
+    @Override
+    public int getLen() {
+        final int N = shape.length;
+        int len = getItemsize();
+        for (int k = 0; k < N; k++) {
+            len *= shape[k];
+        }
+        return len;
+    }
+
+    /**
+     * Retrieve the byte at the given index in the underlying storage treated as a flat sequence of
+     * bytes. This byte-index will have been computed from the item index (which may have been
+     * multi-dimensional), taking into account {@link #index0}, {@link #shape}, {@link #strides},
+     * and the item size. The caller is responsible for validating the original item-index and
+     * raising (typically) an <code>IndexOutOfBoundsException</code>. Misuse of this method may
+     * still result in unchecked exceptions characteristic of the storage implementation.
+     *
+     * @param byteIndex byte-index of location to retrieve
+     * @return the byte at byteIndex
+     */
+    abstract protected byte byteAtImpl(int byteIndex) throws IndexOutOfBoundsException;
+
+    /**
+     * Store the byte at the given index in the underlying storage treated as a flat sequence of
+     * bytes. This byte-index will have been computed from the item index (which may have been
+     * multi-dimensional), taking into account {@link #index0}, {@link #shape}, {@link #strides},
+     * and the item size. The caller is responsible for validating the original item-index and
+     * raising (typically) an <code>IndexOutOfBoundsException</code>. Misuse of this method may
+     * still result in unchecked exceptions characteristic of the storage implementation. This
+     * method must implement the check for read-only character, raising a <code>BufferError</code>
+     * in the case of a violation.
+     *
+     * @param value to store
+     * @param byteIndex byte-index of location to retrieve
+     * @throws PyException(BufferError) if this object is read-only.
+     */
+    abstract protected void storeAtImpl(byte value, int byteIndex)
+            throws IndexOutOfBoundsException, PyException;
+
     /**
      * {@inheritDoc}
      * <p>
-     * The default implementation in <code>BaseBuffer</code> deals with the general one-dimensional
-     * case, with any item size and stride.
+     * The <code>BaseBuffer</code> implementation delegates to {@link #byteAtImpl(int)} via
+     * <code>byteAtImpl(byteIndex(index))</code>.
      */
     @Override
-    public int getLen() {
-        // Correct if one-dimensional. Override with itemsize*product(shape).
-        return shape[0] * getItemsize();
-    }
-
-    @Override
     public byte byteAt(int index) throws IndexOutOfBoundsException {
-        return storage[calcIndex(index)];
-    }
-
-    @Override
-    public int intAt(int index) throws IndexOutOfBoundsException {
-        return 0xff & byteAt(index);
-    }
-
-    @Override
-    public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
-        if (isReadonly()) {
-            throw notWritable();
-        }
-        storage[calcIndex(index)] = value;
+        return byteAtImpl(byteIndex(index));
     }
 
     /**
-     * Convert an item index (for a one-dimensional buffer) to an absolute byte index in the actual
-     * storage being shared by the exporter. See {@link #calcIndex(int...)} for discussion.
-     *
-     * @param index from consumer
-     * @return index in actual storage
+     * {@inheritDoc}
+     * <p>
+     * The <code>BaseBuffer</code> implementation delegates to {@link #byteAtImpl(int)} via
+     * <code>byteAtImpl(byteIndex(index))</code>, cast unsigned to an <code>int</code>.
      */
-    protected int calcIndex(int index) throws IndexOutOfBoundsException {
-        // Treat as one-dimensional
-        return index0 + index * getStrides()[0];
+    @Override
+    public int intAt(int index) throws IndexOutOfBoundsException {
+        return 0xff & byteAtImpl(byteIndex(index));
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The <code>BaseBuffer</code> implementation delegates to {@link #storeAtImpl(byte, int)} via
+     * <code>storeAtImpl(value, byteIndex(index))</code>.
+     */
+    @Override
+    public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
+        storeAtImpl(value, byteIndex(index));
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The <code>BaseBuffer</code> implementation delegates to {@link #byteAtImpl(int)} via
+     * <code>byteAtImpl(byteIndex(indices))</code>.
+     */
     @Override
     public byte byteAt(int... indices) throws IndexOutOfBoundsException {
-        return storage[calcIndex(indices)];
+        return byteAtImpl(byteIndex(indices));
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The <code>BaseBuffer</code> implementation delegates to {@link #byteAtImpl(int)} via
+     * <code>byteAtImpl(byteIndex(indices))</code>, cast unsigned to an <code>int</code>.
+     */
     @Override
     public int intAt(int... indices) throws IndexOutOfBoundsException {
         return 0xff & byteAt(indices);
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The <code>BaseBuffer</code> implementation delegates to {@link #storeAtImpl(byte, int)} via
+     * <code>storeAtImpl(value, byteIndex(indices))</code>.
+     */
     @Override
     public void storeAt(byte value, int... indices) throws IndexOutOfBoundsException, PyException {
-        if (isReadonly()) {
-            throw notWritable();
+        storeAtImpl(value, byteIndex(indices));
+    }
+
+    /**
+     * Convert an item index (for a one-dimensional buffer) to a checked absolute byte index in the
+     * actual storage being shared by the exporter. See {@link #byteIndex(int[])} for discussion.
+     *
+     * @param index item-index from consumer
+     * @return corresponding byte-index in actual storage
+     * @throws IndexOutOfBoundsException if the index <0 or ≥<code>shape[0]</code>
+     */
+    // XXX Consider making this part of the PyBuffer interface
+    protected int byteIndex(int index) throws IndexOutOfBoundsException {
+        // Treat as one-dimensional
+        if (index < 0 || index >= shape[0]) {
+            throw new IndexOutOfBoundsException();
         }
-        storage[calcIndex(indices)] = value;
+        return index0 + index * strides[0];
     }
 
     /**
@@ -298,25 +373,28 @@
      * to allow a sub-class to define, in one place, an indexing calculation that maps the index as
      * provided by the consumer into an index in the storage known to the buffer.
      * <p>
-     * In the usual case where the storage is referenced via the {@link #storage} and
-     * {@link #index0} members, the buffer implementation may use <code>storage[calcIndex(i)]</code>
+     * In the usual case where the storage is referenced via a <code>storage</code> member and
+     * {@link #index0} member, the buffer implementation may use <code>storage[byteIndex(i)]</code>
      * to reference the (first byte of) the item x[i]. This is what the default implementation of
-     * accessors in <code>BaseBuffer</code> will do. In the simplest cases, calling
-     * <code>calcIndex</code> may be an overhead to avoid, and an implementation will specialise the
+     * accessors in <code>BaseArrayBuffer</code> will do. In the simplest cases, calling
+     * <code>byteIndex</code> may be an overhead to avoid, and an implementation will specialise the
      * accessors. The default implementation here is suited to N-dimensional arrays.
      *
-     * @param indices of the item from the consumer
-     * @return corresponding absolute index in storage
+     * @param indices n-dimensional item-index from consumer
+     * @return corresponding byte-index in actual storage
+     * @throws IndexOutOfBoundsException if any index <0 or ≥<code>shape[i]</code>
      */
-    protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
+    // XXX Consider making this part of the PyBuffer interface
+    protected int byteIndex(int... indices) throws IndexOutOfBoundsException {
         final int N = checkDimension(indices);
         // In general: index0 + sum(k=0,N-1) indices[k]*strides[k]
         int index = index0;
-        if (N > 0) {
-            int[] strides = getStrides();
-            for (int k = 0; k < N; k++) {
-                index += indices[k] * strides[k];
+        for (int k = 0; k < N; k++) {
+            int ik = indices[k];
+            if (ik < 0 || ik >= shape[k]) {
+                throw new IndexOutOfBoundsException();
             }
+            index += ik * strides[k];
         }
         return index;
     }
@@ -324,24 +402,22 @@
     /**
      * Calculate the absolute byte index in the storage array of the last item of the exported data
      * (if we are not using indirection). This is the greatest value attained by
-     * {@link #calcIndex(int...)}. The first byte not used will be one <code>itemsize</code> more
+     * {@link #byteIndex(int...)}. The first byte not used will be one <code>itemsize</code> more
      * than the returned value.
      *
      * @return greatest absolute index in storage
      */
-    protected int calcGreatestIndex() throws IndexOutOfBoundsException {
+    protected int calcGreatestIndex() {
         final int N = shape.length;
         // If all the strides are positive, the maximal value is found from:
         // index = index0 + sum(k=0,N-1) (shape[k]-1)*strides[k]
         // but in general, for any k where strides[k]<=0, the term should be zero.
         int index = index0;
-        if (N > 0) {
-            int[] strides = getStrides();
-            for (int k = 0; k < N; k++) {
-                int stride = strides[k];
-                if (stride > 0) {
-                    index += (shape[k] - 1) * stride;
-                }
+        int[] strides = getStrides();
+        for (int k = 0; k < N; k++) {
+            int stride = strides[k];
+            if (stride > 0) {
+                index += (shape[k] - 1) * stride;
             }
         }
         return index;
@@ -350,23 +426,21 @@
     /**
      * Calculate the absolute byte index in the storage array of the first item of the exported data
      * (if we are not using indirection). This is the least value attained by
-     * {@link #calcIndex(int...)}.
+     * {@link #byteIndex(int...)}.
      *
      * @return least absolute index in storage
      */
-    protected int calcLeastIndex() throws IndexOutOfBoundsException {
+    protected int calcLeastIndex() {
         final int N = shape.length;
         // If all the strides are positive, the maximal value is just index0,
         // but in general, we must allow strides[k]<=0 for some k:
         // index = index0 + sum(k=0,N-1) (strides[k]<0) ? (shape[k]-1)*strides[k] : 0
         int index = index0;
-        if (N > 0) {
-            int[] strides = getStrides();
-            for (int k = 0; k < N; k++) {
-                int stride = strides[k];
-                if (stride < 0) {
-                    index += (shape[k] - 1) * stride;
-                }
+        int[] strides = getStrides();
+        for (int k = 0; k < N; k++) {
+            int stride = strides[k];
+            if (stride < 0) {
+                index += (shape[k] - 1) * stride;
             }
         }
         return index;
@@ -381,147 +455,97 @@
     @Override
     public void copyTo(byte[] dest, int destPos) throws IndexOutOfBoundsException {
         // Note shape[0] is the number of items in the array
-        copyTo(0, dest, destPos, shape[0]);
+        copyTo(0, dest, destPos, getSize());
     }
 
     /**
      * {@inheritDoc}
      * <p>
      * The default implementation in <code>BaseBuffer</code> deals with the general one-dimensional
-     * case of arbitrary item size and stride.
+     * case of arbitrary item size and stride, but is unable to optimise access to sequential bytes.
      */
     @Override
-    public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
-            throws IndexOutOfBoundsException {
+    public void copyTo(int srcIndex, byte[] dest, int destPos, int count)
+            throws IndexOutOfBoundsException, PyException {
 
-        // Data is here in the buffers
-        int s = calcIndex(srcIndex);
-        int d = destPos;
+        checkDimension(1);
 
-        // Pick up attributes necessary to choose an efficient copy strategy
         int itemsize = getItemsize();
-        int stride = getStrides()[0];
-        int skip = stride - itemsize;
+        int s = srcIndex, d = destPos;
 
-        // Strategy depends on whether items are laid end-to-end contiguously or there are gaps
-        if (skip == 0) {
-            // stride == itemsize: straight copy of contiguous bytes
-            System.arraycopy(storage, s, dest, d, length * itemsize);
-
-        } else if (itemsize == 1) {
-            // Non-contiguous copy: single byte items
-            int limit = s + length * stride;
-            for (; s < limit; s += stride) {
-                dest[d++] = storage[s];
+        if (itemsize == 1) {
+            // Single byte items
+            for (int i = 0; i < count; i++) {
+                dest[d++] = byteAt(s++);
             }
-
         } else {
-            // Non-contiguous copy: each time, copy itemsize bytes then skip
-            int limit = s + length * stride;
-            for (; s < limit; s += skip) {
-                int t = s + itemsize;
-                while (s < t) {
-                    dest[d++] = storage[s++];
+            // Multi-byte items
+            for (int i = 0; i < count; i++) {
+                int p = byteIndex(s++);
+                for (int j = 0; j < itemsize; j++) {
+                    dest[d++] = byteAtImpl(p + j);
                 }
             }
         }
-
     }
 
     /**
      * {@inheritDoc}
      * <p>
      * The default implementation in <code>BaseBuffer</code> deals with the general one-dimensional
-     * case of arbitrary item size and stride.
+     * case of arbitrary item size and stride, but is unable to optimise access to sequential bytes.
      */
     @Override
-    public void copyFrom(byte[] src, int srcPos, int destIndex, int length)
+    public void copyFrom(byte[] src, int srcPos, int destIndex, int count)
             throws IndexOutOfBoundsException, PyException {
 
-        // Block operation if read-only
-        if (isReadonly()) {
-            throw notWritable();
-        }
+        checkDimension(1);
+        checkWritable();
 
-        // Data is here in the buffers
-        int s = srcPos;
-        int d = calcIndex(destIndex);
+        int itemsize = getItemsize();
+        int d = destIndex, s = srcPos;
 
-        // Pick up attributes necessary to choose an efficient copy strategy
-        int itemsize = getItemsize();
-        int stride = getStrides()[0];
-        int skip = stride - itemsize;
-
-        // Strategy depends on whether items are laid end-to-end or there are gaps
-        if (skip == 0) {
-            // Straight copy of contiguous bytes
-            System.arraycopy(src, srcPos, storage, d, length * itemsize);
-
-        } else if (itemsize == 1) {
-            // Non-contiguous copy: single byte items
-            int limit = d + length * stride;
-            for (; d != limit; d += stride) {
-                storage[d] = src[s++];
+        if (itemsize == 1) {
+            // Single byte items
+            for (int i = 0; i < count; i++) {
+                storeAt(src[s++], d++);
             }
-
         } else {
-            // Non-contiguous copy: each time, copy itemsize bytes then skip
-            int limit = d + length * stride;
-            for (; d != limit; d += skip) {
-                int t = d + itemsize;
-                while (d < t) {
-                    storage[d++] = src[s++];
+            // Multi-byte items
+            for (int i = 0; i < count; i++) {
+                int p = byteIndex(d++);
+                for (int j = 0; j < itemsize; j++) {
+                    storeAtImpl(src[s++], p++);
                 }
             }
         }
-
     }
 
     /**
      * {@inheritDoc}
      * <p>
      * The default implementation in <code>BaseBuffer</code> deals with the general one-dimensional
-     * case.
+     * case of arbitrary item size and stride, but is unable to optimise access to sequential bytes.
      */
     @Override
     public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
 
-        // Block operation if read-only and same length
-        if (isReadonly()) {
-            throw notWritable();
-        } else if (src.getLen() != getLen() || src.getItemsize() != getItemsize()) {
+        checkWritable();
+
+        int itemsize = getItemsize();
+        int count = getSize();
+
+        // Block operation if different item or overall size (permit reshape)
+        if (src.getItemsize() != itemsize || src.getLen() != count * itemsize) {
             throw differentStructure();
         }
 
-        // Data is here in the buffers
-        int s = 0;
-        int d = calcIndex(0);
-
-        // Pick up attributes necessary to choose an efficient copy strategy
-        int itemsize = getItemsize();
-        int stride = getStrides()[0];
-
-        // Strategy depends on whether items are laid end-to-end or there are gaps
-        if (stride == itemsize) {
-            // Straight copy to contiguous bytes
-            src.copyTo(storage, d);
-
-        } else if (itemsize == 1) {
-            // Non-contiguous copy: single byte items
-            int limit = d + src.getLen() * stride;
-            for (; d != limit; d += stride) {
-                storage[d] = src.byteAt(s++);
-            }
-
-        } else {
-            // Non-contiguous copy: each time, copy itemsize bytes then skip
-            int limit = d + src.getShape()[0] * stride;
-            for (; d != limit; d += stride) {
-                Pointer srcItem = src.getPointer(s++);
-                System.arraycopy(srcItem.storage, srcItem.offset, storage, d, itemsize);
-            }
+        // XXX Re-think this using ByteBuffer when the API provides a byteIndex() method
+        assert itemsize == 1;
+        // XXX This only moves the first byte of each item
+        for (int i = 0; i < count; i++) {
+            storeAt(src.byteAt(i), i);
         }
-
     }
 
     @Override
@@ -536,7 +560,7 @@
     }
 
     /**
-     * Allow an exporter to re-use a BaseBytes even if it has been "finally" released. Many
+     * Allow an exporter to re-use this object again even if it has been "finally" released. Many
      * sub-classes of <code>BaseBytes</code> can be re-used even after a final release by consumers,
      * simply by incrementing the <code>exports</code> count again: the navigation arrays and the
      * buffer view of the exporter's state all remain valid. We do not let consumers do this through
@@ -574,6 +598,7 @@
         if (--exports == 0) {
             // This is a final release.
             releaseAction();
+            // XXX Consider adding release of root if root!=this so sliced need not
         } else if (exports < 0) {
             // Buffer already had 0 exports. (Put this right, in passing.)
             exports = 0;
@@ -592,55 +617,79 @@
     }
 
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length) {
-        return getBufferSlice(flags, start, length, 1);
+    public PyBuffer getBufferSlice(int flags, int start, int count) {
+        return getBufferSlice(flags, start, count, 1);
     }
 
     // Let the sub-class implement
-    // @Override public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {}
+    // @Override public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {}
+
+    /**
+     * Create a new <code>java.nio.ByteBuffer</code> on the underlying storage, such that
+     * positioning this buffer to a particular byte using {@link #byteIndex(int)} or
+     * {@link #byteIndex(int[])} positions it at the first byte of the item so indexed.
+     */
+    abstract protected ByteBuffer getNIOByteBufferImpl();
 
     @Override
     public ByteBuffer getNIOByteBuffer() {
-        // The buffer spans the whole storage, which may include data not in the view
-        ByteBuffer b = ByteBuffer.wrap(storage);
-        b.limit(calcGreatestIndex() + getItemsize()).position(index0);
-        // Return as read-only if it is.
-        return isReadonly() ? b.asReadOnlyBuffer() : b;
+        // The buffer spans the whole storage
+        ByteBuffer b = getNIOByteBufferImpl();
+        // For the one-dimensional contiguous case it makes sense to set the limit:
+        if (shape.length == 1) {
+            int stride = strides[0];
+            if (getItemsize() == stride) {
+                b.limit(index0 + shape[0] * stride);
+            }
+        }
+        // The buffer is positioned at item[0]
+        b.position(index0);
+        return b;
     }
 
     @Override
     public ByteBuffer getNIOByteBuffer(int index) {
         // The buffer spans the whole storage but is positioned at index
-        ByteBuffer b = getNIOByteBuffer();
-        b.position(calcIndex(index));
+        ByteBuffer b = getNIOByteBufferImpl();
+        b.position(byteIndex(index));
         return b;
     }
 
     @Override
     public ByteBuffer getNIOByteBuffer(int... indices) {
         // The buffer spans the whole storage but is positioned at indices[]
-        ByteBuffer b = getNIOByteBuffer();
-        b.position(calcIndex(indices));
-        // Return as read-only if it is.
+        ByteBuffer b = getNIOByteBufferImpl();
+        b.position(byteIndex(indices));
         return b;
     }
 
+    @Override
+    public boolean hasArray() {
+        // AS_ARRAY is a non-navigation flag, so is inverted in gFeatureFlags
+        return (gFeatureFlags & AS_ARRAY) == 0; // i.e. featureFlags & AS_ARRAY is true
+    }
+
     @SuppressWarnings("deprecation")
     @Override
     public Pointer getBuf() {
-        return new Pointer(storage, index0);
+        checkHasArray();
+        return new Pointer(getNIOByteBuffer().array(), index0);
     }
 
     @SuppressWarnings("deprecation")
     @Override
     public Pointer getPointer(int index) throws IndexOutOfBoundsException {
-        return new Pointer(storage, calcIndex(index));
+        Pointer p = getBuf();
+        p.offset = byteIndex(index);
+        return p;
     }
 
     @SuppressWarnings("deprecation")
     @Override
     public Pointer getPointer(int... indices) throws IndexOutOfBoundsException {
-        return new Pointer(storage, calcIndex(indices));
+        Pointer p = getBuf();
+        p.offset = byteIndex(indices);
+        return p;
     }
 
     @Override
@@ -654,10 +703,73 @@
         return null;
     }
 
+    private boolean isCContiguous() {
+        /*
+         * If we were to compute the strides array for a C-contiguous array, the last stride would
+         * equal the item size, and generally stride[k-1] = shape[k]*stride[k]. This is the basis of
+         * the test. However, note that for any k where shape[k]==1 there is no "next sub-array"
+         * and no discontiguity.
+         */
+        final int N = shape.length;
+        /*
+         * size is the stride in bytes-index from item[i0,i1,...,ik,0,...,0] to
+         * item[i0,i1,...,ik+1,0,...,0]. Start the iteration at the largest k. An increment of one
+         * in the last index makes a stride of the item size.
+         */
+        int size = getItemsize();
+        for (int k = N - 1; k >= 0; k--) {
+            int nk = shape[k];
+            if (nk > 1) {
+                if (strides[k] != size) {
+                    return false;
+                }
+                size *= nk;
+            }
+        }
+        return true;
+    }
+
+    private boolean isFortranContiguous() {
+        /*
+         * If we were to compute the strides array for a Fortran-contiguous array, the first stride would
+         * equal the item size, and generally stride[k+1] = shape[k]*stride[k]. This is the basis of
+         * the test. However, note that for any k where shape[k]==1 there is no "next sub-array"
+         * and no discontiguity.
+         */
+        final int N = shape.length;
+        /*
+         * size is the stride in bytes-index from item[0,...,0,ik,0,...,0] to
+         * item[0,...,0,ik+1,0,...,0]. Start the iteration at k=0. An increment of one
+         * in the first index makes a stride of the item size.
+         */
+        int size = getItemsize();
+        for (int k = 0; k < N; k++) {
+            int nk = shape[k];
+            if (nk > 1) {
+                if (strides[k] != size) {
+                    return false;
+                }
+                size *= nk;
+            }
+        }
+        return true;
+    }
+
     @Override
     public boolean isContiguous(char order) {
-        // Correct for one-dimensional buffers
-        return true;
+        if (getSuboffsets() != null) {
+            return false;
+        }
+        switch (order) {
+            case 'C':
+                return isCContiguous();
+            case 'F':
+                return isFortranContiguous();
+            case 'A':
+                return isCContiguous() || isFortranContiguous();
+            default:
+                return false;
+        }
     }
 
     @Override
@@ -683,7 +795,7 @@
     protected void releaseAction() {}
 
     /**
-     * Some <code>PyBuffer</code>s, those created by slicing a <code>PyBuffer</code> are related to
+     * Some <code>PyBuffer</code>s, those created by slicing a <code>PyBuffer</code>, are related to
      * a root <code>PyBuffer</code>. During creation of such a slice, we need to supply a value for
      * this root. If the present object is not itself a slice, this root is the object itself; if
      * the buffer is already a slice, it is the root it was given at creation time. Often this is
@@ -697,6 +809,20 @@
     }
 
     /**
+     * The toString() method of a buffer reproduces the values in the buffer (as unsigned integers)
+     * as the character codes of a <code>String</code>.
+     */
+    @Override
+    public String toString() {
+        int n = getLen();
+        StringBuilder sb = new StringBuilder(n);
+        for (int i = 0; i < n; i++) {
+            sb.appendCodePoint(intAt(i));
+        }
+        return sb.toString();
+    }
+
+    /**
      * Check the number of indices (but not their values), raising a Python BufferError if this does
      * not match the number of dimensions. This is a helper for N-dimensional arrays.
      *
@@ -728,17 +854,25 @@
     }
 
     /**
-     * The toString() method of a buffer reproduces the values in the buffer (as unsigned integers)
-     * as the character codes of a <code>String</code>.
+     * Check that the buffer is writable.
+     *
+     * @throws PyException (TypeError) if not
      */
-    @Override
-    public String toString() {
-        int n = getLen();
-        StringBuilder sb = new StringBuilder(n);
-        for (int i = 0; i < n; i++) {
-            sb.appendCodePoint(intAt(i));
+    protected void checkWritable() throws PyException {
+        if (isReadonly()) {
+            throw notWritable();
         }
-        return sb.toString();
+    }
+
+    /**
+     * Check that the buffer is backed by an array the client can access as byte[].
+     *
+     * @throws PyException (BufferError) if not
+     */
+    protected void checkHasArray() throws PyException {
+        if (!hasArray()) {
+            throw bufferIsNot("accessible as a Java array");
+        }
     }
 
     /**
diff --git a/src/org/python/core/buffer/SimpleBuffer.java b/src/org/python/core/buffer/SimpleBuffer.java
--- a/src/org/python/core/buffer/SimpleBuffer.java
+++ b/src/org/python/core/buffer/SimpleBuffer.java
@@ -1,7 +1,5 @@
 package org.python.core.buffer;
 
-import java.nio.ByteBuffer;
-
 import org.python.core.PyBuffer;
 import org.python.core.PyException;
 import org.python.core.util.StringUtil;
@@ -9,12 +7,7 @@
 /**
  * Buffer API over a read-only one-dimensional array of one-byte items.
  */
-public class SimpleBuffer extends BaseBuffer {
-
-    /**
-     * The strides array for this type is always a single element array with a 1 in it.
-     */
-    protected static final int[] SIMPLE_STRIDES = {1};
+public class SimpleBuffer extends BaseArrayBuffer {
 
     /**
      * Provide an instance of <code>SimpleBuffer</code> with navigation variables partly
@@ -27,7 +20,7 @@
         super(CONTIGUITY | SIMPLE);
         // Initialise navigation
         shape = new int[1];
-        strides = SIMPLE_STRIDES;
+        strides = CONTIG_STRIDES;
         // suboffsets is always null for this type.
     }
 
@@ -54,8 +47,9 @@
             ArrayIndexOutOfBoundsException {
         this();
         this.storage = storage;         // Exported data
+        // Initialise navigation
         this.index0 = index0;           // Index to be treated as item[0]
-        this.shape[0] = size;           // Number of items in exported data
+        shape[0] = size;                // Number of items in exported data
 
         // Check arguments using the "all non-negative" trick
         if ((index0 | size | storage.length - (index0 + size)) < 0) {
@@ -92,11 +86,8 @@
      * @param storage the array of bytes storing the implementation of the exporting object
      * @throws NullPointerException if <code>storage</code> is null
      */
-    // XXX: "for sub-class use" = should be protected?
-    public SimpleBuffer(byte[] storage) throws NullPointerException {
-        this();
-        this.storage = storage;         // Exported data (index0=0 from initialisation)
-        this.shape[0] = storage.length; // Number of units in whole array
+    protected SimpleBuffer(byte[] storage) throws NullPointerException {
+        this(storage, 0, storage.length);
     }
 
     /**
@@ -114,11 +105,6 @@
         checkRequestFlags(flags);       // Check request is compatible with type
     }
 
-    @Override
-    public boolean isReadonly() {
-        return true;
-    }
-
     /**
      * {@inheritDoc}
      * <p>
@@ -134,72 +120,26 @@
     /**
      * {@inheritDoc}
      * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
+     * In <code>SimpleBuffer</code> the calculation is specialised for one dimension, no striding,
+     * and an item size of 1.
      */
     @Override
-    public byte byteAt(int index) throws IndexOutOfBoundsException {
-        // Implement directly: a bit quicker than the default
-        return storage[index0 + index];
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
-     */
-    @Override
-    public int intAt(int index) throws IndexOutOfBoundsException {
-        // Implement directly: a bit quicker than the default
-        return 0xff & storage[index0 + index];
+    protected int byteIndex(int index) throws IndexOutOfBoundsException {
+        if (index < 0 || index >= shape[0]) {
+            throw new IndexOutOfBoundsException();
+        }
+        return index0 + index;
     }
 
     @Override
-    protected int calcIndex(int index) throws IndexOutOfBoundsException {
-        return index0 + index;
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
-     */
-    @Override
-    public byte byteAt(int... indices) throws IndexOutOfBoundsException {
-        checkDimension(indices.length);
-        return byteAt(indices[0]);
-    }
-
-    @Override
-    protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
-        // BaseBuffer implementation can be simplified since if indices.length!=1 we error.
-        checkDimension(indices.length); // throws if != 1
-        return calcIndex(indices[0]);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
-     */
-    @Override
-    public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
-            throws IndexOutOfBoundsException {
-        System.arraycopy(storage, index0 + srcIndex, dest, destPos, length);
-    }
-
-    @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length) {
-        if (length > 0) {
+    public PyBuffer getBufferSlice(int flags, int start, int count) {
+        if (count > 0) {
             // Translate relative to underlying buffer
             int compIndex0 = index0 + start;
             // Create the slice from the sub-range of the buffer
-            return new SimpleView(getRoot(), flags, storage, compIndex0, length);
+            return new SimpleView(getRoot(), flags, storage, compIndex0, count);
         } else {
-            // Special case for length==0 where above logic would fail. Efficient too.
+            // Special case for count==0 where above logic would fail. Efficient too.
             return new ZeroByteBuffer.View(getRoot(), flags);
         }
     }
@@ -209,22 +149,22 @@
      * <p>
      * <code>SimpleBuffer</code> provides an implementation for slicing contiguous bytes in one
      * dimension. In that case, <i>x(i) = u(r+i)</i> for <i>i = 0..L-1</i> where u is the underlying
-     * buffer, and <i>r</i> and <i>L</i> are the start and length with which <i>x</i> was created
+     * buffer, and <i>r</i> and <i>L</i> are the start and count with which <i>x</i> was created
      * from <i>u</i>. Thus <i>y(k) = u(r+s+km)</i>, that is, the composite offset is <i>r+s</i> and
      * the stride is <i>m</i>.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {
 
-        if (stride == 1 || length < 2) {
+        if (stride == 1 || count < 2) {
             // Unstrided slice of simple buffer is itself simple
-            return getBufferSlice(flags, start, length);
+            return getBufferSlice(flags, start, count);
 
         } else {
             // Translate relative to underlying buffer
             int compIndex0 = index0 + start;
             // Construct a view, taking a lock on the root object (this or this.root)
-            return new Strided1DBuffer.SlicedView(getRoot(), flags, storage, compIndex0, length,
+            return new Strided1DBuffer.SlicedView(getRoot(), flags, storage, compIndex0, count,
                     stride);
         }
     }
diff --git a/src/org/python/core/buffer/SimpleStringBuffer.java b/src/org/python/core/buffer/SimpleStringBuffer.java
--- a/src/org/python/core/buffer/SimpleStringBuffer.java
+++ b/src/org/python/core/buffer/SimpleStringBuffer.java
@@ -30,6 +30,11 @@
      * @param flags consumer requirements
      */
     public SimpleStringBuffer(int flags, String bufString) {
+        /*
+         * Leaving storage=null is ok because we carefully override every method that uses it,
+         * deferring creation of the storage byte array until we absolutely must have one.
+         */
+        super();
         // Save the backing string
         this.bufString = bufString;
         shape[0] = bufString.length();
@@ -54,32 +59,31 @@
      * This method uses {@link String#charAt(int)} rather than create an actual byte buffer.
      */
     @Override
-    public byte byteAt(int index) throws IndexOutOfBoundsException {
-        // Avoid creating buf by using String.charAt
+    public final byte byteAtImpl(int index) {
         return (byte)bufString.charAt(index);
     }
 
     /**
      * {@inheritDoc}
      * <p>
+     * In <code>SimpleStringBuffer</code> we can simply return the argument.
+     */
+    @Override
+    protected final int byteIndex(int index) {
+        // We do not check the index because String will do it for us.
+        return index;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
      * This method uses {@link String#charAt(int)} rather than create an actual byte buffer.
      */
     @Override
-    public int intAt(int index) throws IndexOutOfBoundsException {
-        // Avoid creating buf by using String.charAt
-        return bufString.charAt(index);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * This method uses {@link String#charAt(int)} rather than create an actual byte buffer.
-     */
-    @Override
-    public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
+    public void copyTo(int srcIndex, byte[] dest, int destPos, int count)
             throws IndexOutOfBoundsException {
         // Avoid creating buf by using String.charAt
-        int endIndex = srcIndex + length, p = destPos;
+        int endIndex = srcIndex + count, p = destPos;
         for (int i = srcIndex; i < endIndex; i++) {
             dest[p++] = (byte)bufString.charAt(i);
         }
@@ -91,13 +95,13 @@
      * The <code>SimpleStringBuffer</code> implementation avoids creation of a byte buffer.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length) {
-        if (length > 0) {
-            // The new string content is just a sub-string. (Non-copy operation in Java.)
+    public PyBuffer getBufferSlice(int flags, int start, int count) {
+        if (count > 0) {
+            // The new string content is just a sub-string.
             return new SimpleStringView(getRoot(), flags,
-                    bufString.substring(start, start + length));
+                    bufString.substring(start, start + count));
         } else {
-            // Special case for length==0 where start out of bounds sometimes raises exception.
+            // Special case for count==0 where start out of bounds sometimes raises exception.
             return new ZeroByteBuffer.View(getRoot(), flags);
         }
     }
@@ -108,23 +112,26 @@
      * The <code>SimpleStringBuffer</code> implementation creates an actual byte buffer.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {
         if (stride == 1) {
             // Unstrided slice of a SimpleStringBuffer is itself a SimpleStringBuffer.
-            return getBufferSlice(flags, start, length);
+            return getBufferSlice(flags, start, count);
         } else {
             // Force creation of the actual byte array from the String.
             ensureHaveBytes();
             // Now we are effectively a SimpleBuffer, return the strided view.
-            return super.getBufferSlice(flags, start, length, stride);
+            return super.getBufferSlice(flags, start, count, stride);
         }
     }
 
     @Override
-    public ByteBuffer getNIOByteBuffer() {
+    protected ByteBuffer getNIOByteBufferImpl() {
         // Force creation of the actual byte array from the String.
         ensureHaveBytes();
-        return super.getNIOByteBuffer();
+        // The buffer spans the whole storage, which may include data not in the view
+        ByteBuffer b = ByteBuffer.wrap(storage);
+        // Return as read-only.
+        return b.asReadOnlyBuffer();
     }
 
     /**
diff --git a/src/org/python/core/buffer/SimpleWritableBuffer.java b/src/org/python/core/buffer/SimpleWritableBuffer.java
--- a/src/org/python/core/buffer/SimpleWritableBuffer.java
+++ b/src/org/python/core/buffer/SimpleWritableBuffer.java
@@ -36,64 +36,28 @@
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
     public SimpleWritableBuffer(int flags, byte[] storage) throws PyException, NullPointerException {
-        super(storage);                 // Construct SimpleBuffer on whole array
-        addFeatureFlags(WRITABLE);
-        checkRequestFlags(flags);       // Check request is compatible with type
-    }
-
-    @Override
-    public boolean isReadonly() {
-        return false;
+        this(flags, storage, 0, storage.length);
     }
 
     /**
      * {@inheritDoc}
      * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
+     * Declared <code>final</code> returning <code>true</code> in <code>SimpleWritableBuffer</code>
+     * to make checks unnecessary.
      */
     @Override
-    public void storeAt(byte value, int index) {
-        // Implement directly and don't ask whether read-only
-        storage[index0 + index] = value;
+    public final boolean isReadonly() {
+        return false;
     }
 
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
-     */
+    /** Do nothing: the buffer is writable. */
     @Override
-    public void storeAt(byte value, int... indices) {
-        checkDimension(indices.length);
-        storeAt(value, indices[0]);
-    }
+    protected final void checkWritable() {}
 
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
-     */
     @Override
-    public void copyFrom(byte[] src, int srcPos, int destIndex, int length) {
-        System.arraycopy(src, srcPos, storage, index0 + destIndex, length);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
-     * one-dimension.
-     */
-    @Override
-    public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
-        if (src.getLen() != getLen()) {
-            throw differentStructure();
-        }
-        // Get the source to deliver efficiently to our byte storage
-        src.copyTo(storage, index0);
+    protected void storeAtImpl(byte value, int byteIndex) {
+        // Implement directly and don't ask whether read-only
+        storage[byteIndex] = value;
     }
 
     /**
@@ -103,14 +67,14 @@
      * writable.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length) {
-        if (length > 0) {
+    public PyBuffer getBufferSlice(int flags, int start, int count) {
+        if (count > 0) {
             // Translate relative to underlying buffer
             int compIndex0 = index0 + start;
             // Create the slice from the sub-range of the buffer
-            return new SimpleView(getRoot(), flags, storage, compIndex0, length);
+            return new SimpleView(getRoot(), flags, storage, compIndex0, count);
         } else {
-            // Special case for length==0 where above logic would fail. Efficient too.
+            // Special case for count==0 where above logic would fail. Efficient too.
             return new ZeroByteBuffer.View(getRoot(), flags);
         }
     }
@@ -122,18 +86,18 @@
      * writable.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {
 
-        if (stride == 1 || length < 2) {
+        if (stride == 1 || count < 2) {
             // Unstrided slice of simple buffer is itself simple
-            return getBufferSlice(flags, start, length);
+            return getBufferSlice(flags, start, count);
 
         } else {
             // Translate relative to underlying buffer
             int compIndex0 = index0 + start;
             // Construct a view, taking a lock on the root object (this or this.root)
             return new Strided1DWritableBuffer.SlicedView(getRoot(), flags, storage, compIndex0,
-                    length, stride);
+                    count, stride);
         }
     }
 
diff --git a/src/org/python/core/buffer/Strided1DBuffer.java b/src/org/python/core/buffer/Strided1DBuffer.java
--- a/src/org/python/core/buffer/Strided1DBuffer.java
+++ b/src/org/python/core/buffer/Strided1DBuffer.java
@@ -5,7 +5,7 @@
 
 /**
  * Read-only buffer API over a one-dimensional array of one-byte items, that are evenly-spaced in a
- * storage array. The buffer has <code>storage</code>, <code>index0</code> and <code>length</code>
+ * storage array. The buffer has <code>storage</code>, <code>index0</code> and <code>count</code>
  * properties in the usual way, designating a slice (or all) of a byte array, but also a
  * <code>stride</code> property (equal to <code>getStrides()[0]</code>).
  * <p>
@@ -14,7 +14,7 @@
  * Designate by <i>x(j)</i>, for <i>j=0..L-1</i>, the byte at index <i>j</i>, that is, the byte
  * retrieved by <code>x.byteAt(j)</code>. Thus, we store <i>x(j)</i> at <i>u(a+pj)</i>, that is,
  * <i>x(0) = u(a)</i>. When we construct such a buffer, we have to supply <i>a</i> =
- * <code>index0</code>, <i>L</i> = <code>length</code>, and <i>p</i> = <code>stride</code> as the
+ * <code>index0</code>, <i>L</i> = <code>count</code>, and <i>p</i> = <code>stride</code> as the
  * constructor arguments. The last item in the slice <i>x(L-1)</i> is stored at <i>u(a+p(L-1))</i>.
  * For the simple case of positive stride, constructor argument <code>index0</code> is the low index
  * of the range occupied by the data. When the stride is negative, that is to say <i>p<0</i>, and
@@ -28,7 +28,7 @@
  * create the <code>memoryview</code> that is returned as an extended slice of a
  * <code>memoryview</code>.
  */
-public class Strided1DBuffer extends BaseBuffer {
+public class Strided1DBuffer extends BaseArrayBuffer {
 
     /**
      * Step size in the underlying buffer essential to correct translation of an index (or indices)
@@ -38,30 +38,6 @@
     protected int stride;
 
     /**
-     * Provide an instance of <code>Strided1DBuffer</code> with navigation variables partly
-     * initialised, for sub-class use. To complete initialisation, the sub-class normally must
-     * assign the navigational properties and call {@link #checkRequestFlags(int)} passing the
-     * consumer's request flags.
-     *
-     * <pre>
-     * this.storage = storage;          // Exported data
-     * this.index0 = index0;            // Index to be treated as item[0]
-     * this.shape[0] = length;          // Number of items in exported data
-     * this.stride = stride;            // Between items
-     * </pre>
-     *
-     * The pre-defined {@link #strides} field remains <code>null</code> until {@link #getStrides} is
-     * called.
-     */
-    protected Strided1DBuffer() {
-        super(STRIDES);
-        // Initialise navigation
-        shape = new int[1];
-        // strides is created on demand;
-        // suboffsets is always null for this type.
-    }
-
-    /**
      * Provide an instance of <code>Strided1DBuffer</code> with navigation variables initialised,
      * for sub-class use. The buffer ({@link #storage}, {@link #index0}), and the navigation (
      * {@link #shape} array and {@link #stride}) will be initialised from the arguments (which are
@@ -69,27 +45,27 @@
      * <p>
      * The sub-class constructor should check that the intended access is compatible with this
      * object by calling {@link #checkRequestFlags(int)}. (See the source of
-     * {@link Strided1DWritableBuffer#Strided1DWritableBuffer(int, byte[], int, int, int)}
-     * for an example of this use.)
+     * {@link Strided1DWritableBuffer#Strided1DWritableBuffer(int, byte[], int, int, int)} for an
+     * example of this use.)
      *
      * @param storage raw byte array containing exported data
      * @param index0 index into storage of item[0]
-     * @param length number of items in the slice
+     * @param count number of items in the slice
      * @param stride in between successive elements of the new PyBuffer
      * @throws NullPointerException if <code>storage</code> is null
-     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>length</code> and
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>count</code> and
      *             <code>stride</code> are inconsistent with <code>storage.length</code>
      */
-    // XXX: "for sub-class use" = should be protected?
-    public Strided1DBuffer(byte[] storage, int index0, int length, int stride)
+    protected Strided1DBuffer(byte[] storage, int index0, int count, int stride)
             throws ArrayIndexOutOfBoundsException, NullPointerException {
-        this();
+        super(STRIDES);
         this.storage = storage;         // Exported data
         this.index0 = index0;           // Index to be treated as item[0]
-        this.shape[0] = length;         // Number of items in exported data
+        this.shape = new int[] {count}; // Number of items in exported data
         this.stride = stride;           // Between items
+        this.strides = new int[] {stride};
 
-        if (length == 0) {
+        if (count == 0) {
             // Nothing to check as we'll make no accesses
             addFeatureFlags(CONTIGUITY);
 
@@ -99,20 +75,20 @@
 
             if (stride == 1) {
                 lo = index0;                                // First byte of item[0]
-                hi = index0 + length;                       // Last byte of item[L-1] + 1
+                hi = index0 + count;                        // Last byte of item[L-1] + 1
                 addFeatureFlags(CONTIGUITY);
 
             } else if (stride > 1) {
                 lo = index0;                                // First byte of item[0]
-                hi = index0 + (length - 1) * stride + 1;    // Last byte of item[L-1] + 1
+                hi = index0 + (count - 1) * stride + 1;     // Last byte of item[L-1] + 1
 
             } else {
                 hi = index0 + 1;                            // Last byte of item[0] + 1
-                lo = index0 + (length - 1) * stride;        // First byte of item[L-1]
+                lo = index0 + (count - 1) * stride;         // First byte of item[L-1]
             }
 
             // Check indices using "all non-negative" trick
-            if ((length | lo | (storage.length - lo) | hi | (storage.length - hi)) < 0) {
+            if ((count | lo | (storage.length - lo) | hi | (storage.length - hi)) < 0) {
                 throw new ArrayIndexOutOfBoundsException();
             }
         }
@@ -122,31 +98,31 @@
      * Provide an instance of <code>Strided1DBuffer</code> on a particular array of bytes specifying
      * a starting index, the number of items in the result, and a byte-indexing stride. The result
      * of <code>byteAt(i)</code> will be equal to <code>storage[index0+stride*i]</code> (whatever
-     * the sign of <code>stride</code>), valid for <code>0<=i<length</code>. The constructor
+     * the sign of <code>stride</code>), valid for <code>0<=i<count</code>. The constructor
      * checks that all these indices lie within the <code>storage</code> array (unless
-     * <code>length=0</code>).
+     * <code>count=0</code>).
      * <p>
      * The constructed <code>PyBuffer</code> meets the consumer's expectations as expressed in the
      * <code>flags</code> argument, or an exception will be thrown if these are incompatible with
      * the type (e.g. the consumer does not specify that it understands the strides array). Note
      * that the actual range in the <code>storage</code> array, the lowest and highest index, is not
-     * explicitly passed, but is implicit in <code>index0</code>, <code>length</code> and
+     * explicitly passed, but is implicit in <code>index0</code>, <code>count</code> and
      * <code>stride</code>. The constructor checks that these indices lie within the
-     * <code>storage</code> array (unless <code>length=0</code>).
+     * <code>storage</code> array (unless <code>count=0</code>).
      *
      * @param flags consumer requirements
      * @param storage raw byte array containing exported data
      * @param index0 index into storage of item[0]
-     * @param length number of items in the slice
+     * @param count number of items in the slice
      * @param stride in between successive elements of the new PyBuffer
      * @throws NullPointerException if <code>storage</code> is null
-     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>length</code> and
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>count</code> and
      *             <code>stride</code> are inconsistent with <code>storage.length</code>
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
-    public Strided1DBuffer(int flags, byte[] storage, int index0, int length, int stride)
+    public Strided1DBuffer(int flags, byte[] storage, int index0, int count, int stride)
             throws ArrayIndexOutOfBoundsException, NullPointerException, PyException {
-        this(storage, index0, length, stride);
+        this(storage, index0, count, stride);
         checkRequestFlags(flags);   // Check request is compatible with type
 
     }
@@ -157,69 +133,35 @@
     }
 
     @Override
-    public byte byteAt(int index) throws IndexOutOfBoundsException {
-        return storage[index0 + index * stride];
-    }
-
-    @Override
-    protected int calcIndex(int index) throws IndexOutOfBoundsException {
+    protected final int byteIndex(int index) throws IndexOutOfBoundsException {
+        if (index < 0 || index >= shape[0]) {
+            throw new IndexOutOfBoundsException();
+        }
         return index0 + index * stride;
     }
 
-    @Override
-    protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
-        // BaseBuffer implementation can be simplified since if indices.length!=1 we error.
-        checkDimension(indices.length); // throws if != 1
-        return calcIndex(indices[0]);
-    }
-
-    /**
-     * {@inheritDoc} <code>Strided1DBuffer</code> provides a version optimised for strided bytes in
-     * one dimension.
-     */
-    @Override
-    public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
-            throws IndexOutOfBoundsException {
-        // Data is here in the buffers
-        int s = index0 + srcIndex * stride;
-        int d = destPos;
-
-        // Strategy depends on whether items are laid end-to-end contiguously or there are gaps
-        if (stride == 1) {
-            // stride == itemsize: straight copy of contiguous bytes
-            System.arraycopy(storage, s, dest, d, length);
-
-        } else {
-            // Non-contiguous copy: single byte items
-            int limit = s + length * stride;
-            for (; s != limit; s += stride) {
-                dest[d++] = storage[s];
-            }
-        }
-    }
-
     /**
      * {@inheritDoc}
      * <p>
      * <code>Strided1DBuffer</code> provides an implementation for slicing already-strided bytes in
      * one dimension. In that case, <i>x(i) = u(r+ip)</i> for <i>i = 0..L-1</i> where u is the
-     * underlying buffer, and <i>r</i>, <i>p</i> and <i>L</i> are the start, stride and length with
+     * underlying buffer, and <i>r</i>, <i>p</i> and <i>L</i> are the start, stride and count with
      * which <i>x</i> was created from <i>u</i>. Thus <i>y(k) = u(r+sp+kmp)</i>, that is, the
      * composite <code>index0</code> is <i>r+sp</i> and the composite <code>stride</code> is
      * <i>mp</i>.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {
 
-        if (length > 0) {
+        if (count > 0) {
             // Translate start relative to underlying buffer
             int compStride = this.stride * stride;
             int compIndex0 = index0 + start * this.stride;
             // Construct a view, taking a lock on the root object (this or this.root)
-            return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride);
+            return new SlicedView(getRoot(), flags, storage, compIndex0, count, compStride);
 
         } else {
-            // Special case for length==0 where above logic would fail. Efficient too.
+            // Special case for count==0 where above logic would fail. Efficient too.
             return new ZeroByteBuffer.View(getRoot(), flags);
         }
     }
@@ -238,15 +180,6 @@
         return getPointer(indices[0]);
     }
 
-    @Override
-    public int[] getStrides() {
-        if (strides == null) {
-            strides = new int[1];
-            strides[0] = stride;
-        }
-        return strides;
-    }
-
     /**
      * A <code>Strided1DBuffer.SlicedView</code> represents a non-contiguous subsequence of a simple
      * buffer.
@@ -282,6 +215,7 @@
 
         @Override
         public void releaseAction() {
+            // XXX Consider making this automatic within BaseBuffer.release() when getRoot()!=this
             // We have to release the root too if ours was final.
             root.release();
         }
diff --git a/src/org/python/core/buffer/Strided1DWritableBuffer.java b/src/org/python/core/buffer/Strided1DWritableBuffer.java
--- a/src/org/python/core/buffer/Strided1DWritableBuffer.java
+++ b/src/org/python/core/buffer/Strided1DWritableBuffer.java
@@ -14,67 +14,52 @@
      * Provide an instance of <code>Strided1DWritableBuffer</code> on a particular array of bytes
      * specifying a starting index, the number of items in the result, and a byte-indexing stride.
      * The result of <code>byteAt(i)</code> will be equal to <code>storage[index0+stride*i]</code>
-     * (whatever the sign of <code>stride>0</code>), valid for <code>0<=i<length</code>.
+     * (whatever the sign of <code>stride>0</code>), valid for <code>0<=i<count</code>.
      * <p>
      * The constructed <code>PyBuffer</code> meets the consumer's expectations as expressed in the
      * <code>flags</code> argument, or an exception will be thrown if these are incompatible with
      * the type (e.g. the consumer does not specify that it understands the strides array). Note
      * that the actual range in the <code>storage</code> array, the lowest and highest index, is not
-     * explicitly passed, but is implicit in <code>index0</code>, <code>length</code> and
+     * explicitly passed, but is implicit in <code>index0</code>, <code>count</code> and
      * <code>stride</code>. The caller is responsible for checking these fall within the array, or
      * the sub-range the caller is allowed to use.
      *
      * @param flags consumer requirements
      * @param storage raw byte array containing exported data
      * @param index0 index into storage of item[0]
-     * @param length number of items in the slice
+     * @param count number of items in the slice
      * @param stride in between successive elements of the new PyBuffer
      * @throws NullPointerException if <code>storage</code> is null
-     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>length</code> and
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>count</code> and
      *             <code>stride</code> are inconsistent with <code>storage.length</code>
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
-    public Strided1DWritableBuffer(int flags, byte[] storage, int index0, int length, int stride)
+    public Strided1DWritableBuffer(int flags, byte[] storage, int index0, int count, int stride)
             throws ArrayIndexOutOfBoundsException, NullPointerException, PyException {
-        super(storage, index0, length, stride);
+        super(storage, index0, count, stride);
         addFeatureFlags(WRITABLE);
         checkRequestFlags(flags);   // Check request is compatible with type
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * Declared <code>final</code> returning <code>true</code> in
+     * <code>Strided1DWritableBuffer</code> to make checks unnecessary.
+     */
     @Override
-    public boolean isReadonly() {
+    public final boolean isReadonly() {
         return false;
     }
 
+    /** Do nothing: the buffer is writable. */
     @Override
-    public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
-        storage[index0 + index * stride] = value;
-    }
+    protected final void checkWritable() {}
 
-    /**
-     * {@inheritDoc} <code>Strided1DWritableBuffer</code> provides a version optimised for strided
-     * bytes in one dimension.
-     */
     @Override
-    public void copyFrom(byte[] src, int srcPos, int destIndex, int length)
-            throws IndexOutOfBoundsException, PyException {
-
-        // Data is here in the buffers
-        int s = srcPos;
-        int d = index0 + destIndex * stride;
-
-        // Strategy depends on whether items are laid end-to-end or there are gaps
-        if (stride == 1) {
-            // Straight copy of contiguous bytes
-            System.arraycopy(src, srcPos, storage, d, length);
-
-        } else {
-            // Non-contiguous copy: single byte items
-            int limit = d + length * stride;
-            for (; d != limit; d += stride) {
-                storage[d] = src[s++];
-            }
-        }
+    protected void storeAtImpl(byte value, int byteIndex) {
+        // Implement directly and don't ask whether read-only
+        storage[byteIndex] = value;
     }
 
     /**
@@ -84,17 +69,17 @@
      * slice.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {
 
-        if (length > 0) {
+        if (count > 0) {
             // Translate start relative to underlying buffer
             int compStride = this.stride * stride;
             int compIndex0 = index0 + start * this.stride;
             // Construct a view, taking a lock on the root object (this or this.root)
-            return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride);
+            return new SlicedView(getRoot(), flags, storage, compIndex0, count, compStride);
 
         } else {
-            // Special case for length==0 where above logic would fail. Efficient too.
+            // Special case for count==0 where above logic would fail. Efficient too.
             return new ZeroByteBuffer.View(getRoot(), flags);
         }
     }
diff --git a/src/org/python/core/buffer/ZeroByteBuffer.java b/src/org/python/core/buffer/ZeroByteBuffer.java
--- a/src/org/python/core/buffer/ZeroByteBuffer.java
+++ b/src/org/python/core/buffer/ZeroByteBuffer.java
@@ -12,7 +12,7 @@
  * operations like {@link #copyTo(byte[], int)}) and {@link #toString()} efficiently do nothing,
  * instead of calling complicated logic that finally does nothing.
  */
-public class ZeroByteBuffer extends BaseBuffer {
+public class ZeroByteBuffer extends BaseArrayBuffer {
 
     /** Shared instance of a zero-length storage. */
     private static final byte[] EMPTY = new byte[0];
@@ -22,19 +22,25 @@
 
     /**
      * Construct an instance of a zero-length buffer, choosing whether it should report itself to be
-     * read-only through {@link #isReadonly()}. This is moot, as any attempt to write to it produces
-     * an {@link IndexOutOfBoundsException}, but it is less surprising for client code that may ask,
-     * if the readability follows that of the object from which the buffer is derived.
+     * read-only through {@link #isReadonly()} or as having a backing array through
+     * {@link #hasArray()}. These properties are moot, as any attempt to write to the pretended
+     * backing array produces an {@link IndexOutOfBoundsException}, but it is less surprising for
+     * client code that may ask, if the results are customary for the exporting object.
      *
      * @param flags consumer requirements
-     * @param readonly set true if readonly
-     * @throws PyException (BufferError) when expectations do not correspond with the type
+     * @param readonly set true if not to be considered writable
+     * @param hasArray set true if to be considered as backed by an array
+     * @throws PyException (BufferError) when client expectations do not correspond with the type
      */
-    public ZeroByteBuffer(int flags, boolean readonly) throws PyException {
-        super(CONTIGUITY | SIMPLE | (readonly ? 0 : WRITABLE));
+    public ZeroByteBuffer(int flags, boolean readonly, boolean hasArray) throws PyException {
+        super(CONTIGUITY | (readonly ? 0 : WRITABLE));
         this.storage = EMPTY;                       // Empty array
         this.shape = SHAPE;                         // {0}
-        this.strides = SimpleBuffer.SIMPLE_STRIDES; // {1}
+        this.strides = BaseBuffer.CONTIG_STRIDES; // {1}
+        if (!hasArray) {
+            // super() knows we have an array, but this truth is inconvenient here.
+            removeFeatureFlags(AS_ARRAY);
+        }
         checkRequestFlags(flags);
     }
 
@@ -47,7 +53,7 @@
      * In a ZeroByteBuffer, the index is always out of bounds.
      */
     @Override
-    protected int calcIndex(int index) throws IndexOutOfBoundsException {
+    protected int byteIndex(int index) throws IndexOutOfBoundsException {
         // This causes all access to the bytes in to throw (since BaseBuffer calls it).
         throw new IndexOutOfBoundsException();
     }
@@ -56,7 +62,7 @@
      * In a ZeroByteBuffer, if the dimensions are right, the index is out of bounds anyway.
      */
     @Override
-    protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
+    protected int byteIndex(int... indices) throws IndexOutOfBoundsException {
         // Bootless dimension check takes precedence (for consistency with other buffers)
         checkDimension(indices);
         // This causes all access to the bytes to throw (since BaseBuffer calls it).
@@ -79,28 +85,28 @@
      * In a ZeroByteBuffer, there is simply nothing to copy.
      */
     @Override
-    public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
+    public void copyTo(int srcIndex, byte[] dest, int destPos, int count)
             throws IndexOutOfBoundsException, PyException {
         // Nothing to copy
     }
 
     /**
-     * In a ZeroByteBuffer, there is no room for anything, so this throws unless the source length
-     * is zero.
+     * In a ZeroByteBuffer, there is no room for anything, so this throws unless the source count is
+     * zero.
      */
     @Override
-    public void copyFrom(byte[] src, int srcPos, int destIndex, int length)
+    public void copyFrom(byte[] src, int srcPos, int destIndex, int count)
             throws IndexOutOfBoundsException, PyException {
         if (this.isReadonly()) {
             throw notWritable();
-        } else if (length > 0) {
+        } else if (count > 0) {
             throw new IndexOutOfBoundsException();
         }
     }
 
     /**
-     * In a ZeroByteBuffer, there is no room for anything, so this throws unless the source length
-     * is zero.
+     * In a ZeroByteBuffer, there is no room for anything, so this throws unless the source count is
+     * zero.
      */
     @Override
     public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
@@ -116,8 +122,8 @@
      * as a result, with the export count incremented.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length) {
-        if (start == 0 && length <= 0) {
+    public PyBuffer getBufferSlice(int flags, int start, int count) {
+        if (start == 0 && count <= 0) {
             return this.getBuffer(flags);
         } else {
             throw new IndexOutOfBoundsException();
@@ -129,9 +135,9 @@
      * as a result, with the export count incremented.
      */
     @Override
-    public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
-        // It can't matter what the stride is since length is zero, or there's an error.
-        return getBufferSlice(flags, start, length);
+    public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {
+        // It can't matter what the stride is since count is zero, or there's an error.
+        return getBufferSlice(flags, start, count);
     }
 
     /**
@@ -175,7 +181,7 @@
          */
         public View(PyBuffer root, int flags) {
             // Create a new ZeroByteBuffer on who-cares-what byte array
-            super(flags, root.isReadonly());
+            super(flags, root.isReadonly(), root.hasArray());
             // But we still have to get a lease on the root PyBuffer
             this.root = root.getBuffer(FULL_RO);
         }
diff --git a/tests/java/org/python/core/PyBufferTest.java b/tests/java/org/python/core/PyBufferTest.java
--- a/tests/java/org/python/core/PyBufferTest.java
+++ b/tests/java/org/python/core/PyBufferTest.java
@@ -71,13 +71,13 @@
     protected static final int[] sliceSteps = {1, 2, 3, 7};
 
     /** Exception raising requires the Jython interpreter to be initialised **/
-    protected PythonInterpreter interp = new PythonInterpreter();
+    protected static PythonInterpreter interp = new PythonInterpreter();
 
     /** The test material and a buffer created by the test-runner. */
-    private TestSpec spec;
-    ByteMaterial ref;
-    BufferProtocol obj;
-    PyBuffer view;
+    protected TestSpec spec;
+    protected ByteMaterial ref;
+    protected BufferProtocol obj;
+    protected PyBuffer view;
 
     /**
      * Construct an instance to run one test, using one set of test data.
@@ -103,11 +103,11 @@
     /*
      * Values for initialising the exporters.
      */
-    private static final ByteMaterial byteMaterial = new ByteMaterial(10, 16, 3);
-    private static final ByteMaterial abcMaterial = new ByteMaterial("abcdefgh");
-    private static final ByteMaterial stringMaterial = new ByteMaterial("Mon côté fâcheux");
-    private static final ByteMaterial emptyMaterial = new ByteMaterial(new byte[0]);
-    private static final ByteMaterial longMaterial = new ByteMaterial(0, LONG, 5);
+    protected static final ByteMaterial byteMaterial = new ByteMaterial(10, 16, 3);
+    protected static final ByteMaterial abcMaterial = new ByteMaterial("abcdefgh");
+    protected static final ByteMaterial stringMaterial = new ByteMaterial("Mon côté fâcheux");
+    protected static final ByteMaterial emptyMaterial = new ByteMaterial(new byte[0]);
+    protected static final ByteMaterial longMaterial = new ByteMaterial(0, LONG, 5);
 
     /**
      * Generate test data to be held in the testing framework and used to construct tests. This
@@ -161,6 +161,19 @@
         };
         s.add(stringExporter, stringMaterial);
 
+        // Tests with an buffer implementation directly extending BaseBuffer
+
+        ExporterFactory rollYourOwnExporter = new WritableExporterFactory() {
+
+            @Override
+            public BufferProtocol make(ByteMaterial m) {
+                return new RollYourOwnExporter(m.getBytes());
+            }
+
+        };
+        s.add(rollYourOwnExporter, byteMaterial);
+        s.add(rollYourOwnExporter, emptyMaterial);
+
         // Tests with PyByteArray
 
         ExporterFactory pyByteArrayExporter = new WritableExporterFactory() {
@@ -990,10 +1003,12 @@
     @Test
     public void testIsContiguous() {
         announce("isContiguous");
-        // True for all test material and orders (since 1-dimensional)
+        // All test material is 1-dimensional so it's fairly simple and same for all orders
+        int ndim = spec.shape[0], stride = spec.getStride(), itemsize = spec.getItemsize();
+        boolean contig = ndim < 2 || stride == itemsize;
         for (String orderMsg : validOrders) {
             char order = orderMsg.charAt(0);
-            assertTrue(orderMsg, view.isContiguous(order));
+            assertEquals(orderMsg, view.isContiguous(order), contig);
         }
     }
 
@@ -1069,17 +1084,17 @@
     }
 
     /*
-     * ------------------------------------------------------------------------------------------- A
-     * series of custom exporters to permit testing abstracted from the Jython interpreter. These
+     * --------------------------------------------------------------------------------------------
+     * A series of custom exporters to permit testing abstracted from the Jython interpreter. These
      * use the implementation classes in org.python.core.buffer in ways very similar to the
      * implementations of bytearray and str.
-     * -------------------------------------------------------------------------------------------
+     * --------------------------------------------------------------------------------------------
      */
     /**
-     * A class to act as an exporter that uses the SimpleReadonlyBuffer. The exporter exports a new
-     * PyBuffer object to each consumer (although each references the same internal storage) and it
-     * does not track their fate. You are most likely to use this approach with an exporting object
-     * that is immutable (or at least fixed in size).
+     * A class to act as an exporter that uses the SimpleBuffer. The exporter exports a new PyBuffer
+     * object to each consumer (although each references the same internal storage) and it does not
+     * track their fate. You are most likely to use this approach with an exporting object that is
+     * immutable (or at least fixed in size).
      */
     static class SimpleExporter implements BufferProtocol {
 
@@ -1109,7 +1124,8 @@
         protected Reference<BaseBuffer> export;
 
         /**
-         * Try to re-use existing exported buffer, or return null if can't.
+         * Try to re-use existing exported buffer, or return null if can't: modelled after the
+         * buffer re-use strategy in {@link PyByteArray}.
          */
         protected BaseBuffer getExistingBuffer(int flags) {
             BaseBuffer pybuf = null;
@@ -1191,7 +1207,7 @@
      * avoiding the cost of duplicate buffers. This is the case with PyByteArray, which prohibits
      * operations that would resize it, while there are outstanding exports.
      */
-    static class SimpleWritableExporter extends TestableExporter {
+    private static class SimpleWritableExporter extends TestableExporter {
 
         protected byte[] storage;
 
@@ -1226,4 +1242,131 @@
 
     }
 
+    /** A class to act as an exporter that uses the RollYourOwnBuffer. */
+    private static class RollYourOwnExporter extends TestableExporter {
+
+        protected byte[] storage;
+
+        public RollYourOwnExporter(byte[] storage) {
+            this.storage = storage;
+        }
+
+        @Override
+        public PyBuffer getBuffer(int flags) {
+            // If we have already exported a buffer it may still be available for re-use
+            BaseBuffer pybuf = getExistingBuffer(flags);
+            if (pybuf == null) {
+                // No existing export we can re-use
+                pybuf = new RollYourOwnArrayBuffer(flags, storage);
+                // Hold a reference for possible re-use
+                export = new WeakReference<BaseBuffer>(pybuf);
+            }
+            return pybuf;
+        }
+
+    }
+
+    /**
+     * Minimal extension of BaseBuffer in order to test the default implementations there. They're
+     * slow, so mostly we override them in the implementations BaseArrayBuffer and BaseNIOBuffer,
+     * but they still have to be correct. The class represents a one-dimensional, strided array of
+     * bytes, so it can represent a slice of itself.
+     */
+    private static class RollYourOwnArrayBuffer extends BaseBuffer {
+
+        final static int FEATURES = PyBUF.WRITABLE | PyBUF.AS_ARRAY;
+
+        final byte[] storage;
+        final PyBuffer root;
+
+        /**
+         * Create a buffer view of the entire array.
+         *
+         * @param flags consumer requirements
+         * @param storage byte array exported in its entirety
+         */
+        public RollYourOwnArrayBuffer(int flags, byte[] storage) {
+            this(null /* =this */, flags, storage, 0, storage.length, 1);
+        }
+
+        /**
+         * Construct a slice of a one-dimensional byte buffer.
+         *
+         * @param root on which release must be called when this is released
+         * @param flags consumer requirements
+         * @param storage raw byte array containing exported data
+         * @param index0 index into storage of item[0]
+         * @param length number of items in the slice
+         * @param stride in between successive elements of the new PyBuffer
+         * @throws PyException (BufferError) when expectations do not correspond with the type
+         */
+        public RollYourOwnArrayBuffer(PyBuffer root, int flags, byte[] storage, int index0,
+                int length, int stride) throws ArrayIndexOutOfBoundsException,
+                NullPointerException, PyException {
+            // Client will need to navigate using shape and strides if this is a slice
+            super(FEATURES | ((index0 == 0 && stride == 1) ? 0 : PyBUF.STRIDES));
+            this.storage = storage;
+            this.index0 = index0;
+            shape = new int[] {length};
+            strides = new int[] {stride};
+            // Check the potential index range
+            if (length > 0) {
+                int end = index0 + (length - 1) * stride;
+                final int END = storage.length - 1;
+                if (index0 < 0 || index0 > END || end < 0 || end > END) {
+                    throw new IndexOutOfBoundsException();
+                }
+            }
+            // Check client is compatible
+            checkRequestFlags(flags);
+            // Get a lease on the root PyBuffer (read-only). Last in case a check above fails.
+            if (root == null) {
+                this.root = this;
+            } else {
+                this.root = root.getBuffer(FULL_RO);
+            }
+        }
+
+        @Override
+        protected PyBuffer getRoot() {
+            return root;
+        }
+
+        @Override
+        public void releaseAction() {
+            // XXX Consider making this automatic within BaseBuffer.release() when getRoot()!=this
+            /*
+             * ... so that {@link #release()} takes care of this: sub-classes should not propagate
+             * the release themselves when overriding {@link #releaseAction()}.
+             */
+            // We have to release the root too if ours was final and we are not that root.
+            if (root != this) {
+                root.release();
+            }
+        }
+
+        @Override
+        public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
+            int newStart = index0 + start * strides[0];
+            int newStride = strides[0] * stride;
+            return new RollYourOwnArrayBuffer(root, flags, storage, newStart, length, newStride);
+        }
+
+        @Override
+        public ByteBuffer getNIOByteBufferImpl() {
+            return ByteBuffer.wrap(storage);
+        }
+
+        @Override
+        protected byte byteAtImpl(int byteIndex) {
+            return storage[byteIndex];
+        }
+
+        @Override
+        protected void storeAtImpl(byte value, int byteIndex) throws IndexOutOfBoundsException,
+                PyException {
+            storage[byteIndex] = value;
+        }
+
+    }
 }
diff --git a/tests/java/org/python/core/PyBufferTestSupport.java b/tests/java/org/python/core/PyBufferTestSupport.java
--- a/tests/java/org/python/core/PyBufferTestSupport.java
+++ b/tests/java/org/python/core/PyBufferTestSupport.java
@@ -260,7 +260,7 @@
         /** Either {@link PyBUF#FULL_RO} or {@link PyBUF#FULL} according to {@link #readonly}. */
         final int flags;
 
-        /** Allowable basic flag combinations, such as */
+        /** Allowable basic flag combinations, such as {@link PyBUF#STRIDES}. */
         final int[] validFlags;
 
         /** Allowable additional flag combinations, such as {@link PyBUF#FORMAT} */

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list