/* * Copyright (c) 2002-2010 LWJGL Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'LWJGL' nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.lwjgl; import java.lang.reflect.Method; import java.nio.*; /** * This class is a container for architecture independent pointer data. * The interface mirrors the NIO LongBuffer API for convenience. * * @author Spasi */ public class PointerBuffer implements Comparable { private static final boolean is64Bit; static { // Use reflection so that we can compile this class for the Generator. boolean is64 = false; try { Method m = Class.forName("org.lwjgl.Sys").getDeclaredMethod("is64Bit", (Class[])null); is64 = (Boolean)m.invoke(null, (Object[])null); } catch (Throwable t) { // ignore } finally { is64Bit = is64; } } protected final ByteBuffer pointers; protected final Buffer view; protected final IntBuffer view32; protected final LongBuffer view64; /** * Creates a new PointerBuffer with the specified capacity. * * @param capacity the PointerBuffer size, in number of pointers */ public PointerBuffer(final int capacity) { this(BufferUtils.createByteBuffer(capacity * getPointerSize())); } /** * Creates a new PointerBuffer using the specified ByteBuffer as its pointer * data source. This is useful for users that do their own memory management * over a big ByteBuffer, instead of allocating many small ones. * * @param source the source buffer */ public PointerBuffer(final ByteBuffer source) { if ( LWJGLUtil.CHECKS ) checkSource(source); pointers = source.slice().order(source.order()); if ( is64Bit ) { view32 = null; view = view64 = pointers.asLongBuffer(); } else { view = view32 = pointers.asIntBuffer(); view64 = null; } } private static void checkSource(final ByteBuffer source) { if ( !source.isDirect() ) throw new IllegalArgumentException("The source buffer is not direct."); final int alignment = is64Bit ? 8 : 4; if ( (MemoryUtil.getAddress0(source) + source.position()) % alignment != 0 || source.remaining() % alignment != 0 ) throw new IllegalArgumentException("The source buffer is not aligned to " + alignment + " bytes."); } /** * Returns the ByteBuffer that backs this PointerBuffer. * * @return the pointer ByteBuffer */ public ByteBuffer getBuffer() { return pointers; } /** Returns true if the underlying architecture is 64bit. */ public static boolean is64Bit() { return is64Bit; } /** * Returns the pointer size in bytes, based on the underlying architecture. * * @return The pointer size in bytes */ public static int getPointerSize() { return is64Bit ? 8 : 4; } /** * Returns this buffer's capacity. </p> * * @return The capacity of this buffer */ public final int capacity() { return view.capacity(); } /** * Returns this buffer's position. </p> * * @return The position of this buffer */ public final int position() { return view.position(); } /** * Returns this buffer's position, in bytes. </p> * * @return The position of this buffer in bytes. */ public final int positionByte() { return position() * getPointerSize(); } /** * Sets this buffer's position. If the mark is defined and larger than the * new position then it is discarded. </p> * * @param newPosition The new position value; must be non-negative * and no larger than the current limit * * @return This buffer * * @throws IllegalArgumentException If the preconditions on <tt>newPosition</tt> do not hold */ public final PointerBuffer position(int newPosition) { view.position(newPosition); return this; } /** * Returns this buffer's limit. </p> * * @return The limit of this buffer */ public final int limit() { return view.limit(); } /** * Sets this buffer's limit. If the position is larger than the new limit * then it is set to the new limit. If the mark is defined and larger than * the new limit then it is discarded. </p> * * @param newLimit The new limit value; must be non-negative * and no larger than this buffer's capacity * * @return This buffer * * @throws IllegalArgumentException If the preconditions on <tt>newLimit</tt> do not hold */ public final PointerBuffer limit(int newLimit) { view.limit(newLimit); return this; } /** * Sets this buffer's mark at its position. </p> * * @return This buffer */ public final PointerBuffer mark() { view.mark(); return this; } /** * Resets this buffer's position to the previously-marked position. * <p/> * <p> Invoking this method neither changes nor discards the mark's * value. </p> * * @return This buffer * * @throws java.nio.InvalidMarkException If the mark has not been set */ public final PointerBuffer reset() { view.reset(); return this; } /** * Clears this buffer. The position is set to zero, the limit is set to * the capacity, and the mark is discarded. * <p/> * <p> Invoke this method before using a sequence of channel-read or * <i>put</i> operations to fill this buffer. For example: * <p/> * <blockquote><pre> * buf.clear(); // Prepare buffer for reading * in.read(buf); // Read data</pre></blockquote> * <p/> * <p> This method does not actually erase the data in the buffer, but it * is named as if it did because it will most often be used in situations * in which that might as well be the case. </p> * * @return This buffer */ public final PointerBuffer clear() { view.clear(); return this; } /** * Flips this buffer. The limit is set to the current position and then * the position is set to zero. If the mark is defined then it is * discarded. * <p/> * <p> After a sequence of channel-read or <i>put</i> operations, invoke * this method to prepare for a sequence of channel-write or relative * <i>get</i> operations. For example: * <p/> * <blockquote><pre> * buf.put(magic); // Prepend header * in.read(buf); // Read data into rest of buffer * buf.flip(); // Flip buffer * out.write(buf); // Write header + data to channel</pre></blockquote> * <p/> * <p> This method is often used in conjunction with the {@link * java.nio.ByteBuffer#compact compact} method when transferring data from * one place to another. </p> * * @return This buffer */ public final PointerBuffer flip() { view.flip(); return this; } /** * Rewinds this buffer. The position is set to zero and the mark is * discarded. * <p/> * <p> Invoke this method before a sequence of channel-write or <i>get</i> * operations, assuming that the limit has already been set * appropriately. For example: * <p/> * <blockquote><pre> * out.write(buf); // Write remaining data * buf.rewind(); // Rewind buffer * buf.get(array); // Copy data into array</pre></blockquote> * * @return This buffer */ public final PointerBuffer rewind() { view.rewind(); return this; } /** * Returns the number of elements between the current position and the * limit. </p> * * @return The number of elements remaining in this buffer */ public final int remaining() { return view.remaining(); } /** * Returns the number of bytes between the current position and the * limit. </p> * * @return The number of bytes remaining in this buffer */ public final int remainingByte() { return remaining() * getPointerSize(); } /** * Tells whether there are any elements between the current position and * the limit. </p> * * @return <tt>true</tt> if, and only if, there is at least one element * remaining in this buffer */ public final boolean hasRemaining() { return view.hasRemaining(); } /** * Allocates a new pointer buffer. * <p/> * <p> The new buffer's position will be zero, its limit will be its * capacity, and its mark will be undefined. </p> * * @param capacity The new buffer's capacity, in pointers * * @return The new pointer buffer * * @throws IllegalArgumentException If the <tt>capacity</tt> is a negative integer */ public static PointerBuffer allocateDirect(int capacity) { return new PointerBuffer(capacity); } /** * This method is used in slice and duplicate instead of normal object construction, * so that subclasses can return themselves. * * @param source * * @return A new PointerBuffer instance */ protected PointerBuffer newInstance(final ByteBuffer source) { return new PointerBuffer(source); } /** * Creates a new pointer buffer whose content is a shared subsequence of * this buffer's content. * <p/> * <p> The content of the new buffer will start at this buffer's current * position. Changes to this buffer's content will be visible in the new * buffer, and vice versa; the two buffers' position, limit, and mark * values will be independent. * <p/> * <p> The new buffer's position will be zero, its capacity and its limit * will be the number of longs remaining in this buffer, and its mark * will be undefined. The new buffer will be direct if, and only if, this * buffer is direct, and it will be read-only if, and only if, this buffer * is read-only. </p> * * @return The new pointer buffer */ public PointerBuffer slice() { final int pointerSize = getPointerSize(); pointers.position(view.position() * pointerSize); pointers.limit(view.limit() * pointerSize); try { // We're slicing in the constructor. return newInstance(pointers); } finally { pointers.clear(); } } /** * Creates a new pointer buffer that shares this buffer's content. * <p/> * <p> The content of the new buffer will be that of this buffer. Changes * to this buffer's content will be visible in the new buffer, and vice * versa; the two buffers' position, limit, and mark values will be * independent. * <p/> * <p> The new buffer's capacity, limit and position will be * identical to those of this buffer. The new buffer will be direct if, * and only if, this buffer is direct, and it will be read-only if, and * only if, this buffer is read-only. </p> * * @return The new pointer buffer */ public PointerBuffer duplicate() { final PointerBuffer buffer = newInstance(pointers); buffer.position(view.position()); buffer.limit(view.limit()); return buffer; } /** * Creates a new, read-only pointer buffer that shares this buffer's * content. * <p/> * <p> The content of the new buffer will be that of this buffer. Changes * to this buffer's content will be visible in the new buffer; the new * buffer itself, however, will be read-only and will not allow the shared * content to be modified. The two buffers' position, limit, and mark * values will be independent. * <p/> * <p> The new buffer's capacity, limit and position will be * identical to those of this buffer. * <p/> * <p> If this buffer is itself read-only then this method behaves in * exactly the same way as the {@link #duplicate duplicate} method. </p> * * @return The new, read-only pointer buffer */ public PointerBuffer asReadOnlyBuffer() { final PointerBuffer buffer = new PointerBufferR(pointers); buffer.position(view.position()); buffer.limit(view.limit()); return buffer; } public boolean isReadOnly() { return false; } /** * Relative <i>get</i> method. Reads the long at this buffer's * current position, and then increments the position. </p> * * @return The long at the buffer's current position * * @throws BufferUnderflowException If the buffer's current position is not smaller than its limit */ public long get() { if ( is64Bit ) return view64.get(); else return view32.get() & 0x00000000FFFFFFFFL; } /** * Relative <i>put</i> method  <i>(optional operation)</i>. * <p/> * <p> Writes the given long into this buffer at the current * position, and then increments the position. </p> * * @param l The long to be written * * @return This buffer * * @throws BufferOverflowException If this buffer's current position is not smaller than its limit * @throws ReadOnlyBufferException If this buffer is read-only */ public PointerBuffer put(long l) { if ( is64Bit ) view64.put(l); else view32.put((int)l); return this; } /** * Convenience put that accepts PointerWrapper objects. * * @see #put(long) */ public PointerBuffer put(final PointerWrapper pointer) { return put(pointer.getPointer()); } /** * Convenience put on a target ByteBuffer. * * @param target the target ByteBuffer * @param l the long value to be written */ public static void put(final ByteBuffer target, long l) { if ( is64Bit ) target.putLong(l); else target.putInt((int)l); } /** * Absolute <i>get</i> method. Reads the long at the given * index. </p> * * @param index The index from which the long will be read * * @return The long at the given index * * @throws IndexOutOfBoundsException If <tt>index</tt> is negative * or not smaller than the buffer's limit */ public long get(int index) { if ( is64Bit ) return view64.get(index); else return view32.get(index) & 0x00000000FFFFFFFFL; } /** * Absolute <i>put</i> method  <i>(optional operation)</i>. * <p/> * <p> Writes the given long into this buffer at the given * index. </p> * * @param index The index at which the long will be written * @param l The long value to be written * * @return This buffer * * @throws IndexOutOfBoundsException If <tt>index</tt> is negative * or not smaller than the buffer's limit * @throws ReadOnlyBufferException If this buffer is read-only */ public PointerBuffer put(int index, long l) { if ( is64Bit ) view64.put(index, l); else view32.put(index, (int)l); return this; } /** * Convenience put that accepts PointerWrapper objects. * * @see #put(int, long) */ public PointerBuffer put(int index, PointerWrapper pointer) { return put(index, pointer.getPointer()); } /** * Convenience put on a target ByteBuffer. * * @param target the target ByteBuffer * @param index the index at which the long will be written * @param l the long value to be written */ public static void put(final ByteBuffer target, int index, long l) { if ( is64Bit ) target.putLong(index, l); else target.putInt(index, (int)l); } // -- Bulk get operations -- /** * Relative bulk <i>get</i> method. * <p/> * <p> This method transfers longs from this buffer into the given * destination array. If there are fewer longs remaining in the * buffer than are required to satisfy the request, that is, if * <tt>length</tt> <tt>></tt> <tt>remaining()</tt>, then no * longs are transferred and a {@link BufferUnderflowException} is * thrown. * <p/> * <p> Otherwise, this method copies <tt>length</tt> longs from this * buffer into the given array, starting at the current position of this * buffer and at the given offset in the array. The position of this * buffer is then incremented by <tt>length</tt>. * <p/> * <p> In other words, an invocation of this method of the form * <tt>src.get(dst, off, len)</tt> has exactly the same effect as * the loop * <p/> * <pre> * for (int i = off; i < off + len; i++) * dst[i] = src.get(); </pre> * <p/> * except that it first checks that there are sufficient longs in * this buffer and it is potentially much more efficient. </p> * * @param dst The array into which longs are to be written * @param offset The offset within the array of the first long to be * written; must be non-negative and no larger than * <tt>dst.length</tt> * @param length The maximum number of longs to be written to the given * array; must be non-negative and no larger than * <tt>dst.length - offset</tt> * * @return This buffer * * @throws BufferUnderflowException If there are fewer than <tt>length</tt> longs * remaining in this buffer * @throws IndexOutOfBoundsException If the preconditions on the <tt>offset</tt> and <tt>length</tt> * parameters do not hold */ public PointerBuffer get(long[] dst, int offset, int length) { if ( is64Bit ) view64.get(dst, offset, length); else { checkBounds(offset, length, dst.length); if ( length > view32.remaining() ) throw new BufferUnderflowException(); int end = offset + length; for ( int i = offset; i < end; i++ ) dst[i] = view32.get() & 0x00000000FFFFFFFFL; } return this; } /** * Relative bulk <i>get</i> method. * <p/> * <p> This method transfers longs from this buffer into the given * destination array. An invocation of this method of the form * <tt>src.get(a)</tt> behaves in exactly the same way as the invocation * <p/> * <pre> * src.get(a, 0, a.length) </pre> * * @return This buffer * * @throws BufferUnderflowException If there are fewer than <tt>length</tt> longs * remaining in this buffer */ public PointerBuffer get(long[] dst) { return get(dst, 0, dst.length); } /** * Relative bulk <i>put</i> method  <i>(optional operation)</i>. * <p/> * <p> This method transfers the longs remaining in the given source * buffer into this buffer. If there are more longs remaining in the * source buffer than in this buffer, that is, if * <tt>src.remaining()</tt> <tt>></tt> <tt>remaining()</tt>, * then no longs are transferred and a {@link * BufferOverflowException} is thrown. * <p/> * <p> Otherwise, this method copies * <i>n</i> = <tt>src.remaining()</tt> longs from the given * buffer into this buffer, starting at each buffer's current position. * The positions of both buffers are then incremented by <i>n</i>. * <p/> * <p> In other words, an invocation of this method of the form * <tt>dst.put(src)</tt> has exactly the same effect as the loop * <p/> * <pre> * while (src.hasRemaining()) * dst.put(src.get()); </pre> * <p/> * except that it first checks that there is sufficient space in this * buffer and it is potentially much more efficient. </p> * * @param src The source buffer from which longs are to be read; * must not be this buffer * * @return This buffer * * @throws BufferOverflowException If there is insufficient space in this buffer * for the remaining longs in the source buffer * @throws IllegalArgumentException If the source buffer is this buffer * @throws ReadOnlyBufferException If this buffer is read-only */ public PointerBuffer put(PointerBuffer src) { if ( is64Bit ) view64.put(src.view64); else view32.put(src.view32); return this; } /** * Relative bulk <i>put</i> method  <i>(optional operation)</i>. * <p/> * <p> This method transfers longs into this buffer from the given * source array. If there are more longs to be copied from the array * than remain in this buffer, that is, if * <tt>length</tt> <tt>></tt> <tt>remaining()</tt>, then no * longs are transferred and a {@link BufferOverflowException} is * thrown. * <p/> * <p> Otherwise, this method copies <tt>length</tt> longs from the * given array into this buffer, starting at the given offset in the array * and at the current position of this buffer. The position of this buffer * is then incremented by <tt>length</tt>. * <p/> * <p> In other words, an invocation of this method of the form * <tt>dst.put(src, off, len)</tt> has exactly the same effect as * the loop * <p/> * <pre> * for (int i = off; i < off + len; i++) * dst.put(a[i]); </pre> * <p/> * except that it first checks that there is sufficient space in this * buffer and it is potentially much more efficient. </p> * * @param src The array from which longs are to be read * @param offset The offset within the array of the first long to be read; * must be non-negative and no larger than <tt>array.length</tt> * @param length The number of longs to be read from the given array; * must be non-negative and no larger than * <tt>array.length - offset</tt> * * @return This buffer * * @throws BufferOverflowException If there is insufficient space in this buffer * @throws IndexOutOfBoundsException If the preconditions on the <tt>offset</tt> and <tt>length</tt> * parameters do not hold * @throws ReadOnlyBufferException If this buffer is read-only */ public PointerBuffer put(long[] src, int offset, int length) { if ( is64Bit ) view64.put(src, offset, length); else { checkBounds(offset, length, src.length); if ( length > view32.remaining() ) throw new BufferOverflowException(); int end = offset + length; for ( int i = offset; i < end; i++ ) view32.put((int)src[i]); } return this; } /** * Relative bulk <i>put</i> method  <i>(optional operation)</i>. * <p/> * <p> This method transfers the entire content of the given source * long array into this buffer. An invocation of this method of the * form <tt>dst.put(a)</tt> behaves in exactly the same way as the * invocation * <p/> * <pre> * dst.put(a, 0, a.length) </pre> * * @return This buffer * * @throws BufferOverflowException If there is insufficient space in this buffer * @throws ReadOnlyBufferException If this buffer is read-only */ public final PointerBuffer put(long[] src) { return put(src, 0, src.length); } /** * Compacts this buffer  <i>(optional operation)</i>. * <p/> * <p> The longs between the buffer's current position and its limit, * if any, are copied to the beginning of the buffer. That is, the * long at index <i>p</i> = <tt>position()</tt> is copied * to index zero, the long at index <i>p</i> + 1 is copied * to index one, and so forth until the long at index * <tt>limit()</tt> - 1 is copied to index * <i>n</i> = <tt>limit()</tt> - <tt>1</tt> - <i>p</i>. * The buffer's position is then set to <i>n+1</i> and its limit is set to * its capacity. The mark, if defined, is discarded. * <p/> * <p> The buffer's position is set to the number of longs copied, * rather than to zero, so that an invocation of this method can be * followed immediately by an invocation of another relative <i>put</i> * method. </p> * * @return This buffer * * @throws ReadOnlyBufferException If this buffer is read-only */ public PointerBuffer compact() { if ( is64Bit ) view64.compact(); else view32.compact(); return this; } /** * Retrieves this buffer's byte order. * <p/> * <p> The byte order of a pointer buffer created by allocation or by * wrapping an existing <tt>long</tt> array is the {@link * ByteOrder#nativeOrder </code>native order<code>} of the underlying * hardware. The byte order of a pointer buffer created as a <a * href="ByteBuffer.html#views">view</a> of a byte buffer is that of the * byte buffer at the moment that the view is created. </p> * * @return This buffer's byte order */ public ByteOrder order() { if ( is64Bit ) return view64.order(); else return view32.order(); } /** * Returns a string summarizing the state of this buffer. </p> * * @return A summary string */ public String toString() { StringBuilder sb = new StringBuilder(48); sb.append(getClass().getName()); sb.append("[pos="); sb.append(position()); sb.append(" lim="); sb.append(limit()); sb.append(" cap="); sb.append(capacity()); sb.append("]"); return sb.toString(); } /** * Returns the current hash code of this buffer. * <p/> * <p> The hash code of a pointer buffer depends only upon its remaining * elements; that is, upon the elements from <tt>position()</tt> up to, and * including, the element at <tt>limit()</tt> - <tt>1</tt>. * <p/> * <p> Because buffer hash codes are content-dependent, it is inadvisable * to use buffers as keys in hash maps or similar data structures unless it * is known that their contents will not change. </p> * * @return The current hash code of this buffer */ public int hashCode() { int h = 1; int p = position(); for ( int i = limit() - 1; i >= p; i-- ) h = 31 * h + (int)get(i); return h; } /** * Tells whether or not this buffer is equal to another object. * <p/> * <p> Two pointer buffers are equal if, and only if, * <p/> * <p><ol> * <p/> * <li><p> They have the same element type, </p></li> * <p/> * <li><p> They have the same number of remaining elements, and * </p></li> * <p/> * <li><p> The two sequences of remaining elements, considered * independently of their starting positions, are pointwise equal. * </p></li> * <p/> * </ol> * <p/> * <p> A pointer buffer is not equal to any other type of object. </p> * * @param ob The object to which this buffer is to be compared * * @return <tt>true</tt> if, and only if, this buffer is equal to the * given object */ public boolean equals(Object ob) { if ( !(ob instanceof PointerBuffer) ) return false; PointerBuffer that = (PointerBuffer)ob; if ( this.remaining() != that.remaining() ) return false; int p = this.position(); for ( int i = this.limit() - 1, j = that.limit() - 1; i >= p; i--, j-- ) { long v1 = this.get(i); long v2 = that.get(j); if ( v1 != v2 ) { return false; } } return true; } /** * Compares this buffer to another. * <p/> * <p> Two pointer buffers are compared by comparing their sequences of * remaining elements lexicographically, without regard to the starting * position of each sequence within its corresponding buffer. * <p/> * <p> A pointer buffer is not comparable to any other type of object. * * @return A negative integer, zero, or a positive integer as this buffer * is less than, equal to, or greater than the given buffer */ public int compareTo(Object o) { final PointerBuffer that = (PointerBuffer)o; int n = this.position() + Math.min(this.remaining(), that.remaining()); for ( int i = this.position(), j = that.position(); i < n; i++, j++ ) { long v1 = this.get(i); long v2 = that.get(j); if ( v1 == v2 ) continue; if ( v1 < v2 ) return -1; return +1; } return this.remaining() - that.remaining(); } private static void checkBounds(int off, int len, int size) { if ( (off | len | (off + len) | (size - (off + len))) < 0 ) throw new IndexOutOfBoundsException(); } /** * Read-only version of PointerBuffer. * * @author Spasi */ private static final class PointerBufferR extends PointerBuffer { PointerBufferR(final ByteBuffer source) { super(source); } public boolean isReadOnly() { return true; } protected PointerBuffer newInstance(final ByteBuffer source) { return new PointerBufferR(source); } public PointerBuffer asReadOnlyBuffer() { return duplicate(); } public PointerBuffer put(final long l) { throw new ReadOnlyBufferException(); } public PointerBuffer put(final int index, final long l) { throw new ReadOnlyBufferException(); } public PointerBuffer put(final PointerBuffer src) { throw new ReadOnlyBufferException(); } public PointerBuffer put(final long[] src, final int offset, final int length) { throw new ReadOnlyBufferException(); } public PointerBuffer compact() { throw new ReadOnlyBufferException(); } } }