javolution.io.Struct Maven / Gradle / Ivy
/*
* Javolution - Java(TM) Solution for Real-Time and Embedded Systems
* Copyright (C) 2012 - Javolution (http://javolution.org/)
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software is
* freely granted, provided that this notice is preserved.
*/
package javolution.io;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javolution.context.LocalContext;
import javolution.lang.MathLib;
import javolution.lang.Realtime;
import javolution.text.TextBuilder;
/**
* Equivalent to a C/C++ struct
; this class confers
* interoperability between Java classes and C/C++ struct.
*
* Unlike C/C++
, the storage layout of Java objects is not
* determined by the compiler. The layout of objects in memory is deferred
* to run time and determined by the interpreter (or just-in-time compiler).
* This approach allows for dynamic loading and binding; but also makes
* interfacing with C/C++
code difficult. Hence, this class for
* which the memory layout is defined by the initialization order of the
* {@link Struct}'s {@link Member members} and follows the same wordSize
* rules as C/C++ structs
.
*
* This class (as well as the {@link Union} sub-class) facilitates:
*
* - Memory sharing between Java applications and native libraries.
* - Direct encoding/decoding of streams for which the structure
* is defined by legacy C/C++ code.
* - Serialization/deserialization of Java objects (complete control,
* e.g. no class header)
* - Mapping of Java objects to physical addresses (with JNI).
*
*
* Because of its one-to-one mapping, it is relatively easy to convert C
* header files (e.g. OpenGL bindings) to Java {@link Struct}/{@link Union}
* using simple text macros. Here is an example of C struct:
* enum Gender{MALE, FEMALE};
* struct Date {
* unsigned short year;
* unsigned byte month;
* unsigned byte day;
* };
* struct Student {
* enum Gender gender;
* char name[64];
* struct Date birth;
* float grades[10];
* Student* next;
* };
* and here is the Java equivalent using this class:[code]
* public enum Gender { MALE, FEMALE };
* public static class Date extends Struct {
* public final Unsigned16 year = new Unsigned16();
* public final Unsigned8 month = new Unsigned8();
* public final Unsigned8 day = new Unsigned8();
* }
* public static class Student extends Struct {
* public final Enum32 gender = new Enum32(Gender.values());
* public final UTF8String name = new UTF8String(64);
* public final Date birth = inner(new Date());
* public final Float32[] grades = array(new Float32[10]);
* public final Reference32 next = new Reference32();
* }[/code]
* Struct's members are directly accessible:[code]
* Student student = new Student();
* student.gender.set(Gender.MALE);
* student.name.set("John Doe"); // Null terminated (C compatible)
* int age = 2003 - student.birth.year.get();
* student.grades[2].set(12.5f);
* student = student.next.get();[/code]
*
* Applications can work with the raw {@link #getByteBuffer() bytes}
* directly. The following illustrate how {@link Struct} can be used to
* decode/encode UDP messages directly:[code]
* class UDPMessage extends Struct {
* Unsigned16 xxx = new Unsigned16();
* ...
* }
* public void run() {
* byte[] bytes = new byte[1024];
* DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
* UDPMessage message = new UDPMessage();
* message.setByteBuffer(ByteBuffer.wrap(bytes), 0);
* // packet and message are now two different views of the same data.
* while (isListening) {
* multicastSocket.receive(packet);
* int xxx = message.xxx.get();
* ... // Process message fields directly.
* }
* }[/code]
*
* It is relatively easy to map instances of this class to any physical
* address using
*
* JNI. Here is an example:[code]
* import java.nio.ByteBuffer;
* class Clock extends Struct { // Hardware clock mapped to memory.
* Unsigned16 seconds = new Unsigned16(5); // unsigned short seconds:5
* Unsigned16 minutes = new Unsigned16(5); // unsigned short minutes:5
* Unsigned16 hours = new Unsigned16(4); // unsigned short hours:4
* Clock() {
* setByteBuffer(Clock.nativeBuffer(), 0);
* }
* private static native ByteBuffer nativeBuffer();
* }[/code]
* Below is the nativeBuffer()
implementation
* (Clock.c
):[code]
* #include
* #include "Clock.h" // Generated using javah
* JNIEXPORT jobject JNICALL Java_Clock_nativeBuffer (JNIEnv *env, jclass) {
* return (*env)->NewDirectByteBuffer(env, clock_address, buffer_size)
* }[/code]
*
* Bit-fields are supported (see Clock
example above).
* Bit-fields allocation order is defined by the Struct {@link #byteOrder}
* return value. Leftmost bit to rightmost bit if
* BIG_ENDIAN
and rightmost bit to leftmost bit if
* LITTLE_ENDIAN
(same layout as Microsoft Visual C++).
* C/C++ Bit-fields cannot straddle the storage-unit boundary as defined
* by their base type (padding is inserted at the end of the first bit-field
* and the second bit-field is put into the next storage unit).
* It is possible to avoid bit padding by using the {@link BitField}
* member (or a sub-class). In which case the allocation order is always
* from the leftmost to the rightmost bit (same as BIG_ENDIAN
).
*
*
* Finally, it is possible to change the {@link #setByteBuffer ByteBuffer}
* and/or the Struct {@link #setByteBufferPosition position} in its
* ByteBuffer
to allow for a single {@link Struct} object to
* encode/decode multiple memory mapped instances.
*
* Note: Because Struct/Union are basically wrappers around
* java.nio.ByteBuffer
, tutorials/usages for the
* Java NIO package are directly applicable to Struct/Union.
*
* @author Jean-Marie Dautelle
* @version 5.5.1, April 1, 2010
*/
@SuppressWarnings("unchecked")
@Realtime
public class Struct {
/**
* Configurable holding the maximum wordSize in bytes
* (default 4
). Should be a value greater or equal to 1.
*/
public static final LocalContext.Parameter MAXIMUM_ALIGNMENT = new LocalContext.Parameter() {
@Override
protected Integer getDefault() {
return 4;
}
};
/**
* Holds the outer struct if any.
*/
Struct _outer;
/**
* Holds the byte buffer backing the struct (top struct).
*/
ByteBuffer _byteBuffer;
/**
* Holds the offset of this struct relative to the outer struct or
* to the byte buffer if there is no outer.
*/
int _outerOffset;
/**
* Holds this struct alignment in bytes (largest word size of its members).
*/
int _alignment = 1;
/**
* Holds this struct's length.
*/
int _length;
/**
* Holds the index position during construction.
* This is the index a the first unused byte available.
*/
int _index;
/**
* Holds the word size during construction (for bit fields).
* This is the size of the last word used.
*/
int _wordSize;
/**
* Holds the bits used in the word during construction (for bit fields).
* This is the number of bits used in the last word.
*/
int _bitsUsed;
/**
* Indicates if the index has to be reset for each new field (
* true
only for Union subclasses).
*/
boolean _resetIndex;
/**
* Holds bytes array for Stream I/O when byteBuffer has no intrinsic array.
*/
byte[] _bytes;
/**
* Default constructor.
*/
public Struct() {
_resetIndex = isUnion();
}
/**
* Returns the size in bytes of this struct. The size includes
* tail padding to satisfy the struct word size requirement
* (defined by the largest word size of its {@link Member members}).
*
* @return the C/C++ sizeof(this)
.
*/
public final int size() {
return (_alignment <= 1) ? _length
: ((_length + _alignment - 1) / _alignment) * _alignment;
}
/**
* Returns the outer of this struct or null
if this struct
* is not an inner struct.
*
* @return the outer struct or null
.
*/
public Struct outer() {
return _outer;
}
/**
* Returns the byte buffer for this struct. This method will allocate
* a new direct buffer if none has been set.
*
* Changes to the buffer's content are visible in this struct,
* and vice versa.
* The buffer of an inner struct is the same as its parent struct.
* If no byte buffer has been {@link Struct#setByteBuffer set},
* a direct buffer is allocated with a capacity equals to this
* struct's {@link Struct#size() size}.
*
* @return the current byte buffer or a new direct buffer if none set.
* @see #setByteBuffer
*/
public final ByteBuffer getByteBuffer() {
if (_outer != null) return _outer.getByteBuffer();
return (_byteBuffer != null) ? _byteBuffer : newBuffer();
}
private synchronized ByteBuffer newBuffer() {
if (_byteBuffer != null) return _byteBuffer; // Synchronized check.
ByteBuffer bf = ByteBuffer.allocateDirect(size());
bf.order(byteOrder());
setByteBuffer(bf, 0);
return _byteBuffer;
}
/**
* Sets the current byte buffer for this struct.
* The specified byte buffer can be mapped to memory for direct memory
* access or can wrap a shared byte array for I/O purpose
* (e.g. DatagramPacket
).
* The capacity of the specified byte buffer should be at least the
* {@link Struct#size() size} of this struct plus the offset position.
*
* @param byteBuffer the new byte buffer.
* @param position the position of this struct in the specified byte buffer.
* @return this
* @throws IllegalArgumentException if the specified byteBuffer has a
* different byte order than this struct.
* @throws UnsupportedOperationException if this struct is an inner struct.
* @see #byteOrder()
*/
public final Struct setByteBuffer(ByteBuffer byteBuffer, int position) {
if (byteBuffer.order() != byteOrder()) throw new IllegalArgumentException(
"The byte order of the specified byte buffer"
+ " is different from this struct byte order");
if (_outer != null) throw new UnsupportedOperationException(
"Inner struct byte buffer is inherited from outer");
_byteBuffer = byteBuffer;
_outerOffset = position;
return this;
}
/**
* Sets the byte position of this struct within its byte buffer.
*
* @param position the position of this struct in its byte buffer.
* @return this
* @throws UnsupportedOperationException if this struct is an inner struct.
*/
public final Struct setByteBufferPosition(int position) {
return setByteBuffer(this.getByteBuffer(), position);
}
/**
* Returns the absolute byte position of this struct within its associated
* {@link #getByteBuffer byte buffer}.
*
* @return the absolute position of this struct (can be an inner struct)
* in the byte buffer.
*/
public final int getByteBufferPosition() {
return (_outer != null) ? _outer.getByteBufferPosition() + _outerOffset
: _outerOffset;
}
/**
* Reads this struct from the specified input stream
* (convenience method when using Stream I/O). For better performance,
* use of Block I/O (e.g. java.nio.channels.*
) is recommended.
* This method behaves appropriately when not all of the data is available
* from the input stream. Incomplete data is extremely common when the
* input stream is associated with something like a TCP connection.
* The typical usage pattern in those scenarios is to repeatedly call
* read() until the entire message is received.
*
* @param in the input stream being read from.
* @return the number of bytes read (typically the {@link #size() size}
* of this struct.
* @throws IOException if an I/O error occurs.
*/
public int read(InputStream in) throws IOException {
ByteBuffer buffer = getByteBuffer();
int size = size();
int remaining = size - buffer.position();
if (remaining == 0) remaining = size;// at end so move to beginning
int alreadyRead = size - remaining; // typically 0
if (buffer.hasArray()) {
int offset = buffer.arrayOffset() + getByteBufferPosition();
int bytesRead = in
.read(buffer.array(), offset + alreadyRead, remaining);
buffer.position(getByteBufferPosition() + alreadyRead + bytesRead
- offset);
return bytesRead;
} else {
synchronized (buffer) {
if (_bytes == null) {
_bytes = new byte[size()];
}
int bytesRead = in.read(_bytes, 0, remaining);
buffer.position(getByteBufferPosition() + alreadyRead);
buffer.put(_bytes, 0, bytesRead);
return bytesRead;
}
}
}
/**
* Writes this struct to the specified output stream
* (convenience method when using Stream I/O). For better performance,
* use of Block I/O (e.g. java.nio.channels.*
) is recommended.
*
* @param out the output stream to write to.
* @throws IOException if an I/O error occurs.
*/
public void write(OutputStream out) throws IOException {
ByteBuffer buffer = getByteBuffer();
if (buffer.hasArray()) {
int offset = buffer.arrayOffset() + getByteBufferPosition();
out.write(buffer.array(), offset, size());
} else {
synchronized (buffer) {
if (_bytes == null) {
_bytes = new byte[size()];
}
buffer.position(getByteBufferPosition());
buffer.get(_bytes);
out.write(_bytes);
}
}
}
/**
* Returns this struct address. This method allows for structs
* to be referenced (e.g. pointer) from other structs.
*
* @return the struct memory address.
* @throws UnsupportedOperationException if the struct buffer is not
* a direct buffer.
* @see Reference32
* @see Reference64
*/
public final long address() {
ByteBuffer thisBuffer = this.getByteBuffer();
if (thisBuffer instanceof sun.nio.ch.DirectBuffer)
return ((sun.nio.ch.DirectBuffer)thisBuffer).address();
throw new UnsupportedOperationException();
}
/**
* Returns the String
representation of this struct
* in the form of its constituing bytes (hexadecimal). For example:[code]
* public static class Student extends Struct {
* Utf8String name = new Utf8String(16);
* Unsigned16 year = new Unsigned16();
* Float32 grade = new Float32();
* }
* Student student = new Student();
* student.name.set("John Doe");
* student.year.set(2003);
* student.grade.set(12.5f);
* System.out.println(student);
*
* 4A 6F 68 6E 20 44 6F 65 00 00 00 00 00 00 00 00
* 07 D3 00 00 41 48 00 00[/code]
*
* @return a hexadecimal representation of the bytes content for this
* struct.
*/
public String toString() {
TextBuilder tmp = new TextBuilder();
final int size = size();
final ByteBuffer buffer = getByteBuffer();
final int start = getByteBufferPosition();
for (int i = 0; i < size; i++) {
int b = buffer.get(start + i) & 0xFF;
tmp.append(HEXA[b >> 4]);
tmp.append(HEXA[b & 0xF]);
tmp.append(((i & 0xF) == 0xF) ? '\n' : ' ');
}
return tmp.toString();
}
private static final char[] HEXA = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
///////////////////
// CONFIGURATION //
///////////////////
/**
* Indicates if this struct's members are mapped to the same location
* in memory (default false
). This method is useful for
* applications extending {@link Struct} with new member types in order to
* create unions from these new structs. For example:[code]
* public abstract class FortranStruct extends Struct {
* public class FortranString extends Member {...}
* protected FortranString[] array(FortranString[] array, int stringLength) { ... }
* }
* public abstract class FortranUnion extends FortranStruct {
* // Inherits new members and methods.
* public final isUnion() {
* return true;
* }
* }[/code]
*
* @return true
if this struct's members are mapped to
* to the same location in memory; false
* otherwise.
* @see Union
*/
public boolean isUnion() {
return false;
}
/**
* Returns the byte order for this struct (configurable).
* The byte order is inherited by inner structs. Sub-classes may change
* the byte order by overriding this method. For example:[code]
* public class TopStruct extends Struct {
* ... // Members initialization.
* public ByteOrder byteOrder() {
* // TopStruct and its inner structs use hardware byte order.
* return ByteOrder.nativeOrder();
* }
* }}[/code]
*
* @return the byte order when reading/writing multibyte values
* (default: network byte order, BIG_ENDIAN
).
*/
public ByteOrder byteOrder() {
return (_outer != null) ? _outer.byteOrder() : ByteOrder.BIG_ENDIAN;
}
/**
* Indicates if this struct is packed (configurable).
* By default, {@link Member members} of a struct are aligned on the
* boundary corresponding to the member base type; padding is performed
* if necessary. This directive is not inherited by inner structs.
* Sub-classes may change the packing directive by overriding this method.
* For example:[code]
* public class MyStruct extends Struct {
* ... // Members initialization.
* public boolean isPacked() {
* return true; // MyStruct is packed.
* }
* }}[/code]
*
* @return true
if word size requirements are ignored.
* false
otherwise (default).
*/
public boolean isPacked() {
return false;
}
/**
* Defines the specified struct as inner of this struct.
*
* @param struct the inner struct.
* @return the specified struct.
* @throws IllegalArgumentException if the specified struct is already
* an inner struct.
*/
protected S inner(S struct) {
if (struct._outer != null) throw new IllegalArgumentException(
"struct: Already an inner struct");
Member inner = new Member(struct.size() << 3, struct._alignment); // Update indexes.
struct._outer = this;
struct._outerOffset = inner.offset();
return (S) struct;
}
/**
* Defines the specified array of structs as inner structs.
* The array is populated if necessary using the struct component
* default constructor (which must be public).
*
* @param structs the struct array.
* @return the specified struct array.
* @throws IllegalArgumentException if the specified array contains
* inner structs.
*/
protected S[] array(S[] structs) {
Class> structClass = null;
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
for (int i = 0; i < structs.length;) {
S struct = structs[i];
if (struct == null) {
try {
if (structClass == null) {
String arrayName = structs.getClass().getName();
String structName = arrayName.substring(2,
arrayName.length() - 1);
structClass = Class.forName(structName);
if (structClass == null) { throw new IllegalArgumentException(
"Struct class: " + structName + " not found"); }
}
struct = (S) structClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
structs[i++] = inner(struct);
}
_resetIndex = resetIndexSaved;
return (S[]) structs;
}
/**
* Defines the specified two-dimensional array of structs as inner
* structs. The array is populated if necessary using the struct component
* default constructor (which must be public).
*
* @param structs the two dimensional struct array.
* @return the specified struct array.
* @throws IllegalArgumentException if the specified array contains
* inner structs.
*/
protected S[][] array(S[][] structs) {
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
for (int i = 0; i < structs.length; i++) {
array(structs[i]);
}
_resetIndex = resetIndexSaved;
return (S[][]) structs;
}
/**
* Defines the specified three dimensional array of structs as inner
* structs. The array is populated if necessary using the struct component
* default constructor (which must be public).
*
* @param structs the three dimensional struct array.
* @return the specified struct array.
* @throws IllegalArgumentException if the specified array contains
* inner structs.
*/
protected S[][][] array(S[][][] structs) {
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
for (int i = 0; i < structs.length; i++) {
array(structs[i]);
}
_resetIndex = resetIndexSaved;
return (S[][][]) structs;
}
/**
* Defines the specified array member. For predefined members,
* the array is populated when empty; custom members should use
* literal (populated) arrays.
*
* @param arrayMember the array member.
* @return the specified array member.
* @throws UnsupportedOperationException if the specified array
* is empty and the member type is unknown.
*/
protected M[] array(M[] arrayMember) {
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
if (BOOL.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Bool();
}
} else if (SIGNED_8.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Signed8();
}
} else if (UNSIGNED_8.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Unsigned8();
}
} else if (SIGNED_16.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Signed16();
}
} else if (UNSIGNED_16.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Unsigned16();
}
} else if (SIGNED_32.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Signed32();
}
} else if (UNSIGNED_32.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Unsigned32();
}
} else if (SIGNED_64.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Signed64();
}
} else if (FLOAT_32.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Float32();
}
} else if (FLOAT_64.isInstance(arrayMember)) {
for (int i = 0; i < arrayMember.length;) {
arrayMember[i++] = (M) this.new Float64();
}
} else {
throw new UnsupportedOperationException(
"Cannot create member elements, the arrayMember should "
+ "contain the member instances instead of null");
}
_resetIndex = resetIndexSaved;
return (M[]) arrayMember;
}
private static final Class extends Bool[]> BOOL = new Bool[0].getClass();
private static final Class extends Signed8[]> SIGNED_8 = new Signed8[0]
.getClass();
private static final Class extends Unsigned8[]> UNSIGNED_8 = new Unsigned8[0]
.getClass();
private static final Class extends Signed16[]> SIGNED_16 = new Signed16[0]
.getClass();
private static final Class extends Unsigned16[]> UNSIGNED_16 = new Unsigned16[0]
.getClass();
private static final Class extends Signed32[]> SIGNED_32 = new Signed32[0]
.getClass();
private static final Class extends Unsigned32[]> UNSIGNED_32 = new Unsigned32[0]
.getClass();
private static final Class extends Signed64[]> SIGNED_64 = new Signed64[0]
.getClass();
private static final Class extends Float32[]> FLOAT_32 = new Float32[0]
.getClass();
private static final Class extends Float64[]> FLOAT_64 = new Float64[0]
.getClass();
/**
* Defines the specified two-dimensional array member. For predefined
* members, the array is populated when empty; custom members should use
* literal (populated) arrays.
*
* @param arrayMember the two-dimensional array member.
* @return the specified array member.
* @throws UnsupportedOperationException if the specified array
* is empty and the member type is unknown.
*/
protected M[][] array(M[][] arrayMember) {
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
for (int i = 0; i < arrayMember.length; i++) {
array(arrayMember[i]);
}
_resetIndex = resetIndexSaved;
return (M[][]) arrayMember;
}
/**
* Defines the specified three-dimensional array member. For predefined
* members, the array is populated when empty; custom members should use
* literal (populated) arrays.
*
* @param arrayMember the three-dimensional array member.
* @return the specified array member.
* @throws UnsupportedOperationException if the specified array
* is empty and the member type is unknown.
*/
protected M[][][] array(M[][][] arrayMember) {
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
for (int i = 0; i < arrayMember.length; i++) {
array(arrayMember[i]);
}
_resetIndex = resetIndexSaved;
return (M[][][]) arrayMember;
}
/**
* Defines the specified array of UTF-8 strings, all strings having the
* specified length (convenience method).
*
* @param array the string array.
* @param stringLength the length of the string elements.
* @return the specified string array.
*/
protected UTF8String[] array(UTF8String[] array, int stringLength) {
boolean resetIndexSaved = _resetIndex;
if (_resetIndex) {
_index = 0;
_resetIndex = false; // Ensures the array elements are sequential.
}
for (int i = 0; i < array.length; i++) {
array[i] = new UTF8String(stringLength);
}
_resetIndex = resetIndexSaved;
return array;
}
/**
* Reads the specified bits from this Struct as an long (signed) integer
* value.
*
* @param bitOffset the bit start position in the Struct.
* @param bitSize the number of bits.
* @return the specified bits read as a signed long.
* @throws IllegalArgumentException if
* (bitOffset + bitSize - 1) / 8 >= this.size()
*/
public long readBits(int bitOffset, int bitSize) {
if ((bitOffset + bitSize - 1) >> 3 >= this.size()) throw new IllegalArgumentException(
"Attempt to read outside the Struct");
int offset = bitOffset >> 3;
int bitStart = bitOffset - (offset << 3);
bitStart = (byteOrder() == ByteOrder.BIG_ENDIAN) ? bitStart : 64
- bitSize - bitStart;
int index = getByteBufferPosition() + offset;
long value = readByteBufferLong(index);
value <<= bitStart; // Clears preceding bits.
value >>= (64 - bitSize); // Signed shift.
return value;
}
private long readByteBufferLong(int index) {
ByteBuffer byteBuffer = getByteBuffer();
if (index + 8 < byteBuffer.limit()) return byteBuffer.getLong(index);
// Else possible buffer overflow.
if (byteBuffer.order() == ByteOrder.LITTLE_ENDIAN) {
return (readByte(index, byteBuffer) & 0xff)
+ ((readByte(++index, byteBuffer) & 0xff) << 8)
+ ((readByte(++index, byteBuffer) & 0xff) << 16)
+ ((readByte(++index, byteBuffer) & 0xffL) << 24)
+ ((readByte(++index, byteBuffer) & 0xffL) << 32)
+ ((readByte(++index, byteBuffer) & 0xffL) << 40)
+ ((readByte(++index, byteBuffer) & 0xffL) << 48)
+ ((readByte(++index, byteBuffer) & 0xffL) << 56);
} else {
return (((long) readByte(index, byteBuffer)) << 56)
+ ((readByte(++index, byteBuffer) & 0xffL) << 48)
+ ((readByte(++index, byteBuffer) & 0xffL) << 40)
+ ((readByte(++index, byteBuffer) & 0xffL) << 32)
+ ((readByte(++index, byteBuffer) & 0xffL) << 24)
+ ((readByte(++index, byteBuffer) & 0xff) << 16)
+ ((readByte(++index, byteBuffer) & 0xff) << 8)
+ (readByte(++index, byteBuffer) & 0xffL);
}
}
private static byte readByte(int index, ByteBuffer byteBuffer) {
return (index < byteBuffer.limit()) ? byteBuffer.get(index) : 0;
}
/**
* Writes the specified bits into this Struct.
*
* @param value the bits value as a signed long.
* @param bitOffset the bit start position in the Struct.
* @param bitSize the number of bits.
* @throws IllegalArgumentException if
* (bitOffset + bitSize - 1) / 8 >= this.size()
*/
public void writeBits(long value, int bitOffset, int bitSize) {
if ((bitOffset + bitSize - 1) >> 3 >= this.size()) throw new IllegalArgumentException(
"Attempt to write outside the Struct");
int offset = bitOffset >> 3;
int bitStart = (byteOrder() == ByteOrder.BIG_ENDIAN) ? bitOffset
- (offset << 3) : 64 - bitSize - (bitOffset - (offset << 3));
long mask = -1L;
mask <<= bitStart; // Clears preceding bits
mask >>>= (64 - bitSize); // Unsigned shift.
mask <<= 64 - bitSize - bitStart;
value <<= (64 - bitSize - bitStart);
value &= mask; // Protects against out of range values.
int index = getByteBufferPosition() + offset;
long oldValue = readByteBufferLong(index);
long resetValue = oldValue & (~mask);
long newValue = resetValue | value;
writeByteBufferLong(index, newValue);
}
private void writeByteBufferLong(int index, long value) {
ByteBuffer byteBuffer = getByteBuffer();
if (index + 8 < byteBuffer.limit()) {
byteBuffer.putLong(index, value);
return;
}
// Else possible buffer overflow.
if (byteBuffer.order() == ByteOrder.LITTLE_ENDIAN) {
writeByte(index, byteBuffer, (byte) value);
writeByte(++index, byteBuffer, (byte) (value >> 8));
writeByte(++index, byteBuffer, (byte) (value >> 16));
writeByte(++index, byteBuffer, (byte) (value >> 24));
writeByte(++index, byteBuffer, (byte) (value >> 32));
writeByte(++index, byteBuffer, (byte) (value >> 40));
writeByte(++index, byteBuffer, (byte) (value >> 48));
writeByte(++index, byteBuffer, (byte) (value >> 56));
} else {
writeByte(index, byteBuffer, (byte) (value >> 56));
writeByte(++index, byteBuffer, (byte) (value >> 48));
writeByte(++index, byteBuffer, (byte) (value >> 40));
writeByte(++index, byteBuffer, (byte) (value >> 32));
writeByte(++index, byteBuffer, (byte) (value >> 24));
writeByte(++index, byteBuffer, (byte) (value >> 16));
writeByte(++index, byteBuffer, (byte) (value >> 8));
writeByte(++index, byteBuffer, (byte) value);
}
}
private static void writeByte(int index, ByteBuffer byteBuffer, byte value) {
if (index < byteBuffer.limit()) {
byteBuffer.put(index, value);
}
}
/////////////
// MEMBERS //
/////////////
/**
* This inner class represents the base class for all {@link Struct}
* members. It allows applications to define additional member types.
* For example:[code]
* public class MyStruct extends Struct {
* BitSet bits = new BitSet(256);
* ...
* public BitSet extends Member {
* public BitSet(int nbrBits) {
* super(nbrBits, 0); // Direct bit access.
* }
* public boolean get(int i) { ... }
* public void set(int i, boolean value) { ...}
* }
* }[/code]
*/
protected class Member {
/**
* Holds the relative offset (in bytes) of this member within its struct.
*/
private final int _offset;
/**
* Holds the relative bit offset of this member to its struct offset.
*/
private final int _bitIndex;
/**
* Holds the bit length of this member.
*/
private final int _bitLength;
/**
* Base constructor for custom member types.
*
* The word size can be zero, in which case the {@link #offset}
* of the member does not change, only {@link #bitIndex} is
* incremented.
*
* @param bitLength the number of bits or 0
* to force next member on next word boundary.
* @param wordSize the word size in bytes used when accessing
* this member data or 0
if the data is accessed
* at the bit level.
*/
protected Member(int bitLength, int wordSize) {
_bitLength = bitLength;
// Resets index if union.
if (_resetIndex) {
_index = 0;
}
// Check if we can merge bitfields (always true if no word boundary).
if ((wordSize == 0)
|| ((bitLength != 0) && (wordSize == _wordSize) && ((_bitsUsed + bitLength) <= (wordSize << 3)))) {
_offset = _index - _wordSize;
_bitIndex = _bitsUsed;
_bitsUsed += bitLength;
// Straddling word boundary only possible if (wordSize == 0)
while (_bitsUsed > (_wordSize << 3)) {
_index++;
_wordSize++;
_length = MathLib.max(_length, _index);
}
return; // Bit field merge done.
}
// Check alignment.
if (!isPacked()) {
// Updates struct's alignment constraint, based on largest word size.
if ((_alignment < wordSize)) {
_alignment = wordSize;
}
// Adds padding if misaligned.
int misaligned = _index % wordSize;
if (misaligned != 0) {
_index += wordSize - misaligned;
}
}
// Sets member indices.
_offset = _index;
_bitIndex = 0;
// Update struct indices.
_index += MathLib.max(wordSize, (bitLength + 7) >> 3);
_wordSize = wordSize;
_bitsUsed = bitLength;
_length = MathLib.max(_length, _index);
// size and index may differ because of {@link Union}
}
/**
* Returns the outer {@link Struct struct} container.
*
* @return the outer struct.
*/
public final Struct struct() {
return Struct.this;
}
/**
* Returns the byte offset of this member in its struct.
* Equivalent to C/C++ offsetof(struct(), this)
*
* @return the offset of this member in the Struct.
*/
public final int offset() {
return _offset;
}
/**
* Holds the bit offset of this member (if any).
* The actual position of the bits data depends upon the endianess and
* the word size.
*/
public final int bitIndex() {
return _bitIndex;
}
/**
* Returns the number of bits in this member. Can be zero if this
* member is used to force the next member to the next word boundary.
*
* @return the number of bits in the member.
*/
public final int bitLength() {
return _bitLength;
}
// Returns the member int value.
final int get(int wordSize, int word) {
final int shift = (byteOrder() == ByteOrder.BIG_ENDIAN) ? (wordSize << 3)
- bitIndex() - bitLength()
: bitIndex();
word >>= shift;
int mask = 0xFFFFFFFF >>> (32 - bitLength());
return word & mask;
}
// Sets the member int value.
final int set(int value, int wordSize, int word) {
final int shift = (byteOrder() == ByteOrder.BIG_ENDIAN) ? (wordSize << 3)
- bitIndex() - bitLength()
: bitIndex();
int mask = 0xFFFFFFFF >>> (32 - bitLength());
mask <<= shift;
value <<= shift;
return (word & ~mask) | (value & mask);
}
// Returns the member long value.
final long get(int wordSize, long word) {
final int shift = (byteOrder() == ByteOrder.BIG_ENDIAN) ? (wordSize << 3)
- bitIndex() - bitLength()
: bitIndex();
word >>= shift;
long mask = 0xFFFFFFFFFFFFFFFFL >>> (64 - bitLength());
return word & mask;
}
// Sets the member long value.
final long set(long value, int wordSize, long word) {
final int shift = (byteOrder() == ByteOrder.BIG_ENDIAN) ? (wordSize << 3)
- bitIndex() - bitLength()
: bitIndex();
long mask = 0xFFFFFFFFFFFFFFFFL >>> (64 - bitLength());
mask <<= shift;
value <<= shift;
return (word & ~mask) | (value & mask);
}
}
///////////////////////
// PREDEFINED FIELDS //
///////////////////////
/**
* This class represents a UTF-8 character string, null terminated
* (for C/C++ compatibility)
*/
public class UTF8String extends Member {
private final UTF8ByteBufferWriter _writer = new UTF8ByteBufferWriter();
private final UTF8ByteBufferReader _reader = new UTF8ByteBufferReader();
private final int _length;
public UTF8String(int length) {
super(length << 3, 1);
_length = length; // Takes into account 0 terminator.
}
public void set(String string) {
final ByteBuffer buffer = getByteBuffer();
synchronized (buffer) {
try {
int index = getByteBufferPosition() + offset();
buffer.position(index);
_writer.setOutput(buffer);
if (string.length() < _length) {
_writer.write(string);
_writer.write(0); // Marks end of string.
} else if (string.length() > _length) { // Truncates.
_writer.write(string.substring(0, _length));
} else { // Exact same length.
_writer.write(string);
}
} catch (IOException e) { // Should never happen.
throw new Error(e.getMessage());
} finally {
_writer.reset();
}
}
}
public String get() {
final ByteBuffer buffer = getByteBuffer();
synchronized (buffer) {
TextBuilder tmp = new TextBuilder();
try {
int index = getByteBufferPosition() + offset();
buffer.position(index);
_reader.setInput(buffer);
for (int i = 0; i < _length; i++) {
char c = (char) _reader.read();
if (c == 0) { // Null terminator.
return tmp.toString();
} else {
tmp.append(c);
}
}
return tmp.toString();
} catch (IOException e) { // Should never happen.
throw new Error(e.getMessage());
} finally {
_reader.reset();
}
}
}
public String toString() {
return this.get();
}
}
/**
* This class represents a 8 bits boolean with true
represented
* by 1
and false
represented by 0
.
*/
public class Bool extends Member {
public Bool() {
super(8, 1);
}
public Bool(int nbrOfBits) {
super(nbrOfBits, 1);
}
public boolean get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().get(index);
word = (bitLength() == 8) ? word : get(1, word);
return word != 0;
}
public void set(boolean value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 8) {
getByteBuffer().put(index, (byte) (value ? -1 : 0));
} else {
getByteBuffer().put(
index,
(byte) set(value ? -1 : 0, 1, getByteBuffer()
.get(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 8 bits signed integer.
*/
public class Signed8 extends Member {
public Signed8() {
super(8, 1);
}
public Signed8(int nbrOfBits) {
super(nbrOfBits, 1);
}
public byte get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().get(index);
return (byte) ((bitLength() == 8) ? word : get(1, word));
}
public void set(byte value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 8) {
getByteBuffer().put(index, value);
} else {
getByteBuffer().put(index,
(byte) set(value, 1, getByteBuffer().get(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 8 bits unsigned integer.
*/
public class Unsigned8 extends Member {
public Unsigned8() {
super(8, 1);
}
public Unsigned8(int nbrOfBits) {
super(nbrOfBits, 1);
}
public short get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().get(index);
return (short) (0xFF & ((bitLength() == 8) ? word : get(1, word)));
}
public void set(short value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 8) {
getByteBuffer().put(index, (byte) value);
} else {
getByteBuffer().put(index,
(byte) set(value, 1, getByteBuffer().get(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 16 bits signed integer.
*/
public class Signed16 extends Member {
public Signed16() {
super(16, 2);
}
public Signed16(int nbrOfBits) {
super(nbrOfBits, 2);
}
public short get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getShort(index);
return (short) ((bitLength() == 16) ? word : get(2, word));
}
public void set(short value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 16) {
getByteBuffer().putShort(index, value);
} else {
getByteBuffer().putShort(index,
(short) set(value, 2, getByteBuffer().getShort(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 16 bits unsigned integer.
*/
public class Unsigned16 extends Member {
public Unsigned16() {
super(16, 2);
}
public Unsigned16(int nbrOfBits) {
super(nbrOfBits, 2);
}
public int get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getShort(index);
return 0xFFFF & ((bitLength() == 16) ? word : get(2, word));
}
public void set(int value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 16) {
getByteBuffer().putShort(index, (short) value);
} else {
getByteBuffer().putShort(index,
(short) set(value, 2, getByteBuffer().getShort(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 32 bits signed integer.
*/
public class Signed32 extends Member {
public Signed32() {
super(32, 4);
}
public Signed32(int nbrOfBits) {
super(nbrOfBits, 4);
}
public int get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getInt(index);
return (bitLength() == 32) ? word : get(4, word);
}
public void set(int value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 32) {
getByteBuffer().putInt(index, value);
} else {
getByteBuffer().putInt(index,
set(value, 4, getByteBuffer().getInt(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 32 bits unsigned integer.
*/
public class Unsigned32 extends Member {
public Unsigned32() {
super(32, 4);
}
public Unsigned32(int nbrOfBits) {
super(nbrOfBits, 4);
}
public long get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getInt(index);
return 0xFFFFFFFFL & ((bitLength() == 32) ? word : get(4, word));
}
public void set(long value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 32) {
getByteBuffer().putInt(index, (int) value);
} else {
getByteBuffer().putInt(index,
set((int) value, 4, getByteBuffer().getInt(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 64 bits signed integer.
*/
public class Signed64 extends Member {
public Signed64() {
super(64, 8);
}
public Signed64(int nbrOfBits) {
super(nbrOfBits, 8);
}
public long get() {
final int index = getByteBufferPosition() + offset();
long word = getByteBuffer().getLong(index);
return (bitLength() == 64) ? word : get(8, word);
}
public void set(long value) {
final int index = getByteBufferPosition() + offset();
if (bitLength() == 64) {
getByteBuffer().putLong(index, value);
} else {
getByteBuffer().putLong(index,
set(value, 8, getByteBuffer().getLong(index)));
}
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents an arbitrary size (unsigned) bit field with
* no word size constraint (they can straddle words boundaries).
*/
public class BitField extends Member {
public BitField(int nbrOfBits) {
super(nbrOfBits, 0);
}
public long longValue() {
long signedValue = readBits(bitIndex() + (offset() << 3),
bitLength());
return ~(-1L << bitLength()) & signedValue;
}
public int intValue() {
return (int) longValue();
}
public short shortValue() {
return (short) longValue();
}
public byte byteValue() {
return (byte) longValue();
}
public void set(long value) {
writeBits(value, bitIndex() + (offset() << 3), bitLength());
}
public String toString() {
return String.valueOf(longValue());
}
}
/**
* This class represents a 32 bits float (C/C++/Java float
).
*/
public class Float32 extends Member {
public Float32() {
super(32, 4);
}
public float get() {
final int index = getByteBufferPosition() + offset();
return getByteBuffer().getFloat(index);
}
public void set(float value) {
final int index = getByteBufferPosition() + offset();
getByteBuffer().putFloat(index, value);
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 64 bits float (C/C++/Java double
).
*/
public class Float64 extends Member {
public Float64() {
super(64, 8);
}
public double get() {
final int index = getByteBufferPosition() + offset();
return getByteBuffer().getDouble(index);
}
public void set(double value) {
final int index = getByteBufferPosition() + offset();
getByteBuffer().putDouble(index, value);
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 32 bits reference (C/C++ pointer) to
* a {@link Struct} object (other types may require a {@link Struct}
* wrapper).
* Note: For references which can be externally modified, an application
* may want to check the {@link #isUpToDate up-to-date} status of
* the reference. For out-of-date references, a {@link Struct}
* can be created at the address specified by {@link #value}
* (using JNI) and the reference {@link #set set} accordingly.
*/
public class Reference32 extends Member {
private S _struct;
public Reference32() {
super(32, 4);
}
public void set(S struct) {
final int index = getByteBufferPosition() + offset();
if (struct != null) {
getByteBuffer().putInt(index, (int) struct.address());
} else {
getByteBuffer().putInt(index, 0);
}
_struct = struct;
}
public S get() {
return _struct;
}
public int value() {
final int index = getByteBufferPosition() + offset();
return getByteBuffer().getInt(index);
}
public boolean isUpToDate() {
final int index = getByteBufferPosition() + offset();
if (_struct != null) {
return getByteBuffer().getInt(index) == (int) _struct.address();
} else {
return getByteBuffer().getInt(index) == 0;
}
}
}
/**
* This class represents a 64 bits reference (C/C++ pointer) to
* a {@link Struct} object (other types may require a {@link Struct}
* wrapper).
* Note: For references which can be externally modified, an application
* may want to check the {@link #isUpToDate up-to-date} status of
* the reference. For out-of-date references, a new {@link Struct}
* can be created at the address specified by {@link #value}
* (using JNI) and then {@link #set set} to the reference.
*/
public class Reference64 extends Member {
private S _struct;
public Reference64() {
super(64, 8);
}
public void set(S struct) {
final int index = getByteBufferPosition() + offset();
if (struct != null) {
getByteBuffer().putLong(index, struct.address());
} else if (struct == null) {
getByteBuffer().putLong(index, 0L);
}
_struct = struct;
}
public S get() {
return _struct;
}
public long value() {
final int index = getByteBufferPosition() + offset();
return getByteBuffer().getLong(index);
}
public boolean isUpToDate() {
final int index = getByteBufferPosition() + offset();
if (_struct != null) {
return getByteBuffer().getLong(index) == _struct.address();
} else {
return getByteBuffer().getLong(index) == 0L;
}
}
}
/**
* This class represents a 8 bits {@link Enum}.
*/
public class Enum8> extends Member {
private final T[] _values;
public Enum8(T[] values) {
super(8, 1);
_values = values;
}
public Enum8(T[] values, int nbrOfBits) {
super(nbrOfBits, 1);
_values = values;
}
public T get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().get(index);
return _values[0xFF & get(1, word)];
}
public void set(T e) {
int value = e.ordinal();
if (_values[value] != e) throw new IllegalArgumentException(
"enum: "
+ e
+ ", ordinal value does not reflect enum values position");
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().get(index);
getByteBuffer().put(index, (byte) set(value, 1, word));
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 16 bits {@link Enum}.
*/
public class Enum16> extends Member {
private final T[] _values;
public Enum16(T[] values) {
super(16, 2);
_values = values;
}
public Enum16(T[] values, int nbrOfBits) {
super(nbrOfBits, 2);
_values = values;
}
public T get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getShort(index);
return _values[0xFFFF & get(2, word)];
}
public void set(T e) {
int value = e.ordinal();
if (_values[value] != e) throw new IllegalArgumentException(
"enum: "
+ e
+ ", ordinal value does not reflect enum values position");
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getShort(index);
getByteBuffer().putShort(index, (short) set(value, 2, word));
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 32 bits {@link Enum}.
*/
public class Enum32> extends Member {
private final T[] _values;
public Enum32(T[] values) {
super(32, 4);
_values = values;
}
public Enum32(T[] values, int nbrOfBits) {
super(nbrOfBits, 4);
_values = values;
}
public T get() {
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getInt(index);
return _values[get(4, word)];
}
public void set(T e) {
int value = e.ordinal();
if (_values[value] != e) throw new IllegalArgumentException(
"enum: "
+ e
+ ", ordinal value does not reflect enum values position");
final int index = getByteBufferPosition() + offset();
int word = getByteBuffer().getInt(index);
getByteBuffer().putInt(index, set(value, 4, word));
}
public String toString() {
return String.valueOf(this.get());
}
}
/**
* This class represents a 64 bits {@link Enum}.
*/
public class Enum64> extends Member {
private final T[] _values;
public Enum64(T[] values) {
super(64, 8);
_values = values;
}
public Enum64(T[] values, int nbrOfBits) {
super(nbrOfBits, 8);
_values = values;
}
public T get() {
final int index = getByteBufferPosition() + offset();
long word = getByteBuffer().getLong(index);
return _values[(int) get(8, word)];
}
public void set(T e) {
long value = e.ordinal();
if (_values[(int) value] != e) throw new IllegalArgumentException(
"enum: "
+ e
+ ", ordinal value does not reflect enum values position");
final int index = getByteBufferPosition() + offset();
long word = getByteBuffer().getLong(index);
getByteBuffer().putLong(index, set(value, 8, word));
}
public String toString() {
return String.valueOf(this.get());
}
}
}