Anonymous avatar Anonymous committed e8553ae Merge

Merge buffer API

Comments (0)

Files changed (17)

src/org/python/core/BaseBytes.java

 import java.util.List;
 import java.util.ListIterator;
 
+import org.python.core.buffer.SimpleReadonlyBuffer;
+
 /**
  * Base class for Jython bytearray (and bytes in due course) that provides most of the Java API,
  * including Java List behaviour. Attempts to modify the contents through this API will throw a
  * </ul>
  * since the default implementations will otherwise throw an exception.
  */
-public abstract class BaseBytes extends PySequence implements MemoryViewProtocol, List<PyInteger> {
+public abstract class BaseBytes extends PySequence implements List<PyInteger> {
 
     /**
      * Simple constructor of empty zero-length array of defined type.
 
     /*
      * ============================================================================================
-     * Support for memoryview
-     * ============================================================================================
-     *
-     * This is present in order to facilitate development of PyMemoryView which a full
-     * implementation of bytearray would depend on, while at the same time a full implementation of
-     * memoryview depends on bytearray.
-     */
-    /**
-     * Get hold of a <code>memoryview</code> on the current byte array.
-     *
-     * @see MemoryViewProtocol#getMemoryView()
-     */
-    @Override
-    public MemoryView getMemoryView() {
-        if (mv == null) {
-            mv = new MemoryViewImpl();
-        }
-        return mv;
-    }
-
-    private MemoryView mv;
-
-    /**
-     * All instances of BaseBytes have one dimension with stride one.
-     */
-    private static final PyTuple STRIDES = new PyTuple(Py.One);
-
-    /**
-     * Very simple MemoryView for one-dimensional byte array. This lacks any actual access to the
-     * underlying storage as the interface is not presently defined.
-     */
-    private class MemoryViewImpl implements MemoryView {
-
-        private final PyTuple SHAPE = new PyTuple(new PyInteger(storage.length));
-
-        @Override
-        public String get_format() {
-            return "B";
-        }
-
-        @Override
-        public int get_itemsize() {
-            return 1;
-        }
-
-        @Override
-        public PyTuple get_shape() {
-            return SHAPE;
-        }
-
-        @Override
-        public int get_ndim() {
-            return 1;
-        }
-
-        @Override
-        public PyTuple get_strides() {
-            return STRIDES;
-        }
-
-        @Override
-        public boolean get_readonly() {
-            return true;
-        }
-
-    }
-
-    /*
-     * ============================================================================================
      * Support for construction and initialisation
      * ============================================================================================
      *
              */
             init((BaseBytes)arg);
 
-        } else if (arg instanceof MemoryViewProtocol) {
+        } else if (arg instanceof BufferProtocol) {
             /*
              * bytearray copy of object supporting Jython implementation of PEP 3118
              */
-            init(((MemoryViewProtocol)arg).getMemoryView());
+            init((BufferProtocol)arg);
 
         } else {
             /*
 
     /**
      * Helper for <code>__new__</code> and <code>__init__</code> and the Java API constructor from
-     * objects supporting the Jython implementation of PEP 3118 (memoryview) in subclasses.
+     * objects supporting the Jython implementation of PEP 3118 (Buffer API) in subclasses.
      *
-     * @param value a memoryview object consistent with the slice assignment
-     * @throws PyException(NotImplementedError) until memoryview is properly supported
-     * @throws PyException(TypeError) if the memoryview is not byte-oriented
+     * @param value an object bearing the Buffer API and consistent with the slice assignment
      */
-    protected void init(MemoryView value) throws PyException {
-        // XXX Support memoryview once means of access to bytes is defined
-        Py.NotImplementedError("memoryview not yet supported in bytearray");
-        String format = value.get_format();
-        boolean isBytes = format == null || "B".equals(format);
-        if (value.get_ndim() != 1 || !isBytes) {
-            Py.TypeError("memoryview value must be byte-oriented");
-        } else {
-            // Dimensions are given as a PyTuple (although only one)
-            int len = value.get_shape().pyget(0).asInt();
-            // XXX Access to memoryview bytes to go here
-        }
+    protected void init(BufferProtocol value) throws PyException {
+        // Get the buffer view
+        PyBuffer view = value.getBuffer(PyBUF.SIMPLE);
+        // Create storage for the bytes and have the view drop them in
+        newStorage(view.getLen());
+        view.copyTo(storage, offset);
     }
 
     /**
-     * Helper for the Java API constructor from a {@link #View}. View is (perhaps) a stop-gap while
-     * there is no Jython implementation of PEP 3118 (memoryview).
+     * Helper for the Java API constructor from a {@link #View}. View is (perhaps) a stop-gap until
+     * the Jython implementation of PEP 3118 (buffer API) is embedded.
      *
      * @param value a byte-oriented view
      */

src/org/python/core/BufferPointer.java

+package org.python.core;
+
+/**
+ * A class that references a contiguous slice of a <code>byte[]</code> array for use in the buffer
+ * API. This class simply bundles together a refernce to an array, a starting offset within that
+ * array, and specification of the number of bytes that may validly be accessed at that offset. It
+ * is used by the Jython buffer API roughly where the CPython buffer API uses a C (char *) pointer,
+ * or such a pointer and a length.
+ */
+public class BufferPointer {
+
+    /**
+     * Reference to the array holding the bytes. 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 the data being pointed to. */
+    public final int offset;
+    /** Number of bytes within the array comprising the data being pointed to. */
+    public final int size;
+
+    /**
+     * Refer to a contiguous slice of the given array.
+     * 
+     * @param storage array at reference
+     * @param offset index of the first byte
+     * @param size number of bytes being referred to
+     */
+    public BufferPointer(byte[] storage, int offset, int size) {
+        if ((offset | size | (storage.length-(offset + size))) < 0) {
+            throw Py.BufferError("Indexing error in buffer API");
+        }
+        this.storage = storage;
+        this.offset = offset;
+        this.size = size;
+    }
+
+    /**
+     * Refer to the whole of a byte array.
+     * 
+     * @param storage array at reference
+     */
+    public BufferPointer(byte[] storage) {
+        this.storage = storage;
+        this.offset = 0;
+        this.size = storage.length;
+    }
+}

src/org/python/core/BufferProtocol.java

+package org.python.core;
+
+/**
+ * Interface marking an object as capable of exposing its internal state as a {@link PyBuffer}.
+ */
+public interface BufferProtocol {
+
+    /**
+     * Method by which the consumer requests the buffer from the exporter. The consumer
+     * provides information on its intended method of navigation and the optional
+     * features the buffer object must provide.
+     * 
+     * @param flags specification of options and the navigational capabilities of the consumer
+     * @return exported buffer
+     */
+    PyBuffer getBuffer(int flags);
+}

src/org/python/core/MemoryView.java

-package org.python.core;
-
-public interface MemoryView {
-    // readonly attributes XXX just the boring stuff so far
-
-    public String get_format();
-    public int get_itemsize();
-    public PyTuple get_shape();
-    public int get_ndim();
-    public PyTuple get_strides();
-    public boolean get_readonly();
-}

src/org/python/core/MemoryViewProtocol.java

-package org.python.core;
-
-public interface MemoryViewProtocol {
-
-    public MemoryView getMemoryView();
-}

src/org/python/core/Py.java

     public static PyException MemoryError(String message) {
         return new PyException(Py.MemoryError, message);
     }
+
     public static PyObject BufferError;
+    public static PyException BufferError(String message) {
+        return new PyException(Py.BufferError, message);
+    }
+
     public static PyObject ArithmeticError;
     public static PyObject LookupError;
     public static PyObject StandardError;

src/org/python/core/PyBUF.java

+package org.python.core;
+
+/**
+ * This interface provides a base for the key interface of the buffer API, {@link PyBuffer},
+ * including symbolic constants used by the consumer of a <code>PyBuffer</code> to specify its
+ * requirements. The Jython buffer API emulates the CPython buffer API closely.
+ * <ul>
+ * <li>There are two reasons for separating parts of <code>PyBuffer</code> into this interface: The
+ * constants defined in CPython have the names <code>PyBUF_SIMPLE</code>,
+ * <code>PyBUF_WRITABLE</code>, etc., and the trick of defining ours here means we can write
+ * {@link PyBUF#SIMPLE}, {@link PyBUF#WRITABLE}, etc. so source code looks similar.</li>
+ * <li>It is not so easy in Java as it is in C to treat a <code>byte</code> array as storing
+ * anything other than <code>byte</code>, and we prepare for the possibility of buffers with a
+ * series of different primitive types by defining here, those methods that would be in common
+ * between <code>(Byte)Buffer</code> and an assumed future <code>FloatBuffer</code> or
+ * <code>TypedBuffer&lt;T&gt;</code>. (Compare <code>java.nio.Buffer</code>.)</li>
+ * </ul>
+ * Except for other interfaces, it is unlikely any classes would implement <code>PyBUF</code>
+ * directly.
+ */
+public interface PyBUF {
+
+    /**
+     * Determine whether the consumer is entitled to write to the exported storage.
+     *
+     * @return true if writing is not allowed, false if it is.
+     */
+    boolean isReadonly();
+
+    /**
+     * The number of dimensions to the buffer. This number is the length of the <code>shape</code>
+     * array.
+     *
+     * @return number of dimensions
+     */
+    int getNdim();
+
+    /**
+     * An array reporting the size of the buffer, considered as a multidimensional array, in each
+     * dimension and (by its length) number of dimensions. The size is the size in "items". An item
+     * is the amount of buffer content addressed by one index or set of indices. In the simplest
+     * case an item is a single unit (byte), and there is one dimension. In complex cases, the array
+     * is multi-dimensional, and the item at each location is multi-unit (multi-byte). The consumer
+     * must not modify this array.
+     *
+     * @return the dimensions of the buffer as an array
+     */
+    int[] getShape();
+
+    /**
+     * The number of units (bytes) stored in each indexable item.
+     *
+     * @return the number of units (bytes) comprising each item.
+     */
+    int getItemsize();
+
+    /**
+     * The total number of units (bytes) stored, which will be the product of the elements of the
+     * shape, and the item size.
+     *
+     * @return the total number of units stored.
+     */
+    int getLen();
+
+    /**
+     * A buffer is (usually) coupled to the internal state of an exporting Python object, and that
+     * object may have to restrict its behaviour while the buffer exists. The consumer must
+     * therefore say when it has finished.
+     */
+    void release();
+
+    /**
+     * The "strides" array gives the distance in the storage array between adjacent items (in each
+     * dimension). If the rawest parts of the buffer API, the consumer of the buffer is able to
+     * navigate the exported storage. The "strides" array is part of the support for interpreting
+     * the buffer as an n-dimensional array of items. In the one-dimensional case, the "strides"
+     * array is In more dimensions, it provides the coefficients of the "addressing polynomial".
+     * (More on this in the CPython documentation.) The consumer must not modify this array.
+     *
+     * @return the distance in the storage array between adjacent items (in each dimension)
+     */
+    int[] getStrides();
+
+    /**
+     * The "suboffsets" array is a further part of the support for interpreting the buffer as an
+     * n-dimensional array of items, where the array potentially uses indirect addressing (like a
+     * real Java array of arrays, in fact). This is only applicable when there are more than 1
+     * dimension and works in conjunction with the <code>strides</code> array. (More on this in the
+     * CPython documentation.) When used, <code>suboffsets[k]</code> is an integer index, bit a byte
+     * offset as in CPython. The consumer must not modify this array.
+     *
+     * @return
+     */
+    int[] getSuboffsets();
+
+    /**
+     * Enquire whether the array is represented contiguously in the backing storage, according to C
+     * or Fortran ordering. A one-dimensional contiguous array is both.
+     *
+     * @param order 'C', 'F' or 'A', as the storage order is C, Fortran or either.
+     * @return true iff the array is stored contiguously in the order specified
+     */
+    boolean isContiguous(char order);
+
+    /* Constants taken from CPython object.h in v3.3.0a */
+
+    /**
+     * The maximum allowed number of dimensions (NumPy restriction?).
+     */
+    static final int MAX_NDIM = 64;
+    /**
+     * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
+     * specify that it expects to write to the buffer contents. getBuffer will raise an exception if
+     * the exporter's buffer cannot meet this requirement.
+     */
+    static final int WRITABLE = 0x0001;
+    /**
+     * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
+     * specify that it assumes a simple one-dimensional organisation of the exported storage with
+     * item size of one. getBuffer will raise an exception if the consumer sets this flag and the
+     * exporter's buffer cannot be navigated that simply.
+     */
+    static final int SIMPLE = 0;
+    /**
+     * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
+     * specify that it requires {@link PyBuffer#getFormat()} to return the type of the unit (rather
+     * than return <code>null</code>).
+     */
+    // I don't understand why we need this, or why format MUST be null of this is not set.
+    static final int FORMAT = 0x0004;
+    /**
+     * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
+     * specify that it it is prepared to navigate the buffer as multi-dimensional.
+     * <code>getBuffer</code> will raise an exception if consumer does not specify the flag but the
+     * exporter's buffer cannot be navigated without taking into account its multiple dimensions.
+     */
+    static final int ND = 0x0008 | SIMPLE;    // Differs from CPython by or'ing in SIMPLE
+    /**
+     * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
+     * specify that it it expects to use the "strides" array. <code>getBuffer</code> will raise an
+     * exception if consumer does not specify the flag but the exporter's buffer cannot be navigated
+     * without using the "strides" array.
+     */
+    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 raise an
+     * exception if the exporter's buffer is not C-ordered. <code>C_CONTIGUOUS</code> implies
+     * <code>STRIDES</code>.
+     */
+    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> 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
+     *
+     * getBuffer will raise an exception if the exporter's buffer is not contiguous.
+     * <code>ANY_CONTIGUOUS</code> implies <code>STRIDES</code>.
+     */
+    static final int ANY_CONTIGUOUS = 0x0080 | STRIDES;
+    /**
+     * A constant used by the consumer in its call to {@link BufferProtocol#getBuffer(int)} to
+     * specify that it understands the "suboffsets" array. <code>getBuffer</code> will raise an
+     * exception if consumer does not specify the flag but the exporter's buffer cannot be navigated
+     * without understanding the "suboffsets" array. <code>INDIRECT</code> implies
+     * <code>STRIDES</code>.
+     */
+    static final int INDIRECT = 0x0100 | STRIDES;
+    /**
+     * Equivalent to <code>(ND | WRITABLE)</code>
+     */
+    static final int CONTIG = ND | WRITABLE;
+    /**
+     * Equivalent to <code>ND</code>
+     */
+    static final int CONTIG_RO = ND;
+    /**
+     * Equivalent to <code>(STRIDES | WRITABLE)</code>
+     */
+    static final int STRIDED = STRIDES | WRITABLE;
+    /**
+     * Equivalent to <code>STRIDES</code>
+     */
+    static final int STRIDED_RO = STRIDES;
+    /**
+     * Equivalent to <code>(STRIDES | WRITABLE | FORMAT)</code>
+     */
+    static final int RECORDS = STRIDES | WRITABLE | FORMAT;
+    /**
+     * Equivalent to <code>(STRIDES | FORMAT)</code>
+     */
+    static final int RECORDS_RO = STRIDES | FORMAT;
+    /**
+     * Equivalent to <code>(INDIRECT | WRITABLE | FORMAT)</code>
+     */
+    static final int FULL = INDIRECT | WRITABLE | FORMAT;
+    /**
+     * Equivalent to <code>(INDIRECT | FORMAT)</code>
+     */
+    static final int FULL_RO = INDIRECT | FORMAT;
+
+    /* Constants for readability, not standard for CPython */
+
+    /**
+     * Field mask, use as in <code>if ((capabilityFlags&ORGANISATION) == STRIDES) ...</code>.
+     */
+    static final int ORGANISATION = SIMPLE | ND | STRIDES | INDIRECT;
+    /**
+     * Field mask, use as in if <code>((capabilityFlags&ORGANIZATION) == STRIDES) ...</code>.
+     *
+     * @see #ORGANISATION
+     */
+    static final int ORGANIZATION = ORGANISATION;
+    /**
+     * 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, irrespective of whether
+     * the strides array is to be provided. <code>getBuffer</code> will raise an
+     * exception if the exporter's buffer is not C-ordered. <code>C_CONTIGUOUS = IS_C_CONTIGUOUS | STRIDES</code>.
+     */
+    static final int IS_C_CONTIGUOUS = C_CONTIGUOUS & ~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, irrespective of whether
+     * the strides array is to be provided. <code>getBuffer</code> will raise an
+     * exception if the exporter's buffer is not Fortran-ordered. <code>F_CONTIGUOUS = IS_F_CONTIGUOUS | STRIDES</code>.
+     */
+    static final int IS_F_CONTIGUOUS = F_CONTIGUOUS & ~STRIDES;
+    /**
+     * Field mask, use as in <code>if (capabilityFlags&CONTIGUITY== ... ) ...</code>.
+     */
+    static final int CONTIGUITY = (C_CONTIGUOUS | F_CONTIGUOUS | ANY_CONTIGUOUS) & ~STRIDES;
+}

src/org/python/core/PyBuffer.java

+package org.python.core;
+
+/**
+ * The Jython buffer API for access to a byte array within an exporting object. This interface is
+ * the counterpart of the CPython <code>Py_buffer</code> struct. Several concrete types implement
+ * this interface in order to provide tailored support for different storage organisations.
+ */
+public interface PyBuffer extends PyBUF {
+
+    /*
+     * The different behaviours required as the actual structure of the buffer changes (from one
+     * exporter to another, that is) should be dealt with using polymorphism. The implementation of
+     * those types may then calculate indices etc. without checking e.g for whether the strides
+     * array must be used, or the array is C or F contiguous, since they know the answer to these
+     * questions already, and can just get on with the request in their own way.
+     *
+     * The issue of consumer requests is different: the strides array will be present if the
+     * consumer asked for it, yet the methods of the buffer implementation do not have to use it
+     * (and won't).
+     */
+
+    // Informational methods inherited from PyBUF
+    //
+    // boolean isReadonly();
+    // int getNdim();
+    // int[] getShape();
+    // int getLen();
+
+    /**
+     * Return the byte indexed from a one-dimensional buffer with item size one. This is part of the
+     * fully-encapsulated API: the exporter takes care of navigating the structure of the buffer.
+     * Results are undefined where the number of dimensions is not one or if
+     * <code>itemsize&gt;1</code>.
+     *
+     * @param index to retrieve from
+     * @return the item at index, which is a byte
+     */
+    byte byteAt(int index) throws IndexOutOfBoundsException;
+
+    /**
+     * Return the unsigned byte value indexed from a one-dimensional buffer with item size one. This
+     * is part of the fully-encapsulated API: the exporter takes care of navigating the structure of
+     * the buffer. Results are undefined where the number of dimensions is not one or if
+     * <code>itemsize&gt;1</code>.
+     *
+     * @param index to retrieve from
+     * @return the item at index, treated as an unsigned byte, <code>=0xff & byteAt(index)</code>
+     */
+    int intAt(int index) throws IndexOutOfBoundsException;
+
+    /**
+     * Store the given byte at the indexed location in of a one-dimensional buffer with item size
+     * one. This is part of the fully-encapsulated API: the exporter takes care of navigating the
+     * structure of the buffer. Results are undefined where the number of dimensions is not one or
+     * if <code>itemsize&gt;1</code>.
+     *
+     * @param value to store
+     * @param index to location
+     */
+    void storeAt(byte value, int index) throws IndexOutOfBoundsException;
+
+    // Access to n-dimensional array
+    //
+    /**
+     * Return the byte indexed from an N-dimensional buffer with item size one. This is part of the
+     * fully-encapsulated API: the exporter takes care of navigating the structure of the buffer.
+     * The indices must be correct in length and value for the array shape. Results are undefined
+     * where <code>itemsize&gt;1</code>.
+     *
+     * @param indices specifying location to retrieve from
+     * @return the item at location, which is a byte
+     */
+    byte byteAt(int... indices) throws IndexOutOfBoundsException;
+
+    /**
+     * Return the unsigned byte value indexed from an N-dimensional buffer with item size one. This
+     * is part of the fully-encapsulated API: the exporter takes care of navigating the structure of
+     * the buffer. The indices must be correct in length and value for the array shape. Results are
+     * undefined where <code>itemsize&gt;1</code>.
+     *
+     * @param index to retrieve from
+     * @return the item at location, treated as an unsigned byte, <code>=0xff & byteAt(index)</code>
+     */
+    int intAt(int... indices) throws IndexOutOfBoundsException;
+
+    /**
+     * Store the given byte at the indexed location in of an N-dimensional buffer with item size
+     * one. This is part of the fully-encapsulated API: the exporter takes care of navigating the
+     * structure of the buffer. The indices must be correct in length and value for the array shape.
+     * Results are undefined where <code>itemsize&gt;1</code>.
+     *
+     * @param value to store
+     * @param indices specifying location to store at
+     */
+    void storeAt(byte value, int... indices) throws IndexOutOfBoundsException;
+
+    // Bulk access in one dimension
+    //
+    /**
+     * Copy the contents of the buffer to the destination byte array. The number of bytes will be
+     * that returned by {@link #getLen()}, and the order is the natural ordering according to the
+     * contiguity type.
+     *
+     * @param dest destination byte array
+     * @param destPos 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;
+
+    /**
+     * 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.
+     *
+     * @param srcIndex starting index in the source buffer
+     * @param dest destination byte array
+     * @param destPos index in the destination array of the byte [0,...]
+     * @param length number of bytes 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
+            throws IndexOutOfBoundsException;
+
+    /**
+     * 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 otherwise not defined.
+     *
+     * @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
+     * @throws IndexOutOfBoundsException if access out of bounds in source or destination
+     * @throws PyException (BufferError) if read-only buffer
+     */
+    void copyFrom(byte[] src, int srcPos, int destIndex, int length)    // mimic arraycopy args
+            throws IndexOutOfBoundsException, PyException;
+
+    // Bulk access in n-dimensions may be added here if desired semantics can be settled
+    //
+
+    // Buffer management inherited from PyBUF
+    //
+    // void release();
+
+    // Direct access to actual storage
+    //
+    /**
+     * 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
+     * where <code>obj</code> has type <code>BufferProtocol</code>:
+     *
+     * <pre>
+     *
+     * PyBuffer a = obj.getBuffer();
+     * int itemsize = a.getItemsize();
+     * BufferPointer b = a.getBuf();
+     * </pre>
+     *
+     * the item with index <code>k</code> is in the array <code>b.storage</code> at index
+     * <code>[b.offset + k*itemsize]</code> to <code>[b.offset + (k+1)*itemsize - 1]</code>
+     * 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 shape, strides and sub-offsets provided by the API.
+     *
+     * @return structure defining the byte[] slice that is the shared data
+     */
+    BufferPointer getBuf();
+
+    /**
+     * Return a structure describing the slice of a byte array that holds a single item from the
+     * data being exported to the consumer. For a one-dimensional contiguous buffer, assuming the
+     * following client code where <code>obj</code> has type <code>BufferProtocol</code>:
+     *
+     * <pre>
+     * int k = ... ;
+     * PyBuffer a = obj.getBuffer();
+     * int itemsize = a.getItemsize();
+     * BufferPointer b = a.getPointer(k);
+     * </pre>
+     *
+     * the item with index <code>k</code> is in the array <code>b.storage</code> at index
+     * <code>[b.offset]</code> to <code>[b.offset + itemsize - 1]</code> inclusive. And if
+     * <code>itemsize==1</code>, the item is simply the byte <code>b.storage[b.offset]</code>
+     * <p>
+     * Essentially this is a method for computing the offset of a particular index. Although
+     * <code>b.size==itemsize</code>, the client is free to navigate the underlying buffer
+     * <code>b.storage</code> without respecting these boundaries.
+     *
+     * @param index in the buffer to position the pointer
+     * @return structure defining the byte[] slice that is the shared data
+     */
+    BufferPointer getPointer(int index);
+
+    /**
+     * Return a structure describing the slice of a byte array that holds a single item from the
+     * data being exported to the consumer, in the case that array may be multi-dimensional. For an
+     * 3-dimensional contiguous buffer, assuming the following client code where <code>obj</code>
+     * has type <code>BufferProtocol</code>:
+     *
+     * <pre>
+     * int i, j, k = ... ;
+     * PyBuffer a = obj.getBuffer();
+     * int itemsize = a.getItemsize();
+     * BufferPointer 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
+     * <code>[b.offset]</code> to <code>[b.offset + itemsize - 1]</code> inclusive. And if
+     * <code>itemsize==1</code>, the item is simply the byte <code>b.storage[b.offset]</code>
+     * <p>
+     * Essentially this is a method for computing the offset of a particular index. Although
+     * <code>b.size==itemsize</code>, the client is free to navigate the underlying buffer
+     * <code>b.storage</code> without respecting these boundaries.
+     * <p>
+     * If the buffer is also non-contiguous, <code>b.storage[b.offset]</code> is still the (first
+     * byte of) the item at index [0,...,0]. However, it is necessary to navigate <code>b</code>
+     * using the shape, strides and sub-offsets provided by the API.
+     *
+     * @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);
+
+    // Inherited from PyBUF and belonging here
+    //
+    // int[] getStrides();
+    // int[] getSuboffsets();
+    // boolean isContiguous(char order);
+
+    // Interpretation of bytes as items
+    /**
+     * A format string in the language of Python structs describing how the bytes of each item
+     * should be interpreted (or null if {@link PyBUF#FORMAT} was not part of the consumer's flags).
+     * <p>
+     * This is provided for compatibility with CPython. Jython only implements "B" so far, and it is
+     * debatable whether anything fancier than "&lt;n&gt;B" can be supported in Java.
+     *
+     * @return the format string
+     */
+    String getFormat();
+
+    // Inherited from PyBUF and belonging here
+    //
+    // int getItemsize();
+}

src/org/python/core/PyByteArray.java

 
 import java.util.Arrays;
 
+import org.python.core.buffer.SimpleBuffer;
 import org.python.expose.ExposedClassMethod;
 import org.python.expose.ExposedMethod;
 import org.python.expose.ExposedNew;
  *
  */
 @ExposedType(name = "bytearray", base = PyObject.class, doc = BuiltinDocs.bytearray_doc)
-public class PyByteArray extends BaseBytes {
+public class PyByteArray extends BaseBytes implements BufferProtocol {
 
     public static final PyType TYPE = PyType.fromClass(PyByteArray.class);
 
     }
 
     /**
-     * Create a new array filled exactly by a copy of the contents of the source, which is a
-     * memoryview.
+     * Create a new array filled exactly by a copy of the contents of the source, which is an
+     * object supporting the Jython version of the PEP 3118 buffer API.
      *
      * @param value source of the bytes (and size)
      */
-    public PyByteArray(MemoryViewProtocol value) {
+    public PyByteArray(BufferProtocol value) {
         super(TYPE);
-        init(value.getMemoryView());
+        init(value);
     }
 
     /**
         init(arg);
     }
 
+    /*
+     * ============================================================================================
+     * Support for the Buffer API
+     * ============================================================================================
+     *
+     * The buffer API allows other classes to access the storage directly.
+     */
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The {@link PyBuffer} returned from this method is a one-dimensional array of single byte
+     * items, that allows modification of the object state but <b>prohibits resizing</b> the byte array.
+     * This prohibition is not only on the consumer of the view but extends to any other operations,
+     * such as any kind or insertion or deletion.
+     */
+    @Override
+    public synchronized PyBuffer getBuffer(int flags) {
+        exportCount++;
+        return new SimpleBuffer(this, new BufferPointer(storage, offset, size), flags) {
+
+            @Override
+            public void releaseAction() {
+                // synchronise on the same object as getBuffer()
+                synchronized (obj) {
+                    exportCount--;
+                }
+            }
+        };
+    }
+
+    /**
+     * Test to see if the byte array may be resized and raise a BufferError if not.
+     *
+     * @throws PyException (BufferError) if there are buffer exports preventing a resize
+     */
+    protected void resizeCheck() throws PyException {
+         // XXX Quite likely this is not called in all the places it should be
+         if (exportCount!=0) {
+            throw Py.BufferError("Existing exports of data: object cannot be re-sized");
+        }
+    }
+
+    /**
+     * Count of PyBuffer exports not yet released, used to prevent untimely resizing.
+     */
+    private int exportCount;
+
+
     /* ============================================================================================
      * API for org.python.core.PySequence
      * ============================================================================================
              */
             setslice(start, stop, step, (BaseBytes)value);
 
-        } else if (value instanceof MemoryViewProtocol) {
+        } else if (value instanceof BufferProtocol) {
             /*
              * Value supports Jython implementation of PEP 3118, and can be can be inserted without
              * making a copy.
              */
-            setslice(start, stop, step, ((MemoryViewProtocol)value).getMemoryView());
+            setslice(start, stop, step, (BufferProtocol)value);
 
         } else {
             /*
      * @param start the position of the first element.
      * @param stop one more than the position of the last element.
      * @param step the step size.
-     * @param value a memoryview object consistent with the slice assignment
+     * @param value an object supporting the buffer API consistent with the slice assignment
      * @throws PyException(SliceSizeError) if the value size is inconsistent with an extended slice
      */
-    private void setslice(int start, int stop, int step, MemoryView value) throws PyException {
-        // XXX Support memoryview once means of access to bytes is defined
-        throw Py.NotImplementedError("memoryview not yet supported in bytearray");
+    private void setslice(int start, int stop, int step, BufferProtocol value) throws PyException {
+        PyBuffer view = value.getBuffer(PyBUF.SIMPLE);
+
+
+        int len = view.getLen();
+
+        if (step == 1) {
+            // Delete this[start:stop] and open a space of the right size
+            storageReplace(start, stop - start, len);
+            view.copyTo(storage, start+offset);
+
+        } else {
+            // This is an extended slice which means we are replacing elements
+            int n = sliceLength(start, stop, step);
+            if (n != len) {
+                throw SliceSizeError("bytes", len, n);
+            }
+
+            for (int io = start + offset, j = 0; j < n; io += step, j++) {
+                storage[io] = view.byteAt(j);    // Assign this[i] = value[j]
+            }
+        }
     }
 
     /**
             return;
         }
 
+        // This will not be possible if this object has buffer exports
+        resizeCheck();
+
         // Compute some handy points of reference
         final int L = storage.length;
         final int f = offset;
             return; // Everything stays where it is.
         }
 
+        // This will not be possible if this object has buffer exports
+        resizeCheck();
+
         // Compute some handy points of reference
         final int L = storage.length;
         final int f = offset;
             return; // Everything stays where it is.
         }
 
+        // This will not be possible if this object has buffer exports
+        resizeCheck();
+
         // Compute some handy points of reference
         final int L = storage.length;
         final int f = offset;

src/org/python/core/PyMemoryView.java

 package org.python.core;
 
 import org.python.expose.ExposedGet;
-import org.python.expose.ExposedMethod;
 import org.python.expose.ExposedNew;
 import org.python.expose.ExposedType;
 
+/**
+ * Class implementing the Python <code>memoryview</code> type, at present highly incomplete. It
+ * provides a wrapper around the Jython buffer API, but slice operations and most others are
+ * missing.
+ */
 @ExposedType(name = "memoryview", base = PyObject.class, isBaseType = false)
 public class PyMemoryView extends PyObject {
 
+    // XXX This should probably extend PySequence to get the slice behaviour
+
     public static final PyType TYPE = PyType.fromClass(PyMemoryView.class);
 
-    MemoryView backing;
+    /**
+     * The buffer exported by the object. We do not a present implement the buffer sharing strategy
+     * used by CPython <code>memoryview</code>.
+     */
+    private PyBuffer backing;
+    /** Cache the result of getting shape here. */
+    private PyTuple shape;
+    /** Cache the result of getting strides here. */
+    private PyTuple strides;
 
-    public PyMemoryView(MemoryViewProtocol obj) {
-        backing = obj.getMemoryView();
+    /**
+     * Construct a PyMemoryView from an object that bears the necessary BufferProtocol interface.
+     * The buffer so obtained will be writable if the underlying object permits it.
+     * 
+     * @param obj object that will export the buffer
+     */
+    public PyMemoryView(BufferProtocol obj) {
+        /*
+         * Ask for the full set of facilities (strides, indirect, etc.) from the object in case they
+         * are necessary for navigation, but only ask for read access. If the object is writable,
+         * the PyBuffer will be writable.
+         */
+        backing = obj.getBuffer(PyBUF.FULL_RO);
     }
 
     @ExposedNew
-    static PyObject memoryview_new(PyNewWrapper new_, boolean init, PyType subtype, PyObject[] args,
-                              String[] keywords) {
+    static PyObject memoryview_new(PyNewWrapper new_, boolean init, PyType subtype,
+            PyObject[] args, String[] keywords) {
         PyObject obj = args[0];
-        if (obj instanceof MemoryViewProtocol) {
-            return new PyMemoryView((MemoryViewProtocol)obj);
+        if (obj instanceof BufferProtocol) {
+            return new PyMemoryView((BufferProtocol)obj);
+        } else {
+            throw Py.TypeError("cannot make memory view because object does not have "
+                    + "the buffer interface");
         }
-        else throw Py.TypeError("cannot make memory view because object does not have the buffer interface");
     }
 
-    @ExposedGet(name = "format")
-    public String get_format() {
-        return backing.get_format();
+    @ExposedGet(doc = format_doc)
+    public String format() {
+        return backing.getFormat();
     }
 
-    @ExposedGet(name = "itemsize")
-    public int get_itemsize() {
-        return backing.get_itemsize();
+    @ExposedGet(doc = itemsize_doc)
+    public int itemsize() {
+        return backing.getItemsize();
     }
 
-    @ExposedGet(name = "shape")
-    public PyTuple get_shape() {
-        return backing.get_shape();
+    @ExposedGet(doc = shape_doc)
+    public PyTuple shape() {
+        if (shape == null) {
+            shape = tupleOf(backing.getShape());
+        }
+        return shape;
     }
 
-    @ExposedGet(name = "ndim")
-    public int get_ndim() {
-        return backing.get_ndim();
+    @ExposedGet(doc = ndim_doc)
+    public int ndim() {
+        return backing.getShape().length;
     }
 
-    @ExposedGet(name = "strides")
-    public PyTuple get_strides() {
-        return backing.get_strides();
+    @ExposedGet(doc = strides_doc)
+    public PyTuple strides() {
+        if (strides == null) {
+            strides = tupleOf(backing.getStrides());
+        }
+        return strides;
     }
 
-    @ExposedGet(name = "readonly")
-    public boolean get_readonly() {
-        return backing.get_readonly();
+    @ExposedGet(doc = readonly_doc)
+    public boolean readonly() {
+        return backing.isReadonly();
     }
 
+    /**
+     * Make an integer array into a PyTuple of PyInteger values.
+     * 
+     * @param x the array
+     * @return the PyTuple
+     */
+    private PyTuple tupleOf(int[] x) {
+        PyInteger[] pyx = new PyInteger[x.length];
+        for (int k = 0; k < x.length; k++) {
+            pyx[k] = new PyInteger(x[k]);
+        }
+        return new PyTuple(pyx, false);
+    }
+
+    /*
+     * These strings are adapted from the on-line documentation as the attributes do not come with
+     * any docstrings.
+     */
+    private final static String memoryview_tobytes_doc = "tobytes()\n\n"
+            + "Return the data in the buffer as a bytestring (an object of class str).\n\n"
+            + ">>> m = memoryview(\"abc\")\n" + ">>> m.tobytes()\n" + "'abc'";
+
+    private final static String memoryview_tolist_doc = "tolist()\n\n"
+            + "Return the data in the buffer as a list of integers.\n\n"
+            + ">>> memoryview(\"abc\").tolist()\n" + "[97, 98, 99]";
+
+    private final static String format_doc = "format\n"
+            + "A string containing the format (in struct module style) for each element in\n"
+            + "the view. This defaults to 'B', a simple bytestring.\n";
+
+    private final static String itemsize_doc = "itemsize\n"
+            + "The size in bytes of each element of the memoryview.\n";
+
+    private final static String shape_doc = "shape\n"
+            + "A tuple of integers the length of ndim giving the shape of the memory as an\n"
+            + "N-dimensional array.\n";
+
+    private final static String ndim_doc = "ndim\n"
+            + "An integer indicating how many dimensions of a multi-dimensional array the\n"
+            + "memory represents.\n";
+
+    private final static String strides_doc = "strides\n"
+            + "A tuple of integers the length of ndim giving the size in bytes to access\n"
+            + "each element for each dimension of the array.\n";
+
+    private final static String readonly_doc = "readonly\n"
+            + "A bool indicating whether the memory is read only.\n";
+
 }
-
-

src/org/python/core/PyString.java

 /// Copyright (c) Corporation for National Research Initiatives
 package org.python.core;
 
+import java.math.BigInteger;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+
+import org.python.core.StringFormatter.DecimalFormatTemplate;
+import org.python.core.buffer.SimpleStringBuffer;
 import org.python.core.stringlib.FieldNameIterator;
 import org.python.core.stringlib.InternalFormatSpec;
 import org.python.core.stringlib.InternalFormatSpecParser;
 import org.python.expose.ExposedType;
 import org.python.expose.MethodType;
 
-import java.math.BigInteger;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-
 /**
  * A builtin python string.
  */
 @ExposedType(name = "str", doc = BuiltinDocs.str_doc)
-public class PyString extends PyBaseString implements MemoryViewProtocol
+public class PyString extends PyBaseString implements BufferProtocol
 {
     public static final PyType TYPE = PyType.fromClass(PyString.class);
     protected String string; // cannot make final because of Python intern support
         return codePoints;
     }
 
-    public MemoryView getMemoryView() {
-        return new MemoryView() {
-            // beginning of support
-            public String get_format() {
-                 return "B";
-            }
-            public int get_itemsize() {
-                return 2;
-            }
-            public PyTuple get_shape() {
-                return new PyTuple(Py.newInteger(getString().length()));
-            }
-            public int get_ndim() {
-                return 1;
-            }
-            public PyTuple get_strides() {
-                return new PyTuple(Py.newInteger(1));
-            }
-            public boolean get_readonly() {
-                return true;
-            }
-        };
+    /**
+     * Create a read-only buffer view of the contents of the string, treating it as a sequence of
+     * unsigned bytes. The caller specifies its requirements and navigational capabilities in the
+     * <code>flags</code> argument (see the constants in class {@link PyBUF} for an explanation).
+     * 
+     * @param flags consumer requirements
+     * @return the requested buffer
+     */
+    public PyBuffer getBuffer(int flags) {
+        /*
+         * Return a buffer, but specialised to defer construction of the buf object.
+         */
+        return new SimpleStringBuffer(this, getString(), flags);
     }
 
     public String substring(int start, int end) {

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;
+import org.python.core.PyBuffer;
+import org.python.core.PyException;
+
+/**
+ * Base implementation of the Buffer API for implementations to extend. The default implementation
+ * provides some mechanisms for checking the consumer's capabilities against those stated as
+ * necessary by the exporter. Default implementations of methods are provided for the standard array
+ * organisations. The implementors of simple buffers will find it more efficient to override methods
+ * to which performance might be sensitive with a calculation specific to their actual type.
+ * <p>
+ * The default implementation raises a read-only exception for those methods that store data in the
+ * buffer, and {@link #isReadonly()} returns <code>true</code>. Writable types must override this
+ * implementation. Default implementations of other methods are generally oriented towards
+ * contiguous N-dimensional arrays.
+ * <p>
+ * At the time of writing, only the SIMPLE organisation (one-dimensional, of item size one) is used
+ * in the Jython core.
+ */
+public abstract class BaseBuffer implements PyBuffer {
+
+    /**
+     * The object from which this buffer export must be released (see {@link PyBuffer#release()}).
+     * This is normally the original exporter of this buffer and the owner of the underlying
+     * storage. Exceptions to this occur when some other object is managing release (this is the
+     * case when a <code>memoryview</code> has provided the buffer), and when disposal can safely be
+     * left to the Java garbage collector (local temporaries and perhaps exports from immutable
+     * objects).
+     */
+    protected BufferProtocol obj;
+    /**
+     * The dimensions of the array represented by the buffer. The length of the <code>shape</code>
+     * array is the number of dimensions. The <code>shape</code> array should always be created and
+     * filled (difference from CPython).
+     */
+    protected int[] shape;
+    /**
+     * Step sizes in the underlying buffer essential to correct translation of an index (or indices)
+     * into an index into the storage. This reference will be <code>null</code> if not needed for
+     * the storage organisation, and not requested by the consumer in <code>flags</code>. If it is
+     * either necessary for the buffer navigation, or requested by the consumer in flags, the
+     * <code>strides</code> array must be correctly filled to at least the length of the
+     * <code>shape</code> array.
+     */
+    protected int[] strides;
+    /**
+     * Reference to a structure that wraps the underlying storage that the exporter is sharing with
+     * the consumer.
+     */
+    protected BufferPointer buf;
+    /**
+     * Bit pattern using the constants defined in {@link PyBUF} that records the actual capabilities
+     * this buffer offers. See {@link #assignCapabilityFlags(int, int, int, int)}.
+     */
+    protected int capabilityFlags;
+
+    /**
+     * The result of the operation is to set the {@link #capabilityFlags} according to the
+     * capabilities this instance should support. This method is normally called in the constructor
+     * of each particular sub-class of <code>BaseBuffer</code>, passing in a <code>flags</code>
+     * argument that originated in the consumer's call to {@link BufferProtocol#getBuffer(int)}.
+     * <p>
+     * The consumer supplies as a set of <code>flags</code>, using constants from {@link PyBUF}, the
+     * capabilities that it expects from the buffer. These include a statement of which navigational
+     * arrays it will use ( <code>shape</code>, <code>strides</code>, and <code>suboffsets</code>),
+     * whether it wants the <code>format</code> string set so it describes the item type or left
+     * null, and whether it expects the buffer to be writable. The consumer flags are taken by this
+     * method both as a statement of needs to be met by the buffer, and as a statement of
+     * capabilities in the consumer to navigate different buffers.
+     * <p>
+     * In its call to this method, the exporter specifies the capabilities it requires the consumer
+     * to have (and indicate by asking for them in <code>flags</code>) in order to navigate the
+     * buffer successfully. For example, if the buffer is a strided array, the consumer must specify
+     * that it expects the <code>strides</code> array. Otherwise the method concludes the consumer
+     * is not capable of the navigation required. Capabilities specified in the
+     * <code>requiredFlags</code> must appear in the consumer's <code>flags</code> request. If any
+     * don't, a Python <code>BufferError</code> will be raised. If there is no error these flags
+     * will be set in <code>capabilityFlags</code> as required of the buffer.
+     * <p>
+     * The exporter specifies some capabilities it <i>allows</i> the consumer to request, such as
+     * the <code>format</code> string. Depending on the type of exporter, the navigational arrays (
+     * <code>shape</code>, <code>strides</code>, and <code>suboffsets</code>) may also be allowed
+     * rather than required. Capabilities specified in the <code>allowedFlags</code>, if they also
+     * appear in the consumer's <code>flags</code>, will be set in <code>capabilityFlags</code>.
+     * <p>
+     * The exporter specifies some capabilities that will be supplied whether requested or not. For
+     * example (and it might be the only one) this is used only to express that an unstrided,
+     * one-dimensional array is <code>C_CONTIGUOUS</code>, <code>F_CONTIGUOUS</code>, and
+     * <code>ANY_CONTIGUOUS</code>, all at once. Capabilities specified in the
+     * <code>impliedFlags</code>, will be set in <code>capabilityFlags</code> whether in the
+     * consumer's <code>flags</code> or not.
+     * <p>
+     * Capabilities specified in the consumer's <code>flags</code> request, if they do not appear in
+     * the exporter's <code>requiredFlags</code> <code>allowedFlags</code> or
+     * <code>impliedFlags</code>, will cause a Python <code>BufferError</code>.
+     * <p>
+     * Note that this method cannot actually set the <code>shape</code>, <code>strides</code> and
+     * <code>suboffsets</code> properties: the implementation of the specific buffer type must do
+     * that based on the <code>capabilityFlags</code>. This forms a partial counterpart to CPython
+     * <code>PyBuffer_FillInfo()</code> but it is not specific to the simple type of buffer, and
+     * covers the flag processing of all buffer types. This is complex (in CPython) and the Jython
+     * approach attempts to be compatible yet comprehensible.
+     */
+    protected void assignCapabilityFlags(int flags, int requiredFlags, int allowedFlags,
+            int impliedFlags) {
+
+        // Ensure what may be requested includes what must be and what comes unasked
+        allowedFlags = allowedFlags | requiredFlags | impliedFlags;
+
+        // Look for request flags (other than buffer organisation) outside what is allowed
+        int syndrome = flags & ~(allowedFlags | ORGANISATION);
+
+        if (syndrome != 0) {
+            // Some flag was set that is neither required nor allowed
+            if ((syndrome & WRITABLE) != 0) {
+                throw notWritable();
+            } else if ((syndrome & C_CONTIGUOUS) != 0) {
+                throw bufferIsNot("C-contiguous");
+            } else if ((syndrome & F_CONTIGUOUS) != 0) {
+                throw bufferIsNot("Fortran-contiguous");
+            } else if ((syndrome & ANY_CONTIGUOUS) != 0) {
+                throw bufferIsNot("contiguous");
+            } else {
+                // Catch-all error (never in practice?)
+                throw bufferIsNot("capable of matching request");
+            }
+
+        } else if ((flags & requiredFlags) != requiredFlags) {
+            // This buffer needs more capability to navigate than the consumer has requested
+            if ((flags & ND) != ND) {
+                throw bufferRequires("shape");
+            } else if ((flags & STRIDES) != STRIDES) {
+                throw bufferRequires("strides");
+            } else if ((flags & INDIRECT) != INDIRECT) {
+                throw bufferRequires("suboffsets");
+            } else {
+                // Catch-all error
+                throw bufferRequires("feature consumer lacks");
+            }
+
+        } else {
+            // These flags control returns from (default) getShape etc..
+            capabilityFlags = (flags & allowedFlags) | impliedFlags;
+            // Note that shape and strides are still to be initialised
+        }
+
+        /*
+         * Caller must responds to the requested/required capabilities with shape and strides arrays
+         * suited to the actual type of buffer.
+         */
+    }
+
+    /**
+     * Provide an instance of BaseBuffer or a sub-class meeting the consumer's expectations as
+     * expressed in the flags argument. Compare CPython:
+     *
+     * <pre>
+     * int PyBuffer_FillInfo(Py_buffer *view, PyObject *exporter,
+     *                       void *buf, Py_ssize_t len,
+     *                       int readonly, int flags)
+     * </pre>
+     *
+     * @param exporter the exporting object
+     * @param buf descriptor for the exported buffer itself
+     */
+    protected BaseBuffer(BufferProtocol exporter, BufferPointer buf) {
+        // Exporting object (is allowed to be null)
+        this.obj = exporter;
+        // Exported data (not normally allowed to be null)
+        this.buf = buf;
+    }
+
+    @Override
+    public boolean isReadonly() {
+        // Default position is read only: mutable buffers must override
+        return true;
+    }
+
+    @Override
+    public int getNdim() {
+        return shape.length;
+    }
+
+    @Override
+    public int[] getShape() {
+        // Difference from CPython: never null, even when the consumer doesn't request it.
+        return shape;
+    }
+
+    @Override
+    public int getLen() {
+        // Correct if contiguous. Override if strided or indirect with itemsize*product(shape).
+        return buf.size;
+    }
+
+    // Let the sub-class implement:
+    // @Override public byte byteAt(int index) throws IndexOutOfBoundsException {}
+
+    @Override
+    public int intAt(int index) throws IndexOutOfBoundsException {
+        return 0xff & byteAt(index);
+    }
+
+    @Override
+    public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
+        throw notWritable();
+    }
+
+    // Let the sub-class implement:
+    // @Override public byte byteAt(int... indices) throws IndexOutOfBoundsException {}
+
+    @Override
+    public int intAt(int... indices) throws IndexOutOfBoundsException {
+        return 0xff & byteAt(indices);
+    }
+
+    @Override
+    public void storeAt(byte value, int... indices) throws IndexOutOfBoundsException, PyException {
+        throw notWritable();
+    }
+
+    @Override
+    public void copyTo(byte[] dest, int destPos) throws IndexOutOfBoundsException {
+        // Correct for contiguous arrays (if destination expects same F or C contiguity)
+        copyTo(0, dest, destPos, getLen());
+    }
+
+    // Let the sub-class implement:
+    // @Override public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
+    // throws IndexOutOfBoundsException {}
+
+    @Override
+    public void copyFrom(byte[] src, int srcPos, int destIndex, int length)
+            throws IndexOutOfBoundsException, PyException {
+        throw notWritable();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * The implementation here calls {@link #releaseAction()}, which the implementer of a specific
+     * buffer type should override with the necessary actions to release the buffer from the
+     * exporter. It is not an error to call this method more than once (difference from CPython), or
+     * on a temporary buffer that needs no release action. If not released explicitly, it will be
+     * called during object finalisation (before garbage collection) of the buffer object.
+     */
+    @Override
+    public final void release() {
+        if (obj != null) {
+            releaseAction();
+        }
+        obj = null;
+    }
+
+    @Override
+    public BufferPointer getBuf() {
+        return buf;
+    }
+
+    // Let the sub-class implement:
+    // @Override public BufferPointer getPointer(int index) { return null; }
+    // @Override public BufferPointer getPointer(int... indices) { return null; }
+
+    @Override
+    public int[] getStrides() {
+        return strides;
+    }
+
+    @Override
+    public int[] getSuboffsets() {
+        return null;
+    }
+
+    @Override
+    public boolean isContiguous(char order) {
+        return true;
+    }
+
+    @Override
+    public String getFormat() {
+        // Avoid having to have an actual 'format' member
+        return ((capabilityFlags & FORMAT) == 0) ? null : "B";
+    }
+
+    @Override
+    public int getItemsize() {
+        // Avoid having to have an actual 'itemsize' member
+        return 1;
+    }
+
+    /**
+     * Ensure buffer, if not released sooner, is released from the exporter during object
+     * finalisation (before garbage collection) of the buffer object.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        release();
+        super.finalize();
+    }
+
+    /**
+     * This method will be called when the consumer calls {@link #release()} (to be precise, only on
+     * the first call). The default implementation does nothing. Override this method to add release
+     * behaviour specific to exporter. A common convention is to do this within the definition of
+     * {@link BufferProtocol#getBuffer(int)} within the exporting class, where a nested class is
+     * finally defined.
+     */
+    protected void releaseAction() {}
+
+    /**
+     * Check the number of indices (but not their values), raising a Python BufferError if this does
+     * not match the number of dimensions.
+     *
+     * @param indices into the buffer (to test)
+     * @return number of dimensions
+     * @throws PyException (BufferError) if wrong number of indices
+     */
+    final int checkDimension(int[] indices) throws PyException {
+        int ndim = shape.length;
+        if (indices.length != ndim) {
+            if (indices.length < ndim) {
+                throw Py.BufferError("too few indices supplied");
+            } else {
+                throw Py.BufferError("too many indices supplied");
+            }
+        }
+        return ndim;
+    }
+
+    /**
+     * Convenience method to create (for the caller to throw) a
+     * <code>BufferError("underlying buffer is not writable")</code>.
+     *
+     * @return the error as a PyException
+     */
+    protected PyException notWritable() {
+        return bufferIsNot("writable");
+    }
+
+    /**
+     * Convenience method to create (for the caller to throw) a
+     * <code>BufferError("underlying buffer is not {property}")</code>.
+     *
+     * @param property
+     * @return the error as a PyException
+     */
+    protected PyException bufferIsNot(String property) {
+        return Py.BufferError("underlying buffer is not " + property);
+    }
+
+    /**
+     * Convenience method to create (for the caller to throw) a
+     * <code>BufferError("underlying buffer requires {feature}")</code>.
+     *
+     * @param feature
+     * @return the error as a PyException
+     */
+    protected PyException bufferRequires(String feature) {
+        return Py.BufferError("underlying buffer requires " + feature);
+    }
+
+}

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

+package org.python.core.buffer;
+
+import org.python.core.BufferPointer;
+import org.python.core.BufferProtocol;
+
+/**
+ * Buffer API over a writable one-dimensional array of one-byte items.
+ */
+public class SimpleBuffer extends SimpleReadonlyBuffer {
+
+    /**
+     * <code>SimpleBuffer</code> allows consumer requests that are the same as
+     * <code>SimpleReadonlyBuffer</code>, with the addition of WRITABLE.
+     */
+    protected static final int ALLOWED_FLAGS = WRITABLE | SimpleReadonlyBuffer.ALLOWED_FLAGS;
+
+    /**
+     * Provide an instance of <code>SimpleBuffer</code> in a default, semi-constructed state. The
+     * sub-class constructor takes responsibility for completing construction with a call to
+     * {@link #assignCapabilityFlags(int, int, int, int)}.
+     *
+     * @param exporter the exporting object
+     * @param buf wrapping the array of bytes storing the implementation of the object
+     */
+    protected SimpleBuffer(BufferProtocol exporter, BufferPointer buf) {
+        super(exporter, buf);
+    }
+
+    /**
+     * Provide an instance of SimpleBuffer meeting the consumer's expectations as expressed in the
+     * flags argument.
+     *
+     * @param exporter the exporting object
+     * @param buf wrapping the array of bytes storing the implementation of the object
+     * @param flags consumer requirements
+     */
+    public SimpleBuffer(BufferProtocol exporter, BufferPointer buf, int flags) {
+        super(exporter, buf);
+        assignCapabilityFlags(flags, REQUIRED_FLAGS, ALLOWED_FLAGS, IMPLIED_FLAGS);
+        fillInfo();
+    }
+
+    @Override
+    public boolean isReadonly() {
+        return false;
+    }
+
+    @Override
+    public void storeAt(byte value, int index) {
+        buf.storage[buf.offset + index] = value;
+    }
+
+    @Override
+    public void storeAt(byte value, int... indices) {
+        if (indices.length != 1) {
+            checkDimension(indices);
+        }
+        storeAt(value, indices[0]);
+    }
+
+    @Override
+    public void copyFrom(byte[] src, int srcPos, int destIndex, int length) {
+        System.arraycopy(src, srcPos, buf.storage, buf.offset + destIndex, length);
+    }
+
+}

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

+package org.python.core.buffer;
+
+import org.python.core.BufferPointer;
+import org.python.core.BufferProtocol;
+
+/**
+ * Buffer API over a one-dimensional array of one-byte items providing read-only API. A writable
+ * simple buffer will extend this implementation.
+ */
+public class SimpleReadonlyBuffer extends BaseBuffer {
+
+    /**
+     * Using the PyBUF constants, express capabilities the consumer must request if it is to
+     * navigate the storage successfully. (None.)
+     */
+    public static final int REQUIRED_FLAGS = 0;
+    /**
+     * Using the PyBUF constants, express capabilities the consumer may request so it can navigate
+     * the storage in its chosen way. The buffer instance has to implement these mechanisms if and
+     * only if they are requested. (FORMAT | ND | STRIDES | INDIRECT)
+     */
+    public static final int ALLOWED_FLAGS = FORMAT | ND | STRIDES | INDIRECT;
+    /**
+     * Using the PyBUF constants, express capabilities the consumer doesn't need to request because
+     * they will be there anyway. (One-dimensional arrays (including those sliced with step size
+     * one) are C- and F-contiguous.)
+     */
+    public static final int IMPLIED_FLAGS = CONTIGUITY;
+    /**
+     * The strides array for this type is always a single element array with a 1 in it.
+     */
+    protected static final int[] SIMPLE_STRIDES = {1};
+
+    /**
+     * Partial counterpart to CPython <code>PyBuffer_FillInfo()</code> specific to the simple type
+     * of buffer and called from the constructor. The base constructor will already have been
+     * called, filling {@link #buf} and {@link #obj}. And the method
+     * {@link #assignCapabilityFlags(int, int, int, int)} has set {@link #capabilityFlags}.
+     */
+    protected void fillInfo() {
+        /*
+         * We will already have called: assignCapabilityFlags(flags, requiredFlags, allowedFlags,
+         * impliedFlags); So capabilityFlags holds the requests for shape, strides, writable, etc..
+         */
+        // Difference from CPython: never null, even when the consumer doesn't request it
+        shape = new int[1];
+        shape[0] = getLen();
+
+        // Following CPython: provide strides only when the consumer requests it
+        if ((capabilityFlags & STRIDES) == STRIDES) {
+            strides = SIMPLE_STRIDES;
+        }
+
+        // Even when the consumer requests suboffsets, the exporter is allowed to supply null.
+        // In theory, the exporter could require that it be requested and still supply null.
+    }
+
+    /**
+     * Provide an instance of <code>SimpleReadonlyBuffer</code> in a default, semi-constructed
+     * state. The sub-class constructor takes responsibility for completing construction including a
+     * call to {@link #assignCapabilityFlags(int, int, int, int)}.
+     *
+     * @param exporter the exporting object
+     * @param buf wrapping the array of bytes storing the implementation of the object
+     */
+    protected SimpleReadonlyBuffer(BufferProtocol exporter, BufferPointer buf) {
+        super(exporter, buf);
+    }
+
+    /**
+     * Provide an instance of SimpleReadonlyBuffer meeting the consumer's expectations as expressed
+     * in the flags argument.
+     *
+     * @param exporter the exporting object
+     * @param buf wrapping the array of bytes storing the implementation of the object
+     * @param flags consumer requirements
+     */
+    public SimpleReadonlyBuffer(BufferProtocol exporter, BufferPointer buf, int flags) {
+        super(exporter, buf);
+        assignCapabilityFlags(flags, REQUIRED_FLAGS, ALLOWED_FLAGS, IMPLIED_FLAGS);
+        fillInfo();
+    }
+
+    @Override
+    public int getNdim() {
+        return 1;
+    }
+
+    @Override
+    public byte byteAt(int index) throws IndexOutOfBoundsException {
+        // offset is not necessarily zero
+        return buf.storage[buf.offset + index];
+    }
+
+    @Override
+    public int intAt(int index) throws IndexOutOfBoundsException {
+        // Implement directly: a bit quicker than the default
+        return 0xff & buf.storage[buf.offset + index];
+    }
+
+    @Override
+    public byte byteAt(int... indices) throws IndexOutOfBoundsException {
+        if (indices.length != 1) {
+            checkDimension(indices);
+        }
+        return byteAt(indices[0]);
+    }
+
+    @Override
+    public void copyTo(int srcIndex, byte[] dest, int destPos, int length)
+            throws IndexOutOfBoundsException {
+        System.arraycopy(buf.storage, buf.offset + srcIndex, dest, destPos, length);
+    }
+
+    @Override
+    public BufferPointer getPointer(int index) {
+        return new BufferPointer(buf.storage, buf.offset + index, 1);
+    }
+
+    @Override
+    public BufferPointer getPointer(int... indices) {
+        if (indices.length != 1) {
+            checkDimension(indices);
+        }
+        return getPointer(indices[0]);
+    }
+
+}

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

+package org.python.core.buffer;
+
+import org.python.core.BufferPointer;
+import org.python.core.BufferProtocol;
+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
+ * 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. This class overrides those methods in
+ * SimpleReadonlyBuffer that would access the <code>buf</code> attribute to work out their results
+ * from the String instead.
+ */
+public class SimpleStringBuffer extends SimpleReadonlyBuffer {
+
+    /**
+     * The string backing this PyBuffer. A substitute for {@link #buf} until we can no longer avoid
+     * creating it.
+     */
+    private String bufString;
+
+    /**
+     * Partial counterpart to CPython <code>PyBuffer_FillInfo()</code> specific to the simple type
+     * of buffer and called from the constructor. The base constructor will already have been
+     * called, filling {@link #bufString} and {@link #obj}. And the method
+     * {@link #assignCapabilityFlags(int, int, int, int)} has set {@link #capabilityFlags}.
+     */
+    protected void fillInfo(String bufString) {
+        /*
+         * We will already have called: assignCapabilityFlags(flags, requiredFlags, allowedFlags,
+         * impliedFlags); So capabilityFlags holds the requests for shape, strides, writable, etc..
+         */
+        // Save the backing string
+        this.bufString = bufString;
+
+        // Difference from CPython: never null, even when the consumer doesn't request it
+        shape = new int[1];
+        shape[0] = bufString.length();
+
+        // Following CPython: provide strides only when the consumer requests it
+        if ((capabilityFlags & STRIDES) == STRIDES) {
+            strides = SIMPLE_STRIDES;
+        }
+
+        // Even when the consumer requests suboffsets, the exporter is allowed to supply null.
+        // In theory, the exporter could require that it be requested and still supply null.
+    }
+
+    /**
+     * Provide an instance of SimpleReadonlyBuffer meeting the consumer's expectations as expressed
+     * in the flags argument.
+     *
+     * @param exporter the exporting object
+     * @param bufString storing the implementation of the object
+     * @param flags consumer requirements
+     */
+    public SimpleStringBuffer(BufferProtocol exporter, String bufString, int flags) {
+        super(exporter, null);
+        assignCapabilityFlags(flags, REQUIRED_FLAGS, ALLOWED_FLAGS, IMPLIED_FLAGS);
+        fillInfo(bufString);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method uses {@link String#length()} rather than create an actual byte buffer.
+     */
+    @Override
+    public int getLen() {
+        // Avoid creating buf by using String.length
+        return bufString.length();
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * 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
+        return (byte)bufString.charAt(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)
+            throws IndexOutOfBoundsException {
+        // Avoid creating buf by using String.charAt
+        int endIndex = srcIndex + length, p = destPos;
+        for (int i = srcIndex; i < endIndex; i++) {
+            dest[p++] = (byte)bufString.charAt(i);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method creates an actual byte buffer from the 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));
+        }
+        return buf;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method creates an actual byte buffer from the String if none yet exists.
+     */
+    @Override
+    public BufferPointer getPointer(int index) {
+        getBuf(); // Ensure buffer created
+        return super.getPointer(index);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method creates an actual byte buffer from the String if none yet exists.
+     */
+    @Override
+    public BufferPointer getPointer(int... indices) {
+        getBuf(); // Ensure buffer created
+        return super.getPointer(indices);
+    }
+
+}

tests/java/org/python/core/BaseBytesTest.java

 import java.util.List;
 import java.util.Random;
 
+import junit.framework.TestCase;
+
+import org.python.core.buffer.SimpleBuffer;
 import org.python.util.PythonInterpreter;
 
-import junit.framework.TestCase;
-
 /**
  * Unit test of org.python.core.BaseBytes, a class that supplies much of the behaviour of the Jython
  * bytearray. In fact, it supplies almost all the immutable behaviour, and is abstract. In order to
         return new MyBytes(value);
     }
 
-    public BaseBytes getInstance(MemoryViewProtocol value) throws PyException {
+    public BaseBytes getInstance(BufferProtocol value) throws PyException {
         return new MyBytes(value);
     }
 
          * 
          * @param value source of the bytes (and size)
          */
-        public MyBytes(MemoryViewProtocol value) {
+        public MyBytes(BufferProtocol value) {
             super(TYPE);
-            init(value.getMemoryView());
+            init((BufferProtocol)value.getBuffer(PyBUF.SIMPLE));
         }
 
         /**
 
     /**
      * An object that for test purposes (of construction and slice assignment) contains an array of
-     * values that it is able to offer for reading through the MemoryView interface.
+     * values that it is able to offer for reading through the PyBuffer interface.
      */
-    public static class MemoryViewable extends PyObject implements MemoryViewProtocol {
+    public static class BufferedObject extends PyObject implements BufferProtocol {
 
-        public static final PyType TYPE = PyType.fromClass(MemoryViewable.class);
+        public static final PyType TYPE = PyType.fromClass(BufferedObject.class);
 
-        private MemoryView mv;
         private byte[] store;
 
         /**
          * 
          * @param value integers to store
          */
-        MemoryViewable(int[] value) {
+        BufferedObject(int[] value) {
             super(TYPE);
             int n = value.length;
             store = new byte[n];
         }
 
         @Override
-        public MemoryView getMemoryView() {
-            if (mv == null) {
-                mv = new MemoryViewImpl();
-            }
-            return mv;
+        public PyBuffer getBuffer(int flags) {
+            return new SimpleBuffer(this, new BufferPointer(store), flags);
         }
 
-        /**
-         * All instances of MemoryViewable have one dimension with stride one.
-         */
-        private static final PyTuple STRIDES = new PyTuple(Py.One);
-
-        /**
-         * Very simple MemoryView for one-dimensional byte array.
-         */
-        class MemoryViewImpl implements MemoryView {
-
-            private final PyTuple shape = new PyTuple(new PyInteger(store.length));
-
-            @Override
-            public String get_format() {
-                return "B";
-            }
-
-            @Override
-            public int get_itemsize() {
-                return 1;
-            }
-
-            @Override
-            public PyTuple get_shape() {
-                return shape;
-            }
-
-            @Override
-            public int get_ndim() {
-                return 1;
-            }
-
-            @Override
-            public PyTuple get_strides() {
-                return STRIDES;
-            }
-
-            @Override
-            public boolean get_readonly() {
-                return true;
-            }
-
-        }
     }
 
     /**

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

+package org.python.core;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.python.core.buffer.SimpleBuffer;
+import org.python.core.buffer.SimpleReadonlyBuffer;
+import org.python.core.buffer.SimpleStringBuffer;
+import org.python.util.PythonInterpreter;
+
+/**
+ * Test the several implementations (and exporters) of the PyBuffer interface provided in the Jython
+ * core.
+ * <p>
+ * The approach is to create test material once that has the necessary variety in byte array values,
+ * then for each test, when the JUnit framework creates an instance of the function-specific test,
+ * to use this material to create instances of each read-only type and each writable type. Writable
+ * instance types go onto the lists buffersToRead and buffersToWrite, while read-only instances go
+ * onto the lists buffersToRead and buffersToFailToWrite.
+ * <p>
+ * In general, tests of methods that read data apply themselves to all the elements of the
+ * buffersToRead list, while tests of methods that write data apply themselves to all the elements
+ * of the buffersToWrite list and check that members of the buffersToFailToWrite list raise an
+ * exception.
+ * <p>
+ * The Jython buffer API follows the structures of the CPython buffer API so that it supports in
+ * principle the use of multi-dimensional, strided add indirect array structures as buffers.
+ * However, actual buffers in the Jython core, and therefore these tests, limit themselves to one
+ * dimensional contiguous buffers with a simple organisation. Some tests apply directly to the
+ * N-dimensional cases, and some need a complete re-think. Sub-classing this test would probably be
+ * a good way to extend it to a wider range.
+ */
+public class PyBufferTest extends TestCase {
+
+    /**
+     * Generated constructor
+     *
+     * @param name