package org.vertexium.accumulo.iterator.model;
import org.apache.accumulo.core.data.Value;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
// We are doing custom serialization to make this as fast as possible since this can get called many times
public class EdgeInfo {
public static final String CHARSET_NAME = "UTF-8";
private byte[] bytes;
private transient long timestamp;
private transient String label;
private transient String vertexId;
private transient boolean decoded;
// here for serialization
protected EdgeInfo() {
}
public EdgeInfo(String label, String vertexId) {
this(label, vertexId, System.currentTimeMillis());
}
public EdgeInfo(String label, String vertexId, long timestamp) {
if (label == null) {
throw new IllegalArgumentException("label cannot be null");
}
if (vertexId == null) {
throw new IllegalArgumentException("vertexId cannot be null");
}
this.label = label;
this.vertexId = vertexId;
this.timestamp = timestamp;
this.decoded = true;
}
public EdgeInfo(byte[] bytes, long timestamp) {
this.timestamp = timestamp;
this.bytes = bytes;
}
public String getLabel() {
decodeBytes();
return label;
}
public String getVertexId() {
decodeBytes();
return vertexId;
}
// fast access method to avoid creating a new instance of an EdgeInfo
public static String getVertexId(Value value) {
byte[] buffer = value.get();
int offset = 0;
// skip label
int strLen = readInt(buffer, offset);
offset += 4;
if (strLen > 0) {
offset += strLen;
}
strLen = readInt(buffer, offset);
return readString(buffer, offset, strLen);
}
private void decodeBytes() {
if (!decoded) {
int offset = 0;
int strLen = readInt(this.bytes, offset);
offset += 4;
this.label = readString(this.bytes, offset, strLen);
offset += strLen;
strLen = readInt(this.bytes, offset);
offset += 4;
this.vertexId = readString(this.bytes, offset, strLen);
this.decoded = true;
}
}
public byte[] getLabelBytes() {
// Used to use ByteBuffer here but it was to slow
int labelBytesLength = readInt(this.bytes, 0);
return Arrays.copyOfRange(this.bytes, 4, 4 + labelBytesLength);
}
public static EdgeInfo parse(Value value, long timestamp) {
return new EdgeInfo(value.get(), timestamp);
}
public byte[] getBytes() {
if (bytes == null) {
try {
byte[] labelBytes = label.getBytes(CHARSET_NAME);
int labelBytesLength = labelBytes.length;
byte[] vertexIdBytes = vertexId.getBytes(CHARSET_NAME);
int vertexIdBytesLength = vertexIdBytes.length;
int len = 4 + labelBytesLength + 4 + vertexIdBytesLength;
byte[] buffer = new byte[len];
int offset = 0;
writeInt(labelBytesLength, buffer, offset);
offset += 4;
if (labelBytes != null) {
System.arraycopy(labelBytes, 0, buffer, offset, labelBytesLength);
offset += labelBytesLength;
}
writeInt(vertexIdBytesLength, buffer, offset);
offset += 4;
if (vertexIdBytes != null) {
System.arraycopy(vertexIdBytes, 0, buffer, offset, vertexIdBytesLength);
}
this.bytes = buffer;
} catch (UnsupportedEncodingException ex) {
throw new VertexiumAccumuloIteratorException("Could not encode edge info", ex);
}
}
return bytes;
}
private void writeInt(int value, byte[] buffer, int offset) {
buffer[offset++] = (byte) ((value >> 24) & 0xff);
buffer[offset++] = (byte) ((value >> 16) & 0xff);
buffer[offset++] = (byte) ((value >> 8) & 0xff);
buffer[offset] = (byte) (value & 0xff);
}
private static int readInt(byte[] buffer, int offset) {
return ((buffer[offset] & 0xff) << 24)
| ((buffer[offset + 1] & 0xff) << 16)
| ((buffer[offset + 2] & 0xff) << 8)
| ((buffer[offset + 3] & 0xff));
}
private static String readString(byte[] buffer, int offset, int length) {
byte[] d = new byte[length];
System.arraycopy(buffer, offset, d, 0, length);
try {
return new String(d, CHARSET_NAME);
} catch (IOException ex) {
throw new VertexiumAccumuloIteratorException("Could not decode edge info", ex);
}
}
public Value toValue() {
return new Value(getBytes());
}
public long getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return "EdgeInfo{" +
"vertexId='" + vertexId + '\'' +
", label='" + label + '\'' +
'}';
}
}