/*
* Copyright (c) 2012-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.common.io;
import com.google.common.base.Throwables;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Provides utility methods for working with {@link Streamable}s and/or data streams.
*
* @author pron
*/
public final class Streamables {
/**
* Serializes a {@link Streamable} into a byte array.
* @param streamable The object to serialize.
* @return A byte array containing the serialized object.
*/
public static byte[] toByteArray(Streamable streamable) {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(streamable.size());
final DataOutputStream dos = new DataOutputStream(baos);
streamable.write(dos);
dos.flush();
baos.close();
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Reads a {@link Streamable}'s serialized contents from a byte array.
* The serialized form begins in the beginning of the array and extends to the end of the array.
* <p>
* Same as calling {@link #fromByteArray(co.paralleluniverse.common.io.Streamable, byte[], int, int) fromByteArray(streamable, array, 0, array.length)}.
*
* @param streamable The object whose contents are to be read.
* @param array The array from which to read the serialized form.
*/
public static void fromByteArray(Streamable streamable, byte[] array) {
fromByteArray(streamable, array, 0, array.length);
}
/**
* Reads a {@link Streamable}'s serialized contents from a byte array.
* The serialized form begins in the given offset and extends to the end of the array.
* <p>
* Same as calling {@link #fromByteArray(co.paralleluniverse.common.io.Streamable, byte[], int, int) fromByteArray(streamable, array, offset, array.length - offset)}.
*
* @param streamable The object whose contents are to be read.
* @param array The array from which to read the serialized form.
* @param offset The offset into the array from which to start reading the serialized form.
*/
public static void fromByteArray(Streamable streamable, byte[] array, int offset) {
fromByteArray(streamable, array, offset, array.length - offset);
}
/**
* Reads a {@link Streamable}'s serialized contents from a byte array.
*
* @param streamable The object whose contents are to be read.
* @param array The array from which to read the serialized form.
* @param offset The offset into the array from which to start reading the serialized form.
* @param length The length of the serialized form to read from the array.
*/
public static void fromByteArray(Streamable streamable, byte[] array, int offset, int length) {
try {
final ByteArrayInputStream bais = new ByteArrayInputStream(array, offset, length);
final DataInputStream dis = new DataInputStream(bais);
streamable.read(dis);
bais.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Writes the given {@link ByteBuffer} into the given {@link DataOutput}.
*
* @param out The {@link DataOutput} into which the buffer will be written.
* @param buffer The buffer to write into the {@link DataOutput}.
* @throws IOException
*/
public static void writeBuffer(DataOutput out, ByteBuffer buffer) throws IOException {
if (buffer.hasArray()) {
out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
buffer.position(buffer.limit());
} else {
final byte[] array = new byte[buffer.remaining()];
buffer.get(array);
assert buffer.remaining() == 0 && buffer.position() == buffer.limit();
out.write(array);
}
}
/**
* Returns the length in bytes of a string's UTF-8 encoding.
*
* @param str The string to measure.
* @return The length in bytes of a string's UTF-8 encoding.
*/
public static int calcUtfLength(String str) {
final int strlen = str.length();
int utflen = 0;
for (int i = 0; i < strlen; i++) {
int c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
return utflen;
}
//////////////////////
private final ConcurrentMap<Byte, ClassInfo> types = new ConcurrentHashMap<Byte, ClassInfo>();
public <T extends Streamable> void register(byte qualifier, Class<T> clazz) {
ClassInfo old = types.putIfAbsent(qualifier, new ClassInfo(clazz));
if (old != null) {
throw new RuntimeException("Qualifier " + qualifier + " is already registered to class " + old.clazz.getName());
}
}
public Object read(DataInput in) throws IOException {
final byte qualifier = in.readByte();
final ClassInfo ci = types.get(qualifier);
if (ci == null)
throw new IOException("Cannot read object. No class registered for qualifier " + qualifier);
final Streamable obj = (Streamable) ci.construct(qualifier);
obj.read(in);
return obj;
}
public Object fromByteArray(byte[] array) throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(array);
final DataInputStream dis = new DataInputStream(bais);
final Object obj = read(dis);
bais.close();
return obj;
}
private static class ClassInfo<T> {
public final Class<T> clazz;
private final boolean constructorWithQualifier;
private final Constructor<T> constructor;
public ClassInfo(Class<T> clazz) {
try {
boolean _constructorWithQualifier = false;
Constructor<T> _constructor = null;
try {
_constructor = clazz.getDeclaredConstructor(byte.class);
_constructorWithQualifier = true;
} catch (NoSuchMethodException ex) {
}
if (_constructor == null) {
try {
_constructor = clazz.getDeclaredConstructor();
_constructorWithQualifier = false;
} catch (NoSuchMethodException ex) {
}
}
if (_constructor == null)
throw new IllegalArgumentException("Class " + clazz.getName() + " does not have a default constructor or one that takes a byte as parameter");
this.clazz = clazz;
this.constructorWithQualifier = _constructorWithQualifier;
this.constructor = _constructor;
} catch (SecurityException ex) {
throw new RuntimeException(ex);
}
}
public T construct(byte type) {
try {
if (constructorWithQualifier)
return constructor.newInstance(type);
else
return constructor.newInstance();
} catch (InstantiationException ex) {
throw Throwables.propagate(ex);
} catch (IllegalAccessException ex) {
throw Throwables.propagate(ex);
} catch (IllegalArgumentException ex) {
throw Throwables.propagate(ex);
} catch (InvocationTargetException ex) {
throw Throwables.propagate(ex);
}
}
}
}