package water;
import water.fvec.Vec;
import water.network.SocketChannelFactory;
import javax.print.DocFlavor;
import java.io.IOException;
import java.nio.channels.ByteChannel;
import java.sql.Timestamp;
import static water.ExternalFrameHandler.*;
/**
* Various utilities methods to help with external frame handling.
*/
public class ExternalFrameUtils {
/**
* Hints for expected types in order to improve performance network communication.
* We make use of these to send and receive data we actually have.
*
* Each supported type for conversion has to be specified here
*/
public static final byte EXPECTED_BOOL = 0;
public static final byte EXPECTED_BYTE = 1;
public static final byte EXPECTED_CHAR = 2;
public static final byte EXPECTED_SHORT = 3;
public static final byte EXPECTED_INT = 4;
public static final byte EXPECTED_FLOAT = 5;
public static final byte EXPECTED_LONG = 6;
public static final byte EXPECTED_DOUBLE = 7;
public static final byte EXPECTED_STRING = 8;
public static final byte EXPECTED_TIMESTAMP = 9;
/**
* Get connection to a specific h2o node. The caller of this method is usually non-H2O node who wants to read H2O
* frames or write to H2O frames from non-H2O environment, such as Spark executor.
*/
public static ByteChannel getConnection(String h2oNodeHostname, int h2oNodeApiPort) throws IOException{
SocketChannelFactory socketFactory = SocketChannelFactory.instance(H2OSecurityManager.instance());
return H2ONode.openChan(TCPReceiverThread.TCP_EXTERNAL, socketFactory, h2oNodeHostname, h2oNodeApiPort +1);
}
public static ByteChannel getConnection(String ipPort) throws IOException{
String[] split = ipPort.split(":");
return getConnection(split[0], Integer.parseInt(split[1]));
}
public static byte[] vecTypesFromExpectedTypes(byte[] expectedTypes){
byte[] vecTypes = new byte[expectedTypes.length];
for (int i = 0; i < expectedTypes.length; i++) {
switch (expectedTypes[i]){
case EXPECTED_BOOL: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_BYTE: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_CHAR: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_SHORT: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_INT: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_LONG: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_FLOAT: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_DOUBLE: vecTypes[i] = Vec.T_NUM; break;
case EXPECTED_STRING: vecTypes[i] = Vec.T_STR; break;
case EXPECTED_TIMESTAMP: vecTypes[i] = Vec.T_TIME; break;
default: throw new IllegalArgumentException("Unknown expected type: "+expectedTypes[i]);
}
}
return vecTypes;
}
public static byte[] prepareExpectedTypes(Class[] javaClasses){
byte[] expectedTypes = new byte[javaClasses.length];
for (int i = 0; i < javaClasses.length; i++) {
Class clazz = javaClasses[i];
if(clazz == Boolean.class){
expectedTypes[i] = EXPECTED_BOOL;
} else if(clazz == Byte.class){
expectedTypes[i] = EXPECTED_BYTE;
}else if(clazz == Short.class){
expectedTypes[i] = EXPECTED_SHORT;
}else if(clazz == Character.class){
expectedTypes[i] = EXPECTED_CHAR;
}else if(clazz == Integer.class){
expectedTypes[i] = EXPECTED_INT;
}else if(clazz == Long.class){
expectedTypes[i] = EXPECTED_LONG;
}else if(clazz == Float.class){
expectedTypes[i] = EXPECTED_FLOAT;
}else if(clazz == Double.class){
expectedTypes[i] = EXPECTED_DOUBLE;
}else if(clazz == String.class){
expectedTypes[i] = EXPECTED_STRING;
}else if(clazz == Timestamp.class){
expectedTypes[i] = EXPECTED_TIMESTAMP;
}else{
throw new IllegalArgumentException("Unknown java class " + clazz);
}
}
return expectedTypes;
}
static void sendBoolean(AutoBuffer ab, ByteChannel channel, boolean data) throws IOException{
sendBoolean(ab, channel, data ? (byte)1 : (byte)0);
}
static void sendBoolean(AutoBuffer ab, ByteChannel channel, byte boolData) throws IOException{
ab.put1(boolData);
putMarkerAndSend(ab, channel, boolData);
}
static void sendByte(AutoBuffer ab, ByteChannel channel, byte data) throws IOException{
ab.put1(data);
putMarkerAndSend(ab, channel, data);
}
static void sendChar(AutoBuffer ab, ByteChannel channel, char data) throws IOException{
ab.put2(data);
putMarkerAndSend(ab, channel, data);
}
static void sendShort(AutoBuffer ab, ByteChannel channel, short data) throws IOException{
ab.put2s(data);
putMarkerAndSend(ab, channel, data);
}
static void sendInt(AutoBuffer ab, ByteChannel channel, int data) throws IOException{
ab.putInt(data);
putMarkerAndSend(ab, channel, data);
}
static void sendLong(AutoBuffer ab, ByteChannel channel, long data) throws IOException{
ab.put8(data);
putMarkerAndSend(ab, channel, data);
}
static void sendFloat(AutoBuffer ab, ByteChannel channel, float data) throws IOException{
ab.put4f(data);
writeToChannel(ab, channel);
}
static void sendDouble(AutoBuffer ab, ByteChannel channel, double data) throws IOException{
ab.put8d(data);
writeToChannel(ab, channel);
}
static void sendString(AutoBuffer ab, ByteChannel channel, String data) throws IOException{
ab.putStr(data);
if(data != null && data.equals(STR_MARKER_NEXT_BYTE_FOLLOWS)){
ab.put1(MARKER_ORIGINAL_VALUE);
}
writeToChannel(ab, channel);
}
static void sendTimestamp(AutoBuffer ab, ByteChannel channel, long time) throws IOException{
sendLong(ab, channel, time);
}
static void sendTimestamp(AutoBuffer ab, ByteChannel channel, Timestamp data) throws IOException{
sendLong(ab, channel, data.getTime());
}
static void sendNA(AutoBuffer ab, ByteChannel channel, byte expectedType) throws IOException{
switch (expectedType){
case EXPECTED_BOOL: // // fall through to byte since BOOL is internally stored in frame as number (byte)
case EXPECTED_BYTE:
ab.put1(NUM_MARKER_NEXT_BYTE_FOLLOWS);
ab.put1(MARKER_NA);
writeToChannel(ab, channel);
break;
case EXPECTED_CHAR:
ab.put2(NUM_MARKER_NEXT_BYTE_FOLLOWS);
ab.put1(MARKER_NA);
writeToChannel(ab, channel);
break;
case EXPECTED_SHORT:
ab.put2s(NUM_MARKER_NEXT_BYTE_FOLLOWS);
ab.put1(MARKER_NA);
writeToChannel(ab, channel);
break;
case EXPECTED_INT:
ab.putInt(NUM_MARKER_NEXT_BYTE_FOLLOWS);
ab.put1(MARKER_NA);
writeToChannel(ab, channel);
break;
case EXPECTED_TIMESTAMP: // fall through to long since TIMESTAMP is internally stored in frame as long
case EXPECTED_LONG:
ab.put8(NUM_MARKER_NEXT_BYTE_FOLLOWS);
ab.put1(MARKER_NA);
writeToChannel(ab, channel);
break;
case EXPECTED_FLOAT:
ab.put4f(Float.NaN);
writeToChannel(ab, channel);
break;
case EXPECTED_DOUBLE:
ab.put8d(Double.NaN);
writeToChannel(ab, channel);
break;
case EXPECTED_STRING:
ab.putStr(STR_MARKER_NEXT_BYTE_FOLLOWS);
ab.put1(MARKER_NA);
writeToChannel(ab, channel);
break;
default:
throw new IllegalArgumentException("Unknown expected type " + expectedType);
}
}
public static boolean isNA(AutoBuffer ab, boolean data){
return isNA(ab, data ? (long) 1: 0);
}
public static boolean isNA(AutoBuffer ab, long data){
return data == NUM_MARKER_NEXT_BYTE_FOLLOWS && ab.get1() == MARKER_NA;
}
public static boolean isNA(double data){
return Double.isNaN(data);
}
public static boolean isNA(AutoBuffer ab, Timestamp data){
return isNA(ab, data.getTime());
}
public static boolean isNA(AutoBuffer ab, String data){
return data != null && data.equals(STR_MARKER_NEXT_BYTE_FOLLOWS) && ab.get1() == MARKER_NA;
}
/**
* Sends another byte as a marker if it's needed and send the data
*/
private static void putMarkerAndSend(AutoBuffer ab, ByteChannel channel, long data) throws IOException{
if(data == NUM_MARKER_NEXT_BYTE_FOLLOWS){
// we need to send another byte because zero is represented as 00 ( 2 bytes )
ab.put1(MARKER_ORIGINAL_VALUE);
}
writeToChannel(ab, channel);
}
static void writeToChannel(AutoBuffer ab, ByteChannel channel) throws IOException {
ab.flipForReading();
channel.write(ab._bb);
ab.clearForWriting(H2O.MAX_PRIORITY);
}
}