All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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: * [code] * 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; * };[/code]

*

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 (if supported by the platform). * This method allows for structs to be referenced (e.g. pointer) * from other structs. * * @return the struct memory address. * @throws UnsupportedOperationException if not supported by the platform. * @see Reference32 * @see Reference64 */ public final long address() { try { Class dbClass = Class.forName("sun.nio.ch.DirectBuffer"); java.lang.reflect.Method address = dbClass.getDeclaredMethod( "address", new Class[0]); return ((Long) address.invoke(this.getByteBuffer(), (Object[]) null)).longValue(); } catch (Throwable error) { error.printStackTrace(); throw new UnsupportedOperationException( "Method Struct.address() not supported on this platform."); } } /** * 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 BOOL = new Bool[0].getClass(); private static final Class SIGNED_8 = new Signed8[0] .getClass(); private static final Class UNSIGNED_8 = new Unsigned8[0] .getClass(); private static final Class SIGNED_16 = new Signed16[0] .getClass(); private static final Class UNSIGNED_16 = new Unsigned16[0] .getClass(); private static final Class SIGNED_32 = new Signed32[0] .getClass(); private static final Class UNSIGNED_32 = new Unsigned32[0] .getClass(); private static final Class SIGNED_64 = new Signed64[0] .getClass(); private static final Class FLOAT_32 = new Float32[0] .getClass(); private static final Class 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()); } } }