strangedb/src/main/java/org/warp/cowdb/database/DatabaseObjectsIO.java

606 lines
20 KiB
Java

package org.warp.cowdb.database;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
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.warp.cowdb.*;
import org.warp.jcwdb.ann.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Supplier;
public class DatabaseObjectsIO implements IObjectsIO {
private final IDatabaseTools databaseTools;
private final DatabaseReferencesIO referencesIO;
private final Object accessLock = new Object();
private final DatabaseDataInitializer dataInitializer;
private Kryo kryo = new Kryo();
public DatabaseObjectsIO(IDatabaseTools databaseTools, DatabaseReferencesIO referencesIO) {
this.databaseTools = databaseTools;
this.referencesIO = referencesIO;
this.dataInitializer = new DatabaseDataInitializer(this);
kryo.setRegistrationRequired(false);
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++);
registerClass(TreeSet.class, id++);
registerClass(SortedSet.class, id++);
registerClass(SortedMap.class, id++);
}
@Override
public <T extends EnhancedObject> T loadEnhancedObject(long reference, Class<T> objectType) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return null;
}
int serializedVersion = Byte.toUnsignedInt(buffer.get());
int fieldsCount = buffer.getInt();
int methodsCount = buffer.getInt();
long[] fieldRefs = new long[fieldsCount];
long[] methodRefs = new long[methodsCount];
for (int i = 0; i < fieldsCount; i++) {
fieldRefs[i] = buffer.getLong();
}
for (int i = 0; i < methodsCount; i++) {
methodRefs[i] = buffer.getLong();
}
return preloadEnhancedObject(objectType, serializedVersion, fieldRefs, methodRefs);
}
}
@SuppressWarnings("unchecked")
Object loadData(DBDataType propertyType, long dataReference, Supplier<Class<?>> returnType) throws IOException {
switch (propertyType) {
case ENHANCED_OBJECT:
return loadEnhancedObject(dataReference, (Class<? extends EnhancedObject>) returnType.get());
case OBJECT:
return loadObject(dataReference);
case REFERENCES_LIST:
return loadReferencesList(dataReference);
case BOOLEAN:
return loadBoolean(dataReference);
case BYTE:
return loadByte(dataReference);
case SHORT:
return loadShort(dataReference);
case CHAR:
return loadChar(dataReference);
case INTEGER:
return loadInt(dataReference);
case LONG:
return loadLong(dataReference);
default:
throw new NullPointerException("Unknown data type");
}
}
<T> void setData(long reference, DBDataType propertyType, T loadedPropertyValue) throws IOException {
switch (propertyType) {
case BOOLEAN:
setBoolean(reference, loadedPropertyValue != null && (boolean) loadedPropertyValue);
break;
case BYTE:
setByte(reference, loadedPropertyValue == null ? 0 : (byte) loadedPropertyValue);
break;
case SHORT:
setShort(reference, loadedPropertyValue == null ? 0 : (short) loadedPropertyValue);
break;
case CHAR:
setChar(reference, loadedPropertyValue == null ? 0 : (char) loadedPropertyValue);
break;
case INTEGER:
setInt(reference, loadedPropertyValue == null ? 0 : (int) loadedPropertyValue);
break;
case LONG:
setLong(reference, loadedPropertyValue == null ? 0 : (long) loadedPropertyValue);
break;
case OBJECT:
setObject(reference, loadedPropertyValue);
break;
case REFERENCES_LIST:
setReferencesList(reference, (LongArrayList) loadedPropertyValue);
break;
case ENHANCED_OBJECT:
setEnhancedObject(reference, (EnhancedObject) loadedPropertyValue);
break;
}
}
@Override
public <T extends EnhancedObject> void setEnhancedObject(long reference, T value) throws IOException {
synchronized (accessLock) {
if (value != null) {
EnhancedObjectFullInfo objectFullInfo = value.getAllInfo();
if (objectFullInfo == null || objectFullInfo.getFieldReferences() == null || objectFullInfo.getPropertyReferences() == null) {
throw new NullPointerException("An EnhancedObject has been initialized using the empty constructor!");
}
int totalSize = Byte.BYTES + Integer.BYTES * 2 + objectFullInfo.getFieldReferences().length * Long.BYTES + objectFullInfo.getPropertyReferences().length * Long.BYTES;
ByteBuffer buffer = ByteBuffer.allocate(totalSize);
buffer.put((byte) objectFullInfo.getVersion());
buffer.putInt(objectFullInfo.getFieldReferences().length);
buffer.putInt(objectFullInfo.getPropertyReferences().length);
for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) {
buffer.putLong(objectFullInfo.getFieldReferences()[i]);
}
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
buffer.putLong(objectFullInfo.getPropertyReferences()[i]);
}
long[] fieldReferences = objectFullInfo.getFieldReferences();
DBDataType[] fieldTypes = objectFullInfo.getFieldTypes();
Field[] fields = objectFullInfo.getFields();
long[] propertyReferences = objectFullInfo.getPropertyReferences();
DBDataType[] propertyTypes = objectFullInfo.getPropertyTypes();
Object[] propertyValues = objectFullInfo.getLoadedPropertyValues();
for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) {
if (fields[i] != null) {
try {
setData(fieldReferences[i], fieldTypes[i], fields[i].get(value));
} catch (IllegalAccessException e) {
throw new IOException(e);
}
}
}
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
if (propertyValues[i] != null) {
setData(propertyReferences[i], propertyTypes[i], propertyValues[i]);
}
}
buffer.flip();
referencesIO.writeToReference(reference, totalSize, buffer);
} else {
referencesIO.writeToReference(reference, 0, null);
}
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T loadObject(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return null;
}
buffer.rewind();
byte[] data = buffer.array();
return (T) kryo.readClassAndObject(new Input(data));
}
}
@Override
public LongArrayList loadReferencesList(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return null;
}
int itemsCount = buffer.getInt();
LongArrayList arrayList = new LongArrayList();
for (int i = 0; i < itemsCount; i++) {
arrayList.add(buffer.getLong());
}
return arrayList;
}
}
@Override
public boolean loadBoolean(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return false;
}
return buffer.get() == 1;
}
}
@Override
public byte loadByte(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return 0;
}
return buffer.get();
}
}
@Override
public short loadShort(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return 0;
}
return buffer.getShort();
}
}
@Override
public char loadChar(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return 0;
}
return buffer.getChar();
}
}
@Override
public int loadInt(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return 0;
}
return buffer.getInt();
}
}
@Override
public long loadLong(long reference) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = referencesIO.readFromReference(reference);
if (buffer.limit() == 0) {
return 0;
}
return buffer.getLong();
}
}
@Override
public <T> void setObject(long reference, T value) throws IOException {
synchronized (accessLock) {
if (value != null) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Output output = new Output(outputStream);
kryo.writeClassAndObject(output, value);
output.flush();
byte[] data = outputStream.toByteArray();
ByteBuffer dataByteBuffer = ByteBuffer.wrap(data);
referencesIO.writeToReference(reference, data.length, dataByteBuffer);
} else {
referencesIO.writeToReference(reference, 0, null);
}
}
}
@Override
public void setReferencesList(long reference, LongArrayList value) throws IOException {
synchronized (accessLock) {
if (value != null) {
int items = value.size();
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES * items + Integer.BYTES);
buffer.putInt(items);
for (int i = 0; i < items; i++) {
buffer.putLong(value.getLong(i));
}
buffer.flip();
referencesIO.writeToReference(reference, buffer.limit(), buffer);
} else {
referencesIO.writeToReference(reference, 0, null);
}
}
}
@Override
public void setBoolean(long reference, boolean value) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES);
buffer.put(value ? (byte) 1 : (byte) 0);
buffer.flip();
referencesIO.writeToReference(reference, Byte.BYTES, buffer);
}
}
@Override
public void setByte(long reference, byte value) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES);
buffer.put(value);
buffer.flip();
referencesIO.writeToReference(reference, Byte.BYTES, buffer);
}
}
@Override
public void setShort(long reference, short value) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);
buffer.putShort(value);
buffer.flip();
referencesIO.writeToReference(reference, Short.BYTES, buffer);
}
}
@Override
public void setChar(long reference, char value) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = ByteBuffer.allocate(Character.BYTES);
buffer.putChar(value);
buffer.flip();
referencesIO.writeToReference(reference, Character.BYTES, buffer);
}
}
@Override
public void setInt(long reference, int value) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(value);
buffer.flip();
referencesIO.writeToReference(reference, Integer.BYTES, buffer);
}
}
@Override
public void setLong(long reference, long value) throws IOException {
synchronized (accessLock) {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(value);
buffer.flip();
referencesIO.writeToReference(reference, Long.BYTES, buffer);
}
}
@Override
public long newNullObject() throws IOException {
synchronized (accessLock) {
return referencesIO.allocateReference();
}
}
@Override
public void loadProperty(EnhancedObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException {
synchronized (accessLock) {
obj.setProperty(propertyId, loadData(propertyType, propertyUID, property::getReturnType));
}
}
@Override
public void registerClass(Class<?> type, int id) {
if (id < -100) {
throw new IllegalArgumentException();
}
kryo.register(type, 100 + id);
}
private <T extends EnhancedObject> void preloadEnhancedObjectProperties(T obj, long[] propertyReferences) {
// Declare the variables needed to get the biggest property Id
Method[] unorderedPropertyGetters = obj.getPropertyGetters();
Method[] unorderedPropertySetters = obj.getPropertySetters();
// 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, propertyReferences, setterMethods, getterMethods);
}
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;
}
private <T extends EnhancedObject> void preloadEnhancedObjectFields(T obj, long[] fieldReferences) 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, fieldReferences[fieldId]);
fields[fieldId] = field;
orderedFieldTypes[fieldId] = fieldType;
}
// Set fields metadata
obj.setFields(fields, orderedFieldTypes, fieldReferences);
}
<T extends EnhancedObject> void loadField(T obj, Field field, DBDataType fieldType, long fieldReference) throws IOException {
Object data = loadData(fieldType, fieldReference, field::getType);
try {
if (fieldType == DBDataType.OBJECT && data != null) {
if (!field.getType().isInstance(data)) {
throw new IOException("There is an attempt to load an object of type " + data.getClass() + " into a field of type " + field.getType());
}
}
FieldUtils.writeField(field, obj, data, true);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
<T extends EnhancedObject> Field[] getFields(T obj) {
return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class);
}
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;
}
private <T extends EnhancedObject> T toInstance(Class<T> type) throws IOException {
try {
T obj = type.getConstructor().newInstance();
obj.setDatabaseTools(databaseTools);
return obj;
} catch (NoSuchMethodException e) {
throw new IOException("You must declare a public empty constructor in class " + type + ": public " + type.getSimpleName() + "()", e);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new IOException(e);
}
}
private <T extends EnhancedObject> T preloadEnhancedObject(Class<T> objectType, int serializedVersion, long[] fieldRefs, long[] methodRefs) throws IOException {
// Instantiate the class to an object
T obj = toInstance(objectType);
// Check the serialized version
DBClass dbClass = objectType.getAnnotation(DBClass.class);
int classVersion = 0;
if (dbClass != null) {
classVersion = dbClass.version();
}
if (classVersion > serializedVersion) {
DatabaseEnhancedObjectUpgrader enhancedObjectUpgrader = new DatabaseEnhancedObjectUpgrader(this, fieldRefs, methodRefs);
dataInitializer.initializeDBObject(obj);
obj.onUpgrade(serializedVersion, enhancedObjectUpgrader);
} else if (classVersion < serializedVersion) {
throw new IllegalStateException("The serialized class is more recent than the current version of that class!");
} else {
preloadEnhancedObjectFields(obj, fieldRefs);
preloadEnhancedObjectProperties(obj, methodRefs);
}
return obj;
}
public long[] allocateNewUIDs(int quantity) throws IOException {
long[] ids = new long[quantity];
for (int i = 0; i < quantity; i++) {
ids[i] = newNullObject();
}
return ids;
}
@Override
public IDataInitializer getDataInitializer() {
return dataInitializer;
}
}