/* * Copyright (C) 2003-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.io.IOException; import java.io.ByteArrayOutputStream; import java.math.BigInteger; import java.util.Collection; /** * OSCJavaToByteArrayConverter is a helper class that translates * from Java types to their byte stream representations according to * the OSC spec. * * The implementation is based on * <a href=" http://www.emergent.de/">Markus Gaelli</a> and * Iannis Zannos' OSC implementation in Squeak. * * This version includes bug fixes and improvements from * Martin Kaltenbrunner and Alex Potsides. * * @author Chandrasekhar Ramakrishnan * @author Martin Kaltenbrunner * @author Alex Potsides */ public class OSCJavaToByteArrayConverter { private ByteArrayOutputStream stream = new ByteArrayOutputStream(); private byte[] intBytes = new byte[4]; private byte[] longintBytes = new byte[8]; public OSCJavaToByteArrayConverter() { } /** * Line up the Big end of the bytes to a 4 byte boundary. * @return byte[] * @param bytes byte[] */ private byte[] alignBigEndToFourByteBoundry(byte[] bytes) { int mod = bytes.length % 4; // if the remainder == 0 then return the bytes otherwise pad the bytes // to lineup correctly if (mod == 0) { return bytes; } int pad = 4 - mod; byte[] newBytes = new byte[pad + bytes.length]; // for (int i = 0; i < pad; i++) // newBytes[i] = 0; // for (int i = 0; i < bytes.length; i++) // newBytes[pad + i] = bytes[i]; System.arraycopy(bytes, 0, newBytes, pad, bytes.length); return newBytes; } /** * Pad the stream to have a size divisible by 4. */ public void appendNullCharToAlignStream() { int mod = stream.size() % 4; int pad = 4 - mod; for (int i = 0; i < pad; i++) { stream.write(0); } } /** * Convert the contents of the output stream to a byte array. * @return the byte array containing the byte stream */ public byte[] toByteArray() { return stream.toByteArray(); } /** * Write bytes into the byte stream. * @param bytes bytes to be written */ public void write(byte[] bytes) { writeUnderHandler(bytes); } /** * Write an integer into the byte stream. * @param i the integer to be written */ public void write(int i) { writeInteger32ToByteArray(i); } /** * Write a float into the byte stream. * @param f floating point number to be written */ public void write(Float f) { writeInteger32ToByteArray(Float.floatToIntBits(f.floatValue())); } /** * @param i the integer to be written */ public void write(Integer i) { writeInteger32ToByteArray(i.intValue()); } /** * @param i the integer to be written */ public void write(BigInteger i) { writeInteger64ToByteArray(i.longValue()); } /** * Write a string into the byte stream. * @param aString the string to be written */ public void write(String aString) { /* XXX to be revised ... int stringLength = aString.length(); // this is a deprecated method -- should use get char and convert // the chars to bytes // aString.getBytes(0, stringLength, stringBytes, 0); aString.getChars(0, stringLength, stringChars, 0); // pad out to align on 4 byte boundry int mod = stringLength % 4; int pad = 4 - mod; for (int i = 0; i < pad; i++) stringChars[stringLength++] = 0; // convert the chars into bytes and write them out for (int i = 0; i < stringLength; i++) { stringBytes[i] = (byte) (stringChars[i] & 0x00FF); } stream.write(stringBytes, 0, stringLength); */ byte[] stringBytes = aString.getBytes(); // pad out to align on 4 byte boundry int mod = aString.length() % 4; int pad = 4 - mod; byte[] newBytes = new byte[pad + stringBytes.length]; System.arraycopy(stringBytes, 0, newBytes, 0, stringBytes.length); try { stream.write(newBytes); } catch (IOException e) { throw new RuntimeException("You're screwed:" + " IOException writing to a ByteArrayOutputStream", e); } } /** * Write a char into the byte stream. * @param c the character to be written */ public void write(char c) { stream.write(c); } /** * Write an object into the byte stream. * @param anObject one of Float, String, Integer, BigInteger, or array of * these. */ public void write(Object anObject) { // Can't do switch on class if (null == anObject) { } else if (anObject instanceof Object[]) { Object[] theArray = (Object[]) anObject; for (int i = 0; i < theArray.length; ++i) { write(theArray[i]); } } else if (anObject instanceof Float) { write((Float) anObject); } else if (anObject instanceof String) { write((String) anObject); } else if (anObject instanceof Integer) { write((Integer) anObject); } else if (anObject instanceof BigInteger) { write((BigInteger) anObject); } } /** * Write the type tag for the type represented by the class * @param c Class of a Java object in the arguments */ public void writeType(Class c) { // A big ol' case statement -- what's polymorphism mean, again? // I really wish I could extend the base classes! // use the appropriate flags to tell SuperCollider what kind of // thing it is looking at if (Integer.class.equals(c)) { stream.write('i'); } else if (java.math.BigInteger.class.equals(c)) { stream.write('h'); } else if (Float.class.equals(c)) { stream.write('f'); } else if (Double.class.equals(c)) { stream.write('d'); } else if (String.class.equals(c)) { stream.write('s'); } else if (Character.class.equals(c)) { stream.write('c'); } } /** * Write the types for an array element in the arguments. * @param array array of base Objects */ public void writeTypesArray(Object[] array) { // A big ol' case statement in a for loop -- what's polymorphism mean, // again? // I really wish I could extend the base classes! for (int i = 0; i < array.length; i++) { if (array[i] == null) { } else if (Boolean.TRUE.equals(array[i])) { // Create a way to deal with Boolean type objects stream.write('T'); } else if (Boolean.FALSE.equals(array[i])) { stream.write('F'); } else { // this is an object -- write the type for the class writeType(array[i].getClass()); } } } /** * Write types for the arguments. * @param types the arguments to an OSCMessage */ public void writeTypes(Collection<Object> types) { // A big ol' case statement in a for loop -- what's polymorphism mean, // again? // I really wish I could extend the base classes! for (Object type : types) { if (null == type) { continue; } // if the array at i is a type of array write a [ // This is used for nested arguments if (type.getClass().isArray()) { stream.write('['); // fill the [] with the SuperCollider types corresponding to // the object (e.g., Object of type String needs -s). writeTypesArray((Object[]) type); // close the array stream.write(']'); continue; } // Create a way to deal with Boolean type objects if (Boolean.TRUE.equals(type)) { stream.write('T'); continue; } if (Boolean.FALSE.equals(type)) { stream.write('F'); continue; } // go through the array and write the superCollider types as shown // in the above method. // The classes derived here are used as the arg to the above method. writeType(type.getClass()); } // align the stream with padded bytes appendNullCharToAlignStream(); } /** * Write bytes to the stream, catching IOExceptions and converting them to * RuntimeExceptions. * @param bytes byte[] */ private void writeUnderHandler(byte[] bytes) { try { stream.write(alignBigEndToFourByteBoundry(bytes)); } catch (IOException e) { throw new RuntimeException("You're screwed:" + " IOException writing to a ByteArrayOutputStream"); } } /** * Write a 32 bit integer to the byte array without allocating memory. * @param value a 32 bit integer. */ private void writeInteger32ToByteArray(int value) { //byte[] intBytes = new byte[4]; //I allocated the this buffer globally so the GC has less work intBytes[3] = (byte)value; value >>>= 8; intBytes[2] = (byte)value; value >>>= 8; intBytes[1] = (byte)value; value >>>= 8; intBytes[0] = (byte)value; try { stream.write(intBytes); } catch (IOException ex) { throw new RuntimeException("You're screwed:" + " IOException writing to a ByteArrayOutputStream", ex); } } /** * Write a 64 bit integer to the byte array without allocating memory. * @param value a 64 bit integer. */ private void writeInteger64ToByteArray(long value) { longintBytes[7] = (byte)value; value >>>= 8; longintBytes[6] = (byte)value; value >>>= 8; longintBytes[5] = (byte)value; value >>>= 8; longintBytes[4] = (byte)value; value >>>= 8; longintBytes[3] = (byte)value; value >>>= 8; longintBytes[2] = (byte)value; value >>>= 8; longintBytes[1] = (byte)value; value >>>= 8; longintBytes[0] = (byte)value; try { stream.write(longintBytes); } catch (IOException ex) { throw new RuntimeException("You're screwed:" + " IOException writing to a ByteArrayOutputStream", ex); } } }