package org.jgroups.util; import org.jgroups.Global; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.nio.ByteBuffer; /** * Class (similar to (and partly copied from) java.nio.Bits) containing helper methods to encode variables * (e.g. ints, long, List<Address> etc) to memory (byte buffer) or output streams and read variables * from memory or input streams.<p/> * The write methods write a type (e.g. an int or a char) to a buffer ({@link ByteBuffer} or output stream, using * <a href="https://developers.google.com/protocol-buffers/docs/encoding">variable-length encoding</a>. If * there are not enough byte in the buffer to write a type, a {@link java.nio.BufferOverflowException} is thrown. * If the variable cannot be written to the output stream, an IOException is thrown. * <p/> * The read methods read a variable-length encoded type from a buffer or input stream. If there are fewer bytes in * the buffer than needed to read the type, a {@link java.nio.BufferUnderflowException} is thrown. If the read fails, * an IOException is thrown. * <p/> * The size() methods return the number of bytes used to encode the given type with variable-length encoding. * <p/> * There are additional helper methods to write/read custom JGroups types, e.g. address lists, Views etc * <p/> * Note that methods to read/write atomic types (char, int etc) should only be used if variable-length encoding is * desired; otherwise {@link DataOutput#writeInt(int)} or {@link ByteBuffer#putInt(int)} should be used instead. * <p/> * At the time of writing this (Feb 2014), most methods have not yet been implemented. * @author Bela Ban * @author Sanne Grinovero * @since 3.5 */ public final class Bits { private Bits() { throw new InstantiationError( "Must not instantiate this class" ); } // -------------------- char ------------------------ // // No compression of chars as they only use 2 bytes public static char makeChar(byte[] buf, int offset) { return (char) ((buf[offset + 1] & 0xFF) + (buf[offset] << 8)); } public static void writeChar(char c, byte[] buf, int offset) { buf[offset+1]=(byte)c; buf[offset]=(byte)(c >>> 8); } public static char readChar(byte[] buf, int offset) { return makeChar(buf, offset); } // -------------------- short ----------------------- // // No implementations as encoding a char doesn't use much space public static short makeShort(byte a, byte b) { return (short)((a << 8) | (b & 0xff)); } public static short makeShort(byte a) { return (short) (a & 0xff); } public static void writeShort(short s, byte[] buf, int offset) { buf[offset+1]=(byte)s; buf[offset]=(byte)(s >>> 8); } public static short readShort(byte[] buf, int offset) { return (short)((buf[offset+1] & 0xFF) + (buf[offset] << 8)); } // --------------------- int ------------------------ // /** * Writes an int to a ByteBuffer * @param num the int to be written * @param buf the buffer */ public static void writeInt(int num, ByteBuffer buf) { if(num == 0) { buf.put((byte)0); return; } final byte bytes_needed=bytesRequiredFor(num); buf.put(bytes_needed); for(int i=0; i < bytes_needed; i++) buf.put(getByteAt(num, i)); } /** * Writes an int to an output stream * @param num the int to be written * @param out the output stream */ public static void writeInt(int num, DataOutput out) throws IOException { if(num == 0) { out.write(0); return; } final byte bytes_needed=bytesRequiredFor(num); out.write(bytes_needed); for(int i=0; i < bytes_needed; i++) out.write(getByteAt(num, i)); } public static void writeInt(int num, byte[] buf, int offset) { buf[offset+3]=(byte)num; buf[offset+2]=(byte)(num >>> 8); buf[offset+1]=(byte)(num >>> 16); buf[offset]=(byte)(num >>> 24); } public static void writeIntCompressed(int num, byte[] buf, int offset) { if(num == 0) { buf[offset]=0; return; } final byte bytes_needed=bytesRequiredFor(num); buf[offset++]=bytes_needed; for(int i=0; i < bytes_needed; i++) buf[offset++]=getByteAt(num, i); } /** * Reads an int from a buffer. * @param buf the buffer * @return the int read from the buffer */ public static int readInt(ByteBuffer buf) { byte len=buf.get(); if(len == 0) return 0; return makeInt(buf, len); } /** * Reads an int from an input stream * @param in the input stream * @return the int read from the input stream */ public static int readInt(DataInput in) throws IOException { byte len=in.readByte(); if(len == 0) return 0; return makeInt(in, len); } public static int readInt(byte[] buf, int offset) { return ((buf[offset+3] & 0xFF)) + ((buf[offset+2] & 0xFF) << 8) + ((buf[offset+1] & 0xFF) << 16) + ((buf[offset]) << 24); } public static int readIntCompressed(byte[] buf, int offset) { byte len=buf[offset++]; if(len == 0) return 0; return makeInt(buf, offset, len); } /** * Computes the size of a variable-length encoded int * @param num the int * @return the number of bytes needed to variable-length encode num */ public static int size(int num) { return (byte)(num == 0? 1 : bytesRequiredFor(num) +1); } // -------------------- long ------------------------ // /** * Writes a long to a ByteBuffer * @param num the long to be written * @param buf the buffer */ public static void writeLong(long num, ByteBuffer buf) { if(num == 0) { buf.put((byte)0); return; } final byte bytes_needed=bytesRequiredFor(num); buf.put(bytes_needed); for(int i=0; i < bytes_needed; i++) buf.put(getByteAt(num, i)); } /** * Writes a long to out in variable-length encoding. Note that currently variable-length encoding is <em>not</em> * used (a similar mechanism is used); this will be implemented later. * @param num the long * @param out the output stream to write num to * @throws IOException */ public static void writeLong(final long num, final DataOutput out) throws IOException { if(num == 0) { out.write(0); return; } final byte bytes_needed=bytesRequiredFor(num); out.write(bytes_needed); for(int i=0; i < bytes_needed; i++) out.write(getByteAt(num, i)); } public static void writeLong(long num, byte[] buf, int offset) { buf[offset+7]=(byte)num; buf[offset+6]=(byte)(num >>> 8); buf[offset+5]=(byte)(num >>> 16); buf[offset+4]=(byte)(num >>> 24); buf[offset+3]=(byte)(num >>> 32); buf[offset+2]=(byte)(num >>> 40); buf[offset+1]=(byte)(num >>> 48); buf[offset]=(byte)(num >>> 56); } public static void writeLongCompressed(long num, byte[] buf, int offset) { if(num == 0) { buf[offset]=0; return; } final byte bytes_needed=bytesRequiredFor(num); buf[offset++]=bytes_needed; for(int i=0; i < bytes_needed; i++) buf[offset++]=getByteAt(num, i); } /** * Reads a long from a buffer. * @param buf the buffer * @return the long read from the buffer */ public static long readLong(ByteBuffer buf) { byte len=buf.get(); if(len == 0) return 0; return makeLong(buf, len); } /** * Reads a variable-length encoded long from an input stream. Note that currently variable-length encoding is <em>not</em> * used (a similar mechanism is used); this will be implemented later. * @param in the input stream * @return the long read from the input stream * @throws IOException */ public static long readLong(DataInput in) throws IOException { byte len=in.readByte(); if(len == 0) return 0; return makeLong(in, len); } public static long readLong(byte[] buf, int offset) { return ((buf[offset+7] & 0xFFL)) + ((buf[offset+6] & 0xFFL) << 8) + ((buf[offset+5] & 0xFFL) << 16) + ((buf[offset+4] & 0xFFL) << 24) + ((buf[offset+3] & 0xFFL) << 32) + ((buf[offset+2] & 0xFFL) << 40) + ((buf[offset+1] & 0xFFL) << 48) + (((long) buf[offset]) << 56); } public static long readLongCompressed(byte[] buf, int offset) { byte len=buf[offset++]; if(len == 0) return 0; return makeLong(buf, offset, len); } /** * Computes the size of a variable-length encoded long. Note that this is <em>not</em> currently using * variable-length encoding (will be implemented later). * @param num the long * @return the number of bytes needed to variable-length encode num */ public static int size(long num) { return (byte)(num == 0? 1 : bytesRequiredFor(num) +1); } // ------------------ long seq ---------------------- // /** * Writes 2 sequence numbers (seqnos) in compressed format to buf. * The seqnos are non-negative and hr is guaranteed to be >= hd. * <p/> * Once variable-length encoding has been implemented, this method will probably get dropped as we can simply * write the 2 longs individually. * @param hd the highest delivered seqno. Guaranteed to be a positive number * @param hr the highest received seqno. Guaranteed to be a positive number. Greater than or equal to hd * @param buf the buffer to write to */ public static void writeLongSequence(long hd, long hr, ByteBuffer buf) { if(hr < hd) throw new IllegalArgumentException("hr (" + hr + ") has to be >= hd (" + hd + ")"); if(hd == 0 && hr == 0) { buf.put((byte)0); return; } long delta=hr - hd; // encode highest_delivered followed by delta byte bytes_for_hd=bytesRequiredFor(hd), bytes_for_delta=bytesRequiredFor(delta); byte bytes_needed=encodeLength(bytes_for_hd, bytes_for_delta); buf.put(bytes_needed); for(int i=0; i < bytes_for_hd; i++) buf.put(getByteAt(hd, i)); for(int i=0; i < bytes_for_delta; i++) buf.put(getByteAt(delta, i)); } /** * Writes 2 sequence numbers (seqnos) in compressed format to an output stream. * The seqnos are non-negative and hr is guaranteed to be >= hd. * <p/> * Once variable-length encoding has been implemented, this method will probably get dropped as we can simply * write the 2 longs individually. * @param hd the highest delivered seqno. Guaranteed to be a positive number * @param hr the highest received seqno. Guaranteed to be a positive number. Greater than or equal to hd * @param out the output stream to write to */ public static void writeLongSequence(long hd, long hr, DataOutput out) throws IOException { if(hr < hd) throw new IllegalArgumentException("hr (" + hr + ") has to be >= hd (" + hd + ")"); if(hd == 0 && hr == 0) { out.write(0); return; } long delta=hr - hd; // encode highest_delivered followed by delta byte bytes_for_hd=bytesRequiredFor(hd), bytes_for_delta=bytesRequiredFor(delta); byte bytes_needed=encodeLength(bytes_for_hd, bytes_for_delta); out.write(bytes_needed); for(int i=0; i < bytes_for_hd; i++) out.write(getByteAt(hd, i)); for(int i=0; i < bytes_for_delta; i++) out.write(getByteAt(delta, i)); } /** * Reads 2 compressed longs from buf into seqnos * <p/> * Once variable-length encoding has been implemented, this method will probably get dropped as we can simply * read the 2 longs individually. * @param buf the buffer to read from * @param seqnos the array to read the seqnos into, needs to have a length of 2 */ public static void readLongSequence(ByteBuffer buf, long seqnos[]) { byte len=buf.get(); if(len == 0) { seqnos[0]=seqnos[1]=0; return; } byte len1=firstNibble(len), len2=secondNibble(len); seqnos[0]=makeLong(buf, len1); seqnos[1]=makeLong(buf, len2) + seqnos[0]; } /** * Reads 2 compressed longs into an array of 2 longs. * <p/> * Once variable-length encoding has been implemented, this method will probably get dropped as we can simply * read the 2 longs individually. * @param in the input stream to read from * @param seqnos the array to read the seqnos into, needs to have a length of 2 * @param index the index of the first element to be written; the seqnos are written to seqnos[index] and seqnos[index+1] */ public static void readLongSequence(DataInput in, long[] seqnos, int index) throws IOException { byte len=in.readByte(); if(len == 0) { seqnos[index]=seqnos[index+1]=0; return; } byte len1=firstNibble(len), len2=secondNibble(len); seqnos[index]=makeLong(in, len1); seqnos[index+1]=makeLong(in, len2) + seqnos[index]; } public static byte size(long hd, long hr) { if(hd == 0 && hr == 0) return 1; byte num_bytes_for_hd=bytesRequiredFor(hd), num_bytes_for_delta=bytesRequiredFor(hr - hd); return (byte)(num_bytes_for_hd + num_bytes_for_delta + 1); } public static int makeInt(byte[] buf, int offset, int bytes_to_read) { int retval=0; for(int i=0; i < bytes_to_read; i++) { byte b=buf[offset + i]; retval |= ((int)b & 0xff) << (i * 8); } return retval; } public static int makeInt(ByteBuffer buffer, int bytes_to_read) { int retval=0; for(int i=0; i < bytes_to_read; i++) { byte b=buffer.get(); retval |= ((int)b & 0xff) << (i * 8); } return retval; } public static int makeInt(DataInput in, int bytes_to_read) throws IOException { int retval=0; for(int i=0; i < bytes_to_read; i++) { byte b=in.readByte(); retval |= ((int)b & 0xff) << (i * 8); } return retval; } public static long makeLong(byte[] buf, int offset, int bytes_to_read) { long retval=0; for(int i=0; i < bytes_to_read; i++) { byte b=buf[offset + i]; retval |= ((long)b & 0xff) << (i * 8); } return retval; } public static long makeLong(ByteBuffer buffer, int bytes_to_read) { long retval=0; for(int i=0; i < bytes_to_read; i++) { byte b=buffer.get(); retval |= ((long)b & 0xff) << (i * 8); } return retval; } public static long makeLong(DataInput in, int bytes_to_read) throws IOException { long retval=0; for(int i=0; i < bytes_to_read; i++) { byte b=in.readByte(); retval |= ((long)b & 0xff) << (i * 8); } return retval; } // -------------------- float ----------------------- // /** * Writes a float to a ByteBuffer * @param num the float to be written * @param buf the buffer */ public static void writeFloat(float num, ByteBuffer buf) { writeInt(Float.floatToIntBits(num), buf); } /** * Writes a float to an output stream * @param num the float to be written * @param out the output stream */ public static void writeFloat(float num, DataOutput out) throws IOException { writeInt(Float.floatToIntBits(num), out); } public static void writeFloat(float num, byte[] buf, int offset) { writeInt(Float.floatToIntBits(num), buf, offset); } /** * Reads a a float from a buffer. * @param buf the buffer * @return the float read from the buffer */ public static float readFloat(ByteBuffer buf) { return Float.intBitsToFloat(readInt(buf)); } /** * Reads a a float from an input stream. * @param in the input stream * @return the float read from the input stream */ public static float readFloat(DataInput in) throws IOException { return Float.intBitsToFloat(readInt(in)); } public static float readFloat(byte[] buf, int offset) { return Float.intBitsToFloat(readInt(buf, offset)); } /** * Computes the size of a variable-length encoded float * @param num the float * @return the number of bytes needed to variable-length encode num */ public static int size(float num) { return size(Float.floatToIntBits(num)); } // -------------------- double ---------------------- // /** * Writes a double to a ByteBuffer * @param num the double to be written * @param buf the buffer */ public static void writeDouble(double num, ByteBuffer buf) { writeLong(Double.doubleToLongBits(num), buf); } /** * Writes a double to an output stream * @param num the double to be written * @param out the output stream */ public static void writeDouble(double num, DataOutput out) throws IOException { writeLong(Double.doubleToLongBits(num), out); } public static void writeDouble(double num, byte[] buf, int offset) { writeLong(Double.doubleToLongBits(num), buf, offset); } /** * Reads a double from a buffer. * @param buf the buffer * @return the double read from the buffer */ public static double readDouble(ByteBuffer buf) { return Double.longBitsToDouble(readLong(buf)); } /** * Reads a double from an input stream * @param in the input stream * @return the double read from the input stream */ public static double readDouble(DataInput in) throws IOException { return Double.longBitsToDouble(readLong(in)); } public static double readDouble(byte[] buf, int offset) { return Double.longBitsToDouble(readLong(buf, offset)); } /** * Computes the size of a variable-length encoded double * @param num the double * @return the number of bytes needed to variable-length encode num */ public static int size(double num) { return size(Double.doubleToLongBits(num)); } // -------------------- String ---------------------- // /** * Writes a string to buf. The length of the string is written first, followed by the chars (as single-byte values). * Multi-byte values are truncated: only the lower byte of each multi-byte char is written, similar to * {@link DataOutput#writeChars(String)}. * @param s the string * @param buf the buffer */ public static void writeString(String s, ByteBuffer buf) { buf.put((byte)(s != null? 1 : 0)); if(s != null) { byte[] bytes=s.getBytes(); writeInt(bytes.length, buf); buf.put(bytes); } } /** * Writes a string to buf. The length of the string is written first, followed by the chars (as single-byte values). * Multi-byte values are truncated: only the lower byte of each multi-byte char is written, similar to * {@link DataOutput#writeChars(String)}. * @param s the string * @param out the output stream */ public static void writeString(String s, DataOutput out) throws IOException { if(s != null) { out.write(1); out.writeUTF(s); } else out.write(0); } /** * Reads a string from buf. The length is read first, followed by the chars. Each char is a single byte * @param buf the buffer * @return the string read from buf */ public static String readString(ByteBuffer buf) { if(buf.get() == 0) return null; int len=readInt(buf); if(buf.isDirect()) { byte[] bytes=new byte[len]; buf.get(bytes); return new String(bytes); } else { byte[] bytes=buf.array(); return new String(bytes, buf.arrayOffset() + buf.position(), len); } } /** * Reads a string from buf. The length is read first, followed by the chars. Each char is a single byte * @param in the input stream * @return the string read from buf */ public static String readString(DataInput in) throws IOException { int b=in.readByte(); if(b == 1) return in.readUTF(); return null; } /** * Measures the number of bytes required to encode a string, taking multibyte characters into account. Measures * strings written by {@link DataOutput#writeUTF(String)}. * @param str the string * @return the number of bytes required for encoding str */ public static int sizeUTF(String str) { int len=str != null? str.length() : 0, utflen=2; if(len == 0) return utflen; for(int i = 0; i < len; i++) { int c=str.charAt(i); if((c >= 0x0001) && (c <= 0x007F)) utflen++; else if (c > 0x07FF) utflen += 3; else utflen += 2; } return utflen; } public static int size(String str) { if(str == null) return Global.BYTE_SIZE; byte[] bytes=str.getBytes(); return Global.BYTE_SIZE + size(bytes.length) + bytes.length; } // ------------------ AsciiString ------------------- // /** * Writes an AsciiString to buf. The length of the string is written first, followed by the chars (as single-byte values). * @param s the string * @param buf the buffer */ public static void writeAsciiString(AsciiString s, ByteBuffer buf) { short length=(short)(s != null? s.length() : -1); buf.putShort(length); if(s != null) buf.put(s.chars()); } /** * Writes an AsciiString to buf. The length of the string is written first, followed by the chars (as single-byte values). * @param s the string * @param out the output stream */ public static void writeAsciiString(AsciiString s, DataOutput out) throws IOException { short length=(short)(s != null? s.length() : -1); out.writeShort(length); if(s != null) out.write(s.chars()); } /** * Reads an AsciiString from buf. The length is read first, followed by the chars. Each char is a single byte * @param buf the buffer * @return the string read from buf */ public static AsciiString readAsciiString(ByteBuffer buf) { short len=buf.getShort(); if(len < 0) return null; AsciiString retval=new AsciiString(len); buf.get(retval.chars()); return retval; } /** * Reads an AsciiString from buf. The length is read first, followed by the chars. Each char is a single byte * @param in the input stream * @return the string read from buf */ public static AsciiString readAsciiString(DataInput in) throws IOException { short len=in.readShort(); if(len < 0) return null; AsciiString retval=new AsciiString(len); in.readFully(retval.chars()); return retval; } /** * Measures the number of bytes required to encode an AsciiSring. * @param str the string * @return the number of bytes required for encoding str */ public static int size(AsciiString str) { return str == null? Global.SHORT_SIZE : Global.SHORT_SIZE + str.length(); } /** * Encodes the number of bytes needed into a single byte. The first number is encoded in the first nibble (the * first 4 bits), the second number in the second nibble * @param len1 The number of bytes needed to store a long. Must be between 0 and 8 * @param len2 The number of bytes needed to store a long. Must be between 0 and 8 * @return The byte storing the 2 numbers len1 and len2 */ protected static byte encodeLength(byte len1, byte len2) { byte retval=len2; retval |= (len1 << 4); return retval; } /* protected static byte[] decodeLength(byte len) { return new byte[]{(byte)((len & 0xff) >> 4),(byte)(len & ~0xf0)}; // 0xf0 is the first nibble set (11110000) }*/ protected static byte firstNibble(byte len) { return (byte)((len & 0xff) >> 4); // 0xf0 is the first nibble set (11110000) } protected static byte secondNibble(byte len) { return (byte)(len & ~0xf0); // 0xf0 is the first nibble set (11110000) } protected static byte bytesRequiredFor(long number) { if(number >> 56 != 0) return 8; if(number >> 48 != 0) return 7; if(number >> 40 != 0) return 6; if(number >> 32 != 0) return 5; if(number >> 24 != 0) return 4; if(number >> 16 != 0) return 3; if(number >> 8 != 0) return 2; return 1; } protected static byte bytesRequiredFor(int number) { if(number >> 24 != 0) return 4; if(number >> 16 != 0) return 3; if(number >> 8 != 0) return 2; return 1; } static protected byte getByteAt(long num, int index) { return (byte)((num >> (index * 8))); } }