Updated to 1.5.0

This commit is contained in:
Andrea Cavalli 2019-03-02 17:47:24 +01:00
parent ba07259cfc
commit ba103ddde1
28 changed files with 1511 additions and 1002 deletions

View File

@ -1,3 +1,3 @@
# JCWDB
Experimental database
Experimental databaseReference

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -5,6 +5,4 @@ import java.io.IOException;
public interface IDatabase {
void close() throws IOException;
IDataInitializer getDataInitializer();
IObjectsIO getObjectsIO();
}

View 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();
}

View File

@ -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();
}

View File

@ -28,4 +28,6 @@ public interface IReferencesMetadata {
* Close file
*/
void close() throws IOException;
long getFirstFreeReference();
}

View 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() {
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View 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();
}
}
}

View 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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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)

View File

@ -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);
}
}

View 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;
}
}

View File

@ -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) {

View File

@ -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)