Browse Source

fixed formatting

Sebastian Weiß 9 năm trước cách đây
mục cha
commit
dccec876c5

+ 709 - 520
jme3-lwjgl3/src/main/java/com/jme3/lwjgl3/utils/APIBuffer.java

@@ -16,526 +16,715 @@ import org.lwjgl.system.MemoryUtil;
 import static org.lwjgl.system.MemoryUtil.*;
 
 /**
- * Helper class for alternative API functions. Instead of the user
- * passing their own buffer, thread-local instances of this class
- * are used internally instead.
+ * Helper class for alternative API functions. Instead of the user passing their
+ * own buffer, thread-local instances of this class are used internally instead.
  */
 public class APIBuffer {
 
-	private static final int DEFAULT_CAPACITY = 128;
-
-	private ByteBuffer buffer;
-	private long       address;
-
-	private int offset;
-
-	private int stackDepth;
-	private int[] stack = new int[4];
-
-	public APIBuffer() {
-		buffer = BufferUtils.createByteBuffer(DEFAULT_CAPACITY);
-		address = memAddress(buffer);
-	}
-
-	/** Resets the parameter offset to 0. */
-	public APIBuffer reset() {
-		offset = 0;
-		return this;
-	}
-
-	/** Pushes the current parameter offset to a stack. */
-	public APIBuffer push() {
-		if ( stackDepth == stack.length )
-			stack = Arrays.copyOf(stack, stack.length << 1);
-
-		stack[stackDepth++] = offset;
-
-		// Upward align the current offset to the pointer size.
-		offset = (offset + (POINTER_SIZE - 1)) & -POINTER_SIZE;
-
-		return this;
-	}
-
-	/** Restores the last pushed parameter offset. */
-	public APIBuffer pop() {
-		offset = stack[--stackDepth];
-		return this;
-	}
-
-	/** Returns the current parameter offset. */
-	public int getOffset() {
-		return offset;
-	}
-
-	/** Sets the current parameter offset. */
-	public void setOffset(int offset) {
-		this.offset = offset;
-	}
-
-	/** Returns the memory address of the internal {@link ByteBuffer}. This address may change after a call to one of the {@code <type>Param()} methods. */
-	public long address() {
-		return address;
-	}
-
-	/** Returns the memory address of the specified {@code offset}. This address may change after a call to one of the {@code <type>Param()} methods. */
-	public long address(int offset) {
-		return address + offset;
-	}
-
-	/**
-	 * Returns the memory address of the specified {@code offset} or {@link MemoryUtil#NULL NULL} if the specified {@code value} is null. This address may
-	 * change after a call to one of the {@code <type>Param()} methods.
-	 */
-	public long addressSafe(Object value, int offset) {
-		return value == null ? NULL : address(offset);
-	}
-
-	/** Returns the {@link ByteBuffer} that backs this {@link APIBuffer}. */
-	public ByteBuffer buffer() {
-		return buffer;
-	}
-
-	private void ensureCapacity(int capacity) {
-		if ( capacity <= buffer.capacity() )
-			return;
-
-		ByteBuffer resized = BufferUtils.createByteBuffer(mathRoundPoT(capacity));
-
-		resized.put(buffer);
-		resized.clear();
-
-		buffer = resized;
-		address = memAddress(resized);
-	}
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-	private int param(int bytes) {
-		return param(bytes, bytes);
-	}
-
-	private int param(int bytes, int alignment) {
-		// Upward align the current offset to the specified alignment
-		int param = (offset + (alignment - 1)) & -alignment;
-		ensureCapacity(offset = param + bytes);
-		return param;
-	}
-
-	/** Ensures space for an additional boolean value and returns the address offset. */
-	public int booleanParam() { return param(1); }
-
-	/** Ensures space for an additional byte value and returns the address offset. */
-	public int byteParam() { return param(1); }
-
-	/** Ensures space for an additional short value and returns the address offset. */
-	public int shortParam() { return param(2); }
-
-	/** Ensures space for an additional int value and returns the address offset. */
-	public int intParam() { return param(4); }
-
-	/** Ensures space for an additional long value and returns the address offset. */
-	public int longParam() { return param(8); }
-
-	/** Ensures space for an additional float value and returns the address offset. */
-	public int floatParam() { return param(4); }
-
-	/** Ensures space for an additional double value and returns the address offset. */
-	public int doubleParam() { return param(8); }
-
-	/** Ensures space for an additional pointer value and returns the address offset. */
-	public int pointerParam() { return param(POINTER_SIZE); }
-
-	/** Ensures space for an additional buffer with the specified size (in bytes) and returns the address offset. */
-	public int bufferParam(int size) { return param(size, POINTER_SIZE); }
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-	/** Ensures space for an additional boolean value, sets the specified value at the allocated offset and returns that offset. */
-	public int booleanParam(boolean value) {
-		int offset = booleanParam();
-		buffer.put(offset, value ? (byte)1 : (byte)0);
-		return offset;
-	}
-
-	/** Ensures space for an additional byte value, sets the specified value at the allocated offset and returns that offset. */
-	public int byteParam(byte value) {
-		int offset = byteParam();
-		buffer.put(offset, value);
-		return offset;
-	}
-
-	/** Ensures space for an additional short value, sets the specified value at the allocated offset and returns that offset. */
-	public int shortParam(short value) {
-		int offset = shortParam();
-		buffer.putShort(offset, value);
-		return offset;
-	}
-
-	/** Ensures space for an additional int value, sets the specified value at the allocated offset and returns that offset. */
-	public int intParam(int value) {
-		int offset = intParam();
-		buffer.putInt(offset, value);
-		return offset;
-	}
-
-	/** Ensures space for an additional long value, sets the specified value at the allocated offset and returns that offset. */
-	public int longParam(long value) {
-		int offset = longParam();
-		buffer.putLong(offset, value);
-		return offset;
-	}
-
-	/** Ensures space for an additional float value, sets the specified value at the allocated offset and returns that offset. */
-	public int floatParam(float value) {
-		int offset = floatParam();
-		buffer.putFloat(offset, value);
-		return offset;
-	}
-
-	/** Ensures space for an additional double value, sets the specified value at the allocated offset and returns that offset. */
-	public int doubleParam(double value) {
-		int offset = doubleParam();
-		buffer.putDouble(offset, value);
-		return offset;
-	}
-
-	/** Ensures space for an additional pointer value, sets the specified value at the allocated offset and returns that offset. */
-	public int pointerParam(long value) {
-		int offset = pointerParam();
-		PointerBuffer.put(buffer, offset, value);
-		return offset;
-	}
-	// ----
-
-	/** Ensures space for an additional pointer buffer, sets the specified memory addresses and returns the address offset. */
-	public int pointerArrayParam(long... pointers) {
-		int buffersAddress = bufferParam(pointers.length << POINTER_SHIFT);
-		for ( int i = 0; i < pointers.length; i++ )
-			pointerParam(buffersAddress, i, pointers[i]);
-
-		return buffersAddress;
-	}
-
-	/** Ensures space for an additional pointer buffer, sets the memory addresses of the specified buffers and returns the address offset. */
-	public int pointerArrayParam(ByteBuffer... buffers) {
-		int buffersAddress = bufferParam(buffers.length << POINTER_SHIFT);
-		for ( int i = 0; i < buffers.length; i++ )
-			pointerParam(buffersAddress, i, memAddress(buffers[i]));
-
-		return buffersAddress;
-	}
-
-	/**
-	 * Ensures space for two additional pointer buffers, sets the memory addresses and remaining bytes of the specified buffers and returns the address
-	 * offset.
-	 */
-	public int pointerArrayParamp(ByteBuffer... buffers) {
-		int buffersAddress = pointerArrayParam(buffers);
-
-		int buffersLengths = bufferParam(buffers.length << POINTER_SHIFT);
-		for ( int i = 0; i < buffers.length; i++ )
-			pointerParam(buffersLengths, i, buffers[i].remaining());
-
-		return buffersAddress;
-	}
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-        /**
-	 * ASCII encodes the specified strings with a null-terminator and ensures space for a buffer filled with the memory addresses of the encoded strings.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the memory address buffer
-	 */
-	public int pointerArrayParamASCII(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memASCII(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * ASCII encodes the specified strings and ensures space for two additional buffers filled with the lengths and memory addresses of the encoded strings,
-	 * respectively. The lengths are 4-bytes integers and the memory address buffer starts immediately after the lengths buffer.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the lengths buffer
-	 */
-	public int pointerArrayParamASCIIi(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		int lengthsAddress = bufferParam(strings.length << 2);
-
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memASCII(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-			intParam(lengthsAddress, i, buffer.remaining());
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * ASCII encodes the specified strings and ensures space for two additional buffers filled with the lengths and memory addresses of the encoded strings,
-	 * respectively. The lengths are pointer-sized integers and the memory address buffer starts immediately after the lengths buffer.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the lengths buffer
-	 */
-	public int pointerArrayParamASCIIp(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		int lengthsAddress = bufferParam(strings.length << POINTER_SHIFT);
-
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memASCII(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-			pointerParam(lengthsAddress, i, buffer.remaining());
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * UTF8 encodes the specified strings with a null-terminator and ensures space for a buffer filled with the memory addresses of the encoded strings.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the memory address buffer
-	 */
-	public int pointerArrayParamUTF8(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memUTF8(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * UTF8 encodes the specified strings and ensures space for two additional buffers filled with the lengths and memory addresses of the encoded strings,
-	 * respectively. The lengths are 4-bytes integers and the memory address buffer starts immediately after the lengths buffer.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the lengths buffer
-	 */
-	public int pointerArrayParamUTF8i(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		int lengthsAddress = bufferParam(strings.length << 2);
-
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memUTF8(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-			intParam(lengthsAddress, i, buffer.remaining());
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * UTF8 encodes the specified strings and ensures space for two additional buffers filled with the lengths and memory addresses of the encoded strings,
-	 * respectively. The lengths are pointer-sized integers and the memory address buffer starts immediately after the lengths buffer.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the lengths buffer
-	 */
-	public int pointerArrayParamUTF8p(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		int lengthsAddress = bufferParam(strings.length << POINTER_SHIFT);
-
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memUTF8(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-			pointerParam(lengthsAddress, i, buffer.remaining());
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * UTF16 encodes the specified strings with a null-terminator and ensures space for a buffer filled with the memory addresses of the encoded strings.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the memory address buffer
-	 */
-	public int pointerArrayParamUTF16(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memUTF16(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * UTF16 encodes the specified strings and ensures space for two additional buffers filled with the lengths and memory addresses of the encoded strings,
-	 * respectively. The lengths are 4-bytes integers and the memory address buffer starts immediately after the lengths buffer.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the lengths buffer
-	 */
-	public int pointerArrayParamUTF16i(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		int lengthsAddress = bufferParam(strings.length << 2);
-
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memUTF16(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-			intParam(lengthsAddress, i, buffer.remaining());
-		}
-
-		return buffersAddress;
-	}
-
-	/**
-	 * UTF16 encodes the specified strings and ensures space for two additional buffers filled with the lengths and memory addresses of the encoded strings,
-	 * respectively. The lengths are pointer-sized integers and the memory address buffer starts immediately after the lengths buffer.
-	 *
-	 * <p>The encoded buffers must be later freed with {@link #pointerArrayFree(int, int)}.</p>
-	 *
-	 * @return the offset to the lengths buffer
-	 */
-	public int pointerArrayParamUTF16p(CharSequence... strings) {
-		int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
-		int lengthsAddress = bufferParam(strings.length << POINTER_SHIFT);
-
-		for ( int i = 0; i < strings.length; i++ ) {
-			ByteBuffer buffer = MemoryUtil.memUTF16(strings[i]);
-
-			pointerParam(buffersAddress, i, memAddress(buffer));
-			pointerParam(lengthsAddress, i, buffer.remaining());
-		}
-
-		return buffersAddress;
-	}
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-	/** Frees {@code length} memory blocks stored in the APIBuffer, starting at the specified {@code offset}. */
-	public void pointerArrayFree(int offset, int length) {
-		for ( int i = 0; i < length; i++ )
-			nmemFree(pointerValue(offset + (i << POINTER_SHIFT)));
-	}
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-	/** Sets an int value at the specified index of the int buffer that starts at the specified offset. */
-	public void intParam(int offset, int index, int value) {
-		buffer.putInt(offset + (index << 2), value);
-	}
-
-	/** Sets a pointer value at the specified index of the pointer buffer that starts at the specified offset. */
-	public void pointerParam(int offset, int index, long value) {
-		PointerBuffer.put(buffer, offset + (index << POINTER_SHIFT), value);
-	}
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-        /** Ensures space for the specified string encoded in ASCII, encodes the string at the allocated offset and returns that offset. */
-	public int stringParamASCII(CharSequence value, boolean nullTerminated) {
-		if ( value == null )
-			return -1;
-
-		int offset = bufferParam(value.length() + (nullTerminated ? 1 : 0));
-		MemoryUtil.memASCII(value, nullTerminated, buffer, offset);
-		return offset;
-	}
-
-	/** Ensures space for the specified string encoded in UTF-8, encodes the string at the allocated offset and returns that offset. */
-	public int stringParamUTF8(CharSequence value, boolean nullTerminated) {
-		if ( value == null )
-			return -1;
-
-		int encodedLen = MemoryUtil.memLengthUTF8(value, nullTerminated);
-		int offset = bufferParam(encodedLen);
-		MemoryUtil.memUTF8(value, nullTerminated, buffer, offset);
-		return offset;
-	}
-
-	/** Ensures space for the specified string encoded in UTF-16, encodes the string at the allocated offset and returns that offset. */
-	public int stringParamUTF16(CharSequence value, boolean nullTerminated) {
-		if ( value == null )
-			return -1;
-
-		int offset = bufferParam((value.length() + (nullTerminated ? 1 : 0)) << 1);
-		MemoryUtil.memUTF16(value, nullTerminated, buffer, offset);
-		return offset;
-	}
-
-	// ---------------------------------------------------------------------------------------------------------------------
-
-	/** Returns the boolean value at the specified offset. */
-	public boolean booleanValue(int offset) { return buffer.get(offset) != 0; }
-
-	/** Returns the boolean value at the specified offset. */
-	public byte byteValue(int offset) { return buffer.get(offset); }
-
-	/** Returns the short value at the specified offset. */
-	public short shortValue(int offset) { return buffer.getShort(offset); }
-
-	/** Returns the int value at the specified offset. */
-	public int intValue(int offset) { return buffer.getInt(offset); }
-
-	/** Returns the long value at the specified offset. */
-	public long longValue(int offset) { return buffer.getLong(offset); }
-
-	/** Returns the float value at the specified offset. */
-	public float floatValue(int offset) { return buffer.getFloat(offset); }
-
-	/** Returns the double value at the specified offset. */
-	public double doubleValue(int offset) { return buffer.getDouble(offset); }
-
-	/** Returns the pointer value at the specified offset. */
-	public long pointerValue(int offset) { return PointerBuffer.get(buffer, offset); }
-
-        /** Returns the ASCII string value at the specified byte range. */
-	public String stringValueASCII(int offset, int limit) {
-		buffer.position(offset);
-		buffer.limit(limit);
-		try {
-			return MemoryUtil.memASCII(buffer);
-		} finally {
-			buffer.clear();
-		}
-	}
-
-	/** Returns the UTF8 string value at the specified byte range. */
-	public String stringValueUTF8(int offset, int limit) {
-		buffer.position(offset);
-		buffer.limit(limit);
-		try {
-			return MemoryUtil.memUTF8(buffer);
-		} finally {
-			buffer.clear();
-		}
-	}
-
-	/** Returns the UTF16 string value at the specified byte range. */
-	public String stringValueUTF16(int offset, int limit) {
-		buffer.position(offset);
-		buffer.limit(limit);
-		try {
-			return MemoryUtil.memUTF16(buffer);
-		} finally {
-			buffer.clear();
-		}
-	}
-}
+    private static final int DEFAULT_CAPACITY = 128;
+
+    private ByteBuffer buffer;
+    private long address;
+
+    private int offset;
+
+    private int stackDepth;
+    private int[] stack = new int[4];
+
+    public APIBuffer() {
+        buffer = BufferUtils.createByteBuffer(DEFAULT_CAPACITY);
+        address = memAddress(buffer);
+    }
+
+    /**
+     * Resets the parameter offset to 0.
+     */
+    public APIBuffer reset() {
+        offset = 0;
+        return this;
+    }
+
+    /**
+     * Pushes the current parameter offset to a stack.
+     */
+    public APIBuffer push() {
+        if (stackDepth == stack.length) {
+            stack = Arrays.copyOf(stack, stack.length << 1);
+        }
+
+        stack[stackDepth++] = offset;
+
+        // Upward align the current offset to the pointer size.
+        offset = (offset + (POINTER_SIZE - 1)) & -POINTER_SIZE;
+
+        return this;
+    }
+
+    /**
+     * Restores the last pushed parameter offset.
+     */
+    public APIBuffer pop() {
+        offset = stack[--stackDepth];
+        return this;
+    }
+
+    /**
+     * Returns the current parameter offset.
+     */
+    public int getOffset() {
+        return offset;
+    }
+
+    /**
+     * Sets the current parameter offset.
+     */
+    public void setOffset(int offset) {
+        this.offset = offset;
+    }
+
+    /**
+     * Returns the memory address of the internal {@link ByteBuffer}. This
+     * address may change after a call to one of the {@code <type>Param()}
+     * methods.
+     */
+    public long address() {
+        return address;
+    }
+
+    /**
+     * Returns the memory address of the specified {@code offset}. This address
+     * may change after a call to one of the {@code <type>Param()} methods.
+     */
+    public long address(int offset) {
+        return address + offset;
+    }
+
+    /**
+     * Returns the memory address of the specified {@code offset} or
+     * {@link MemoryUtil#NULL NULL} if the specified {@code value} is null. This
+     * address may change after a call to one of the {@code <type>Param()}
+     * methods.
+     */
+    public long addressSafe(Object value, int offset) {
+        return value == null ? NULL : address(offset);
+    }
+
+    /**
+     * Returns the {@link ByteBuffer} that backs this {@link APIBuffer}.
+     */
+    public ByteBuffer buffer() {
+        return buffer;
+    }
+
+    private void ensureCapacity(int capacity) {
+        if (capacity <= buffer.capacity()) {
+            return;
+        }
+
+        ByteBuffer resized = BufferUtils.createByteBuffer(mathRoundPoT(capacity));
+
+        resized.put(buffer);
+        resized.clear();
+
+        buffer = resized;
+        address = memAddress(resized);
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    private int param(int bytes) {
+        return param(bytes, bytes);
+    }
+
+    private int param(int bytes, int alignment) {
+        // Upward align the current offset to the specified alignment
+        int param = (offset + (alignment - 1)) & -alignment;
+        ensureCapacity(offset = param + bytes);
+        return param;
+    }
+
+    /**
+     * Ensures space for an additional boolean value and returns the address
+     * offset.
+     */
+    public int booleanParam() {
+        return param(1);
+    }
+
+    /**
+     * Ensures space for an additional byte value and returns the address
+     * offset.
+     */
+    public int byteParam() {
+        return param(1);
+    }
+
+    /**
+     * Ensures space for an additional short value and returns the address
+     * offset.
+     */
+    public int shortParam() {
+        return param(2);
+    }
+
+    /**
+     * Ensures space for an additional int value and returns the address offset.
+     */
+    public int intParam() {
+        return param(4);
+    }
+
+    /**
+     * Ensures space for an additional long value and returns the address
+     * offset.
+     */
+    public int longParam() {
+        return param(8);
+    }
+
+    /**
+     * Ensures space for an additional float value and returns the address
+     * offset.
+     */
+    public int floatParam() {
+        return param(4);
+    }
+
+    /**
+     * Ensures space for an additional double value and returns the address
+     * offset.
+     */
+    public int doubleParam() {
+        return param(8);
+    }
+
+    /**
+     * Ensures space for an additional pointer value and returns the address
+     * offset.
+     */
+    public int pointerParam() {
+        return param(POINTER_SIZE);
+    }
+
+    /**
+     * Ensures space for an additional buffer with the specified size (in bytes)
+     * and returns the address offset.
+     */
+    public int bufferParam(int size) {
+        return param(size, POINTER_SIZE);
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    /**
+     * Ensures space for an additional boolean value, sets the specified value
+     * at the allocated offset and returns that offset.
+     */
+    public int booleanParam(boolean value) {
+        int offset = booleanParam();
+        buffer.put(offset, value ? (byte) 1 : (byte) 0);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional byte value, sets the specified value at
+     * the allocated offset and returns that offset.
+     */
+    public int byteParam(byte value) {
+        int offset = byteParam();
+        buffer.put(offset, value);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional short value, sets the specified value at
+     * the allocated offset and returns that offset.
+     */
+    public int shortParam(short value) {
+        int offset = shortParam();
+        buffer.putShort(offset, value);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional int value, sets the specified value at
+     * the allocated offset and returns that offset.
+     */
+    public int intParam(int value) {
+        int offset = intParam();
+        buffer.putInt(offset, value);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional long value, sets the specified value at
+     * the allocated offset and returns that offset.
+     */
+    public int longParam(long value) {
+        int offset = longParam();
+        buffer.putLong(offset, value);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional float value, sets the specified value at
+     * the allocated offset and returns that offset.
+     */
+    public int floatParam(float value) {
+        int offset = floatParam();
+        buffer.putFloat(offset, value);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional double value, sets the specified value at
+     * the allocated offset and returns that offset.
+     */
+    public int doubleParam(double value) {
+        int offset = doubleParam();
+        buffer.putDouble(offset, value);
+        return offset;
+    }
+
+    /**
+     * Ensures space for an additional pointer value, sets the specified value
+     * at the allocated offset and returns that offset.
+     */
+    public int pointerParam(long value) {
+        int offset = pointerParam();
+        PointerBuffer.put(buffer, offset, value);
+        return offset;
+    }
+    // ----
+
+    /**
+     * Ensures space for an additional pointer buffer, sets the specified memory
+     * addresses and returns the address offset.
+     */
+    public int pointerArrayParam(long... pointers) {
+        int buffersAddress = bufferParam(pointers.length << POINTER_SHIFT);
+        for (int i = 0; i < pointers.length; i++) {
+            pointerParam(buffersAddress, i, pointers[i]);
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * Ensures space for an additional pointer buffer, sets the memory addresses
+     * of the specified buffers and returns the address offset.
+     */
+    public int pointerArrayParam(ByteBuffer... buffers) {
+        int buffersAddress = bufferParam(buffers.length << POINTER_SHIFT);
+        for (int i = 0; i < buffers.length; i++) {
+            pointerParam(buffersAddress, i, memAddress(buffers[i]));
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * Ensures space for two additional pointer buffers, sets the memory
+     * addresses and remaining bytes of the specified buffers and returns the
+     * address offset.
+     */
+    public int pointerArrayParamp(ByteBuffer... buffers) {
+        int buffersAddress = pointerArrayParam(buffers);
+
+        int buffersLengths = bufferParam(buffers.length << POINTER_SHIFT);
+        for (int i = 0; i < buffers.length; i++) {
+            pointerParam(buffersLengths, i, buffers[i].remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    /**
+     * ASCII encodes the specified strings with a null-terminator and ensures
+     * space for a buffer filled with the memory addresses of the encoded
+     * strings.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the memory address buffer
+     */
+    public int pointerArrayParamASCII(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memASCII(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * ASCII encodes the specified strings and ensures space for two additional
+     * buffers filled with the lengths and memory addresses of the encoded
+     * strings, respectively. The lengths are 4-bytes integers and the memory
+     * address buffer starts immediately after the lengths buffer.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the lengths buffer
+     */
+    public int pointerArrayParamASCIIi(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        int lengthsAddress = bufferParam(strings.length << 2);
+
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memASCII(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+            intParam(lengthsAddress, i, buffer.remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * ASCII encodes the specified strings and ensures space for two additional
+     * buffers filled with the lengths and memory addresses of the encoded
+     * strings, respectively. The lengths are pointer-sized integers and the
+     * memory address buffer starts immediately after the lengths buffer.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the lengths buffer
+     */
+    public int pointerArrayParamASCIIp(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        int lengthsAddress = bufferParam(strings.length << POINTER_SHIFT);
+
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memASCII(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+            pointerParam(lengthsAddress, i, buffer.remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * UTF8 encodes the specified strings with a null-terminator and ensures
+     * space for a buffer filled with the memory addresses of the encoded
+     * strings.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the memory address buffer
+     */
+    public int pointerArrayParamUTF8(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memUTF8(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * UTF8 encodes the specified strings and ensures space for two additional
+     * buffers filled with the lengths and memory addresses of the encoded
+     * strings, respectively. The lengths are 4-bytes integers and the memory
+     * address buffer starts immediately after the lengths buffer.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the lengths buffer
+     */
+    public int pointerArrayParamUTF8i(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        int lengthsAddress = bufferParam(strings.length << 2);
+
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memUTF8(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+            intParam(lengthsAddress, i, buffer.remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * UTF8 encodes the specified strings and ensures space for two additional
+     * buffers filled with the lengths and memory addresses of the encoded
+     * strings, respectively. The lengths are pointer-sized integers and the
+     * memory address buffer starts immediately after the lengths buffer.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the lengths buffer
+     */
+    public int pointerArrayParamUTF8p(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        int lengthsAddress = bufferParam(strings.length << POINTER_SHIFT);
+
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memUTF8(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+            pointerParam(lengthsAddress, i, buffer.remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * UTF16 encodes the specified strings with a null-terminator and ensures
+     * space for a buffer filled with the memory addresses of the encoded
+     * strings.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the memory address buffer
+     */
+    public int pointerArrayParamUTF16(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memUTF16(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * UTF16 encodes the specified strings and ensures space for two additional
+     * buffers filled with the lengths and memory addresses of the encoded
+     * strings, respectively. The lengths are 4-bytes integers and the memory
+     * address buffer starts immediately after the lengths buffer.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the lengths buffer
+     */
+    public int pointerArrayParamUTF16i(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        int lengthsAddress = bufferParam(strings.length << 2);
+
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memUTF16(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+            intParam(lengthsAddress, i, buffer.remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    /**
+     * UTF16 encodes the specified strings and ensures space for two additional
+     * buffers filled with the lengths and memory addresses of the encoded
+     * strings, respectively. The lengths are pointer-sized integers and the
+     * memory address buffer starts immediately after the lengths buffer.
+     *
+     * <p>
+     * The encoded buffers must be later freed with
+     * {@link #pointerArrayFree(int, int)}.</p>
+     *
+     * @return the offset to the lengths buffer
+     */
+    public int pointerArrayParamUTF16p(CharSequence... strings) {
+        int buffersAddress = bufferParam(strings.length << POINTER_SHIFT);
+        int lengthsAddress = bufferParam(strings.length << POINTER_SHIFT);
+
+        for (int i = 0; i < strings.length; i++) {
+            ByteBuffer buffer = MemoryUtil.memUTF16(strings[i]);
+
+            pointerParam(buffersAddress, i, memAddress(buffer));
+            pointerParam(lengthsAddress, i, buffer.remaining());
+        }
+
+        return buffersAddress;
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    /**
+     * Frees {@code length} memory blocks stored in the APIBuffer, starting at
+     * the specified {@code offset}.
+     */
+    public void pointerArrayFree(int offset, int length) {
+        for (int i = 0; i < length; i++) {
+            nmemFree(pointerValue(offset + (i << POINTER_SHIFT)));
+        }
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    /**
+     * Sets an int value at the specified index of the int buffer that starts at
+     * the specified offset.
+     */
+    public void intParam(int offset, int index, int value) {
+        buffer.putInt(offset + (index << 2), value);
+    }
+
+    /**
+     * Sets a pointer value at the specified index of the pointer buffer that
+     * starts at the specified offset.
+     */
+    public void pointerParam(int offset, int index, long value) {
+        PointerBuffer.put(buffer, offset + (index << POINTER_SHIFT), value);
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    /**
+     * Ensures space for the specified string encoded in ASCII, encodes the
+     * string at the allocated offset and returns that offset.
+     */
+    public int stringParamASCII(CharSequence value, boolean nullTerminated) {
+        if (value == null) {
+            return -1;
+        }
+
+        int offset = bufferParam(value.length() + (nullTerminated ? 1 : 0));
+        MemoryUtil.memASCII(value, nullTerminated, buffer, offset);
+        return offset;
+    }
+
+    /**
+     * Ensures space for the specified string encoded in UTF-8, encodes the
+     * string at the allocated offset and returns that offset.
+     */
+    public int stringParamUTF8(CharSequence value, boolean nullTerminated) {
+        if (value == null) {
+            return -1;
+        }
+
+        int encodedLen = MemoryUtil.memLengthUTF8(value, nullTerminated);
+        int offset = bufferParam(encodedLen);
+        MemoryUtil.memUTF8(value, nullTerminated, buffer, offset);
+        return offset;
+    }
+
+    /**
+     * Ensures space for the specified string encoded in UTF-16, encodes the
+     * string at the allocated offset and returns that offset.
+     */
+    public int stringParamUTF16(CharSequence value, boolean nullTerminated) {
+        if (value == null) {
+            return -1;
+        }
+
+        int offset = bufferParam((value.length() + (nullTerminated ? 1 : 0)) << 1);
+        MemoryUtil.memUTF16(value, nullTerminated, buffer, offset);
+        return offset;
+    }
+
+    // ---------------------------------------------------------------------------------------------------------------------
+    /**
+     * Returns the boolean value at the specified offset.
+     */
+    public boolean booleanValue(int offset) {
+        return buffer.get(offset) != 0;
+    }
+
+    /**
+     * Returns the boolean value at the specified offset.
+     */
+    public byte byteValue(int offset) {
+        return buffer.get(offset);
+    }
+
+    /**
+     * Returns the short value at the specified offset.
+     */
+    public short shortValue(int offset) {
+        return buffer.getShort(offset);
+    }
+
+    /**
+     * Returns the int value at the specified offset.
+     */
+    public int intValue(int offset) {
+        return buffer.getInt(offset);
+    }
+
+    /**
+     * Returns the long value at the specified offset.
+     */
+    public long longValue(int offset) {
+        return buffer.getLong(offset);
+    }
+
+    /**
+     * Returns the float value at the specified offset.
+     */
+    public float floatValue(int offset) {
+        return buffer.getFloat(offset);
+    }
+
+    /**
+     * Returns the double value at the specified offset.
+     */
+    public double doubleValue(int offset) {
+        return buffer.getDouble(offset);
+    }
+
+    /**
+     * Returns the pointer value at the specified offset.
+     */
+    public long pointerValue(int offset) {
+        return PointerBuffer.get(buffer, offset);
+    }
+
+    /**
+     * Returns the ASCII string value at the specified byte range.
+     */
+    public String stringValueASCII(int offset, int limit) {
+        buffer.position(offset);
+        buffer.limit(limit);
+        try {
+            return MemoryUtil.memASCII(buffer);
+        } finally {
+            buffer.clear();
+        }
+    }
+
+    /**
+     * Returns the UTF8 string value at the specified byte range.
+     */
+    public String stringValueUTF8(int offset, int limit) {
+        buffer.position(offset);
+        buffer.limit(limit);
+        try {
+            return MemoryUtil.memUTF8(buffer);
+        } finally {
+            buffer.clear();
+        }
+    }
+
+    /**
+     * Returns the UTF16 string value at the specified byte range.
+     */
+    public String stringValueUTF16(int offset, int limit) {
+        buffer.position(offset);
+        buffer.limit(limit);
+        try {
+            return MemoryUtil.memUTF16(buffer);
+        } finally {
+            buffer.clear();
+        }
+    }
+}

+ 199 - 173
jme3-lwjgl3/src/main/java/com/jme3/lwjgl3/utils/APIUtil.java

@@ -4,12 +4,6 @@
  */
 package com.jme3.lwjgl3.utils;
 
-import com.jme3.lwjgl3.utils.APIBuffer;
-import org.lwjgl.system.linux.LinuxLibrary;
-import org.lwjgl.system.macosx.MacOSXLibrary;
-import org.lwjgl.system.windows.WindowsLibrary;
-
-import java.io.PrintStream;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.HashMap;
@@ -20,173 +14,205 @@ import java.util.regex.Pattern;
 /**
  * Utility class useful to API bindings. [INTERNAL USE ONLY]
  *
- * <p>Method names in this class are prefixed with {@code api} to avoid ambiguities when used with static imports.</p>
+ * <p>
+ * Method names in this class are prefixed with {@code api} to avoid ambiguities
+ * when used with static imports.</p>
  */
 public final class APIUtil {
 
-	private static final ThreadLocal<APIBuffer> API_BUFFERS = new ThreadLocal<APIBuffer>() {
-		@Override
-		protected APIBuffer initialValue() {
-			return new APIBuffer();
-		}
-	};
-
-	private APIUtil() {
-	}
-
-
-	/** Returns a thread-local {@link APIBuffer} that has been reset. */
-	public static APIBuffer apiBuffer() {
-		return API_BUFFERS.get().reset();
-	}
-
-	/**
-	 * Returns a thread-local {@link APIBuffer}, without resetting it. This makes the APIBuffer work like a stack when used in nested API calls. The user is
-	 * responsible for resetting the {@link APIBuffer} to an appropriate state before the nested call returns.
-	 *
-	 * @see APIBuffer#pop
-	 */
-	public static APIBuffer apiStack() {
-		return API_BUFFERS.get().push();
-	}
-
-	/** A data class for API versioning information. */
-	public static class APIVersion {
-
-		/** Returns the API major version. */
-		public final int major;
-		/** Returns the API minor version. */
-		public final int minor;
-
-		/** Returns the API revision. May be null. */
-		public final String revision;
-		/** Returns the API implementation-specific versioning information. May be null. */
-		public final String implementation;
-
-		public APIVersion(int major, int minor) {
-			this(major, minor, null, null);
-		}
-
-		public APIVersion(int major, int minor, String revision, String implementation) {
-			this.major = major;
-			this.minor = minor;
-			this.revision = revision;
-			this.implementation = implementation;
-		}
-
-	}
-
-	/**
-	 * Parses a version string. The version string must have the format {@code MAJOR.MINOR.REVISION IMPL}, where {@code MAJOR} is the major version (integer),
-	 * {@code MINOR} is the minor version (integer), {@code REVISION} is the revision version (string, optional) and {@code IMPL} is implementation-specific
-	 * information (string, optional).
-	 *
-	 * @param version the API version string
-	 *
-	 * @return the parsed {@link APIVersion}
-	 */
-	public static APIVersion apiParseVersion(String version) {
-		return apiParseVersion(version, null);
-	}
-
-	/**
-	 * Parses a version string. The version string must have the format {@code PREFIX MAJOR.MINOR.REVISION IMPL}, where {@code PREFIX} is the specified prefix
-	 * (string, optional), {@code MAJOR} is the major version (integer), {@code MINOR} is the minor version (integer), {@code REVISION} is the revision version
-	 * (string, optional) and {@code IMPL} is implementation-specific information (string, optional).
-	 *
-	 * @param version the version string
-	 * @param prefix  the version string prefix, may be null
-	 *
-	 * @return the parsed {@link APIVersion}
-	 */
-	public static APIVersion apiParseVersion(String version, String prefix) {
-		String pattern = "([0-9]+)[.]([0-9]+)([.]\\S+)?\\s*(.+)?";
-		if ( prefix != null )
-			pattern = prefix + "\\s+" + pattern;
-
-		Matcher matcher = Pattern.compile(pattern).matcher(version);
-		if ( !matcher.matches() )
-			throw new IllegalArgumentException(String.format("Malformed API version string [%s]", version));
-
-		return new APIVersion(
-			Integer.parseInt(matcher.group(1)),
-			Integer.parseInt(matcher.group(2)),
-			matcher.group(3),
-			matcher.group(4)
-		);
-	}
-
-	public static String apiUnknownToken(int token) {
-		return apiUnknownToken("Unknown", token);
-	}
-
-	public static String apiUnknownToken(String description, int token) {
-		return String.format("%s [0x%X]", description, token);
-	}
-
-	/**
-	 * Returns a map of public static final integer fields in the specified classes, to their String representations. An optional filter can be specified to
-	 * only include specific fields. The target map may be null, in which case a new map is allocated and returned.
-	 *
-	 * <p>This method is useful when debugging to quickly identify values returned from an API.</p>
-	 *
-	 * @param filter       the filter to use (optional)
-	 * @param target       the target map (optional)
-	 * @param tokenClasses the classes to get tokens from
-	 *
-	 * @return the token map
-	 */
-	public static Map<Integer, String> apiClassTokens(TokenFilter filter, Map<Integer, String> target, Class<?>... tokenClasses) {
-		if ( target == null )
-			target = new HashMap<Integer, String>(64);
-
-		int TOKEN_MODIFIERS = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
-
-		for ( Class<?> tokenClass : tokenClasses ) {
-			if ( tokenClass == null )
-				continue;
-
-			for ( Field field : tokenClass.getDeclaredFields() ) {
-				// Get only <public static final int> fields.
-				if ( (field.getModifiers() & TOKEN_MODIFIERS) == TOKEN_MODIFIERS && field.getType() == int.class ) {
-					try {
-						int value = field.getInt(null);
-						if ( filter != null && !filter.accept(field, value) )
-							continue;
-
-						String name = target.get(value);
-						target.put(value, name == null ? field.getName() : name + "|" + field.getName());
-					} catch (IllegalAccessException e) {
-						// Ignore
-					}
-				}
-			}
-		}
-
-		return target;
-	}
-
-	public static Class<?> apiOptionalClass(String className) {
-		try {
-			return Class.forName(className);
-		} catch (ClassNotFoundException e) {
-			return null;
-		}
-	}
-
-	/** Simple interface for Field filtering. */
-	public interface TokenFilter {
-
-		/**
-		 * Should return true if the specified Field passes the filter.
-		 *
-		 * @param field the Field to test
-		 * @param value the integer value of the field
-		 *
-		 * @return true if the Field is accepted
-		 */
-		boolean accept(Field field, int value);
-
-	}
-
-}
+    private static final ThreadLocal<APIBuffer> API_BUFFERS = new ThreadLocal<APIBuffer>() {
+        @Override
+        protected APIBuffer initialValue() {
+            return new APIBuffer();
+        }
+    };
+
+    private APIUtil() {
+    }
+
+    /**
+     * Returns a thread-local {@link APIBuffer} that has been reset.
+     */
+    public static APIBuffer apiBuffer() {
+        return API_BUFFERS.get().reset();
+    }
+
+    /**
+     * Returns a thread-local {@link APIBuffer}, without resetting it. This
+     * makes the APIBuffer work like a stack when used in nested API calls. The
+     * user is responsible for resetting the {@link APIBuffer} to an appropriate
+     * state before the nested call returns.
+     *
+     * @see APIBuffer#pop
+     */
+    public static APIBuffer apiStack() {
+        return API_BUFFERS.get().push();
+    }
+
+    /**
+     * A data class for API versioning information.
+     */
+    public static class APIVersion {
+
+        /**
+         * Returns the API major version.
+         */
+        public final int major;
+        /**
+         * Returns the API minor version.
+         */
+        public final int minor;
+
+        /**
+         * Returns the API revision. May be null.
+         */
+        public final String revision;
+        /**
+         * Returns the API implementation-specific versioning information. May
+         * be null.
+         */
+        public final String implementation;
+
+        public APIVersion(int major, int minor) {
+            this(major, minor, null, null);
+        }
+
+        public APIVersion(int major, int minor, String revision, String implementation) {
+            this.major = major;
+            this.minor = minor;
+            this.revision = revision;
+            this.implementation = implementation;
+        }
+
+    }
+
+    /**
+     * Parses a version string. The version string must have the format
+     * {@code MAJOR.MINOR.REVISION IMPL}, where {@code MAJOR} is the major
+     * version (integer), {@code MINOR} is the minor version (integer),
+     * {@code REVISION} is the revision version (string, optional) and
+     * {@code IMPL} is implementation-specific information (string, optional).
+     *
+     * @param version the API version string
+     *
+     * @return the parsed {@link APIVersion}
+     */
+    public static APIVersion apiParseVersion(String version) {
+        return apiParseVersion(version, null);
+    }
+
+    /**
+     * Parses a version string. The version string must have the format
+     * {@code PREFIX MAJOR.MINOR.REVISION IMPL}, where {@code PREFIX} is the
+     * specified prefix (string, optional), {@code MAJOR} is the major version
+     * (integer), {@code MINOR} is the minor version (integer), {@code REVISION}
+     * is the revision version (string, optional) and {@code IMPL} is
+     * implementation-specific information (string, optional).
+     *
+     * @param version the version string
+     * @param prefix the version string prefix, may be null
+     *
+     * @return the parsed {@link APIVersion}
+     */
+    public static APIVersion apiParseVersion(String version, String prefix) {
+        String pattern = "([0-9]+)[.]([0-9]+)([.]\\S+)?\\s*(.+)?";
+        if (prefix != null) {
+            pattern = prefix + "\\s+" + pattern;
+        }
+
+        Matcher matcher = Pattern.compile(pattern).matcher(version);
+        if (!matcher.matches()) {
+            throw new IllegalArgumentException(String.format("Malformed API version string [%s]", version));
+        }
+
+        return new APIVersion(
+                Integer.parseInt(matcher.group(1)),
+                Integer.parseInt(matcher.group(2)),
+                matcher.group(3),
+                matcher.group(4)
+        );
+    }
+
+    public static String apiUnknownToken(int token) {
+        return apiUnknownToken("Unknown", token);
+    }
+
+    public static String apiUnknownToken(String description, int token) {
+        return String.format("%s [0x%X]", description, token);
+    }
+
+    /**
+     * Returns a map of public static final integer fields in the specified
+     * classes, to their String representations. An optional filter can be
+     * specified to only include specific fields. The target map may be null, in
+     * which case a new map is allocated and returned.
+     *
+     * <p>
+     * This method is useful when debugging to quickly identify values returned
+     * from an API.</p>
+     *
+     * @param filter the filter to use (optional)
+     * @param target the target map (optional)
+     * @param tokenClasses the classes to get tokens from
+     *
+     * @return the token map
+     */
+    public static Map<Integer, String> apiClassTokens(TokenFilter filter, Map<Integer, String> target, Class<?>... tokenClasses) {
+        if (target == null) {
+            target = new HashMap<Integer, String>(64);
+        }
+
+        int TOKEN_MODIFIERS = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL;
+
+        for (Class<?> tokenClass : tokenClasses) {
+            if (tokenClass == null) {
+                continue;
+            }
+
+            for (Field field : tokenClass.getDeclaredFields()) {
+                // Get only <public static final int> fields.
+                if ((field.getModifiers() & TOKEN_MODIFIERS) == TOKEN_MODIFIERS && field.getType() == int.class) {
+                    try {
+                        int value = field.getInt(null);
+                        if (filter != null && !filter.accept(field, value)) {
+                            continue;
+                        }
+
+                        String name = target.get(value);
+                        target.put(value, name == null ? field.getName() : name + "|" + field.getName());
+                    } catch (IllegalAccessException e) {
+                        // Ignore
+                    }
+                }
+            }
+        }
+
+        return target;
+    }
+
+    public static Class<?> apiOptionalClass(String className) {
+        try {
+            return Class.forName(className);
+        } catch (ClassNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Simple interface for Field filtering.
+     */
+    public interface TokenFilter {
+
+        /**
+         * Should return true if the specified Field passes the filter.
+         *
+         * @param field the Field to test
+         * @param value the integer value of the field
+         *
+         * @return true if the Field is accepted
+         */
+        boolean accept(Field field, int value);
+
+    }
+
+}