Updated to 1.5.0
This commit is contained in:
parent
ba07259cfc
commit
ba103ddde1
5
pom.xml
5
pom.xml
@ -6,11 +6,10 @@
|
||||
|
||||
<groupId>org.warp</groupId>
|
||||
<artifactId>jcwdb</artifactId>
|
||||
<version>1.4</version>
|
||||
<version>1.5.0</version>
|
||||
|
||||
<name>jcwdb</name>
|
||||
<!-- FIXME change it to the project's website -->
|
||||
<url>http://www.example.com</url>
|
||||
<url>https://git.ignuranza.net/andreacavalli/JCWDB</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
@ -1,33 +0,0 @@
|
||||
package org.warp.cowdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ByteBufferBackedInputStream extends InputStream {
|
||||
|
||||
ByteBuffer buf;
|
||||
|
||||
public ByteBufferBackedInputStream(ByteBuffer buf) {
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
}
|
||||
return buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int read(byte[] bytes, int off, int len)
|
||||
throws IOException {
|
||||
if (!buf.hasRemaining()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = Math.min(len, buf.remaining());
|
||||
buf.get(bytes, off, len);
|
||||
return len;
|
||||
|
||||
}
|
||||
}
|
@ -1,34 +1,14 @@
|
||||
package org.warp.cowdb;
|
||||
|
||||
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.jcwdb.ann.*;
|
||||
import org.warp.cowdb.database.*;
|
||||
|
||||
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.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_ID;
|
||||
|
||||
public class Database implements IDatabase {
|
||||
public class Database implements IDatabase, IDatabaseTools {
|
||||
|
||||
private final IDatabaseTools databaseTools;
|
||||
private final DatabaseFileIO fileIO;
|
||||
private final DatabaseBlocksIO blocksIO;
|
||||
private final DatabaseBlocksMetadata blocksMetadata;
|
||||
@ -47,22 +27,13 @@ public class Database implements IDatabase {
|
||||
if (Files.notExists(referencesMetaFile)) {
|
||||
Files.createFile(referencesMetaFile);
|
||||
}
|
||||
this.databaseTools = this;
|
||||
this.fileIO = new DatabaseFileIO(dataFile);
|
||||
this.blocksMetadata = new DatabaseBlocksMetadata(blocksMetaFile);
|
||||
this.blocksIO = new DatabaseBlocksIO(fileIO, blocksMetadata);
|
||||
this.referencesMetadata = new DatabaseReferencesMetadata(referencesMetaFile);
|
||||
this.referencesIO = new DatabaseReferencesIO(blocksIO, referencesMetadata);
|
||||
this.objectsIO = new DatabaseObjectsIO(this, referencesIO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IDataInitializer getDataInitializer() {
|
||||
return objectsIO.dataInitializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IObjectsIO getObjectsIO() {
|
||||
return objectsIO;
|
||||
this.objectsIO = new DatabaseObjectsIO(databaseTools, referencesIO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -73,26 +44,18 @@ public class Database implements IDatabase {
|
||||
this.fileIO.close();
|
||||
}
|
||||
|
||||
public <T extends EnhancedObject> T loadRoot(Class<T> type) throws IOException {
|
||||
return loadRoot(type, () -> {
|
||||
T obj = objectsIO.toInstance(type);
|
||||
objectsIO.dataInitializer.initializeDBObject(obj);
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
|
||||
public <T extends EnhancedObject> T loadRoot(Class<T> type, SupplierWithIO<T> ifAbsent) throws IOException {
|
||||
public <T extends EnhancedObject> T loadRoot(Class<T> type, FunctionWithIO<IDatabaseTools, T> ifAbsent) throws IOException {
|
||||
if (loadedRootObject != null) {
|
||||
throw new RuntimeException("Root already set!");
|
||||
}
|
||||
T root;
|
||||
if (referencesMetadata.firstFreeReference > 0) {
|
||||
if (referencesMetadata.getFirstFreeReference() > 0) {
|
||||
root = objectsIO.loadEnhancedObject(0, type);
|
||||
} else {
|
||||
if (objectsIO.newNullObject() != 0) {
|
||||
throw new IOException("Can't allocate root!");
|
||||
} else {
|
||||
root = ifAbsent.getWithIO();
|
||||
root = ifAbsent.apply(Database.this);
|
||||
objectsIO.setEnhancedObject(0, root);
|
||||
}
|
||||
}
|
||||
@ -104,896 +67,14 @@ public class Database implements IDatabase {
|
||||
this.objectsIO.registerClass(type, id);
|
||||
}
|
||||
|
||||
public static class DatabaseDataInitializer implements IDataInitializer {
|
||||
|
||||
private final DatabaseObjectsIO objectsIO;
|
||||
|
||||
public DatabaseDataInitializer(DatabaseObjectsIO objectsIO) {
|
||||
this.objectsIO = objectsIO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeDBObject(EnhancedObject obj) throws IOException {
|
||||
initializeDBObjectFields(obj);
|
||||
initializeDBObjectProperties(obj);
|
||||
}
|
||||
|
||||
private void initializeDBObjectFields(EnhancedObject obj) throws IOException {
|
||||
// Declare the variables needed to get the biggest field Id
|
||||
Field[] unorderedFields = objectsIO.getFields(obj);
|
||||
// Find the biggest field Id
|
||||
int biggestFieldId = objectsIO.getBiggestFieldId(unorderedFields);
|
||||
|
||||
// Allocate new UIDs
|
||||
long[] fieldUIDs = objectsIO.allocateNewUIDs(biggestFieldId + 1);
|
||||
|
||||
// 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();
|
||||
objectsIO.loadField(obj, field, fieldType, fieldUIDs[fieldId]);
|
||||
fields[fieldId] = field;
|
||||
orderedFieldTypes[fieldId] = fieldType;
|
||||
}
|
||||
// Set fields metadata
|
||||
obj.setFields(fields, orderedFieldTypes, fieldUIDs);
|
||||
}
|
||||
|
||||
private void initializeDBObjectProperties(EnhancedObject obj) throws IOException {
|
||||
// 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 = objectsIO.getBiggestPropertyGetterId(unorderedPropertyGetters);
|
||||
int biggestSetter = objectsIO.getBiggestPropertySetterId(unorderedPropertySetters);
|
||||
int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter;
|
||||
|
||||
// Allocate new UIDs
|
||||
long[] propertyUIDs = objectsIO.allocateNewUIDs(biggestPropertyId + 1);
|
||||
|
||||
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);
|
||||
}
|
||||
@Override
|
||||
public void initializeEnhancedObject(EnhancedObject enhancedObject) throws IOException {
|
||||
this.objectsIO.getDataInitializer().initializeDBObject(enhancedObject);
|
||||
}
|
||||
|
||||
public static class DatabaseEnhancedObjectUpgrader implements EnhancedObjectUpgrader {
|
||||
private final DatabaseObjectsIO objectsIO;
|
||||
private final long[] fieldRefs;
|
||||
private final long[] methodRefs;
|
||||
|
||||
public DatabaseEnhancedObjectUpgrader(DatabaseObjectsIO objectsIO, long[] fieldRefs, long[] methodRefs) {
|
||||
this.objectsIO = objectsIO;
|
||||
this.fieldRefs = fieldRefs;
|
||||
this.methodRefs = methodRefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getField(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException {
|
||||
return objectsIO.loadData(type, fieldRefs[id], enhancedClassType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getMethod(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException {
|
||||
return objectsIO.loadData(type, methodRefs[id], enhancedClassType);
|
||||
}
|
||||
@Override
|
||||
public IObjectsIO getObjectsIO() {
|
||||
return objectsIO;
|
||||
}
|
||||
|
||||
public static class DatabaseObjectsIO implements IObjectsIO {
|
||||
|
||||
private final IDatabase database;
|
||||
private final DatabaseReferencesIO referencesIO;
|
||||
|
||||
private final Object accessLock = new Object();
|
||||
private final DatabaseDataInitializer dataInitializer;
|
||||
|
||||
private Kryo kryo = new Kryo();
|
||||
|
||||
private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) {
|
||||
this.database = database;
|
||||
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")
|
||||
private 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");
|
||||
}
|
||||
}
|
||||
|
||||
private <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();
|
||||
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);
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
|
||||
private <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);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends EnhancedObject> Field[] getFields(T obj) {
|
||||
return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class);
|
||||
}
|
||||
|
||||
private 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.database = database;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseReferencesIO implements IReferencesIO {
|
||||
|
||||
private final DatabaseBlocksIO blocksIO;
|
||||
private final DatabaseReferencesMetadata referencesMetadata;
|
||||
|
||||
public DatabaseReferencesIO(DatabaseBlocksIO blocksIO, DatabaseReferencesMetadata referencesMetadata) {
|
||||
this.blocksIO = blocksIO;
|
||||
this.referencesMetadata = referencesMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long allocateReference() throws IOException {
|
||||
return referencesMetadata.newReference(EMPTY_BLOCK_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long allocateReference(int size, ByteBuffer data) throws IOException {
|
||||
long blockId = (size == 0) ? EMPTY_BLOCK_ID : blocksIO.newBlock(size, data);
|
||||
return referencesMetadata.newReference(blockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToReference(long reference, int size, ByteBuffer data) throws IOException {
|
||||
long blockId = (size == 0) ? EMPTY_BLOCK_ID : blocksIO.newBlock(size, data);
|
||||
referencesMetadata.editReference(reference, blockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer readFromReference(long reference) throws IOException {
|
||||
long blockId = referencesMetadata.getReference(reference);
|
||||
return blocksIO.readBlock(blockId);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseReferencesMetadata implements IReferencesMetadata {
|
||||
private final SeekableByteChannel metaFileChannel;
|
||||
private final int REF_META_BYTES_COUNT = Long.BYTES;
|
||||
private long firstFreeReference;
|
||||
|
||||
private DatabaseReferencesMetadata(Path refMetaFile) throws IOException {
|
||||
metaFileChannel = Files.newByteChannel(refMetaFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
firstFreeReference = metaFileChannel.size() / REF_META_BYTES_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getReference(long reference) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(REF_META_BYTES_COUNT);
|
||||
if (reference >= firstFreeReference) {
|
||||
return EMPTY_BLOCK_ID;
|
||||
}
|
||||
SeekableByteChannel currentFileChannel = metaFileChannel.position(reference * REF_META_BYTES_COUNT);
|
||||
currentFileChannel.read(buffer);
|
||||
buffer.flip();
|
||||
long block = buffer.getLong();
|
||||
if (buffer.limit() == 0 || block == 0xFFFFFFFFFFFFFFFFL) {
|
||||
return EMPTY_BLOCK_ID;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newReference(long blockId) throws IOException {
|
||||
long newReference = firstFreeReference++;
|
||||
editReference(newReference, blockId);
|
||||
return newReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void editReference(long reference, long blockId) throws IOException {
|
||||
ByteBuffer data = ByteBuffer.allocate(REF_META_BYTES_COUNT);
|
||||
data.putLong(blockId);
|
||||
SeekableByteChannel currentFileChannel = metaFileChannel.position(reference * REF_META_BYTES_COUNT);
|
||||
data.flip();
|
||||
currentFileChannel.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
metaFileChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseBlocksIO implements IBlocksIO {
|
||||
|
||||
private final DatabaseFileIO fileIO;
|
||||
private final IBlocksMetadata blocksMetadata;
|
||||
|
||||
private DatabaseBlocksIO(DatabaseFileIO fileIO, IBlocksMetadata blocksMetadata) {
|
||||
this.fileIO = fileIO;
|
||||
this.blocksMetadata = blocksMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newBlock(int size, ByteBuffer data) throws IOException {
|
||||
long index = fileIO.writeAtEnd(size, data);
|
||||
return blocksMetadata.newBlock(index, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer readBlock(long blockId) throws IOException {
|
||||
if (blockId == EMPTY_BLOCK_ID) {
|
||||
return ByteBuffer.wrap(new byte[0]);
|
||||
}
|
||||
BlockInfo blockInfo = blocksMetadata.getBlockInfo(blockId);
|
||||
return fileIO.readAt(blockInfo.getIndex(), blockInfo.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseBlocksMetadata implements IBlocksMetadata {
|
||||
private final SeekableByteChannel metaFileChannel;
|
||||
private final int BLOCK_META_BYTES_COUNT = Long.BYTES + Integer.BYTES;
|
||||
private long firstFreeBlock;
|
||||
|
||||
private DatabaseBlocksMetadata(Path metaFile) throws IOException {
|
||||
metaFileChannel = Files.newByteChannel(metaFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
firstFreeBlock = metaFileChannel.size() / BLOCK_META_BYTES_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockInfo getBlockInfo(long blockId) throws IOException {
|
||||
if (blockId == EMPTY_BLOCK_ID) {
|
||||
return EMPTY_BLOCK_INFO;
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(BLOCK_META_BYTES_COUNT);
|
||||
metaFileChannel.position(blockId * BLOCK_META_BYTES_COUNT).read(buffer);
|
||||
buffer.flip();
|
||||
long index = buffer.getLong();
|
||||
int size = buffer.getInt();
|
||||
return new BlockInfo(index, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newBlock(long index, int size) throws IOException {
|
||||
long newBlockId = firstFreeBlock++;
|
||||
ByteBuffer data = ByteBuffer.allocate(BLOCK_META_BYTES_COUNT);
|
||||
data.putLong(index);
|
||||
data.putInt(size);
|
||||
data.flip();
|
||||
metaFileChannel.position(newBlockId * BLOCK_META_BYTES_COUNT).write(data);
|
||||
return newBlockId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
metaFileChannel.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DatabaseFileIO implements IFileIO {
|
||||
|
||||
private final SeekableByteChannel dataFileChannel;
|
||||
private final Object dataAccessLock = new Object();
|
||||
private long firstFreeIndex;
|
||||
|
||||
private DatabaseFileIO(Path dataFile) throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
dataFileChannel = Files.newByteChannel(dataFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
firstFreeIndex = dataFileChannel.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer readAt(long index, int length) throws IOException {
|
||||
ByteBuffer dataBuffer = ByteBuffer.allocate(length);
|
||||
dataFileChannel.position(index).read(dataBuffer);
|
||||
dataBuffer.flip();
|
||||
return dataBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAt(long index, int length, ByteBuffer data) throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
if (data.position() != 0) {
|
||||
throw new IOException("You didn't flip the ByteBuffer!");
|
||||
}
|
||||
if (firstFreeIndex < index + length) {
|
||||
firstFreeIndex = index + length;
|
||||
}
|
||||
dataFileChannel.position(index).write(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long writeAtEnd(int length, ByteBuffer data) throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
long index = firstFreeIndex;
|
||||
firstFreeIndex += length;
|
||||
writeAt(index, length, data);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
dataFileChannel.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package org.warp.cowdb;
|
||||
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
import org.warp.cowdb.database.EnhancedObjectFullInfo;
|
||||
import org.warp.jcwdb.ann.DBClass;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBPropertyGetter;
|
||||
@ -14,7 +15,7 @@ import java.util.Map;
|
||||
|
||||
public abstract class EnhancedObject {
|
||||
protected final int version;
|
||||
protected IDatabase database;
|
||||
protected IDatabaseTools databaseTools;
|
||||
private Field[] fields;
|
||||
private DBDataType[] fieldTypes;
|
||||
private long[] fieldReferences;
|
||||
@ -31,10 +32,10 @@ public abstract class EnhancedObject {
|
||||
version = getClassVersion();
|
||||
}
|
||||
|
||||
public EnhancedObject(IDatabase database) throws IOException {
|
||||
this.database = database;
|
||||
public EnhancedObject(IDatabaseTools databaseTools) throws IOException {
|
||||
this.databaseTools = databaseTools;
|
||||
version = getClassVersion();
|
||||
database.getDataInitializer().initializeDBObject(this);
|
||||
databaseTools.initializeEnhancedObject(this);
|
||||
}
|
||||
|
||||
public void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldReferences) {
|
||||
@ -82,7 +83,7 @@ public abstract class EnhancedObject {
|
||||
private <T> T getProperty(int propertyId) throws IOException {
|
||||
if (!loadedProperties[propertyId]) {
|
||||
long propertyUID = propertyReferences[propertyId];
|
||||
database.getObjectsIO().loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID);
|
||||
databaseTools.getObjectsIO().loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID);
|
||||
}
|
||||
return (T) loadedPropertyValues[propertyId];
|
||||
}
|
||||
@ -113,4 +114,12 @@ public abstract class EnhancedObject {
|
||||
public void onUpgrade(int oldObjectVersion, EnhancedObjectUpgrader enhancedObjectUpgrader) throws IOException {
|
||||
throw new NotImplementedException("Method onUpgrade() is not implemented for class " + this.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
public IDatabaseTools getDatabaseTools() {
|
||||
return databaseTools;
|
||||
}
|
||||
|
||||
public void setDatabaseTools(IDatabaseTools databaseTools) {
|
||||
this.databaseTools = databaseTools;
|
||||
}
|
||||
}
|
||||
|
78
src/main/java/org/warp/cowdb/FunctionWithIO.java
Normal file
78
src/main/java/org/warp/cowdb/FunctionWithIO.java
Normal file
@ -0,0 +1,78 @@
|
||||
package org.warp.cowdb;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a function that accepts one argument and produces a result.
|
||||
*
|
||||
* <p>This is a <a href="package-summary.html">functional interface</a>
|
||||
* whose functional method is {@link #apply(Object)}.
|
||||
*
|
||||
* @param <T> the type of the input to the function
|
||||
* @param <R> the type of the result of the function
|
||||
*
|
||||
* @since 1.8
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FunctionWithIO<T, R> {
|
||||
|
||||
/**
|
||||
* Applies this function to the given argument.
|
||||
*
|
||||
* @param t the function argument
|
||||
* @return the function result
|
||||
*/
|
||||
R apply(T t) throws IOException;
|
||||
|
||||
/**
|
||||
* Returns a composed function that first applies the {@code before}
|
||||
* function to its input, and then applies this function to the result.
|
||||
* If evaluation of either function throws an exception, it is relayed to
|
||||
* the caller of the composed function.
|
||||
*
|
||||
* @param <V> the type of input to the {@code before} function, and to the
|
||||
* composed function
|
||||
* @param before the function to apply before this function is applied
|
||||
* @return a composed function that first applies the {@code before}
|
||||
* function and then applies this function
|
||||
* @throws NullPointerException if before is null
|
||||
*
|
||||
* @see #andThen(FunctionWithIO)
|
||||
*/
|
||||
default <V> FunctionWithIO<V, R> compose(FunctionWithIO<? super V, ? extends T> before) {
|
||||
Objects.requireNonNull(before);
|
||||
return (V v) -> apply(before.apply(v));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a composed function that first applies this function to
|
||||
* its input, and then applies the {@code after} function to the result.
|
||||
* If evaluation of either function throws an exception, it is relayed to
|
||||
* the caller of the composed function.
|
||||
*
|
||||
* @param <V> the type of output of the {@code after} function, and of the
|
||||
* composed function
|
||||
* @param after the function to apply after this function is applied
|
||||
* @return a composed function that first applies this function and then
|
||||
* applies the {@code after} function
|
||||
* @throws NullPointerException if after is null
|
||||
*
|
||||
* @see #compose(FunctionWithIO)
|
||||
*/
|
||||
default <V> FunctionWithIO<T, V> andThen(FunctionWithIO<? super R, ? extends V> after) {
|
||||
Objects.requireNonNull(after);
|
||||
return (T t) -> after.apply(apply(t));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that always returns its input argument.
|
||||
*
|
||||
* @param <T> the type of the input and output objects to the function
|
||||
* @return a function that always returns its input argument
|
||||
*/
|
||||
static <T> FunctionWithIO<T, T> identity() {
|
||||
return t -> t;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,4 @@ import java.io.IOException;
|
||||
public interface IDatabase {
|
||||
|
||||
void close() throws IOException;
|
||||
IDataInitializer getDataInitializer();
|
||||
IObjectsIO getObjectsIO();
|
||||
}
|
||||
|
12
src/main/java/org/warp/cowdb/IDatabaseTools.java
Normal file
12
src/main/java/org/warp/cowdb/IDatabaseTools.java
Normal file
@ -0,0 +1,12 @@
|
||||
package org.warp.cowdb;
|
||||
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public interface IDatabaseTools {
|
||||
void initializeEnhancedObject(EnhancedObject enhancedObject) throws IOException;
|
||||
|
||||
IObjectsIO getObjectsIO();
|
||||
}
|
@ -108,4 +108,6 @@ public interface IObjectsIO {
|
||||
void loadProperty(EnhancedObject enhancedObject, int propertyId, Method propertyGetter, DBDataType propertyType, long propertyUID) throws IOException;
|
||||
|
||||
void registerClass(Class<?> type, int id);
|
||||
|
||||
IDataInitializer getDataInitializer();
|
||||
}
|
||||
|
@ -28,4 +28,6 @@ public interface IReferencesMetadata {
|
||||
* Close file
|
||||
*/
|
||||
void close() throws IOException;
|
||||
|
||||
long getFirstFreeReference();
|
||||
}
|
||||
|
41
src/main/java/org/warp/cowdb/database/DatabaseBlocksIO.java
Normal file
41
src/main/java/org/warp/cowdb/database/DatabaseBlocksIO.java
Normal file
@ -0,0 +1,41 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
import org.warp.cowdb.BlockInfo;
|
||||
import org.warp.cowdb.IBlocksIO;
|
||||
import org.warp.cowdb.IBlocksMetadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_ID;
|
||||
|
||||
public class DatabaseBlocksIO implements IBlocksIO {
|
||||
|
||||
private final DatabaseFileIO fileIO;
|
||||
private final IBlocksMetadata blocksMetadata;
|
||||
|
||||
public DatabaseBlocksIO(DatabaseFileIO fileIO, IBlocksMetadata blocksMetadata) {
|
||||
this.fileIO = fileIO;
|
||||
this.blocksMetadata = blocksMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newBlock(int size, ByteBuffer data) throws IOException {
|
||||
long index = fileIO.writeAtEnd(size, data);
|
||||
return blocksMetadata.newBlock(index, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer readBlock(long blockId) throws IOException {
|
||||
if (blockId == EMPTY_BLOCK_ID) {
|
||||
return ByteBuffer.wrap(new byte[0]);
|
||||
}
|
||||
BlockInfo blockInfo = blocksMetadata.getBlockInfo(blockId);
|
||||
return fileIO.readAt(blockInfo.getIndex(), blockInfo.getSize());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
|
||||
import org.warp.cowdb.BlockInfo;
|
||||
import org.warp.cowdb.IBlocksMetadata;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
public class DatabaseBlocksMetadata implements IBlocksMetadata {
|
||||
private final SeekableByteChannel metaFileChannel;
|
||||
private final int BLOCK_META_BYTES_COUNT = Long.BYTES + Integer.BYTES;
|
||||
private long firstFreeBlock;
|
||||
|
||||
public DatabaseBlocksMetadata(Path metaFile) throws IOException {
|
||||
metaFileChannel = Files.newByteChannel(metaFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
firstFreeBlock = metaFileChannel.size() / BLOCK_META_BYTES_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockInfo getBlockInfo(long blockId) throws IOException {
|
||||
if (blockId == EMPTY_BLOCK_ID) {
|
||||
return EMPTY_BLOCK_INFO;
|
||||
}
|
||||
ByteBuffer buffer = ByteBuffer.allocate(BLOCK_META_BYTES_COUNT);
|
||||
metaFileChannel.position(blockId * BLOCK_META_BYTES_COUNT).read(buffer);
|
||||
buffer.flip();
|
||||
long index = buffer.getLong();
|
||||
int size = buffer.getInt();
|
||||
return new BlockInfo(index, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newBlock(long index, int size) throws IOException {
|
||||
long newBlockId = firstFreeBlock++;
|
||||
ByteBuffer data = ByteBuffer.allocate(BLOCK_META_BYTES_COUNT);
|
||||
data.putLong(index);
|
||||
data.putInt(size);
|
||||
data.flip();
|
||||
metaFileChannel.position(newBlockId * BLOCK_META_BYTES_COUNT).write(data);
|
||||
return newBlockId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
metaFileChannel.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDataInitializer;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
import org.warp.jcwdb.ann.DBPropertyGetter;
|
||||
import org.warp.jcwdb.ann.DBPropertySetter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class DatabaseDataInitializer implements IDataInitializer {
|
||||
|
||||
private final DatabaseObjectsIO objectsIO;
|
||||
|
||||
public DatabaseDataInitializer(DatabaseObjectsIO objectsIO) {
|
||||
this.objectsIO = objectsIO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeDBObject(EnhancedObject obj) throws IOException {
|
||||
initializeDBObjectFields(obj);
|
||||
initializeDBObjectProperties(obj);
|
||||
}
|
||||
|
||||
private void initializeDBObjectFields(EnhancedObject obj) throws IOException {
|
||||
// Declare the variables needed to get the biggest field Id
|
||||
Field[] unorderedFields = objectsIO.getFields(obj);
|
||||
// Find the biggest field Id
|
||||
int biggestFieldId = objectsIO.getBiggestFieldId(unorderedFields);
|
||||
|
||||
// Allocate new UIDs
|
||||
long[] fieldUIDs = objectsIO.allocateNewUIDs(biggestFieldId + 1);
|
||||
|
||||
// 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();
|
||||
objectsIO.loadField(obj, field, fieldType, fieldUIDs[fieldId]);
|
||||
fields[fieldId] = field;
|
||||
orderedFieldTypes[fieldId] = fieldType;
|
||||
}
|
||||
// Set fields metadata
|
||||
obj.setFields(fields, orderedFieldTypes, fieldUIDs);
|
||||
}
|
||||
|
||||
private void initializeDBObjectProperties(EnhancedObject obj) throws IOException {
|
||||
// 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 = objectsIO.getBiggestPropertyGetterId(unorderedPropertyGetters);
|
||||
int biggestSetter = objectsIO.getBiggestPropertySetterId(unorderedPropertySetters);
|
||||
int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter;
|
||||
|
||||
// Allocate new UIDs
|
||||
long[] propertyUIDs = objectsIO.allocateNewUIDs(biggestPropertyId + 1);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
import org.warp.cowdb.EnhancedObjectUpgrader;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DatabaseEnhancedObjectUpgrader implements EnhancedObjectUpgrader {
|
||||
private final DatabaseObjectsIO objectsIO;
|
||||
private final long[] fieldRefs;
|
||||
private final long[] methodRefs;
|
||||
|
||||
public DatabaseEnhancedObjectUpgrader(DatabaseObjectsIO objectsIO, long[] fieldRefs, long[] methodRefs) {
|
||||
this.objectsIO = objectsIO;
|
||||
this.fieldRefs = fieldRefs;
|
||||
this.methodRefs = methodRefs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getField(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException {
|
||||
return objectsIO.loadData(type, fieldRefs[id], enhancedClassType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object getMethod(int id, DBDataType type, Supplier<Class<?>> enhancedClassType) throws IOException {
|
||||
return objectsIO.loadData(type, methodRefs[id], enhancedClassType);
|
||||
}
|
||||
}
|
64
src/main/java/org/warp/cowdb/database/DatabaseFileIO.java
Normal file
64
src/main/java/org/warp/cowdb/database/DatabaseFileIO.java
Normal file
@ -0,0 +1,64 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
|
||||
import org.warp.cowdb.IFileIO;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class DatabaseFileIO implements IFileIO {
|
||||
|
||||
private final SeekableByteChannel dataFileChannel;
|
||||
private final Object dataAccessLock = new Object();
|
||||
private long firstFreeIndex;
|
||||
|
||||
public DatabaseFileIO(Path dataFile) throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
dataFileChannel = Files.newByteChannel(dataFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
firstFreeIndex = dataFileChannel.size();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer readAt(long index, int length) throws IOException {
|
||||
ByteBuffer dataBuffer = ByteBuffer.allocate(length);
|
||||
dataFileChannel.position(index).read(dataBuffer);
|
||||
dataBuffer.flip();
|
||||
return dataBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeAt(long index, int length, ByteBuffer data) throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
if (data.position() != 0) {
|
||||
throw new IOException("You didn't flip the ByteBuffer!");
|
||||
}
|
||||
if (firstFreeIndex < index + length) {
|
||||
firstFreeIndex = index + length;
|
||||
}
|
||||
dataFileChannel.position(index).write(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long writeAtEnd(int length, ByteBuffer data) throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
long index = firstFreeIndex;
|
||||
firstFreeIndex += length;
|
||||
writeAt(index, length, data);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
synchronized (dataAccessLock) {
|
||||
dataFileChannel.close();
|
||||
}
|
||||
}
|
||||
}
|
605
src/main/java/org/warp/cowdb/database/DatabaseObjectsIO.java
Normal file
605
src/main/java/org/warp/cowdb/database/DatabaseObjectsIO.java
Normal file
@ -0,0 +1,605 @@
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
import org.warp.cowdb.IReferencesIO;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_ID;
|
||||
|
||||
public class DatabaseReferencesIO implements IReferencesIO {
|
||||
|
||||
private final DatabaseBlocksIO blocksIO;
|
||||
private final DatabaseReferencesMetadata referencesMetadata;
|
||||
|
||||
public DatabaseReferencesIO(DatabaseBlocksIO blocksIO, DatabaseReferencesMetadata referencesMetadata) {
|
||||
this.blocksIO = blocksIO;
|
||||
this.referencesMetadata = referencesMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long allocateReference() throws IOException {
|
||||
return referencesMetadata.newReference(EMPTY_BLOCK_ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long allocateReference(int size, ByteBuffer data) throws IOException {
|
||||
long blockId = (size == 0) ? EMPTY_BLOCK_ID : blocksIO.newBlock(size, data);
|
||||
return referencesMetadata.newReference(blockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToReference(long reference, int size, ByteBuffer data) throws IOException {
|
||||
long blockId = (size == 0) ? EMPTY_BLOCK_ID : blocksIO.newBlock(size, data);
|
||||
referencesMetadata.editReference(reference, blockId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer readFromReference(long reference) throws IOException {
|
||||
long blockId = referencesMetadata.getReference(reference);
|
||||
return blocksIO.readBlock(blockId);
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
import org.warp.cowdb.IReferencesMetadata;
|
||||
import org.warp.jcwdb.ann.DBClass;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_ID;
|
||||
|
||||
public class DatabaseReferencesMetadata implements IReferencesMetadata {
|
||||
private final SeekableByteChannel metaFileChannel;
|
||||
private final int REF_META_BYTES_COUNT = Long.BYTES;
|
||||
private long firstFreeReference;
|
||||
|
||||
public DatabaseReferencesMetadata(Path refMetaFile) throws IOException {
|
||||
metaFileChannel = Files.newByteChannel(refMetaFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
firstFreeReference = metaFileChannel.size() / REF_META_BYTES_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getReference(long reference) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(REF_META_BYTES_COUNT);
|
||||
if (reference >= firstFreeReference) {
|
||||
return EMPTY_BLOCK_ID;
|
||||
}
|
||||
SeekableByteChannel currentFileChannel = metaFileChannel.position(reference * REF_META_BYTES_COUNT);
|
||||
currentFileChannel.read(buffer);
|
||||
buffer.flip();
|
||||
long block = buffer.getLong();
|
||||
if (buffer.limit() == 0 || block == 0xFFFFFFFFFFFFFFFFL) {
|
||||
return EMPTY_BLOCK_ID;
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long newReference(long blockId) throws IOException {
|
||||
long newReference = firstFreeReference++;
|
||||
editReference(newReference, blockId);
|
||||
return newReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void editReference(long reference, long blockId) throws IOException {
|
||||
ByteBuffer data = ByteBuffer.allocate(REF_META_BYTES_COUNT);
|
||||
data.putLong(blockId);
|
||||
SeekableByteChannel currentFileChannel = metaFileChannel.position(reference * REF_META_BYTES_COUNT);
|
||||
data.flip();
|
||||
currentFileChannel.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
metaFileChannel.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFirstFreeReference() {
|
||||
return firstFreeReference;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package org.warp.cowdb;
|
||||
package org.warp.cowdb.database;
|
||||
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
|
||||
@ -13,7 +13,7 @@ public class EnhancedObjectFullInfo {
|
||||
private final DBDataType[] propertyTypes;
|
||||
private final Object[] loadedPropertyValues;
|
||||
|
||||
EnhancedObjectFullInfo(int version, long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) {
|
||||
public EnhancedObjectFullInfo(int version, long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) {
|
||||
this.version = version;
|
||||
if (version > 255) {
|
||||
throw new IllegalArgumentException();
|
@ -3,8 +3,7 @@ package org.warp.cowdb.lists;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.StringJoiner;
|
||||
@ -19,8 +18,8 @@ public abstract class CowList<T> extends EnhancedObject {
|
||||
|
||||
}
|
||||
|
||||
public CowList(IDatabase database) throws IOException {
|
||||
super(database);
|
||||
public CowList(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
|
||||
public T get(int index) throws IOException {
|
||||
@ -31,7 +30,7 @@ public abstract class CowList<T> extends EnhancedObject {
|
||||
}
|
||||
|
||||
public void add(T value) throws IOException {
|
||||
long uid = database.getObjectsIO().newNullObject();
|
||||
long uid = databaseTools.getObjectsIO().newNullObject();
|
||||
synchronized (indicesAccessLock) {
|
||||
getIndices().add(uid);
|
||||
writeItemToDisk(uid, value);
|
||||
@ -46,7 +45,7 @@ public abstract class CowList<T> extends EnhancedObject {
|
||||
}
|
||||
|
||||
public void set(int index, T value) throws IOException {
|
||||
long uid = database.getObjectsIO().newNullObject();
|
||||
long uid = databaseTools.getObjectsIO().newNullObject();
|
||||
synchronized (indicesAccessLock) {
|
||||
getIndices().set(index, uid);
|
||||
writeItemToDisk(uid, value);
|
||||
@ -54,7 +53,7 @@ public abstract class CowList<T> extends EnhancedObject {
|
||||
}
|
||||
|
||||
public void add(int index, T value) throws IOException {
|
||||
long uid = database.getObjectsIO().newNullObject();
|
||||
long uid = databaseTools.getObjectsIO().newNullObject();
|
||||
synchronized (indicesAccessLock) {
|
||||
getIndices().add(index, uid);
|
||||
writeItemToDisk(uid, value);
|
||||
|
@ -3,6 +3,7 @@ package org.warp.cowdb.lists;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
|
||||
@ -25,19 +26,19 @@ public class EnhancedObjectCowList<T extends EnhancedObject> extends CowList<T>
|
||||
super();
|
||||
}
|
||||
|
||||
public EnhancedObjectCowList(IDatabase database, Class<T> type) throws IOException {
|
||||
super(database);
|
||||
public EnhancedObjectCowList(IDatabaseTools databaseTools, Class<T> type) throws IOException {
|
||||
super(databaseTools);
|
||||
this.type = type;
|
||||
indices = new LongArrayList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T loadItem(long uid) throws IOException {
|
||||
return database.getObjectsIO().loadEnhancedObject(uid, type);
|
||||
return databaseTools.getObjectsIO().loadEnhancedObject(uid, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeItemToDisk(long uid, T item) throws IOException {
|
||||
database.getObjectsIO().setEnhancedObject(uid, item);
|
||||
databaseTools.getObjectsIO().setEnhancedObject(uid, item);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package org.warp.cowdb.lists;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
|
||||
@ -22,18 +22,18 @@ public class ObjectCowList<T> extends CowList<T> {
|
||||
super();
|
||||
}
|
||||
|
||||
public ObjectCowList(IDatabase database) throws IOException {
|
||||
super(database);
|
||||
public ObjectCowList(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
indices = new LongArrayList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T loadItem(long uid) throws IOException {
|
||||
return database.getObjectsIO().loadObject(uid);
|
||||
return databaseTools.getObjectsIO().loadObject(uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeItemToDisk(long uid, T item) throws IOException {
|
||||
database.getObjectsIO().setObject(uid, item);
|
||||
databaseTools.getObjectsIO().setObject(uid, item);
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public class EnhancedClassUpdate {
|
||||
path2 = Files.createTempFile("db-tests-", ".db");
|
||||
path3 = Files.createTempFile("db-tests-", ".db");
|
||||
db = new Database(path1, path2, path3);
|
||||
OldClass root = db.loadRoot(OldClass.class);
|
||||
OldClass root = db.loadRoot(OldClass.class, OldClass::new);
|
||||
root.field1 = "Abc";
|
||||
root.field2 = 12;
|
||||
root.field4 = 13;
|
||||
@ -42,7 +42,7 @@ public class EnhancedClassUpdate {
|
||||
@Test
|
||||
public void shouldUpdateClass() throws IOException {
|
||||
db = new Database(path1, path2, path3);
|
||||
V2Class root = db.loadRoot(V2Class.class);
|
||||
V2Class root = db.loadRoot(V2Class.class, V2Class::new);
|
||||
assertEquals(root.field4, "Abc");
|
||||
assertEquals(root.field2, 12);
|
||||
assertEquals(root.field1, 13L);
|
||||
|
@ -5,6 +5,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
import org.warp.jcwdb.ann.DBPropertyGetter;
|
||||
@ -20,7 +21,7 @@ public class MultipleEnhancedObjects {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = NTestUtils.wrapDb().create((db) -> {
|
||||
root = db.get().loadRoot(RootTwoClasses.class);
|
||||
root = db.get().loadRoot(RootTwoClasses.class, RootTwoClasses::new);
|
||||
});
|
||||
root.class1 = new NTestUtils.RootClass(db.get());
|
||||
db.setRootClassValues(root.class1);
|
||||
@ -58,8 +59,8 @@ public class MultipleEnhancedObjects {
|
||||
super();
|
||||
}
|
||||
|
||||
public RootTwoClasses(IDatabase database) throws IOException {
|
||||
super(database);
|
||||
public RootTwoClasses(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
|
||||
@DBPropertyGetter(id = 0, type = DBDataType.ENHANCED_OBJECT)
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.warp.jcwdb.tests;
|
||||
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.jcwdb.ann.DBClass;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
@ -8,6 +10,7 @@ import org.warp.jcwdb.ann.DBField;
|
||||
import java.io.IOException;
|
||||
|
||||
public class OldClass extends EnhancedObject {
|
||||
|
||||
@DBField(id = 0, type = DBDataType.OBJECT)
|
||||
public String field1;
|
||||
|
||||
@ -16,4 +19,12 @@ public class OldClass extends EnhancedObject {
|
||||
|
||||
@DBField(id = 3, type = DBDataType.INTEGER)
|
||||
public int field4;
|
||||
|
||||
public OldClass() {
|
||||
|
||||
}
|
||||
|
||||
public OldClass(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
}
|
||||
|
333
src/test/java/org/warp/jcwdb/tests/Performance.java
Normal file
333
src/test/java/org/warp/jcwdb/tests/Performance.java
Normal file
@ -0,0 +1,333 @@
|
||||
package org.warp.jcwdb.tests;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.warp.cowdb.Database;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.cowdb.lists.EnhancedObjectCowList;
|
||||
import org.warp.cowdb.lists.ObjectCowList;
|
||||
import org.warp.jcwdb.VariableWrapper;
|
||||
import org.warp.jcwdb.ann.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Vector;
|
||||
|
||||
public class Performance {
|
||||
private static boolean FAST_TESTS;
|
||||
|
||||
private static Path rootDirectory;
|
||||
private static Path dbDataFile;
|
||||
private static Path dbBlocksFile;
|
||||
private static Path dbReferencesFile;
|
||||
private static Database db;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param args args[0] = true for fast tests
|
||||
* @throws IOException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
FAST_TESTS = args.length > 0 && args[0].equalsIgnoreCase("true");
|
||||
rootDirectory = Files.createTempDirectory("performance-tests");
|
||||
generateDb();
|
||||
System.out.println("Performance test started.");
|
||||
System.out.println("-------------------------------------------------------+-----------------------------------------------------------------");
|
||||
System.out.println("Test name Total Time | Time at 1 Time at 10 Time at 100 Time at 1K Time at 10K");
|
||||
System.out.println("-------------------------------------------------------+-----------------------------------------------------------------");
|
||||
testS("Database creation", 3000, Performance::deleteDb, Performance::generateDb, () -> {});
|
||||
testS("Database root creation", 3000, Performance::regenDb, () -> db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new), () -> {});
|
||||
final VariableWrapper<PreloadedListContainer> preloadedListContainer = new VariableWrapper<>(null);
|
||||
final VariableWrapper<SimpleEnhancedObject> simpleEnhancedObjectContainer = new VariableWrapper<>(null);
|
||||
testS("ObjectCowList<Int> creation", 3000, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
}, () -> preloadedListContainer.var.list = new ObjectCowList<>(db), () -> {});
|
||||
testS("ObjectCowList<Int>: Filling with 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
}, () -> {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<EnhancedObject>: Filling with 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.listOfEnhancedObj = new EnhancedObjectCowList<>(db, SimpleEnhancedObject.class);
|
||||
simpleEnhancedObjectContainer.var = new SimpleEnhancedObject(db);
|
||||
simpleEnhancedObjectContainer.var.integerNumber = 10;
|
||||
simpleEnhancedObjectContainer.var.longNumber = 10L;
|
||||
simpleEnhancedObjectContainer.var.object = new ArrayList<>();
|
||||
simpleEnhancedObjectContainer.var.object.add("XHIghicuiHUCB UIVY");
|
||||
simpleEnhancedObjectContainer.var.object.add("ioZ>UIHZXGHXYGY");
|
||||
simpleEnhancedObjectContainer.var.object.add("XJIOUIhcgGuigscwvyv");
|
||||
}, () -> {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.listOfEnhancedObj.add(simpleEnhancedObjectContainer.var);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<Int>: Filling with 10000 items", 10, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
}, () -> {
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<Int>: Filling with 100000 items", 1, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
}, () -> {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<Int>: Loading 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.list.get(i);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<EnhancedObject>: Loading with 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.listOfEnhancedObj = new EnhancedObjectCowList<>(db, SimpleEnhancedObject.class);
|
||||
simpleEnhancedObjectContainer.var = new SimpleEnhancedObject(db);
|
||||
simpleEnhancedObjectContainer.var.integerNumber = 10;
|
||||
simpleEnhancedObjectContainer.var.longNumber = 10L;
|
||||
simpleEnhancedObjectContainer.var.object = new ArrayList<>();
|
||||
simpleEnhancedObjectContainer.var.object.add("XHIghicuiHUCB UIVY");
|
||||
simpleEnhancedObjectContainer.var.object.add("ioZ>UIHZXGHXYGY");
|
||||
simpleEnhancedObjectContainer.var.object.add("XJIOUIhcgGuigscwvyv");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.listOfEnhancedObj.add(simpleEnhancedObjectContainer.var);
|
||||
}
|
||||
}, () -> {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.listOfEnhancedObj.get(i);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<Int>: Loading 10000 items", 10, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
preloadedListContainer.var.list.get(i);
|
||||
}
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<Int>: getLast() with 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {
|
||||
preloadedListContainer.var.list.getLast();
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<EnhancedObject>: getLast() with 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.listOfEnhancedObj = new EnhancedObjectCowList<>(db, SimpleEnhancedObject.class);
|
||||
simpleEnhancedObjectContainer.var = new SimpleEnhancedObject(db);
|
||||
simpleEnhancedObjectContainer.var.integerNumber = 10;
|
||||
simpleEnhancedObjectContainer.var.longNumber = 10L;
|
||||
simpleEnhancedObjectContainer.var.object = new ArrayList<>();
|
||||
simpleEnhancedObjectContainer.var.object.add("XHIghicuiHUCB UIVY");
|
||||
simpleEnhancedObjectContainer.var.object.add("ioZ>UIHZXGHXYGY");
|
||||
simpleEnhancedObjectContainer.var.object.add("XJIOUIhcgGuigscwvyv");
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.listOfEnhancedObj.add(simpleEnhancedObjectContainer.var);
|
||||
}
|
||||
}, () -> {
|
||||
preloadedListContainer.var.listOfEnhancedObj.getLast();
|
||||
}, () -> {});
|
||||
testS("ObjectCowList<Int>: size() with 1000 items", 100, () -> {
|
||||
regenDb();
|
||||
preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new);
|
||||
preloadedListContainer.var.list = new ObjectCowList<>(db);
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
preloadedListContainer.var.list.add(1000);
|
||||
}
|
||||
}, () -> {
|
||||
preloadedListContainer.var.list.size();
|
||||
}, () -> {});
|
||||
System.out.println("-------------------------------------------------------+-----------------------------------------------------------------");
|
||||
System.out.println("Performance test finished.");
|
||||
deleteDb();
|
||||
Files.deleteIfExists(rootDirectory);
|
||||
}
|
||||
|
||||
private static void NtestS(String description, int times, RunnableWithIO beforeAction, RunnableWithIO action, RunnableWithIO afterAction) throws IOException, InterruptedException {
|
||||
}
|
||||
|
||||
|
||||
private static void testS(String description, int times, RunnableWithIO beforeAction, RunnableWithIO action, RunnableWithIO afterAction) throws IOException, InterruptedException {
|
||||
if (FAST_TESTS) {
|
||||
if (times >= 5) {
|
||||
times /= 5;
|
||||
} else if (times >= 2) {
|
||||
times /= 2;
|
||||
}
|
||||
}
|
||||
description = description + " time is";
|
||||
int spacesCount = 40 - description.length();
|
||||
int cutAt = 0;
|
||||
if (spacesCount < 0) {
|
||||
spacesCount = 40 - (description.length() - 40);
|
||||
cutAt = 40;
|
||||
}
|
||||
StringBuilder spaces = new StringBuilder();
|
||||
for (int i = 0; i < spacesCount; i++) {
|
||||
spaces.append(' ');
|
||||
}
|
||||
double[] results = test(times, beforeAction, action, afterAction);
|
||||
if (cutAt > 0) {
|
||||
System.out.println(description.substring(0, cutAt) + " |");
|
||||
}
|
||||
System.out.printf("%s:%s%s%s%n", description.substring(cutAt), spaces, format(results[0]) + " |", results.length > 1 ? (format(results[1]) + (results.length > 2 ? (format(results[2]) + (results.length > 3 ? (format(results[3]) + (results.length > 4 ? (format(results[4]) + (results.length > 5 ? format(results[5]) : "")) : "")) : "")) : "")) : "");
|
||||
}
|
||||
|
||||
private static String format(double result) {
|
||||
String spaces;
|
||||
if (result < 10) {
|
||||
spaces = " ";
|
||||
} else if (result < 100) {
|
||||
spaces = " ";
|
||||
} else if (result < 1000) {
|
||||
spaces = " ";
|
||||
} else {
|
||||
spaces = " ";
|
||||
}
|
||||
return spaces + String.format("%.2fms", result);
|
||||
}
|
||||
|
||||
private static double[] test(int times, RunnableWithIO beforeAction, RunnableWithIO action, RunnableWithIO afterAction) throws IOException, InterruptedException {
|
||||
LongArrayList results = new LongArrayList(times);
|
||||
Thread.sleep(100);
|
||||
System.gc();
|
||||
Thread.sleep(100);
|
||||
for (int i = 0; i < times; i++) {
|
||||
beforeAction.run();
|
||||
long startTime = System.nanoTime();
|
||||
action.run();
|
||||
long elapsedTime = System.nanoTime() - startTime;
|
||||
afterAction.run();
|
||||
results.add(elapsedTime);
|
||||
}
|
||||
double result1 = results.stream().limit(1).mapToLong(val -> val).average().orElse(0.0) / 1000000d;
|
||||
double result10 = results.stream().limit(10).mapToLong(val -> val).average().orElse(0.0) / 1000000d;
|
||||
double result100 = results.stream().limit(100).mapToLong(val -> val).average().orElse(0.0) / 1000000d;
|
||||
double result1000 = results.stream().limit(1000).mapToLong(val -> val).average().orElse(0.0) / 1000000d;
|
||||
double result10000 = results.stream().limit(10000).mapToLong(val -> val).average().orElse(0.0) / 1000000d;
|
||||
double resultMax = results.stream().mapToLong(val -> val).average().orElse(0.0) / 1000000d;
|
||||
if (times <= 1) {
|
||||
return new double[]{resultMax};
|
||||
} else if (times <= 10) {
|
||||
return new double[]{resultMax, result1};
|
||||
} else if (times <= 100) {
|
||||
return new double[]{resultMax, result1, result10};
|
||||
} else if (times <= 1000) {
|
||||
return new double[]{resultMax, result1, result10, result100};
|
||||
} else if (times <= 10000) {
|
||||
return new double[]{resultMax, result1, result10, result100, result1000};
|
||||
} else {
|
||||
return new double[]{resultMax, result1, result10, result100, result1000, result10000};
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateDb() throws IOException {
|
||||
dbDataFile = Files.createFile(rootDirectory.resolve("db_data.dat"));
|
||||
dbBlocksFile = Files.createFile(rootDirectory.resolve("db_blocks.dat"));
|
||||
dbReferencesFile = Files.createFile(rootDirectory.resolve("db_references.dat"));
|
||||
db = new Database(dbDataFile, dbBlocksFile, dbReferencesFile);
|
||||
}
|
||||
|
||||
public static void deleteDb() throws IOException {
|
||||
db.close();
|
||||
Files.deleteIfExists(dbDataFile);
|
||||
Files.deleteIfExists(dbBlocksFile);
|
||||
Files.deleteIfExists(dbReferencesFile);
|
||||
}
|
||||
|
||||
public static void regenDb() throws IOException {
|
||||
deleteDb();
|
||||
generateDb();
|
||||
}
|
||||
|
||||
public static class PreloadedListContainer extends EnhancedObject {
|
||||
|
||||
@DBField(id = 0, type = DBDataType.ENHANCED_OBJECT)
|
||||
public ObjectCowList<Integer> list;
|
||||
|
||||
@DBField(id = 1, type = DBDataType.ENHANCED_OBJECT)
|
||||
public EnhancedObjectCowList<SimpleEnhancedObject> listOfEnhancedObj;
|
||||
|
||||
public PreloadedListContainer() {
|
||||
|
||||
}
|
||||
|
||||
public PreloadedListContainer(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DynamicListContainer extends EnhancedObject {
|
||||
|
||||
public DynamicListContainer() {
|
||||
|
||||
}
|
||||
|
||||
public DynamicListContainer(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
|
||||
|
||||
@DBPropertyGetter(id = 0, type = DBDataType.ENHANCED_OBJECT)
|
||||
public ObjectCowList<Integer> getList() {
|
||||
return getProperty();
|
||||
}
|
||||
|
||||
@DBPropertySetter(id = 1, type = DBDataType.ENHANCED_OBJECT)
|
||||
public void setList(ObjectCowList<Integer> list) {
|
||||
setProperty(list);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleEnhancedObject extends EnhancedObject {
|
||||
public SimpleEnhancedObject() {
|
||||
|
||||
}
|
||||
|
||||
public SimpleEnhancedObject(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
|
||||
@DBField(id = 0, type = DBDataType.OBJECT)
|
||||
public ArrayList<String> object;
|
||||
|
||||
@DBField(id = 1, type = DBDataType.INTEGER)
|
||||
public int integerNumber;
|
||||
|
||||
@DBField(id = 2, type = DBDataType.LONG)
|
||||
public long longNumber;
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@ package org.warp.jcwdb.tests;
|
||||
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.EnhancedObjectUpgrader;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.jcwdb.ann.DBClass;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
@ -19,6 +21,14 @@ public class V2Class extends EnhancedObject {
|
||||
@DBField(id = 3, type = DBDataType.OBJECT)
|
||||
public String field4;
|
||||
|
||||
public V2Class() {
|
||||
|
||||
}
|
||||
|
||||
public V2Class(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(int oldObjectVersion, EnhancedObjectUpgrader enhancedObjectUpgrader) throws IOException {
|
||||
switch (oldObjectVersion) {
|
||||
|
@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.warp.cowdb.Database;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.cowdb.IDatabaseTools;
|
||||
import org.warp.jcwdb.ann.*;
|
||||
|
||||
import java.io.File;
|
||||
@ -277,8 +278,8 @@ public class NTestUtils {
|
||||
|
||||
}
|
||||
|
||||
public RootClass(IDatabase database) throws IOException {
|
||||
super(database);
|
||||
public RootClass(IDatabaseTools databaseTools) throws IOException {
|
||||
super(databaseTools);
|
||||
}
|
||||
|
||||
@DBPropertyGetter(id = 0, type = DBDataType.BOOLEAN)
|
||||
|
Loading…
Reference in New Issue
Block a user