Commits

Anonymous committed 58f8ff3

Buffer api: eliminate BufferPointer as class
Improvements associated with the attempt to make java.nio.ByteBuffer part of the buffer API. The BufferPointer class is now PyBuffer.Pointer and only appears as a return type. Internals of BaseBuffer flattened and the implementation of Sliced1DBuffer made slicker with range-checking in the constructor. JUnit tests (tidied a bit) all pass.

Comments (0)

Files changed (11)

src/org/python/core/BufferPointer.java

-package org.python.core;
-
-/**
- * A class that references a specified <code>byte[]</code> array and an offset in it to be treated
- * as "index zero", for use in the buffer API. This class simply bundles together a reference to an
- * array and a particular offset within that array. It is used by the Jython buffer API roughly
- * where the CPython buffer API uses a C (char *) pointer.
- */
-public class BufferPointer {
-
-    /**
-     * Reference to the backing array. Usually this is the actual storage exported by a Python
-     * object. In some contexts the consumer will be entitled to make changes to the contents of
-     * this array, and in others, not. See {@link PyBuffer#isReadonly()}.
-     */
-    public final byte[] storage;
-    /** Starting position within the array for index calculations: "index zero". */
-    public final int offset;
-
-    /**
-     * Refer to an offset in the given array.
-     *
-     * @param storage array at reference
-     * @param offset index of the first byte
-     */
-    public BufferPointer(byte[] storage, int offset) {
-        // No checks: keep it simple
-        this.storage = storage;
-        this.offset = offset;
-    }
-
-    /**
-     * Refer to the whole of a byte array.
-     *
-     * @param storage array at reference
-     */
-    public BufferPointer(byte[] storage) {
-        this.storage = storage;
-        this.offset = 0;
-    }
-}

src/org/python/core/PyBuffer.java

      * 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 offset, size and stride will be translated from the arguments given, through this
+     * new start index, length and stride will be translated from the arguments given, through this
      * buffer's stride and item size. The consumer always expresses <code>start</code> and
      * <code>strides</code> in terms of the abstract view of this buffer.
      *
 
     // Direct access to actual storage
     //
+
+    /**
+     * 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
+     * object. In some contexts the consumer will be entitled to make changes to the contents of
+     * this array, and in others not. See {@link PyBuffer#isReadonly()}. It is used by the Jython
+     * buffer API roughly where the CPython buffer API uses a C (char *) pointer.
+     */
+    public static class Pointer {
+
+        /** Reference to the array holding the bytes. */
+        public byte[] storage;
+        /** Starting position within the array for the data being pointed to. */
+        public int offset;
+
+        /**
+         * Construct a reference to the given array and offset.
+         *
+         * @param storage array at reference
+         * @param offset index of the reference byte
+         */
+        public Pointer(byte[] storage, int offset) {
+            this.storage = storage;
+            this.offset = offset;
+        }
+    }
+
     /**
      * Return a structure describing the slice of a byte array that holds the data being exported to
      * the consumer. For a one-dimensional contiguous buffer, assuming the following client code
      * <pre>
      * PyBuffer a = obj.getBuffer();
      * int itemsize = a.getItemsize();
-     * BufferPointer b = a.getBuf();
+     * PyBuffer.Pointer b = a.getBuf();
      * </pre>
      *
      * the item with index <code>k</code> is in the array <code>b.storage</code> at index
      * inclusive. And if <code>itemsize==1</code>, the item is simply the byte
      * <code>b.storage[b.offset + k]</code>
      * <p>
-     * If the buffer is multidimensional or non-contiguous, <code>b.storage[b.offset]</code> is
-     * still the (first byte of) the item at index [0] or [0,...,0]. However, it is necessary to
-     * navigate <code>b</code> using the <code>shape</code>, <code>strides</code> and maybe
+     * If the buffer is multidimensional or non-contiguous, <code>storage[offset]</code> is still
+     * the (first byte of) the item at index [0] or [0,...,0]. However, it is necessary to navigate
+     * <code>b.storage</code> using the <code>shape</code>, <code>strides</code> and maybe
      * <code>suboffsets</code> provided by the API.
      *
      * @return structure defining the byte[] slice that is the shared data
      */
-    BufferPointer getBuf();
+    PyBuffer.Pointer getBuf();
 
     /**
      * Return a structure describing the slice of a byte array that points to a single item from the
      * int k = ... ;
      * PyBuffer a = obj.getBuffer();
      * int itemsize = a.getItemsize();
-     * BufferPointer b = a.getPointer(k);
+     * PyBuffer.Pointer b = a.getPointer(k);
      * </pre>
      *
      * the item with index <code>k</code> is in the array <code>b.storage</code> at index
      * @param index in the buffer to position the pointer
      * @return structure defining the byte[] slice that is the shared data
      */
-    BufferPointer getPointer(int index);
+    PyBuffer.Pointer getPointer(int index);
 
     /**
      * Return a structure describing the slice of a byte array that points to a single item from the
      * // ... calculation that assigns i, j, k
      * PyBuffer a = obj.getBuffer();
      * int itemsize = a.getItemsize();
-     * BufferPointer b = a.getPointer(i,j,k);
+     * PyBuffer.Pointer b = a.getPointer(i,j,k);
      * </pre>
      *
      * the item with index <code>[i,j,k]</code> is in the array <code>b.storage</code> at index
      * @param indices multidimensional index at which to position the pointer
      * @return structure defining the byte[] slice that is the shared data
      */
-    BufferPointer getPointer(int... indices);
+    PyBuffer.Pointer getPointer(int... indices);
 
     // Inherited from PyBUF and belonging here
     //
      */
     @Override
     public String toString();
+
 }

src/org/python/core/buffer/BaseBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.BufferProtocol;
 import org.python.core.Py;
 import org.python.core.PyBUF;
      * filled (difference from CPython). This value is returned by {@link #getShape()}.
      */
     protected int[] shape;
+
     /**
      * Step sizes in the underlying buffer essential to correct translation of an index (or indices)
      * into an index into the storage. The <code>strides</code> array should always be created and
      * CPython). This value is returned by {@link #getStrides()}.
      */
     protected int[] strides;
+
     /**
-     * Reference to a structure that wraps the underlying storage that the exporter is sharing with
-     * the consumer.
+     * 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)}).
      */
-    protected BufferPointer buf;
+    protected byte[] storage;
+
+    /**
+     * 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.
+     */
+    protected int index0;
+
     /**
      * Count the number of times {@link #release()} must be called before actual release actions
      * need to take place. Equivalently, this is the number of calls to
      * 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 assign: {@link #buf},
-     * {@link #shape}, and {@link #strides}, and call {@link #checkRequestFlags(int)} passing the
-     * consumer's request flags.
-     *
-     * <pre>
-     * this.buf = buf;                  // Wraps exported data
-     * this.shape = shape;              // Array of dimensions of exported data (in units)
-     * this.strides = strides;          // Byte offset between successive items in each dimension
-     * checkRequestFlags(flags);        // Check request is compatible with type
-     * </pre>
+     * 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
      */
         return shape;
     }
 
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The default implementation in <code>BaseBuffer</code> deals with the general one-dimensional
+     * case, with any item size and stride.
+     */
     @Override
     public int getLen() {
-        // Correct if one-dimensional bytes. Override with itemsize*product(shape).
-        return shape[0];
+        // Correct if one-dimensional. Override with itemsize*product(shape).
+        return shape[0] * getItemsize();
     }
 
     @Override
     public byte byteAt(int index) throws IndexOutOfBoundsException {
-        return buf.storage[calcIndex(index)];
+        return storage[calcIndex(index)];
     }
 
     @Override
         if (isReadonly()) {
             throw notWritable();
         }
-        buf.storage[calcIndex(index)] = value;
+        storage[calcIndex(index)] = value;
     }
 
     /**
      */
     protected int calcIndex(int index) throws IndexOutOfBoundsException {
         // Treat as one-dimensional
-        return buf.offset + index * getStrides()[0];
+        return index0 + index * getStrides()[0];
     }
 
     @Override
     public byte byteAt(int... indices) throws IndexOutOfBoundsException {
-        return buf.storage[calcIndex(indices)];
+        return storage[calcIndex(indices)];
     }
 
     @Override
         if (isReadonly()) {
             throw notWritable();
         }
-        buf.storage[calcIndex(indices)] = value;
+        storage[calcIndex(indices)] = value;
     }
 
     /**
      * Convert a multi-dimensional item index (if we are not using indirection) to an absolute byte
      * index in the actual storage array being shared by the exporter. The purpose of this method is
      * 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 as seen by the buffer.
+     * 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 <code>BufferPointer</code> member
-     * {@link #buf}, the buffer implementation may use <code>buf.storage[calcIndex(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, this is fairly
-     * inefficient, and an implementation will override the accessors to in-line the calculation.
-     * The default implementation here is suited to N-dimensional arrays.
+     * 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>
+     * 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. The default implementation here is suited to N-dimensional arrays.
      *
      * @param indices of the item from the consumer
      * @return index relative to item x[0,...,0] in actual storage
      */
     protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
         final int N = checkDimension(indices);
-        // In general: buf.offset + sum(k=0,N-1) indices[k]*strides[k]
-        int index = buf.offset;
+        // 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++) {
         // 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(buf.storage, s, dest, d, length * itemsize);
+            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++] = buf.storage[s];
+                dest[d++] = storage[s];
             }
 
         } else {
             for (; s < limit; s += skip) {
                 int t = s + itemsize;
                 while (s < t) {
-                    dest[d++] = buf.storage[s++];
+                    dest[d++] = storage[s++];
                 }
             }
         }
         // 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, buf.storage, d, length * itemsize);
+            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) {
-                buf.storage[d] = src[s++];
+                storage[d] = src[s++];
             }
 
         } else {
             for (; d != limit; d += skip) {
                 int t = d + itemsize;
                 while (d < t) {
-                    buf.storage[d++] = src[s++];
+                    storage[d++] = src[s++];
                 }
             }
         }
         // Strategy depends on whether items are laid end-to-end or there are gaps
         if (stride == itemsize) {
             // Straight copy to contiguous bytes
-            src.copyTo(buf.storage, d);
+            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) {
-                buf.storage[d] = src.byteAt(s++);
+                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) {
-                BufferPointer srcItem = src.getPointer(s++);
-                System.arraycopy(srcItem.storage, srcItem.offset, buf.storage, d, itemsize);
+                Pointer srcItem = src.getPointer(s++);
+                System.arraycopy(srcItem.storage, srcItem.offset, storage, d, itemsize);
             }
         }
 
     // @Override public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {}
 
     @Override
-    public BufferPointer getBuf() {
-        return buf;
+    public Pointer getBuf() {
+        return new Pointer(storage, index0);
     }
 
     @Override
-    public BufferPointer getPointer(int index) {
-        return new BufferPointer(buf.storage, calcIndex(index));
+    public Pointer getPointer(int index) throws IndexOutOfBoundsException {
+        return new Pointer(storage, calcIndex(index));
     }
 
     @Override
-    public BufferPointer getPointer(int... indices) {
-        return new BufferPointer(buf.storage, calcIndex(indices));
+    public Pointer getPointer(int... indices) throws IndexOutOfBoundsException {
+        return new Pointer(storage, calcIndex(indices));
     }
 
     @Override
     }
 
     /**
-     * Convenience method for checking arguments to slice formation, that the start and end elements
-     * are within the buffer. An exception is raised if <code>start&lt;0</code> or
-     * <code>start+length&gt;shape[0]</code>. This logic is correct for one-dimensional arrays (of
-     * any item size) and stride. In the context we use this, <code>length</code> is guaranteed by
-     * the acller to be non-negative.
-     *
-     * @param start index to check
-     * @param length number of elements in slice (must be &gt;0)
-     * @throws IndexOutOfBoundsException
-     */
-    protected void checkSlice(int start, int length) throws IndexOutOfBoundsException {
-        // Between the last element of the slice and the end of the buffer there are ...
-        int margin = shape[0] - start - length;
-        if ((start | margin) < 0) {
-            throw new IndexOutOfBoundsException("invalid slice of buffer");
-        }
-    }
-
-    /**
-     * Convenience method for checking arguments to slice formation, that the start and end elements
-     * are within the buffer. An exception is raised if either of <code>start</code> or
-     * <code>end=start+(length-1)*stride+1</code> is <code>&lt;0</code> or <code>&gt;shape[0]</code>
-     * . This logic is correct for one-dimensional arrays (of any item size) and current stride.
-     * Note that the parameter stride is in terms of this biuffer's indexing. In the context we use
-     * this, <code>length</code> is guaranteed by the acller to be non-negative.
-     *
-     * @param start index to check
-     * @param length number of elements in slice (must be &gt;0)
-     * @param stride in buffer elements between items of the slice
-     * @throws IndexOutOfBoundsException
-     */
-    protected void checkSlice(int start, int length, int stride) throws IndexOutOfBoundsException {
-        /*
-         * Simpler to check if we know which is the smaller of the two ends, which depends on the
-         * sign of the stride.
-         */
-        int lo, hi;
-        if (stride > 0) {
-            lo = start;
-            hi = start + (length - 1) * stride + 1;
-        } else {
-            hi = start;
-            lo = start + (length - 1) * stride + 1;
-        }
-        // Between the last element of the slice and the end of the buffer there are ...
-        int margin = shape[0] - hi;
-        if ((lo | margin) < 0) {
-            throw new IndexOutOfBoundsException("invalid slice of buffer");
-        }
-    }
-
-    /**
      * The toString() method of a buffer reproduces the values in the buffer (as unsigned integers)
      * as the character codes of a <code>String</code>.
      */

src/org/python/core/buffer/SimpleBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.PyBuffer;
 import org.python.core.PyException;
 import org.python.core.util.StringUtil;
 
     /**
      * Provide an instance of <code>SimpleBuffer</code> with navigation variables partly
-     * initialised, for sub-class use. One-dimensional arrays without slicing are C- and
-     * F-contiguous. To complete initialisation, the sub-class normally must assign: {@link #buf}
-     * and {@link #shape}[0], and call {@link #checkRequestFlags(int)} passing the consumer's
-     * request flags.
-     *
-     * <pre>
-     * this.buf = buf;              // Wraps exported data
-     * this.shape[0] = n;           // Number of units in exported data
-     * checkRequestFlags(flags);    // Check request is compatible with type
-     * </pre>
+     * initialised, for sub-class use. One-dimensional arrays without strides are C- and
+     * F-contiguous. To complete initialisation, the sub-class must normally assign the buffer (
+     * {@link #storage}, {@link #index0}), and the navigation ({@link #shape} array), and then call
+     * {@link #checkRequestFlags(int)} passing the consumer's request flags.
      */
     protected SimpleBuffer() {
         super(CONTIGUITY | SIMPLE);
     }
 
     /**
+     * Provide an instance of <code>SimpleBuffer</code> with navigation variables initialised, for
+     * sub-class use. The buffer ({@link #storage}, {@link #index0}), and the {@link #shape} array
+     * will be initialised from the arguments (which are checked for range). The {@link #strides} is
+     * set for (one-byte) unit stride. Only the call to {@link #checkRequestFlags(int)}, passing the
+     * consumer's request flags really remains for the sub-class constructor to do.
+     *
+     * <pre>
+     * super(storage, index0, size);
+     * checkRequestFlags(flags);        // Check request is compatible with type
+     * </pre>
+     *
+     * @param storage the array of bytes storing the implementation of the exporting object
+     * @param index0 offset where the data starts in that array (item[0])
+     * @param size the number of bytes occupied
+     * @throws NullPointerException if <code>storage</code> is null
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code> and <code>size</code> are
+     *             inconsistent with <code>storage.length</code>
+     */
+    public SimpleBuffer(byte[] storage, int index0, int size) throws PyException,
+            ArrayIndexOutOfBoundsException {
+        this();
+        this.storage = storage;         // Exported data
+        this.index0 = index0;           // Index to be treated as item[0]
+        this.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) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+    }
+
+    /**
      * Provide an instance of <code>SimpleBuffer</code>, on a slice of a byte array, meeting the
      * consumer's expectations as expressed in the <code>flags</code> argument, which is checked
      * against the capabilities of the buffer type.
      *
      * @param flags consumer requirements
      * @param storage the array of bytes storing the implementation of the exporting object
-     * @param offset where the data starts in that array (item[0])
+     * @param index0 offset where the data starts in that array (item[0])
      * @param size the number of bytes occupied
+     * @throws NullPointerException if <code>storage</code> is null
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code> and <code>size</code> are
+     *             inconsistent with <code>storage.length</code>
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
-    public SimpleBuffer(int flags, byte[] storage, int offset, int size) throws PyException {
+    public SimpleBuffer(int flags, byte[] storage, int index0, int size) throws PyException,
+            ArrayIndexOutOfBoundsException, NullPointerException {
+        this(storage, index0, size);    // Construct checked SimpleBuffer
+        checkRequestFlags(flags);       // Check request is compatible with type
+    }
+
+    /**
+     * Provide an instance of <code>SimpleBuffer</code>, on the entirety of a byte array, with
+     * navigation variables initialised, for sub-class use. The buffer ( {@link #storage},
+     * {@link #index0}), and the navigation ({@link #shape} array) will be initialised from the
+     * array argument.
+     *
+     * @param storage the array of bytes storing the implementation of the exporting object
+     * @throws NullPointerException if <code>storage</code> is null
+     */
+    public SimpleBuffer(byte[] storage) throws NullPointerException {
         this();
-        // Wrap the exported data on a BufferPointer object
-        this.buf = new BufferPointer(storage, offset);
-        this.shape[0] = size;        // Number of units in exported data
-        checkRequestFlags(flags);    // Check request is compatible with type
+        this.storage = storage;         // Exported data (index0=0 from initialisation)
+        this.shape[0] = storage.length; // Number of units in whole array
     }
 
     /**
      *
      * @param flags consumer requirements
      * @param storage the array of bytes storing the implementation of the exporting object
+     * @throws NullPointerException if <code>storage</code> is null
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
-    public SimpleBuffer(int flags, byte[] storage) throws PyException {
-        this(flags, storage, 0, storage.length);
+    public SimpleBuffer(int flags, byte[] storage) throws PyException, NullPointerException {
+        this(storage);                  // Construct SimpleBuffer on whole array
+        checkRequestFlags(flags);       // Check request is compatible with type
     }
 
     @Override
      * one-dimension.
      */
     @Override
+    public int getLen() {
+        // Simplify for one-dimensional contiguous bytes
+        return shape[0];
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * <code>SimpleBuffer</code> provides an implementation optimised for contiguous bytes in
+     * one-dimension.
+     */
+    @Override
     public byte byteAt(int index) throws IndexOutOfBoundsException {
         // Implement directly: a bit quicker than the default
-        return buf.storage[buf.offset + index];
+        return storage[index0 + index];
     }
 
     /**
     @Override
     public int intAt(int index) throws IndexOutOfBoundsException {
         // Implement directly: a bit quicker than the default
-        return 0xff & buf.storage[buf.offset + index];
+        return 0xff & storage[index0 + index];
     }
 
     @Override
     protected int calcIndex(int index) throws IndexOutOfBoundsException {
-        return buf.offset + index;
+        return index0 + index;
     }
 
     /**
     @Override
     public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
             throws IndexOutOfBoundsException {
-        System.arraycopy(buf.storage, buf.offset + srcIndex, dest, destPos, length);
+        System.arraycopy(storage, index0 + srcIndex, dest, destPos, length);
     }
 
     @Override
     public PyBuffer getBufferSlice(int flags, int start, int length) {
         if (length > 0) {
-            // Check the arguments define a slice within this buffer
-            checkSlice(start, length);
             // Translate relative to underlying buffer
-            int compIndex0 = buf.offset + start;
+            int compIndex0 = index0 + start;
             // Create the slice from the sub-range of the buffer
-            return new SimpleView(getRoot(), flags, buf.storage, compIndex0, length);
+            return new SimpleView(getRoot(), flags, storage, compIndex0, length);
         } else {
             // Special case for length==0 where above logic would fail. Efficient too.
             return new ZeroByteBuffer.View(getRoot(), flags);
             return getBufferSlice(flags, start, length);
 
         } else {
-            // Check the arguments define a slice within this buffer
-            checkSlice(start, length, stride);
             // Translate relative to underlying buffer
-            int compIndex0 = buf.offset + start;
+            int compIndex0 = index0 + start;
             // Construct a view, taking a lock on the root object (this or this.root)
-            return new Strided1DBuffer.SlicedView(getRoot(), flags, buf.storage, compIndex0,
-                length, stride);
+            return new Strided1DBuffer.SlicedView(getRoot(), flags, storage, compIndex0, length,
+                    stride);
         }
     }
 
     @Override
-    public BufferPointer getPointer(int index) {
-        return new BufferPointer(buf.storage, buf.offset + index);
+    public Pointer getPointer(int index) throws IndexOutOfBoundsException {
+        return new Pointer(storage, index0 + index);
     }
 
     @Override
-    public BufferPointer getPointer(int... indices) {
+    public Pointer getPointer(int... indices) throws IndexOutOfBoundsException {
         checkDimension(indices.length);
         return getPointer(indices[0]);
     }
     @Override
     public String toString() {
         // For contiguous bytes in one dimension we can avoid the intAt() calls
-        return StringUtil.fromBytes(buf.storage, buf.offset, shape[0]);
+        return StringUtil.fromBytes(storage, index0, shape[0]);
     }
 
     /**

src/org/python/core/buffer/SimpleStringBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.PyBuffer;
 import org.python.core.util.StringUtil;
 
 /**
  * Buffer API that appears to be a one-dimensional array of one-byte items providing read-only API,
  * but which is actually backed by a Java String. Some of the buffer API absolutely needs access to
- * the data as a byte array (those parts that involve a {@link BufferPointer} result), and therefore
+ * the data as a byte array (those parts that involve a {@link PyBuffer.Pointer} result), and therefore
  * this class must create a byte array from the String for them. However, it defers creation of a
  * byte array until that part of the API is actually used. Where possible, this class overrides
  * those methods in SimpleBuffer that would otherwise access the byte array attribute to use the
      * @param flags consumer requirements
      */
     public SimpleStringBuffer(int flags, String bufString) {
-        super();
         // Save the backing string
         this.bufString = bufString;
         shape[0] = bufString.length();
      * <p>
      * The <code>SimpleStringBuffer</code> implementation creates an actual byte buffer.
      */
+    @Override
     public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
         if (stride == 1) {
             // Unstrided slice of simple buffer is itself simple
     }
 
     /**
-     * {@inheritDoc}
-     * <p>
-     * This method creates an actual byte buffer from the String if none yet exists.
+     * This method creates an actual byte array from the underlying String if none yet exists.
      */
-    @Override
-    public BufferPointer getBuf() {
-        if (buf == null) {
-            // We can't avoid creating buf any longer
-            buf = new BufferPointer(StringUtil.toBytes(bufString));
+    private void ensureHaveBytes() {
+        if (storage == null) {
+            // We can't avoid creating the byte array any longer (index0 already correct)
+            storage = StringUtil.toBytes(bufString);
         }
-        return buf;
     }
 
     /**
      * {@inheritDoc}
      * <p>
-     * This method creates an actual byte buffer from the String if none yet exists.
+     * This method creates an actual byte array from the underlying String if none yet exists.
      */
     @Override
-    public BufferPointer getPointer(int index) {
-        getBuf(); // Ensure buffer created
+    public Pointer getBuf() {
+        ensureHaveBytes();
+        return super.getBuf();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method creates an actual byte array from the underlying String if none yet exists.
+     */
+    @Override
+    public Pointer getPointer(int index) {
+        ensureHaveBytes();
         return super.getPointer(index);
     }
 
     /**
      * {@inheritDoc}
      * <p>
-     * This method creates an actual byte buffer from the String if none yet exists.
+     * This method creates an actual byte array from the underlying String if none yet exists.
      */
     @Override
-    public BufferPointer getPointer(int... indices) {
-        getBuf(); // Ensure buffer created
+    public Pointer getPointer(int... indices) {
+        ensureHaveBytes();
         return super.getPointer(indices);
     }
 
     /**
-     * The <code>toString()</code> method of a <code>SimpleStringBuffer</code> simply produces the underlying <code>String</code>.
+     * The <code>toString()</code> method of a <code>SimpleStringBuffer</code> simply produces the
+     * underlying <code>String</code>.
      */
     @Override
     public String toString() {

src/org/python/core/buffer/SimpleWritableBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.PyBuffer;
 import org.python.core.PyException;
 
 public class SimpleWritableBuffer extends SimpleBuffer {
 
     /**
-     * Provide an instance of <code>SimpleWritableBuffer</code>, on a slice of a byte array, meeting the consumer's expectations
-     * as expressed in the <code>flags</code> argument, which is checked against the capabilities of
-     * the buffer type.
+     * Provide an instance of <code>SimpleWritableBuffer</code>, on a slice of a byte array, meeting
+     * the consumer's expectations as expressed in the <code>flags</code> argument, which is checked
+     * against the capabilities of the buffer type.
      *
      * @param flags consumer requirements
      * @param storage the array of bytes storing the implementation of the exporting object
-     * @param offset where the data starts in that array (item[0])
+     * @param index0 offset where the data starts in that array (item[0])
      * @param size the number of bytes occupied
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
-    public SimpleWritableBuffer(int flags, byte[] storage, int offset, int size) throws PyException {
+    public SimpleWritableBuffer(int flags, byte[] storage, int index0, int size)
+            throws PyException, NullPointerException {
+        super(storage, index0, size);   // Construct checked SimpleBuffer
         addFeatureFlags(WRITABLE);
-        // Wrap the exported data on a BufferPointer object
-        this.buf = new BufferPointer(storage, offset);
-        this.shape[0] = size;        // Number of units in exported data
-        checkRequestFlags(flags);    // Check request is compatible with type
+        checkRequestFlags(flags);       // Check request is compatible with type
     }
 
     /**
-     * Provide an instance of <code>SimpleWritableBuffer</code>, on the entirety of a byte array, meeting the consumer's expectations
-     * as expressed in the <code>flags</code> argument, which is checked against the capabilities of
-     * the buffer type.
+     * Provide an instance of <code>SimpleWritableBuffer</code>, on the entirety of a byte array,
+     * meeting the consumer's expectations as expressed in the <code>flags</code> argument, which is
+     * checked against the capabilities of the buffer type.
      *
      * @param flags consumer requirements
      * @param storage the array of bytes storing the implementation of the exporting object
      * @throws PyException (BufferError) when expectations do not correspond with the type
      */
-    public SimpleWritableBuffer(int flags, byte[] storage) throws PyException {
-        this(flags, storage, 0, storage.length);
+    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
     @Override
     public void storeAt(byte value, int index) {
         // Implement directly and don't ask whether read-only
-        buf.storage[buf.offset + index] = value;
+        storage[index0 + index] = value;
     }
 
     /**
      */
     @Override
     public void copyFrom(byte[] src, int srcPos, int destIndex, int length) {
-        System.arraycopy(src, srcPos, buf.storage, buf.offset + destIndex, length);
+        System.arraycopy(src, srcPos, storage, index0 + destIndex, length);
     }
 
     /**
      */
     @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(buf.storage, buf.offset);
+        src.copyTo(storage, index0);
     }
 
     /**
     @Override
     public PyBuffer getBufferSlice(int flags, int start, int length) {
         if (length > 0) {
-            // Check the arguments define a slice within this buffer
-            checkSlice(start, length);
             // Translate relative to underlying buffer
-            int compIndex0 = buf.offset + start;
+            int compIndex0 = index0 + start;
             // Create the slice from the sub-range of the buffer
-            return new SimpleView(getRoot(), flags, buf.storage, compIndex0, length);
+            return new SimpleView(getRoot(), flags, storage, compIndex0, length);
         } else {
             // Special case for length==0 where above logic would fail. Efficient too.
             return new ZeroByteBuffer.View(getRoot(), flags);
             return getBufferSlice(flags, start, length);
 
         } else {
-            // Check the arguments define a slice within this buffer
-            checkSlice(start, length, stride);
             // Translate relative to underlying buffer
-            int compIndex0 = buf.offset + start;
+            int compIndex0 = index0 + start;
             // Construct a view, taking a lock on the root object (this or this.root)
-            return new Strided1DWritableBuffer.SlicedView(getRoot(), flags, buf.storage,
-                compIndex0, length, stride);
+            return new Strided1DWritableBuffer.SlicedView(getRoot(), flags, storage, compIndex0,
+                    length, stride);
         }
     }
 
     /**
-     * A <code>SimpleWritableBuffer.SimpleView</code> represents a contiguous subsequence of
-     * another <code>SimpleWritableBuffer</code>.
+     * A <code>SimpleWritableBuffer.SimpleView</code> represents a contiguous subsequence of another
+     * <code>SimpleWritableBuffer</code>.
      */
     static class SimpleView extends SimpleWritableBuffer {
 
          * @param root buffer which will be acquired and must be released ultimately
          * @param flags the request flags of the consumer that requested the slice
          * @param storage the array of bytes storing the implementation of the exporting object
-         * @param offset where the data starts in that array (item[0])
+         * @param index0 offset where the data starts in that array (item[0])
          * @param size the number of bytes occupied
          */
-        public SimpleView(PyBuffer root, int flags, byte[] storage, int offset, int size) {
+        public SimpleView(PyBuffer root, int flags, byte[] storage, int index0, int size) {
             // Create a new SimpleBuffer on the buffer passed in (part of the root)
-            super(flags, storage, offset, size);
+            super(flags, storage, index0, size);
             // Get a lease on the root PyBuffer
             this.root = root.getBuffer(FULL_RO);
         }

src/org/python/core/buffer/Strided1DBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.PyBuffer;
 import org.python.core.PyException;
 
 /**
  * Read-only buffer API over a one-dimensional array of one-byte items, that are evenly-spaced in a
- * storage array. The buffer has a <code>buf</code> property 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>).
+ * storage array. The buffer has <code>storage</code>, <code>index0</code> and <code>length</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>
- * Let this underlying buffer be the byte array <i>u(i)</i> for <i>i=a..a+N</i>, let <i>x</i> be the
+ * Let this underlying buffer be the byte array <i>u(i)</i> for <i>i=0..N-1</i>, let <i>x</i> be the
  * <code>Strided1DBuffer</code>, and let the stride be <i>p</i>. The storage works as follows.
  * 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>. Then,
- * <ul>
- * <li>when <i>p&gt;0</i>, we store <i>x(j)</i> at <i>u(a+pj)</i>, that is, <i>x(0)</i> is at
- * <i>u(a)</i> and the byte array slice size should be <i>N = (L-1)p+1</i>.</li>
- * <li>when <i>p&lt;0</i>, we store <i>x(j)</i> at <i>u((a+N-1)+pj)</i>, that is, <i>x(0)</i> is at
- * <i>u(a+N-1)</i>, and the byte array slice size should be <i>N = (L-1)(-p)+1</i>.</li>
- * <li><i>p=0</i> is not a useful stride.</li>
- * </ul>
+ * retrieved by <code>x.byteAt(j)</code>. Then, we store <i>x(j)</i> at <i>u(a+pj)</i>, that is,
+ * <i>x(0)</i> is at <i>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
+ * constructor arguments. The last item in the slice <i>x(L-1)</i> is stored at <i>u(a+p(L-1))</i>.
+ * If <i>p&lt;0</i> and <i>L&gt;1</i>, this will be to the left of <i>u(a)</i>, so the constructor
+ * argument index0 is not then the low index of the range occupied by the data. Clearly both these
+ * indexes must be in the range 0 to <i>N-1</i> inclusive, a rule enforced by the constructors
+ * (unless <i>L=0</i>, when it is assumed no array access will take place).
  * <p>
  * The class may be used by exporters to create a strided slice (e.g. to export the diagonal of a
  * matrix) and in particular by other buffers to create strided slices of themselves, such as to
     /**
      * 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: {@link #buf}, {@link #shape}[0], and {@link #stride}, and call
-     * {@link #checkRequestFlags(int)} passing the consumer's request flags.
+     * assign the navigational properties and call {@link #checkRequestFlags(int)} passing the
+     * consumer's request flags.
      *
      * <pre>
-     * this.buf = buf;              // Wraps exported data
-     * setStride(stride);           // Stride, shape[0] and index0 all set consistently
-     * checkRequestFlags(flags);    // Check request is compatible with type
+     * 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
     }
 
     /**
+     * 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
+     * checked for range).
+     *
+     * @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 NullPointerException if <code>storage</code> is null
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>length</code> and
+     *             <code>stride</code> are inconsistent with <code>storage.length</code>
+     */
+    public Strided1DBuffer(byte[] storage, int index0, int length, int stride)
+            throws ArrayIndexOutOfBoundsException, NullPointerException {
+        this();
+        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
+
+        if (length == 0) {
+            // Nothing to check as we'll make no accesses
+            addFeatureFlags(CONTIGUITY);
+
+        } else {
+            // Need to check lowest and highest index against array
+            int lo, hi;
+
+            if (stride == 1) {
+                lo = index0;                                // First byte of item[0]
+                hi = index0 + length;                       // 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
+
+            } else {
+                hi = index0 + 1;                            // Last byte of item[0] + 1
+                lo = index0 + (length - 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) {
+                throw new ArrayIndexOutOfBoundsException();
+            }
+        }
+    }
+
+    /**
      * 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>0</code>), valid for <code>0&lt;=i&lt;length</code>.
+     * the sign of <code>stride</code>), valid for <code>0&lt;=i&lt;length</code>. The constructor
+     * checks that all these indices lie within the <code>storage</code> array (unless
+     * <code>length=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
-     * <code>stride</code>. The caller is responsible for checking these fall within the array, or
-     * the sub-range the caller is allowed to use.
+     * <code>stride</code>. The constructor checks that these indices lie within the
+     * <code>storage</code> array (unless <code>length=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 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
+     *             <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)
-            throws PyException {
-        // Arguments programme the object directly
-        this();
-        this.shape[0] = length;
-        this.buf = new BufferPointer(storage, index0);
-        this.stride = stride;
-        if (stride == 1) {
-            // Really this is a simple buffer
-            addFeatureFlags(CONTIGUITY);
-        }
+            throws ArrayIndexOutOfBoundsException, NullPointerException, PyException {
+        this(storage, index0, length, stride);
         checkRequestFlags(flags);   // Check request is compatible with type
+
     }
 
     @Override
 
     @Override
     public byte byteAt(int index) throws IndexOutOfBoundsException {
-        return buf.storage[buf.offset + index * stride];
+        return storage[index0 + index * stride];
     }
 
     @Override
     protected int calcIndex(int index) throws IndexOutOfBoundsException {
-        return buf.offset + index * stride;
+        return index0 + index * stride;
     }
 
     @Override
     public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
             throws IndexOutOfBoundsException {
         // Data is here in the buffers
-        int s = buf.offset + srcIndex * stride;
+        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(buf.storage, s, dest, d, length);
+            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++] = buf.storage[s];
+                dest[d++] = storage[s];
             }
         }
     }
      * 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
      * which <i>x</i> was created from <i>u</i>. Thus <i>y(k) = u(r+sp+kmp)</i>, that is, the
-     * composite offset is <i>r+sp</i> and the composite stride is <i>mp</i>.
+     * 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) {
 
         if (length > 0) {
-            int compStride;
-
-            if (stride == 1) {
-                // Check the arguments define a slice within this buffer
-                checkSlice(start, length);
-                // Composite stride is same as original stride
-                compStride = this.stride;
-            } else {
-                // Check the arguments define a slice within this buffer
-                checkSlice(start, length, stride);
-                // Composite stride is product
-                compStride = this.stride * stride;
-            }
-
             // Translate start relative to underlying buffer
-            int compIndex0 = buf.offset + start * this.stride;
+            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, buf.storage, compIndex0, length, compStride);
+            return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride);
 
         } else {
             // Special case for length==0 where above logic would fail. Efficient too.
     }
 
     @Override
-    public BufferPointer getPointer(int index) {
-        return new BufferPointer(buf.storage, buf.offset + index * stride);
+    public Pointer getPointer(int index) {
+        return new Pointer(storage, index0 + index * stride);
     }
 
     @Override
-    public BufferPointer getPointer(int... indices) {
+    public Pointer getPointer(int... indices) {
         // BaseBuffer implementation can be simplified since if indices.length!=1 we error.
         checkDimension(indices.length);
         return getPointer(indices[0]);

src/org/python/core/buffer/Strided1DWritableBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.PyBuffer;
 import org.python.core.PyException;
 
      * @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 NullPointerException if <code>storage</code> is null
+     * @throws ArrayIndexOutOfBoundsException if <code>index0</code>, <code>length</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)
-            throws PyException {
-        // Arguments programme the object directly
-        // this();
-        this.shape[0] = length;
-        this.buf = new BufferPointer(storage, index0);
-        this.stride = stride;
-        if (stride == 1) {
-            // Really this is a simple buffer
-            addFeatureFlags(CONTIGUITY);
-        }
+            throws ArrayIndexOutOfBoundsException, NullPointerException, PyException {
+        super(storage, index0, length, stride);
         addFeatureFlags(WRITABLE);
         checkRequestFlags(flags);   // Check request is compatible with type
     }
 
     @Override
     public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
-        buf.storage[buf.offset + index * stride] = value;
+        storage[index0 + index * stride] = value;
     }
 
     /**
 
         // Data is here in the buffers
         int s = srcPos;
-        int d = buf.offset + destIndex * stride;
+        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, buf.storage, d, length);
+            System.arraycopy(src, srcPos, storage, d, length);
 
         } else {
             // Non-contiguous copy: single byte items
             int limit = d + length * stride;
             for (; d != limit; d += stride) {
-                buf.storage[d] = src[s++];
+                storage[d] = src[s++];
             }
         }
     }
     public PyBuffer getBufferSlice(int flags, int start, int length, int stride) {
 
         if (length > 0) {
-            int compStride;
-
-            if (stride == 1) {
-                // Check the arguments define a slice within this buffer
-                checkSlice(start, length);
-                // Composite stride is same as original stride
-                compStride = this.stride;
-            } else {
-                // Check the arguments define a slice within this buffer
-                checkSlice(start, length, stride);
-                // Composite stride is product
-                compStride = this.stride * stride;
-            }
-
             // Translate start relative to underlying buffer
-            int compIndex0 = buf.offset + start * this.stride;
+            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, buf.storage, compIndex0, length, compStride);
+            return new SlicedView(getRoot(), flags, storage, compIndex0, length, compStride);
 
         } else {
             // Special case for length==0 where above logic would fail. Efficient too.

src/org/python/core/buffer/ZeroByteBuffer.java

 package org.python.core.buffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.PyBuffer;
 import org.python.core.PyException;
 
  */
 public class ZeroByteBuffer extends BaseBuffer {
 
-    /** Shared instance of a zero-length buffer. */
-    private static final BufferPointer EMPTY_BUF = new BufferPointer(new byte[0]);
+    /** Shared instance of a zero-length storage. */
+    private static final byte[] EMPTY = new byte[0];
 
     /** Array containing a single zero for the length */
     protected static final int[] SHAPE = {0};
      */
     public ZeroByteBuffer(int flags, boolean readonly) throws PyException {
         super(CONTIGUITY | SIMPLE | (readonly ? 0 : WRITABLE));
-        this.buf = EMPTY_BUF;                       // Wraps empty array
+        this.storage = EMPTY;                       // Empty array
         this.shape = SHAPE;                         // {0}
         this.strides = SimpleBuffer.SIMPLE_STRIDES; // {1}
         checkRequestFlags(flags);
     protected int calcIndex(int... indices) throws IndexOutOfBoundsException {
         // Bootless dimension check takes precedence (for consistency with other buffers)
         checkDimension(indices);
-        // This causes all access to the bytes in to throw (since BaseBuffer calls it).
+        // This causes all access to the bytes to throw (since BaseBuffer calls it).
         throw new IndexOutOfBoundsException();
     }
 
      */
     @Override
     public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
-        throws IndexOutOfBoundsException, PyException {
+            throws IndexOutOfBoundsException, PyException {
         // Nothing to copy
     }
 
      */
     @Override
     public void copyFrom(byte[] src, int srcPos, int destIndex, int length)
-        throws IndexOutOfBoundsException, PyException {
+            throws IndexOutOfBoundsException, PyException {
         if (length > 0) {
             throw new IndexOutOfBoundsException();
         }
     }
 
     /**
+     * {@inheritDoc}
+     * <p>
+     * The implementation in <code>ZeroByteBuffer</code> efficiently returns an empty buffer.
+     */
+    @Override
+    public Pointer getBuf() {
+        // Has to be new because the client is allowed to manipulate the contents.
+        return new Pointer(EMPTY, 0);
+    }
+
+    /**
      * For a ZeroByteBuffer, it's the empty string.
      */
     @Override
             // We have to release the root too if ours was final.
             root.release();
         }
-  }
+    }
 }

src/org/python/core/io/TextIOBase.java

 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 
-import org.python.core.BufferPointer;
 import org.python.core.BufferProtocol;
 import org.python.core.Py;
 import org.python.core.PyArray;
 import org.python.core.PyBuffer;
 import org.python.core.PyObject;
 import org.python.core.PyString;
-import org.python.core.util.StringUtil;
 
 /**
  * Base class for text I/O.
     /**
      * Read into the given PyObject that implements the Jython buffer API (with write access) or is
      * a PyArray.
-     * 
+     *
      * @param buf a PyObject compatible with the buffer API
      * @return the amount of data read as an int
      */

tests/java/org/python/core/PyBufferTest.java

  */
 public class PyBufferTest extends TestCase {
 
-    /** Control amount of output. Instance variable so can be adjusted temporarily per test. */
+    /** Control amount of output. Instance variable so can be adjusted temporarily in test. */
     protected int verbosity = 0;
 
     /**
 
                         // Check changed part of destination
                         assertBytesEqual("copyTo(slice) incorrect", test.material.bytes, srcIndex,
-                                actual, destPos, length);
+                                length, actual, destPos);
                         if (destPos > 0) {
                             assertEquals("data before destination", BLANK, actual[destPos - 1]);
                         }
 
                         // Check changed part of destination
                         assertBytesEqual("copyTo(slice) incorrect", test.material.bytes, srcIndex,
-                                actual, destPos, length);
+                                length, actual, destPos);
                         if (destPos > 0) {
                             assertEquals("data before destination", BLANK, actual[destPos - 1]);
                         }
     }
 
     /**
-     * Check that reusable PyBuffer is re-used, and that non-reusable PyBuffer is not re-used.
-     *
-     * @param subject
-     */
-    private void checkReusable(BufferProtocol subject, PyBuffer previous, PyBuffer latest) {
-        assertNotNull("Re-used PyBuffer reference null", latest);
-        if (subject instanceof PyByteArray) {
-            // Re-use prohibited because might have resized while released
-            assertFalse("PyByteArray buffer reused unexpectedly", latest == previous);
-        } else if (subject instanceof TestableExporter && !((TestableExporter)subject).reusable) {
-            // Special test case where re-use prohibited
-            assertFalse("PyBuffer reused unexpectedly", latest == previous);
-        } else {
-            // Other types of TestableExporter and PyString all re-use
-            assertTrue("PyBuffer not re-used as expected", latest == previous);
-        }
-    }
-
-    /**
      * Test method for {@link org.python.core.PyBuffer#getBufferSlice(int, int, int, int)}.
      */
     public void testGetBufferSliceWithStride() {
                 int flags = test.readonly ? PyBUF.SIMPLE : PyBUF.SIMPLE + PyBUF.WRITABLE;
                 PyBuffer view = test.subject.getBuffer(flags);
 
-                BufferPointer bp = view.getBuf();
+                PyBuffer.Pointer bp = view.getBuf();
                 assertBytesEqual("buffer does not match reference", test.material.bytes, bp);
 
             } else {
                 PyBuffer view = test.subject.getBuffer(flags);
 
                 stride = view.getStrides()[0];  // Just possibly != test.strides when length<=1
-                BufferPointer bp = view.getBuf();
+                PyBuffer.Pointer bp = view.getBuf();
                 assertBytesEqual("buffer does not match reference", test.material.bytes, bp, stride);
             }
 
                 }
 
                 // Get pointer and check contents for correct data
-                BufferPointer bp = view.getPointer(i);
-                int stride = view.getStrides()[0];
-                assertBytesEqual("getPointer value", exp, bp, stride);
+                PyBuffer.Pointer bp = view.getPointer(i);
+                assertBytesEqual("getPointer value", exp, bp);
             }
         }
     }
                     exp[j] = bytes[p + j];
                 }
 
-                // Get pointer and check contents for correct data
+                // Get pointer and check contents for data matching exp
                 index[0] = i;
-                BufferPointer bp = view.getPointer(index);
-                assertBytesEqual("getPointer value", exp, bp.storage, bp.offset);
-// assertEquals("getPointer size wrong", itemsize, bp.size);
+                PyBuffer.Pointer bp = view.getPointer(index);
+                assertBytesEqual("getPointer value", exp, bp);
             }
 
             // Check 2D index throws
             return false;
         }
 
-        /**
-         * Determine whether this object permits it's buffers to re-animate themselves. If not, a
-         * call to getBuffer on a released buffer should not return the same buffer.
-         */
-        public boolean reusable = true;
-
     }
 
     /**
     }
 
     /**
-     * A class to act as an exporter that uses the SimpleBuffer. This permits testing abstracted
-     * from the Jython interpreter.
-     * <p>
-     * The exporter shares a single exported buffer between all consumers and needs to take any
-     * action immediately when that buffer is finally released. You are most likely to use this
-     * approach with an exporting object type that modifies its behaviour while there are active
-     * exports, but where it is worth avoiding the cost of duplicate buffers. This is the case with
-     * PyByteArray, which prohibits operations that would resize it, while there are outstanding
-     * exports.
+     * A class to act as an exporter that uses the SimpleBuffer. The exporter shares a single
+     * exported buffer between all consumers and needs to take any action immediately when that
+     * buffer is finally released. You are most likely to use this approach with an exporting object
+     * type that modifies its behaviour while there are active exports, but where it is worth
+     * 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 {
 
          */
         public SimpleWritableExporter(byte[] storage) {
             this.storage = storage;
-            reusable = false;
         }
 
         @Override
      * @param expected expected byte array
      * @param bp result to test
      */
-    static void assertBytesEqual(String message, byte[] expected, BufferPointer bp) {
+    static void assertBytesEqual(String message, byte[] expected, PyBuffer.Pointer bp) {
         assertBytesEqual(message, expected, bp, 1);
     }
 
      * @param bp result to test
      * @param stride in the storage array
      */
-    static void assertBytesEqual(String message, byte[] expected, BufferPointer bp, int stride) {
-        assertBytesEqual(message, expected, 0, bp.storage, bp.offset, expected.length, stride);
+    static void assertBytesEqual(String message, byte[] expected, PyBuffer.Pointer bp, int stride) {
+        assertBytesEqual(message, expected, 0, expected.length, bp.storage, bp.offset, stride);
     }
 
     /**
      * Customised assert method comparing a buffer pointer to a byte array, usually the one from
      * ByteMaterial.
      *
+     * @param message to issue on failure
      * @param expected expected byte array
-     * @param bp result to test
+     * @param expectedStart where to start the comparison in expected
+     * @param n number of bytes to test
+     * @param bb result to test
+     * @param stride in the storage array
      */
-    static void assertBytesEqual(byte[] expected, BufferPointer bp) {
-        assertBytesEqual("", expected, bp);
+    static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
+            PyBuffer.Pointer bp, int stride) {
+        assertBytesEqual(message, expected, expectedStart, n, bp.storage, bp.offset, stride);
     }
 
     /**
      * @param actual result to test
      */
     static void assertBytesEqual(String message, byte[] expected, byte[] actual) {
-        assertEquals(message, expected.length, actual.length);
-        assertBytesEqual(message, expected, 0, actual, 0, expected.length, 1);
+        assertEquals(message + " (array size)", expected.length, actual.length);
+        assertBytesEqual(message, expected, 0, expected.length, actual, 0, 1);
     }
 
     /**
      * @param actualStart where to start the comparison in actual
      */
     static void assertBytesEqual(String message, byte[] expected, byte[] actual, int actualStart) {
-        assertBytesEqual(message, expected, 0, actual, actualStart, expected.length, 1);
+        assertBytesEqual(message, expected, 0, expected.length, actual, actualStart, 1);
     }
 
     /**
      * @param message to issue on failure
      * @param expected expected byte array
      * @param expectedStart where to start the comparison in expected
+     * @param n number of bytes to test
      * @param actual result to test
      * @param actualStart where to start the comparison in actual
-     * @param n number of bytes to test
      */
-    static void assertBytesEqual(String message, byte[] expected, int expectedStart, byte[] actual,
-            int actualStart, int n) {
-
-        assertBytesEqual(message, expected, expectedStart, actual, actualStart, n, 1);
+    static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
+            byte[] actual, int actualStart) {
+        assertBytesEqual(message, expected, expectedStart, n, actual, actualStart, 1);
     }
 
     /**
      * @param message to issue on failure
      * @param expected expected byte array
      * @param expectedStart where to start the comparison in expected
+     * @param n number of bytes to test
      * @param actual result to test
      * @param actualStart where to start the comparison in actual
-     * @param n number of bytes to test
      * @param stride spacing of bytes in actual array
      */
-    static void assertBytesEqual(String message, byte[] expected, int expectedStart, byte[] actual,
-            int actualStart, int n, int stride) {
+    static void assertBytesEqual(String message, byte[] expected, int expectedStart, int n,
+            byte[] actual, int actualStart, int stride) {
 
         if (actualStart < 0) {
             fail(message + " (start<0 in result)");