package org.python.core.buffer; import java.nio.ByteBuffer; 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 providing variables and accessors for the navigation * arrays, methods for expressing and checking the buffer request flags, methods and mechanism for * get-release counting, boilerplate error checks and their associated exceptions, and default * implementations of some methods for access to the buffer content. The design aim is to ensure * unglamorous common code need only be implemented once. *
* This class leaves undefined the storage mechanism for the bytes (typically byte[] or
* java.nio.ByteBuffer), while remaining definite that it is an indexable sequence of
* bytes. A concrete class that extends this one must provide elementary accessors
* {@link #byteAtImpl(int)}, {@link #storeAtImpl(byte, int)} that abstract this storage, a factory
* {@link #getNIOByteBufferImpl()} for ByteBuffers that wrap the storage, and a factory
* for slices {@link #getBufferSlice(int, int, int, int)}.
*
* The sub-class constructor must specify the feature flags (see
* {@link #BaseBuffer(int, int, int[], int[])}), set {@link #index0}, {@link #shape} and
* {@link #strides}, and finally check the client capabilities with {@link #checkRequestFlags(int)}.
* A sub-class intended to represent slices of an exporter that counts its exports, as part of a
* locking protocol like bytearray's, must override {@link #getRoot()} so that a call
* to {@link #release()} on a view of slice, propagates to the buffer view that provided the slice.
*
* Access methods provided here necessarily work with the abstracted {@link #byteAtImpl(int)}, * {@link #storeAtImpl(byte, int)} interface, but subclasses are able to override them with more * efficient versions that employ knowledge of the particular storage type used. *
* This base implementation is writable only if {@link PyBUF#WRITABLE} is in the feature flags
* passed to the constructor. Otherwise, all methods for write access raise a TypeError
* and {@link #isReadonly()} returns true. However, a client intending to write should
* have presented {@link PyBUF#WRITABLE} in its client request flags when getting the buffer, and
* been prevented by a BufferError exception at that point.
*
* At the time of writing, only one-dimensional buffers of item size one are used in the Jython
* core.
*/
public abstract class BaseBuffer implements PyBuffer {
/**
* The object that exported this buffer (or null if the subclass or exporter
* chooses not to supply a reference).
*/
protected BufferProtocol obj;
/**
* The dimensions of the array represented by the buffer. The length of the shape
* array is the number of dimensions. The shape array should always be created and
* 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 strides array should always be created and
* correctly filled to at least the length of the shape array (difference from
* CPython). This value is returned by {@link #getStrides()}.
*/
protected int[] strides;
/**
* Absolute byte-index in the storage of item[0]. In one dimension, for a positive
* stride this is equal to the offset of the first byte used in whatever
* byte-storage is provided, and for a negative stride it is the first byte of the
* last item. In an N-dimensional buffer with strides of mixed sign, it could be anywhere in the
* data.
*/
protected int index0;
/**
* 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
* {@link BufferProtocol#getBuffer(int)} that have returned this object: one for the call on the
* original exporting object that constructed this, and one for each subsequent
* call to {@link PyBuffer#getBuffer(int)} that returned this.
*/
protected int exports = 1;
/**
* Bit pattern using the constants defined in {@link PyBUF} that records the actual features
* this buffer offers. When checking consumer flags against the features of the buffer, it is an
* error if the consumer requests a capability the buffer does not offer, and it is an error if
* the consumer does not specify that it will use a navigation array the buffer requires.
*
* In order to support efficient checking with {@link #checkRequestFlags(int)} we store a
* mutilated version of the apparent featureFlags in which the non-navigation flags
* are inverted. The syndrome S of the error is computed as follows. Let
* N=1 where we are dealing with a navigation flag, let F be a buffer
* feature flag, and let X be the consumer request flags.
*
*
* A = F N X'
* B = F'N'X
* S = A + B = F N X' + F'N'X
*
*
* In the above, A=0 only if all the navigation flags set in F are
* also set in X, and B=0 only if all the non-navigation flags clear
* in F are also clear in X. S=0 only if both these
* conditions are true and furthermore the positions of the 1s in the syndrome
* S tell us which bits in X are at fault. Now if we define:
* G = N F + N'F' then the syndrome is:
*
*
* S = G (N X' + N'X)
*
*
* Which permits the check in one XOR and one AND operation instead of four ANDs and an OR. The
* down-side is that we have to provide methods for setting and getting the actual flags in
* terms a client might expect them to be expressed. We can recover the original F
* since:
*
*
* N G + N'G' = F
*
*/
private int gFeatureFlags = ~NAVIGATION; // featureFlags = 0
/**
* Construct an instance of BaseBuffer in support of a sub-class, specifying the
* 'feature flags', or at least a starting set to be adjusted later. Also specify the navigation
* ( {@link #index0}, {@link #shape}, and {@link #strides}). These 'feature flags' are the
* features of the buffer exported, not the flags that form the consumer's request. The buffer
* will be read-only unless {@link PyBUF#WRITABLE} is set. {@link PyBUF#FORMAT} is implicitly
* added to the feature flags.
*
* To complete initialisation, the sub-class normally must create its own wrapped byte-storage,
* and call {@link #checkRequestFlags(int)} passing the consumer's request flags.
*
* @param featureFlags bit pattern that specifies the features allowed
* @param index0 index into storage of item[0,...,0]
* @param shape elements in each dimension
* @param strides between successive elements in each dimension
*/
protected BaseBuffer(int featureFlags, int index0, int[] shape, int[] strides) {
setFeatureFlags(featureFlags | FORMAT);
this.index0 = index0;
this.shape = shape;
this.strides = strides;
}
/**
* Get the features of this buffer expressed using the constants defined in {@link PyBUF}. A
* client request may be tested against the consumer's request flags with
* {@link #checkRequestFlags(int)}.
*
* @return capabilities of and navigation required by the exporter/buffer
*/
protected final int getFeatureFlags() {
return NAVIGATION ^ (~gFeatureFlags);
}
/**
* Set the features of this buffer expressed using the constants defined in {@link PyBUF},
* replacing any previous set. Set individual flags or add to those already set by using
* {@link #addFeatureFlags(int)}.
*
* @param flags new value for the feature flags
*/
protected final void setFeatureFlags(int flags) {
gFeatureFlags = (~NAVIGATION) ^ flags;
}
/**
* Add to the features of this buffer expressed using the constants defined in {@link PyBUF},
* setting individual flags specified while leaving those already set. Equivalent to
* setFeatureFlags(flags | getFeatureFlags()).
*
* @param flags to set within the feature flags
*/
protected final void addFeatureFlags(int flags) {
setFeatureFlags(flags | getFeatureFlags());
}
/**
* Remove features from this buffer expressed using the constants defined in {@link PyBUF},
* clearing individual flags specified while leaving others already set. Equivalent to
* {@code setFeatureFlags(~flags & getFeatureFlags())}.
*
* @param flags to clear within the feature flags
*/
protected final void removeFeatureFlags(int flags) {
setFeatureFlags(~flags & getFeatureFlags());
}
/**
* General purpose method to check the consumer request flags (typically the argument to
* {@link BufferProtocol#getBuffer(int)}) against the feature flags (see
* {@link #getFeatureFlags()}) that characterise the features of the buffer, and to raise an
* exception (Python BufferError) with an appropriate message in the case of a
* mismatch. The flags are defined in the interface {@link PyBUF} and are used in two ways.
*
* In a subset of the flags, the consumer specifies assumptions it makes about the index order * (contiguity) of the buffer, and whether it is writable. When the buffer implementation calls * this check method, it has already specified in {@link #setFeatureFlags(int)} what * capabilities this type (or instance) buffer actually has. It is an error, for the consumer to * specify in its request a feature that the buffer does not offer. *
* In a subset of the flags, the consumer specifies the set of navigation arrays (
* shape, strides, and suboffsets) it intends to use in
* navigating the buffer. When the buffer implementation calls this check method, it has already
* specified in {@link #setFeatureFlags(int)} what navigation is necessary for the consumer to
* make sense of the buffer. It is an error for the consumer not to specify the flag
* corresponding to an array that the buffer deems necessary.
*
* @param flags capabilities of and navigation assumed by the consumer
* @throws PyException {@code BufferError} when expectations do not correspond with the buffer
*/
protected void checkRequestFlags(int flags) throws PyException {
/*
* It is an error if any of the navigation flags is 0 when it should be 1, or if any of the
* non-navigation flags is 1 when it should be 0.
*/
int syndrome = gFeatureFlags & (flags ^ NAVIGATION);
if (syndrome != 0) {
throw bufferErrorFromSyndrome(syndrome);
}
}
@Override
public boolean isReadonly() {
// WRITABLE is a non-navigation flag, so is inverted in gFeatureFlags
return (gFeatureFlags & WRITABLE) != 0; // i.e. featureFlags & WRITABLE is false
}
@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;
}
// XXX Consider making this part of the PyBUF interface
protected int getSize() {
final int N = shape.length;
int size = shape[0];
for (int k = 1; k < N; k++) {
size *= shape[k];
}
return size;
}
@Override
public int getLen() {
final int N = shape.length;
int len = getItemsize();
for (int k = 0; k < N; k++) {
len *= shape[k];
}
return len;
}
@Override
public final BufferProtocol getObj() {
return obj;
}
/**
* Retrieve the byte at the given index in the underlying storage treated as a flat sequence of
* bytes. This byte-index will have been computed from the item index (which may have been
* multi-dimensional), taking into account {@link #index0}, {@link #shape}, {@link #strides},
* and the item size. The caller is responsible for validating the original item-index and
* raising (typically) an IndexOutOfBoundsException. Misuse of this method may
* still result in unchecked exceptions characteristic of the storage implementation.
*
* @param byteIndex byte-index of location to retrieve
* @return the byte at byteIndex
*/
abstract protected byte byteAtImpl(int byteIndex) throws IndexOutOfBoundsException;
/**
* Store the byte at the given index in the underlying storage treated as a flat sequence of
* bytes. This byte-index will have been computed from the item index (which may have been
* multi-dimensional), taking into account {@link #index0}, {@link #shape}, {@link #strides},
* and the item size. The caller is responsible for validating the original item-index and
* raising (typically) an IndexOutOfBoundsException. Misuse of this method may
* still result in unchecked exceptions characteristic of the storage implementation. This
* method must implement the check for read-only character, raising a BufferError
* in the case of a violation.
*
* @param value to store
* @param byteIndex byte-index of location to retrieve
* @throws PyException {@code BufferError} if this object is read-only.
*/
abstract protected void storeAtImpl(byte value, int byteIndex)
throws IndexOutOfBoundsException, PyException;
/**
* {@inheritDoc}
*
* The BaseBuffer implementation delegates to {@link #byteAtImpl(int)} via
* byteAtImpl(byteIndex(index)).
*/
@Override
public byte byteAt(int index) throws IndexOutOfBoundsException {
return byteAtImpl(byteIndex(index));
}
/**
* {@inheritDoc}
*
* The BaseBuffer implementation delegates to {@link #byteAtImpl(int)} via
* byteAtImpl(byteIndex(index)), cast unsigned to an int.
*/
@Override
public int intAt(int index) throws IndexOutOfBoundsException {
return 0xff & byteAtImpl(byteIndex(index));
}
/**
* {@inheritDoc}
*
* The BaseBuffer implementation delegates to {@link #storeAtImpl(byte, int)} via
* storeAtImpl(value, byteIndex(index)).
*/
@Override
public void storeAt(byte value, int index) throws IndexOutOfBoundsException, PyException {
storeAtImpl(value, byteIndex(index));
}
/**
* {@inheritDoc}
*
* The BaseBuffer implementation delegates to {@link #byteAtImpl(int)} via
* byteAtImpl(byteIndex(indices)).
*/
@Override
public byte byteAt(int... indices) throws IndexOutOfBoundsException {
return byteAtImpl(byteIndex(indices));
}
/**
* {@inheritDoc}
*
* The BaseBuffer implementation delegates to {@link #byteAtImpl(int)} via
* byteAtImpl(byteIndex(indices)), cast unsigned to an int.
*/
@Override
public int intAt(int... indices) throws IndexOutOfBoundsException {
return 0xff & byteAt(indices);
}
/**
* {@inheritDoc}
*
* The BaseBuffer implementation delegates to {@link #storeAtImpl(byte, int)} via
* storeAtImpl(value, byteIndex(indices)).
*/
@Override
public void storeAt(byte value, int... indices) throws IndexOutOfBoundsException, PyException {
storeAtImpl(value, byteIndex(indices));
}
/*
* In this implementation, we throw IndexOutOfBoundsException if index < 0 or > shape[0], but we
* could rely on the array or ByteBuffer checks when indexing, especially the latter since
* position is checked against limit.
*/
@Override
public int byteIndex(int index) throws IndexOutOfBoundsException {
// Treat as one-dimensional
if (index < 0 || index >= shape[0]) {
throw new IndexOutOfBoundsException();
}
return index0 + index * strides[0];
}
/*
* In this implementation, we throw IndexOutOfBoundsException if any index[i] < 0 or > shape[i].
*/
@Override
public int byteIndex(int... indices) throws IndexOutOfBoundsException {
final int N = checkDimension(indices);
// In general: index0 + sum(k=0,N-1) indices[k]*strides[k]
int index = index0;
for (int k = 0; k < N; k++) {
int ik = indices[k];
if (ik < 0 || ik >= shape[k]) {
throw new IndexOutOfBoundsException();
}
index += ik * strides[k];
}
return index;
}
/**
* Calculate the absolute byte index in the storage array of the last item of the exported data
* (if we are not using indirection). This is the greatest value attained by
* {@link #byteIndex(int...)}. The first byte not used will be one itemsize more
* than the returned value.
*
* @return greatest absolute index in storage
*/
protected int calcGreatestIndex() {
final int N = shape.length;
// If all the strides are positive, the maximal value is found from:
// index = index0 + sum(k=0,N-1) (shape[k]-1)*strides[k]
// but in general, for any k where strides[k]<=0, the term should be zero.
int index = index0;
int[] strides = getStrides();
for (int k = 0; k < N; k++) {
int stride = strides[k];
if (stride > 0) {
index += (shape[k] - 1) * stride;
}
}
return index;
}
/**
* Calculate the absolute byte index in the storage array of the first item of the exported data
* (if we are not using indirection). This is the least value attained by
* {@link #byteIndex(int...)}.
*
* @return least absolute index in storage
*/
protected int calcLeastIndex() {
final int N = shape.length;
// If all the strides are positive, the maximal value is just index0,
// but in general, we must allow strides[k]<=0 for some k:
// index = index0 + sum(k=0,N-1) (strides[k]<0) ? (shape[k]-1)*strides[k] : 0
int index = index0;
int[] strides = getStrides();
for (int k = 0; k < N; k++) {
int stride = strides[k];
if (stride < 0) {
index += (shape[k] - 1) * stride;
}
}
return index;
}
/**
* {@inheritDoc}
*
* The default implementation in BaseBuffer deals with the general one-dimensional
* case of arbitrary item size and stride.
*/
@Override
public void copyTo(byte[] dest, int destPos) throws IndexOutOfBoundsException {
// Note shape[0] is the number of items in the array
copyTo(0, dest, destPos, getSize());
}
/**
* {@inheritDoc}
*
* The default implementation in BaseBuffer deals with the general one-dimensional
* case of arbitrary item size and stride, but is unable to optimise access to sequential bytes.
*/
@Override
public void copyTo(int srcIndex, byte[] dest, int destPos, int count)
throws IndexOutOfBoundsException, PyException {
checkDimension(1);
int itemsize = getItemsize();
int s = srcIndex, d = destPos;
if (itemsize == 1) {
// Single byte items
for (int i = 0; i < count; i++) {
dest[d++] = byteAt(s++);
}
} else {
// Multi-byte items
for (int i = 0; i < count; i++) {
int p = byteIndex(s++);
for (int j = 0; j < itemsize; j++) {
dest[d++] = byteAtImpl(p + j);
}
}
}
}
/**
* {@inheritDoc}
*
* The default implementation in BaseBuffer deals with the general one-dimensional
* case of arbitrary item size and stride, but is unable to optimise access to sequential bytes.
*/
@Override
public void copyFrom(byte[] src, int srcPos, int destIndex, int count)
throws IndexOutOfBoundsException, PyException {
checkDimension(1);
checkWritable();
int itemsize = getItemsize();
int d = destIndex, s = srcPos;
if (itemsize == 1) {
// Single byte items
for (int i = 0; i < count; i++) {
storeAt(src[s++], d++);
}
} else {
// Multi-byte items
for (int i = 0; i < count; i++) {
int p = byteIndex(d++);
for (int j = 0; j < itemsize; j++) {
storeAtImpl(src[s++], p++);
}
}
}
}
/**
* {@inheritDoc}
*
* The default implementation in BaseBuffer deals with the general one-dimensional
* case of arbitrary item size and stride, but is unable to optimise access to sequential bytes.
*/
@Override
public void copyFrom(PyBuffer src) throws IndexOutOfBoundsException, PyException {
checkDimension(1);
checkWritable();
int itemsize = getItemsize();
int count = getSize();
int byteLen = src.getLen();
// Block operation if different item or overall size (permit reshape)
if (src.getItemsize() != itemsize || byteLen != count * itemsize) {
throw differentStructure();
}
/*
* It is not possible in general to know that this and src do not share storage. There is
* always a risk of incorrect results if we do not go via an intermediate byte array.
* Sub-classes may be able to avoid this.
*/
byte[] t = new byte[byteLen];
src.copyTo(t, 0);
this.copyFrom(t, 0, 0, count);
}
@Override
public synchronized PyBuffer getBuffer(int flags) {
if (exports > 0) {
// Always safe to re-export if the current count is not zero
return getBufferAgain(flags);
} else {
// exports==0 so refuse
throw bufferReleased("getBuffer");
}
}
/**
* Allow an exporter to re-use this object again even if it has been "finally" released. Many
* sub-classes of BaseBytes can be re-used even after a final release by consumers,
* simply by incrementing the exports count again: the navigation arrays and the
* buffer view of the exporter's state all remain valid. We do not let consumers do this through
* the {@link PyBuffer} interface: from their perspective, calling {@link PyBuffer#release()}
* should mean the end of their access, although we can't stop them holding a reference to the
* PyBuffer. Only the exporting object, which handles the implementation type is trusted to know
* when re-use is safe.
*
* An exporter will use this method as part of its implementation of
* {@link BufferProtocol#getBuffer(int)}. On return from that, the buffer and the exporting
* object must then be in effectively the same state as if the buffer had just been
* constructed by that method. Exporters that destroy related resources on final release of
* their buffer (by overriding {@link #releaseAction()}), or permit themselves structural change
* invalidating the buffer, must either reconstruct the missing resources or avoid
* getBufferAgain.
*/
public synchronized BaseBuffer getBufferAgain(int flags) {
// If only the request flags are correct for this type, we can re-use this buffer
checkRequestFlags(flags);
// Count another consumer of this
exports += 1;
return this;
}
/**
* {@inheritDoc}
*
* When the final matching release occurs (that is the number of release calls
* equals the number of getBuffer calls), the implementation here calls
* {@link #releaseAction()}, which the implementer of a specific buffer type should override if
* it needs specific actions to take place.
*
* Note that, when this is a sliced view obtained from another PyBuffer the
* implementation in BaseBuffer automatically sends one release()
* Sub-classes should not propagate the release themselves when overriding
* {@link #releaseAction()}.
*/
@Override
public void release() {
if (--exports == 0) {
// This is a final release.
releaseAction();
// We have to release the root too if we are not a root.
PyBuffer root = getRoot();
if (root != this) {
root.release();
}
} else if (exports < 0) {
// Buffer already had 0 exports. (Put this right, in passing.)
exports = 0;
throw bufferReleased("release");
}
}
@Override
public void close() {
release();
}
@Override
public boolean isReleased() {
return exports <= 0;
}
@Override
public PyBuffer getBufferSlice(int flags, int start, int count) {
return getBufferSlice(flags, start, count, 1);
}
// Let the sub-class implement
// @Override public PyBuffer getBufferSlice(int flags, int start, int count, int stride) {}
/**
* Create a new java.nio.ByteBuffer on the underlying storage, such that
* positioning this buffer to a particular byte using {@link #byteIndex(int)} or
* {@link #byteIndex(int[])} positions it at the first byte of the item so indexed.
*/
abstract protected ByteBuffer getNIOByteBufferImpl();
@Override
public ByteBuffer getNIOByteBuffer() {
// The buffer spans the whole storage
ByteBuffer b = getNIOByteBufferImpl();
// For the one-dimensional contiguous case it makes sense to set the limit:
if (shape.length == 1 && isContiguous('A')) {
int stride = strides[0];
if (getItemsize() == stride) {
b.limit(index0 + shape[0] * stride);
}
}
// The buffer is positioned at item[0]
b.position(index0);
return b;
}
@Override
public boolean hasArray() {
// AS_ARRAY is a non-navigation flag, so is inverted in gFeatureFlags
return (gFeatureFlags & AS_ARRAY) == 0; // i.e. featureFlags & AS_ARRAY is true
}
@SuppressWarnings("deprecation")
@Override
public Pointer getBuf() {
checkHasArray();
return new Pointer(getNIOByteBuffer().array(), index0);
}
@SuppressWarnings("deprecation")
@Override
public Pointer getPointer(int index) throws IndexOutOfBoundsException {
Pointer p = getBuf();
p.offset = byteIndex(index);
return p;
}
@SuppressWarnings("deprecation")
@Override
public Pointer getPointer(int... indices) throws IndexOutOfBoundsException {
Pointer p = getBuf();
p.offset = byteIndex(indices);
return p;
}
@Override
public int[] getStrides() {
return strides;
}
@Override
public int[] getSuboffsets() {
// No actual 'suboffsets' member until a sub-class needs it
return null;
}
private boolean isCContiguous() {
/*
* If we were to compute the strides array for a C-contiguous array, the last stride would
* equal the item size, and generally stride[k-1] = shape[k]*stride[k]. This is the basis of
* the test. However, note that for any k where shape[k]==1 there is no "next sub-array" and
* no discontiguity.
*/
final int N = shape.length;
/*
* size is the stride in bytes-index from item[i0,i1,...,ik,0,...,0] to
* item[i0,i1,...,ik+1,0,...,0]. Start the iteration at the largest k. An increment of one
* in the last index makes a stride of the item size.
*/
int size = getItemsize();
for (int k = N - 1; k >= 0; k--) {
int nk = shape[k];
if (nk > 1) {
if (strides[k] != size) {
return false;
}
size *= nk;
}
}
return true;
}
private boolean isFortranContiguous() {
/*
* If we were to compute the strides array for a Fortran-contiguous array, the first stride
* would equal the item size, and generally stride[k+1] = shape[k]*stride[k]. This is the
* basis of the test. However, note that for any k where shape[k]==1 there is no
* "next sub-array" and no discontiguity.
*/
final int N = shape.length;
/*
* size is the stride in bytes-index from item[0,...,0,ik,0,...,0] to
* item[0,...,0,ik+1,0,...,0]. Start the iteration at k=0. An increment of one in the first
* index makes a stride of the item size.
*/
int size = getItemsize();
for (int k = 0; k < N; k++) {
int nk = shape[k];
if (nk > 1) {
if (strides[k] != size) {
return false;
}
size *= nk;
}
}
return true;
}
@Override
public boolean isContiguous(char order) {
if (getSuboffsets() != null) {
return false;
}
switch (order) {
case 'C':
return isCContiguous();
case 'F':
return isFortranContiguous();
case 'A':
return isCContiguous() || isFortranContiguous();
default:
return false;
}
}
@Override
public String getFormat() {
// Avoid having to have an actual 'format' member
return "B";
}
@Override
public int getItemsize() {
// Avoid having to have an actual 'itemsize' member
return 1;
}
/**
* This method will be called when the number of calls to {@link #release()} on this buffer is
* equal to the number of calls to {@link PyBuffer#getBuffer(int)} and to
* {@link BufferProtocol#getBuffer(int)} that returned this buffer. The default implementation
* does nothing. Override this method to add release behaviour specific to an 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 ultimately defined.
*/
protected void releaseAction() {}
/**
* Some PyBuffers, those created by slicing a PyBuffer, are related to
* a root PyBuffer. During creation of such a slice, we need to supply a value for
* this root. If the present object is not itself a slice, this root is the object itself; if
* the buffer is already a slice, it is the root it was given at creation time. Often this is
* the only difference between a slice-view and a directly-exported buffer. Override this method
* in slices to return the root buffer of the slice.
*
* @return this buffer (or the root buffer if this is a sliced view)
*/
protected PyBuffer getRoot() {
return this;
}
/**
* The toString() method of a buffer reproduces the values in the buffer (as unsigned integers)
* as the character codes of a String.
*/
@Override
public String toString() {
int n = getLen();
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.appendCodePoint(intAt(i));
}
return sb.toString();
}
/**
* Check the number of indices (but not their values), raising a Python BufferError if this does
* not match the number of dimensions. This is a helper for N-dimensional arrays.
*
* @param indices into the buffer (to test)
* @return number of dimensions
* @throws PyException {@code BufferError} if wrong number of indices
*/
int checkDimension(int[] indices) throws PyException {
int n = indices.length;
checkDimension(n);
return n;
}
/**
* Check that the number offered is in fact the number of dimensions in this buffer, raising a
* Python BufferError if this does not match the number of dimensions. This is a helper for
* N-dimensional arrays.
*
* @param n number of dimensions being assumed by caller
* @throws PyException {@code BufferError} if wrong number of indices
*/
void checkDimension(int n) throws PyException {
int ndim = getNdim();
if (n != ndim) {
String fmt = "buffer with %d dimension%s accessed as having %d dimension%s";
String msg = String.format(fmt, ndim, ndim == 1 ? "" : "s", n, n, n == 1 ? "" : "s");
throw Py.BufferError(msg);
}
}
/**
* Check that the buffer is writable.
*
* @throws PyException {@code TypeError} if not
*/
protected void checkWritable() throws PyException {
if (isReadonly()) {
throw notWritable();
}
}
/**
* Check that the buffer is backed by an array the client can access as byte[].
*
* @throws PyException {@code BufferError} if not
*/
protected void checkHasArray() throws PyException {
if (!hasArray()) {
throw bufferIsNot("accessible as a Java array");
}
}
/**
* General purpose method to construct an exception to throw according to the syndrome.
*
* @param syndrome of the mis-match between buffer and requested features
* @return PyException (BufferError) specifying the mis-match
*/
private static PyException bufferErrorFromSyndrome(int syndrome) {
if ((syndrome & ND) != 0) {
return bufferRequires("shape array");
} else if ((syndrome & WRITABLE) != 0) {
return bufferIsNot("writable");
} else if ((syndrome & AS_ARRAY) != 0) {
return bufferIsNot("accessible as a Java array");
} else if ((syndrome & C_CONTIGUOUS) != 0) {
return bufferIsNot("C-contiguous");
} else if ((syndrome & F_CONTIGUOUS) != 0) {
return bufferIsNot("Fortran-contiguous");
} else if ((syndrome & ANY_CONTIGUOUS) != 0) {
return bufferIsNot("contiguous");
} else if ((syndrome & STRIDES) != 0) {
return bufferRequires("strides array");
} else if ((syndrome & INDIRECT) != 0) {
return bufferRequires("suboffsets array");
} else {
// Catch-all error (never in practice if this method is complete)
return bufferIsNot("capable of matching request");
}
}
/**
* Convenience method to create (for the caller to throw) a
* TypeError("cannot modify read-only memory").
*
* @return the error as a PyException
*/
protected static PyException notWritable() {
return Py.TypeError("cannot modify read-only memory");
}
/**
* Convenience method to create (for the caller to throw) a
* BufferError("underlying buffer is not {property}").
*
* @param property
* @return the error as a PyException
*/
protected static PyException bufferIsNot(String property) {
return Py.BufferError("underlying buffer is not " + property);
}
/**
* Convenience method to create (for the caller to throw) a
* ValueError("buffer ... different structures").
*
* @return the error as a PyException
*/
protected static PyException differentStructure() {
return Py.ValueError("buffer assignment: lvalue and rvalue have different structures");
}
/**
* Convenience method to create (for the caller to throw) a
* BufferError("buffer structure requires consumer to use {feature}").
*
* @param feature
* @return the error as a PyException
*/
protected static PyException bufferRequires(String feature) {
return Py.BufferError("buffer structure requires consumer to use " + feature);
}
/**
* Convenience method to create (for the caller to throw) a
* BufferError("{operation} operation forbidden on released buffer object").
*
* @param operation name of operation or null
* @return the error as a PyException
*/
protected static PyException bufferReleased(String operation) {
String op = (operation == null) ? "" : operation + " ";
return Py.BufferError(op + "operation forbidden on released buffer object");
}
}