com.lambdazen.bitsy.store.Record Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bitsy Show documentation
Show all versions of bitsy Show documentation
Bitsy v3 is a small, fast, embeddable, durable in-memory graph database that is compatible with Tinkerpop3
package com.lambdazen.bitsy.store;
import java.io.IOException;
import java.io.StringWriter;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.lambdazen.bitsy.BitsyEdge;
import com.lambdazen.bitsy.BitsyErrorCodes;
import com.lambdazen.bitsy.BitsyException;
import com.lambdazen.bitsy.BitsyVertex;
import com.lambdazen.bitsy.IGraphStore;
import com.lambdazen.bitsy.UUID;
/** This class represents a line in a text file captured in the DB */
public class Record {
private static final char[] HEX_CHAR_ARR = "0123456789abcdef".toCharArray();
public static final String newLine = "\n";
//private static final ObjectMapper mapper = new ObjectMapper();
private static final JsonFactory factory = new JsonFactory();
public static enum RecordType {H, // Header
L, // Log marker
V, // Vertex
E, // Edges
T, // Transaction
I, // Index -- stored in meta?.txt files
M}; // Major version -- stored in meta?.txt files
private static final char[] recordChars = new char[] {'H', 'L', 'V', 'E', 'T', 'I', 'M'};
private static final RecordType[] recordTypes = new RecordType[] {RecordType.H, RecordType.L, RecordType.V, RecordType.E, RecordType.T, RecordType.I, RecordType.M};
private static final int numRecChars = recordChars.length;
RecordType type;
String json;
BitsyEdge edge;
BitsyVertex vertex;
public static boolean IS_ANDROID = "The Android Project".equals(System.getProperty("java.specification.vendor"));
public static int ANDROID_EOR = 1234567890;
public static int hashCode(String str) {
if (!IS_ANDROID) {
// Backward compatible for non-Android systems
return str.hashCode();
} else {
return ANDROID_EOR;
}
}
public Record(RecordType type, String json) {
this.type = type;
this.json = json;
}
public void deserialize(ObjectReader vReader, ObjectReader eReader) throws JsonProcessingException, IOException {
if ((type == RecordType.V) && (vReader != null)) {
VertexBeanJson vBean = vReader.readValue(json);
this.vertex = new BitsyVertex(vBean, null, vBean.getState());
}
if ((type == RecordType.E) && (eReader != null)) {
EdgeBeanJson eBean = eReader.readValue(json);
this.edge = new BitsyEdge(eBean, null, eBean.getState());
}
}
public RecordType getType() {
return type;
}
public String getJson() {
return json;
}
// Efficient method to write a vertex -- avoids writeValueAsString
public static void generateVertexLine(StringWriter sw, ObjectMapper mapper, VertexBean vBean) throws JsonGenerationException, JsonMappingException, IOException {
sw.getBuffer().setLength(0);
sw.append('V'); // Record type
sw.append('=');
mapper.writeValue(sw, vBean);
sw.append('#');
int hashCode = hashCode(sw.toString());
sw.append(toHex(hashCode));
sw.append('\n');
}
// Efficient method to write an edge -- avoids writeValueAsString
public static void generateEdgeLine(StringWriter sw, ObjectMapper mapper, EdgeBean eBean) throws JsonGenerationException, JsonMappingException, IOException {
sw.getBuffer().setLength(0);
sw.append('E'); // Record type
sw.append('=');
mapper.writeValue(sw, eBean);
sw.append('#');
int hashCode = hashCode(sw.toString());
sw.append(toHex(hashCode));
sw.append('\n');
}
public static String generateDBLine(RecordType type, String line) {
String dbLine = type + "=" + line + "#";
int hashCode = hashCode(dbLine);
return dbLine + toHex(hashCode) + newLine;
}
public static Record parseRecord(String dbLine, int lineNo, String fileName) {
int hashPos = dbLine.lastIndexOf('#');
if (hashPos < 0) {
throw new BitsyException(BitsyErrorCodes.CHECKSUM_MISMATCH, "Line " + lineNo + " in file " + fileName + " has no hash-code. Encountered " + dbLine);
} else {
String hashCode = dbLine.substring(hashPos + 1);
String expHashCode = toHex(hashCode(dbLine.substring(0, hashPos + 1)));
if ((hashCode == null) || !hashCode.trim().equals(expHashCode)) { // TODO: Currently DB is not portable between Android and Java
throw new BitsyException(BitsyErrorCodes.CHECKSUM_MISMATCH, "Line " + lineNo + " in file " + fileName + " has the wrong hash-code " + hashCode + ". Expected " + expHashCode);
} else {
// All OK
RecordType type = typeFromChar(dbLine.charAt(0));
String json = dbLine.substring(2, hashPos);
return new Record(type, json);
}
}
}
// Faster than RecordType.valueof()
private static RecordType typeFromChar(char recChar) {
for (int i=0; i < numRecChars; i++) {
if (recordChars[i] == recChar) {
return recordTypes[i];
}
}
throw new BitsyException(BitsyErrorCodes.INTERNAL_ERROR, "Unrecognized record type " + recChar);
}
// Faster than Integer.toHexString()
private static String toHex(int input) {
final char[] sb = new char[8];
final int len = (sb.length-1);
for (int i = 0; i <= len; i++) { // MSB
sb[i] = HEX_CHAR_ARR[((int)(input >>> ((len - i)<<2))) & 0xF];
}
return new String(sb);
}
// This method checks to see if a record is obsolete, i.e., its version is not present in the store
public boolean checkObsolete(IGraphStore store, boolean isReorg, int lineNo, String fileName) {
if (type == RecordType.T) {
// Transaction boundaries are obsolete during reorg
return isReorg;
} else if (type == RecordType.L) {
// A log is always obsolete
return false;
} else if ((type != RecordType.V) && (type != RecordType.E)) {
throw new BitsyException(BitsyErrorCodes.INTERNAL_ERROR, "Unhanded record type: " + type);
}
// A V or E record
UUID id = null;
int version = -1;
String state = null;
JsonToken token;
try {
JsonParser parser = factory.createJsonParser(json);
while ((token = parser.nextToken()) != JsonToken.END_OBJECT) {
// Find the version
if (token == JsonToken.FIELD_NAME) {
if (parser.getCurrentName().equals("id")) {
parser.nextToken();
id = UUID.fromString(parser.getText());
continue;
}
if (parser.getCurrentName().equals("v")) {
parser.nextToken();
version = parser.getIntValue();
continue;
}
if (parser.getCurrentName().equals("s")) {
parser.nextToken();
state = parser.getText();
// No need to proceed further
break;
}
}
}
if ((id == null) || (version == -1) || (state == null)) {
throw new BitsyException(BitsyErrorCodes.INTERNAL_ERROR, "Unable to parse record '" + json + "' in file " + fileName + " at line " + lineNo);
}
if (state.equals("D")) {
// Deleted -- can be ignored only on re-orgs
return isReorg;
} else {
if (type == RecordType.V) {
VertexBean curV = store.getVertex(id);
if (curV == null) {
// Doesn't exist anymore, probably deleted later
return true;
} else if (curV.getVersion() != version) {
// Obsolete
return true;
} else {
// Good to go
return false;
}
} else {
assert (type == RecordType.E);
EdgeBean curE = store.getEdge(id);
if (curE == null) {
// Doesn't exist anymore, probably deleted later
return true;
} else if (curE.getVersion() != version) {
// Obsolete
return true;
} else {
// Good to go
return false;
}
}
}
} catch (Exception e) {
throw new BitsyException(BitsyErrorCodes.INTERNAL_ERROR, "Possible bug in code. Error serializing line '" + json + "' in file " + fileName + " at line " + lineNo, e);
}
}
public BitsyVertex getVertex() {
return vertex;
}
public BitsyEdge getEdge() {
return edge;
}
}