Package com.illposed.osc.utility

Source Code of com.illposed.osc.utility.OSCByteArrayToJavaConverter

/*
* 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);
  }
}
TOP

Related Classes of com.illposed.osc.utility.OSCByteArrayToJavaConverter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.