/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.homematic.internal.binrpc;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* A BIN-RPC request for sending data to the Homematic server.
*
* @author Gerhard Riegler
* @since 1.5.0
*/
public class BinRpcRequest {
private byte data[];
private int dataoffset;
private String methodName;
private Collection<Object> args = new ArrayList<Object>();
/**
* Creates a new request with the specified methodName.
*/
public BinRpcRequest(String methodName) {
this.methodName = methodName;
}
/**
* Adds arguments to the method.
*/
public void addArg(Object arg) {
args.add(arg);
}
public String getMethodName() {
return methodName;
}
/**
* Generates the binrpc data.
*/
public byte[] createMessage() {
data = new byte[256];
if (methodName != null) {
addInt(methodName.length());
addString(methodName);
addInt(args.size());
}
addList(args);
byte fullreq[] = new byte[dataoffset + 8];
System.arraycopy(data, 0, fullreq, 8, dataoffset);
fullreq[0] = 'B';
fullreq[1] = 'i';
fullreq[2] = 'n';
addInt(dataoffset);
System.arraycopy(data, dataoffset - 4, fullreq, 4, 4);
return fullreq;
}
private void addByte(byte b) {
if (dataoffset == data.length) {
byte newdata[] = new byte[data.length * 2];
System.arraycopy(data, 0, newdata, 0, data.length);
data = newdata;
}
data[dataoffset++] = b;
}
private void addInt(int n) {
byte d[] = BigInteger.valueOf(n).toByteArray();
for (int c = 0; c < 4 - d.length; c++) {
addByte(n < 0 ? (byte) 0xFF : (byte) 0);
}
for (byte s : d) {
addByte(s);
}
}
private void addDouble(double v) {
double tmp = Math.abs(v);
int exp = 0;
if (tmp != 0 && tmp < 0.5) {
while (tmp < 0.5) {
tmp *= 2;
exp--;
}
} else {
while (tmp >= 1) {
tmp /= 2;
exp++;
}
}
if (v < 0) {
tmp *= -1;
}
int mantissa = (int) Math.round(tmp * 0x40000000);
addInt(mantissa);
addInt(exp);
}
private void addString(String s) {
byte sd[];
try {
sd = s.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException use) {
// Really shouldn't happen, fall back silently to platform encoding
sd = s.getBytes();
}
for (byte ch : sd) {
addByte(ch);
}
}
private void addList(Collection<?> args) {
for (Object o : args) {
if (o.getClass() == String.class) {
addInt(3);
String s = (String) o;
addInt(s.length());
addString(s);
} else if (o.getClass() == Boolean.class) {
addInt(2);
addByte(((Boolean) o).booleanValue() ? (byte) 1 : (byte) 0);
} else if (o.getClass() == Integer.class) {
addInt(1);
addInt(((Integer) o).intValue());
} else if (o.getClass() == Double.class) {
addInt(4);
addDouble(((Double) o).doubleValue());
} else if (o.getClass() == BigInteger.class) {
addInt(4);
addDouble(((BigInteger) o).doubleValue());
} else if (o instanceof List<?>) {
Collection<?> l = (Collection<?>) o;
addInt(0x100);
addInt(l.size());
addList(l);
} else if (o instanceof Map<?, ?>) {
Map<?, ?> l = (Map<?, ?>) o;
addInt(0x101);
addInt(l.size());
for (Map.Entry<?, ?> me : l.entrySet()) {
String key = (String) me.getKey();
addInt(key.length());
addString(key);
addList(Collections.singleton(me.getValue()));
}
}
}
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("methodName", methodName)
.append("args", args.toArray()).toString();
}
}