448 lines
16 KiB
Java
448 lines
16 KiB
Java
package org.warp.jcwdb.ann;
|
|
|
|
import com.esotericsoftware.kryo.Kryo;
|
|
import com.esotericsoftware.kryo.Registration;
|
|
import com.esotericsoftware.kryo.io.Output;
|
|
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
|
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
|
import it.unimi.dsi.fastutil.chars.CharArrayList;
|
|
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
|
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
|
import org.apache.commons.lang3.reflect.MethodUtils;
|
|
import org.warp.jcwdb.Cleanable;
|
|
import org.warp.jcwdb.Cleaner;
|
|
import org.warp.jcwdb.FileIndexManager;
|
|
import org.warp.jcwdb.ann.exampleimpl.ClassWithList;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOError;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.nio.file.Path;
|
|
import java.util.*;
|
|
|
|
public class DatabaseManager implements Cleanable {
|
|
|
|
public static final long MAX_LOADED_INDICES = 100000;
|
|
private final DBObjectIndicesManager objectIndicesManager;
|
|
private final FileIndexManager indices;
|
|
private final Cleaner cleaner;
|
|
private final JCWDatabase jcwDatabase;
|
|
private DBObject loadedRootObject = null;
|
|
private final Kryo kryo = new Kryo();
|
|
private volatile boolean closed;
|
|
|
|
DatabaseManager(JCWDatabase jcwDatabase, Path dataFile, Path metadataFile) throws IOException {
|
|
this.jcwDatabase = jcwDatabase;
|
|
kryo.setRegistrationRequired(true);
|
|
registerDefaultClasses();
|
|
this.indices = new FileIndexManager(dataFile, metadataFile);
|
|
if (!indices.has(0)) {
|
|
allocateNullValue();
|
|
}
|
|
this.objectIndicesManager = new DBObjectIndicesManager(this.indices);
|
|
this.cleaner = new Cleaner(this);
|
|
this.cleaner.start();
|
|
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
try {
|
|
DatabaseManager.this.close();
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}));
|
|
}
|
|
|
|
private void registerDefaultClasses() {
|
|
int id = -90;
|
|
registerClass(boolean[].class, id++);
|
|
registerClass(byte[].class, id++);
|
|
registerClass(short[].class, id++);
|
|
registerClass(char[].class, id++);
|
|
registerClass(int[].class, id++);
|
|
registerClass(long[].class, id++);
|
|
registerClass(Boolean[].class, id++);
|
|
registerClass(Byte[].class, id++);
|
|
registerClass(Short[].class, id++);
|
|
registerClass(Character[].class, id++);
|
|
registerClass(Integer[].class, id++);
|
|
registerClass(Long[].class, id++);
|
|
registerClass(String.class, id++);
|
|
registerClass(String[].class, id++);
|
|
registerClass(Boolean.class, id++);
|
|
registerClass(Byte.class, id++);
|
|
registerClass(Short.class, id++);
|
|
registerClass(Character.class, id++);
|
|
registerClass(Integer.class, id++);
|
|
registerClass(Class.class, id++);
|
|
registerClass(Object.class, id++);
|
|
registerClass(Object[].class, id++);
|
|
registerClass(Long.class, id++);
|
|
registerClass(String.class, id++);
|
|
registerClass(String[].class, id++);
|
|
registerClass(boolean[][].class, id++);
|
|
registerClass(byte[][].class, id++);
|
|
registerClass(short[][].class, id++);
|
|
registerClass(char[][].class, id++);
|
|
registerClass(int[][].class, id++);
|
|
registerClass(long[][].class, id++);
|
|
registerClass(String[][].class, id++);
|
|
registerClass(List.class, id++);
|
|
registerClass(ArrayList.class, id++);
|
|
registerClass(LinkedList.class, id++);
|
|
registerClass(Set.class, id++);
|
|
registerClass(HashSet.class, id++);
|
|
registerClass(LinkedHashSet.class, id++);
|
|
registerClass(Map.class, id++);
|
|
registerClass(HashMap.class, id++);
|
|
registerClass(LinkedHashMap.class, id++);
|
|
registerClass(TreeMap.class, id++);
|
|
registerClass(BooleanArrayList.class, id++);
|
|
registerClass(ByteArrayList.class, id++);
|
|
registerClass(ShortArrayList.class, id++);
|
|
registerClass(CharArrayList.class, id++);
|
|
registerClass(IntArrayList.class, id++);
|
|
registerClass(LongArrayList.class, id++);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public <T extends DBObject> T loadRoot(Class<T> rootType) throws IOException {
|
|
if (loadedRootObject != null) {
|
|
throw new RuntimeException("Root already set!");
|
|
}
|
|
if (isDBObjectNull(0)) {
|
|
try {
|
|
T root = rootType.getConstructor(JCWDatabase.class).newInstance(this.jcwDatabase);
|
|
loadedRootObject = root;
|
|
return root;
|
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
|
throw new IOError(e);
|
|
}
|
|
} else {
|
|
T root = (T) loadDBObject(rootType, 0);
|
|
loadedRootObject = root;
|
|
return root;
|
|
}
|
|
}
|
|
|
|
public void close() throws IOException {
|
|
if (!closed) {
|
|
closed = true;
|
|
DatabaseManager.this.cleaner.stop();
|
|
if (loadedRootObject != null) {
|
|
loadedRootObject.writeToDisk(0);
|
|
}
|
|
indices.close();
|
|
}
|
|
}
|
|
|
|
public void preloadDBObject(DBObject obj, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException {
|
|
preloadDBObjectFields(obj, objectInfo.getFields());
|
|
preloadDBObjectProperties(obj, objectInfo.getProperties());
|
|
}
|
|
|
|
private void preloadDBObjectFields(DBObject obj, long[] fieldUIDs) throws IOException {
|
|
// Declare the variables needed to get the biggest field Id
|
|
Field[] unorderedFields = getFields(obj);
|
|
// Find the biggest field Id
|
|
int biggestFieldId = getBiggestFieldId(unorderedFields);
|
|
|
|
// Declare the other variables
|
|
Field[] fields = new Field[biggestFieldId + 1];
|
|
DBDataType[] orderedFieldTypes = new DBDataType[biggestFieldId + 1];
|
|
|
|
// Load all fields metadata and load them
|
|
for (Field field : unorderedFields) {
|
|
DBField fieldAnnotation = field.getAnnotation(DBField.class);
|
|
int fieldId = fieldAnnotation.id();
|
|
DBDataType fieldType = fieldAnnotation.type();
|
|
loadField(obj, field, fieldType, fieldUIDs[fieldId]);
|
|
fields[fieldId] = field;
|
|
orderedFieldTypes[fieldId] = fieldType;
|
|
}
|
|
// Set fields metadata
|
|
obj.setFields(fields, orderedFieldTypes, fieldUIDs);
|
|
}
|
|
|
|
private void preloadDBObjectProperties(DBObject obj, long[] propertyUIDs) {
|
|
// Declare the variables needed to get the biggest property Id
|
|
Method[] unorderedPropertyGetters = getPropertyGetters(obj);
|
|
Method[] unorderedPropertySetters = getPropertySetters(obj);
|
|
|
|
// Find the biggest property Id
|
|
int biggestGetter = getBiggestPropertyGetterId(unorderedPropertyGetters);
|
|
int biggestSetter = getBiggestPropertySetterId(unorderedPropertySetters);
|
|
int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter;
|
|
|
|
for (Method property : unorderedPropertySetters) {
|
|
DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
int propertyId = fieldAnnotation.id();
|
|
if (propertyId > biggestPropertyId) {
|
|
biggestPropertyId = propertyId;
|
|
}
|
|
}
|
|
|
|
// Declare the other variables
|
|
DBDataType[] propertyTypes = new DBDataType[biggestPropertyId + 1];
|
|
Method[] propertyGetters = new Method[biggestPropertyId + 1];
|
|
Method[] propertySetters = new Method[biggestPropertyId + 1];
|
|
Map<String, DBPropertySetter> setterMethods = new LinkedHashMap<>();
|
|
Map<String, DBPropertyGetter> getterMethods = new LinkedHashMap<>();
|
|
|
|
// Load the properties metadata
|
|
for (Method property : unorderedPropertyGetters) {
|
|
DBPropertyGetter propertyAnnotation = property.getAnnotation(DBPropertyGetter.class);
|
|
int propertyId = propertyAnnotation.id();
|
|
DBDataType propertyType = propertyAnnotation.type();
|
|
propertyTypes[propertyId] = propertyType;
|
|
propertyGetters[propertyId] = property;
|
|
getterMethods.put(property.getName(), propertyAnnotation);
|
|
}
|
|
for (Method property : unorderedPropertySetters) {
|
|
DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
int propertyId = propertyAnnotation.id();
|
|
DBDataType propertyType = propertyAnnotation.type();
|
|
propertyTypes[propertyId] = propertyType;
|
|
propertySetters[propertyId] = property;
|
|
setterMethods.put(property.getName(), propertyAnnotation);
|
|
}
|
|
// Set properties metadata
|
|
obj.setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs, setterMethods, getterMethods);
|
|
}
|
|
|
|
Method[] getPropertyGetters(DBObject obj) {
|
|
return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertyGetter.class);
|
|
}
|
|
|
|
Method[] getPropertySetters(DBObject obj) {
|
|
return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertySetter.class);
|
|
}
|
|
|
|
protected Field[] getFields(DBObject obj) {
|
|
return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class);
|
|
}
|
|
|
|
int getBiggestPropertyGetterId(Method[] unorderedPropertyGetters) {
|
|
int biggestPropertyId = -1;
|
|
for (Method property : unorderedPropertyGetters) {
|
|
DBPropertyGetter fieldAnnotation = property.getAnnotation(DBPropertyGetter.class);
|
|
int propertyId = fieldAnnotation.id();
|
|
if (propertyId > biggestPropertyId) {
|
|
biggestPropertyId = propertyId;
|
|
}
|
|
}
|
|
return biggestPropertyId;
|
|
}
|
|
|
|
int getBiggestPropertySetterId(Method[] unorderedPropertySetters) {
|
|
int biggestPropertyId = -1;
|
|
for (Method property : unorderedPropertySetters) {
|
|
DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
int propertyId = fieldAnnotation.id();
|
|
if (propertyId > biggestPropertyId) {
|
|
biggestPropertyId = propertyId;
|
|
}
|
|
}
|
|
return biggestPropertyId;
|
|
}
|
|
|
|
|
|
protected int getBiggestFieldId(Field[] unorderedFields) {
|
|
int biggestFieldId = -1;
|
|
for (Field field : unorderedFields) {
|
|
DBField fieldAnnotation = field.getAnnotation(DBField.class);
|
|
int propertyId = fieldAnnotation.id();
|
|
if (propertyId > biggestFieldId) {
|
|
biggestFieldId = propertyId;
|
|
}
|
|
}
|
|
return biggestFieldId;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public void loadProperty(DBObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException {
|
|
switch (propertyType) {
|
|
case DATABASE_OBJECT:
|
|
DBObject fieldDBObjectValue = loadDBObject((Class<? extends DBObject>) property.getReturnType(), propertyUID);
|
|
//System.err.println("Loading prop. DBObj " + propertyUID + ":" + fieldDBObjectValue);
|
|
obj.setLoadedProperty(propertyId, fieldDBObjectValue);
|
|
break;
|
|
case OBJECT:
|
|
Object fieldObjectValue = loadObject(propertyUID);
|
|
//System.err.println("Loading prop. Obj " + propertyUID + ":" + fieldObjectValue);
|
|
obj.setLoadedProperty(propertyId, fieldObjectValue);
|
|
break;
|
|
case UID_LIST:
|
|
LongArrayList fieldListObjectValue = loadListObject(propertyUID);
|
|
//System.err.println("Loading prop. LOb " + propertyUID + ":" + fieldListObjectValue);
|
|
obj.setLoadedProperty(propertyId, fieldListObjectValue);
|
|
break;
|
|
case INTEGER:
|
|
int fieldIntValue = loadInt(propertyUID);
|
|
//System.err.println("Loading prop. Int " + propertyUID + ":" + fieldIntValue);
|
|
obj.setLoadedProperty(propertyId, fieldIntValue);
|
|
break;
|
|
default:
|
|
throw new NullPointerException("Unknown Field Type");
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public void loadField(DBObject obj, Field field, DBDataType fieldType, long fieldUID) throws IOException {
|
|
try {
|
|
switch (fieldType) {
|
|
case DATABASE_OBJECT:
|
|
DBObject fieldDBObjectValue = loadDBObject((Class<? extends DBObject>) field.getType(), fieldUID);
|
|
//System.err.println("Loading field DBObj " + fieldUID + ":" + fieldDBObjectValue);
|
|
FieldUtils.writeField(field, obj, fieldDBObjectValue, true);
|
|
break;
|
|
case OBJECT:
|
|
Object fieldObjectValue = loadObject(fieldUID);
|
|
//System.err.println("Loading field Obj " + fieldUID + ":" + fieldObjectValue);
|
|
FieldUtils.writeField(field, obj, fieldObjectValue, true);
|
|
break;
|
|
case UID_LIST:
|
|
LongArrayList fieldListObjectValue = loadListObject(fieldUID);
|
|
//System.err.println("Loading field LOb " + fieldUID + ":" + fieldObjectValue);
|
|
FieldUtils.writeField(field, obj, fieldListObjectValue, true);
|
|
break;
|
|
case INTEGER:
|
|
int fieldIntValue = loadInt(fieldUID);
|
|
//System.err.println("Loading field Int " + fieldUID + ":" + fieldIntValue);
|
|
FieldUtils.writeField(field, obj, fieldIntValue, true);
|
|
break;
|
|
default:
|
|
throw new NullPointerException("Unknown Field Type");
|
|
}
|
|
} catch (IllegalAccessException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
public <T extends DBObject> T loadDBObject(Class<T> type, long propertyUID) throws IOException {
|
|
try {
|
|
DBObjectIndicesManager.DBObjectInfo objectInfo = readUIDs(propertyUID);
|
|
if (objectInfo == null) return null;
|
|
return type.getDeclaredConstructor(JCWDatabase.class, DBObjectIndicesManager.DBObjectInfo.class).newInstance(jcwDatabase, objectInfo);
|
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
|
throw new IOException(e);
|
|
}
|
|
}
|
|
|
|
private boolean isDBObjectNull(long uid) {
|
|
try {
|
|
return !objectIndicesManager.has(uid) || objectIndicesManager.get(uid) == null;
|
|
} catch (IOException ex) {
|
|
throw new IOError(ex);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public <T> T loadObject(long uid) throws IOException {
|
|
return indices.get(uid, (i, size) -> size == 0 ? null : (T) kryo.readClassAndObject(i));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private LongArrayList loadListObject(long uid) throws IOException {
|
|
return indices.get(uid, (i, size) -> {
|
|
if (size == 0) return null;
|
|
LongArrayList list = new LongArrayList();
|
|
int listSize = i.readVarInt(true);
|
|
for (int li = 0; li < listSize; li++) {
|
|
list.add(i.readVarLong(true));
|
|
}
|
|
return list;
|
|
});
|
|
}
|
|
|
|
public int loadInt(long uid) throws IOException {
|
|
return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readInt());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param uid
|
|
* @return
|
|
*/
|
|
public DBObjectIndicesManager.DBObjectInfo readUIDs(long uid) {
|
|
try {
|
|
return objectIndicesManager.get(uid);
|
|
} catch (IOException e) {
|
|
throw new IOError(e);
|
|
}
|
|
}
|
|
|
|
public boolean exists(long uid) {
|
|
return objectIndicesManager.has(uid);
|
|
}
|
|
|
|
public void writeObjectInfo(long uid, long[] fieldUIDs, long[] propertyUIDs) throws IOException {
|
|
//System.err.println("Saving obj. " + uid);
|
|
this.objectIndicesManager.set(uid, fieldUIDs, propertyUIDs);
|
|
}
|
|
|
|
private void writeObjectInfoNull(long uid) throws IOException {
|
|
this.objectIndicesManager.setNull(uid);
|
|
}
|
|
|
|
public <T> void writeObjectProperty(long uid, DBDataType propertyType, T loadedPropertyValue) throws IOException {
|
|
switch (propertyType) {
|
|
case INTEGER:
|
|
indices.set(uid, Integer.BYTES, (o) -> o.writeInt((int) loadedPropertyValue));
|
|
//System.err.println("Saving prop. Int " + uid + ":" + loadedPropertyValue);
|
|
break;
|
|
case OBJECT:
|
|
Output baosOutput = new Output(new ByteArrayOutputStream());
|
|
kryo.writeClassAndObject(baosOutput, loadedPropertyValue);
|
|
//System.err.println("Saving prop. Obj " + uid + ":" + loadedPropertyValue);
|
|
byte[] out = baosOutput.toBytes();
|
|
indices.set(uid, out.length, o -> o.write(out, 0, out.length));
|
|
break;
|
|
case UID_LIST:
|
|
LongArrayList list = (LongArrayList) loadedPropertyValue;
|
|
final int listSize = list.size();
|
|
Output baosListOutput = new Output(Long.BYTES * 100, Long.BYTES * listSize);
|
|
baosListOutput.writeVarInt(listSize, true);
|
|
for (int i = 0; i < listSize; i++) {
|
|
baosListOutput.writeVarLong(list.getLong(i), true);
|
|
}
|
|
//System.err.println("Saving prop. LOb " + uid + ":" + loadedPropertyValue);
|
|
byte[] outList = baosListOutput.toBytes();
|
|
indices.set(uid, outList.length, o -> o.write(outList, 0, outList.length));
|
|
break;
|
|
case DATABASE_OBJECT:
|
|
//System.err.println("Saving prop. DBObj " + uid + ":" + loadedPropertyValue);
|
|
if (loadedPropertyValue == null) {
|
|
writeObjectInfoNull(uid);
|
|
} else {
|
|
((DBObject) loadedPropertyValue).writeToDisk(uid);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void registerClass(Class<?> clazz, int id) {
|
|
kryo.register(clazz, 100 + id);
|
|
}
|
|
|
|
public long allocateNullValue() {
|
|
return indices.add(0);
|
|
}
|
|
|
|
@Override
|
|
public long clean() {
|
|
return 0;//indices.clean();
|
|
}
|
|
|
|
public long[] allocateNewUIDs(int quantity) {
|
|
long[] ids = new long[quantity];
|
|
for (int i = 0; i < quantity; i++) {
|
|
ids[i] = allocateNullValue();
|
|
}
|
|
return ids;
|
|
}
|
|
}
|