/*
* Copyright (C) 2004-2006, C. Ramakrishnan / Illposed Software.
* All rights reserved.
*
* This code is licensed under the BSD 3-Clause license.
* See file LICENSE (or LICENSE.html) for more information.
*/
package com.illposed.osc.utility;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.illposed.osc.OSCBundle;
import com.illposed.osc.OSCMessage;
import com.illposed.osc.OSCPacket;
/**
* Utility class to convert a byte array,
* conforming to the OSC byte stream format,
* into Java objects.
*
* @author Chandrasekhar Ramakrishnan
*/
public class OSCByteArrayToJavaConverter {
private byte[] bytes;
private int bytesLength;
private int streamPosition;
/**
* Creates a helper object for converting from a byte array
* to an {@link OSCPacket} object.
*/
public OSCByteArrayToJavaConverter() {
}
/**
* Converts a byte array into an {@link OSCPacket}
* (either an {@link OSCMessage} or {@link OSCBundle}).
*/
public OSCPacket convert(byte[] byteArray, int bytesLength) {
this.bytes = byteArray;
this.bytesLength = bytesLength;
this.streamPosition = 0;
if (isBundle()) {
return convertBundle();
} else {
return convertMessage();
}
}
/**
* Is my byte array a bundle?
* @return true if it the byte array is a bundle, false o.w.
*/
private boolean isBundle() {
// only need the first 7 to check if it is a bundle
String bytesAsString = new String(bytes, 0, 7);
return bytesAsString.startsWith("#bundle");
}
/**
* Converts the byte array to a bundle.
* Assumes that the byte array is a bundle.
* @return a bundle containing the data specified in the byte stream
*/
private OSCBundle convertBundle() {
// skip the "#bundle " stuff
streamPosition = 8;
Date timestamp = readTimeTag();
OSCBundle bundle = new OSCBundle(timestamp);
OSCByteArrayToJavaConverter myConverter
= new OSCByteArrayToJavaConverter();
while (streamPosition < bytesLength) {
// recursively read through the stream and convert packets you find
int packetLength = ((Integer) readInteger()).intValue();
byte[] packetBytes = new byte[packetLength];
for (int i = 0; i < packetLength; i++) {
packetBytes[i] = bytes[streamPosition++];
}
OSCPacket packet = myConverter.convert(packetBytes, packetLength);
bundle.addPacket(packet);
}
return bundle;
}
/**
* Converts the byte array to a simple message.
* Assumes that the byte array is a message.
* @return a message containing the data specified in the byte stream
*/
private OSCMessage convertMessage() {
OSCMessage message = new OSCMessage();
message.setAddress(readString());
List<Character> types = readTypes();
if (null == types) {
// we are done
return message;
}
moveToFourByteBoundry();
for (int i = 0; i < types.size(); ++i) {
if ('[' == types.get(i).charValue()) {
// we're looking at an array -- read it in
message.addArgument(readArray(types, ++i).toArray());
// then increment i to the end of the array
while (types.get(i).charValue() != ']') {
i++;
}
} else {
message.addArgument(readArgument(types.get(i)));
}
}
return message;
}
/**
* Reads a string from the byte stream.
* @return the next string in the byte stream
*/
private String readString() {
int strLen = lengthOfCurrentString();
char[] stringChars = new char[strLen];
for (int i = 0; i < strLen; i++) {
stringChars[i] = (char) bytes[streamPosition++];
}
moveToFourByteBoundry();
return new String(stringChars);
}
/**
* Reads the types of the arguments from the byte stream.
* @return a char array with the types of the arguments
*/
private List<Character> readTypes() {
// the next byte should be a ','
if (bytes[streamPosition] != 0x2C) {
return null;
}
streamPosition++;
// find out how long the list of types is
int typesLen = lengthOfCurrentString();
if (0 == typesLen) {
return null;
}
// read in the types
List<Character> typesChars = new ArrayList<Character>(typesLen);
for (int i = 0; i < typesLen; i++) {
typesChars.add((char) bytes[streamPosition++]);
}
return typesChars;
}
/**
* Reads an object of the type specified by the type char.
* @param type type of the argument to read
* @return a Java representation of the argument
*/
private Object readArgument(char type) {
switch (type) {
case 'i' :
return readInteger();
case 'h' :
return readBigInteger();
case 'f' :
return readFloat();
case 'd' :
return readDouble();
case 's' :
return readString();
case 'c' :
return readChar();
case 'T' :
return Boolean.TRUE;
case 'F' :
return Boolean.FALSE;
case 't' :
return readTimeTag();
default:
return null;
}
}
/**
* Reads a char from the byte stream.
* @return a {@link Character}
*/
private Object readChar() {
return new Character((char) bytes[streamPosition++]);
}
/**
* Reads a double from the byte stream.
* This just reads a float.
* @return a {@link Double}
*/
private Object readDouble() {
return readFloat();
}
/**
* Reads a float from the byte stream.
* @return a {@link Float}
*/
private Object readFloat() {
byte[] floatBytes = new byte[4];
floatBytes[0] = bytes[streamPosition++];
floatBytes[1] = bytes[streamPosition++];
floatBytes[2] = bytes[streamPosition++];
floatBytes[3] = bytes[streamPosition++];
// int floatBits =
// (floatBytes[0] << 24)
// | (floatBytes[1] << 16)
// | (floatBytes[2] << 8)
// | (floatBytes[3]);
BigInteger floatBits = new BigInteger(floatBytes);
return new Float(Float.intBitsToFloat(floatBits.intValue()));
}
/**
* Reads a Big Integer (64 bit integer) from the byte stream.
* @return a {@link BigInteger}
*/
private Object readBigInteger() {
byte[] longintBytes = new byte[8];
longintBytes[0] = bytes[streamPosition++];
longintBytes[1] = bytes[streamPosition++];
longintBytes[2] = bytes[streamPosition++];
longintBytes[3] = bytes[streamPosition++];
longintBytes[4] = bytes[streamPosition++];
longintBytes[5] = bytes[streamPosition++];
longintBytes[6] = bytes[streamPosition++];
longintBytes[7] = bytes[streamPosition++];
return new BigInteger(longintBytes);
}
/**
* Reads an Integer (32 bit integer) from the byte stream.
* @return an {@link Integer}
*/
private Object readInteger() {
byte[] intBytes = new byte[4];
intBytes[0] = bytes[streamPosition++];
intBytes[1] = bytes[streamPosition++];
intBytes[2] = bytes[streamPosition++];
intBytes[3] = bytes[streamPosition++];
BigInteger intBits = new BigInteger(intBytes);
return new Integer(intBits.intValue());
}
/**
* Reads the time tag and convert it to a Java Date object.
* A timestamp is a 64 bit number representing the time in NTP format.
* The first 32 bits are seconds since 1900, the second 32 bits are
* fractions of a second.
* @return a {@link Date}
*/
private Date readTimeTag() {
byte[] secondBytes = new byte[8];
byte[] fractionBytes = new byte[8];
for (int i = 0; i < 4; i++) {
// clear the higher order 4 bytes
secondBytes[i] = 0; fractionBytes[i] = 0;
}
// while reading in the seconds & fraction, check if
// this timetag has immediate semantics
boolean isImmediate = true;
for (int i = 4; i < 8; i++) {
secondBytes[i] = bytes[streamPosition++];
if (secondBytes[i] > 0) {
isImmediate = false;
}
}
for (int i = 4; i < 8; i++) {
fractionBytes[i] = bytes[streamPosition++];
if (i < 7) {
if (fractionBytes[i] > 0) {
isImmediate = false;
}
} else {
if (fractionBytes[i] > 1) {
isImmediate = false;
}
}
}
if (isImmediate) {
return OSCBundle.TIMESTAMP_IMMEDIATE;
}
BigInteger secsSince1900 = new BigInteger(secondBytes);
long secsSince1970 = secsSince1900.longValue()
- OSCBundle.SECONDS_FROM_1900_TO_1970.longValue();
// no point maintaining times in the distant past
if (secsSince1970 < 0) {
secsSince1970 = 0;
}
long fraction = (new BigInteger(fractionBytes).longValue());
// this line was cribbed from jakarta commons-net's NTP TimeStamp code
fraction = (fraction * 1000) / 0x100000000L;
// I do not know where, but I'm losing 1ms somewhere...
fraction = (fraction > 0) ? fraction + 1 : 0;
long millisecs = (secsSince1970 * 1000) + fraction;
return new Date(millisecs);
}
/**
* Reads an array from the byte stream.
* @param types
* @param pos at which position to start reading
* @return the array that was read
*/
private List<Object> readArray(List<Character> types, int pos) {
int arrayLen = 0;
while (types.get(pos + arrayLen).charValue() != ']') {
arrayLen++;
}
List<Object> array = new ArrayList<Object>(arrayLen);
for (int j = 0; j < arrayLen; j++) {
array.add(readArgument(types.get(pos + j)));
}
return array;
}
/**
* Get the length of the string currently in the byte stream.
*/
private int lengthOfCurrentString() {
int i = 0;
while (bytes[streamPosition + i] != 0) {
i++;
}
return i;
}
/**
* Move to the next byte with an index in the byte array
* which is dividable by four.
*/
private void moveToFourByteBoundry() {
// If i am already at a 4 byte boundry, I need to move to the next one
int mod = streamPosition % 4;
streamPosition += (4 - mod);
}
}