package com.linkedin.thirdeye.api;
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.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Wrapper class to represent an array of dimension values.
* @author kgopalak
*/
public class DimensionKey implements Comparable<DimensionKey> {
private static final Logger LOGGER = LoggerFactory.getLogger(DimensionKey.class);
static MessageDigest md5;
static {
try {
md5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
LOGGER.error("Error initializing md5 message digest toMD5 will fail", e);
}
}
final int hashCode;
private String[] dimensionValues;
/**
* @param dimensionValues
*/
public DimensionKey(String[] dimensionValues) {
this.dimensionValues = dimensionValues;
hashCode = Arrays.hashCode(dimensionValues);
}
/**
* @return
*/
public String[] getDimensionValues() {
return dimensionValues;
}
public String getDimensionValue(List<DimensionSpec> dimensionSpecs, String dimensionName) {
for (int i = 0; i < dimensionSpecs.size(); i++) {
if (dimensionSpecs.get(i).getName().equals(dimensionName)) {
return dimensionValues[i];
}
}
throw new IllegalArgumentException("No dimension " + dimensionName);
}
/**
* @return
* @throws IOException
*/
public byte[] toBytes() throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutput out = new DataOutputStream(baos);
// write the number of dimensions
out.writeInt(dimensionValues.length);
// for each dimension write the length of each dimension followed by the
// values
for (String dimensionValue : dimensionValues) {
byte[] bytes = dimensionValue.getBytes(Charset.forName("utf-8"));
out.writeInt(bytes.length);
out.write(bytes);
}
baos.close();
byte[] byteArray = baos.toByteArray();
try {
DimensionKey key = fromBytes(byteArray);
} catch (Exception e) {
LOGGER.info("input key:{}", Arrays.toString(dimensionValues));
LOGGER.info("generated:{}", Arrays.toString(byteArray));
throw new RuntimeException(e);
}
return byteArray;
}
/**
* @param bytes
* @return
* @throws IOException
*/
public static DimensionKey fromBytes(byte[] bytes) throws IOException {
DataInput in = new DataInputStream(new ByteArrayInputStream(bytes));
// read the number of dimensions
int size = in.readInt();
String[] dimensionValues = new String[size];
// for each dimension read the length of each dimension followed by the
// values
try {
for (int i = 0; i < size; i++) {
int length = in.readInt();
byte[] b = new byte[length];
in.readFully(b);
dimensionValues[i] = new String(b, "UTF-8");
}
} catch (Exception e) {
LOGGER.info(Arrays.toString(bytes), e);
throw new RuntimeException(e);
}
return new DimensionKey(dimensionValues);
}
public byte[] toMD5() {
synchronized (md5) {
byte[] digest = md5.digest(toString().getBytes(Charset.forName("UTF-8")));
return digest;
}
}
/**
* @param that
* @return
*/
public int compareTo(DimensionKey that) {
// assumes both have the same length
int length = Math.min(this.dimensionValues.length, that.dimensionValues.length);
int ret = 0;
for (int i = 0; i < length; i++) {
ret = this.dimensionValues[i].compareTo(that.dimensionValues[i]);
if (ret != 0) {
break;
}
}
return ret;
}
@Override
public int hashCode() {
return hashCode;
}
/**
* compares to dimensionKey instances
*/
@Override
public boolean equals(Object obj) {
// assumes both have the same length
if (obj instanceof DimensionKey) {
DimensionKey that = (DimensionKey) obj;
int length = Math.min(this.dimensionValues.length, that.dimensionValues.length);
boolean ret = true;
for (int i = 0; i < length; i++) {
ret = this.dimensionValues[i].equals(that.dimensionValues[i]);
if (!ret) {
break;
}
}
return ret;
}
return false;
}
@Override
public String toString() {
return Arrays.toString(dimensionValues);
}
public String toCommaSeparatedString() {
if (dimensionValues == null) {
return null;
}
if (dimensionValues.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
String separator = "";
for (String s : dimensionValues) {
sb.append(separator).append(s);
separator = ",";
}
return sb.toString();
}
}