diff --git a/README.md b/README.md index 612cfb5..0afa66a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # JCWDB -Experimental database \ No newline at end of file +Experimental databaseReference \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9fd60fa..57f9d21 100644 --- a/pom.xml +++ b/pom.xml @@ -6,11 +6,10 @@ org.warp jcwdb - 1.4 + 1.5.0 jcwdb - - http://www.example.com + https://git.ignuranza.net/andreacavalli/JCWDB UTF-8 diff --git a/src/main/java/org/warp/cowdb/ByteBufferBackedInputStream.java b/src/main/java/org/warp/cowdb/ByteBufferBackedInputStream.java deleted file mode 100644 index 2c339a0..0000000 --- a/src/main/java/org/warp/cowdb/ByteBufferBackedInputStream.java +++ /dev/null @@ -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; - - } -} diff --git a/src/main/java/org/warp/cowdb/Database.java b/src/main/java/org/warp/cowdb/Database.java index cbb2d0b..0e2857b 100644 --- a/src/main/java/org/warp/cowdb/Database.java +++ b/src/main/java/org/warp/cowdb/Database.java @@ -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 loadRoot(Class type) throws IOException { - return loadRoot(type, () -> { - T obj = objectsIO.toInstance(type); - objectsIO.dataInitializer.initializeDBObject(obj); - return obj; - }); - } - - public T loadRoot(Class type, SupplierWithIO ifAbsent) throws IOException { + public T loadRoot(Class type, FunctionWithIO 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 setterMethods = new LinkedHashMap<>(); - Map 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> enhancedClassType) throws IOException { - return objectsIO.loadData(type, fieldRefs[id], enhancedClassType); - } - - @Override - @SuppressWarnings("unchecked") - public Object getMethod(int id, DBDataType type, Supplier> 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 loadEnhancedObject(long reference, Class 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> returnType) throws IOException { - switch (propertyType) { - case ENHANCED_OBJECT: - return loadEnhancedObject(dataReference, (Class) 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 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 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 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 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 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 setterMethods = new LinkedHashMap<>(); - Map 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 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 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 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 toInstance(Class 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 preloadEnhancedObject(Class 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(); - } - } - } } diff --git a/src/main/java/org/warp/cowdb/EnhancedObject.java b/src/main/java/org/warp/cowdb/EnhancedObject.java index b1dcbda..6149ced 100644 --- a/src/main/java/org/warp/cowdb/EnhancedObject.java +++ b/src/main/java/org/warp/cowdb/EnhancedObject.java @@ -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 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; + } } diff --git a/src/main/java/org/warp/cowdb/FunctionWithIO.java b/src/main/java/org/warp/cowdb/FunctionWithIO.java new file mode 100644 index 0000000..fb15117 --- /dev/null +++ b/src/main/java/org/warp/cowdb/FunctionWithIO.java @@ -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. + * + *

This is a functional interface + * whose functional method is {@link #apply(Object)}. + * + * @param the type of the input to the function + * @param the type of the result of the function + * + * @since 1.8 + */ +@FunctionalInterface +public interface FunctionWithIO { + + /** + * 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 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 FunctionWithIO compose(FunctionWithIO 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 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 FunctionWithIO andThen(FunctionWithIO after) { + Objects.requireNonNull(after); + return (T t) -> after.apply(apply(t)); + } + + /** + * Returns a function that always returns its input argument. + * + * @param the type of the input and output objects to the function + * @return a function that always returns its input argument + */ + static FunctionWithIO identity() { + return t -> t; + } +} + diff --git a/src/main/java/org/warp/cowdb/IDatabase.java b/src/main/java/org/warp/cowdb/IDatabase.java index 2e87d39..0ffeec8 100644 --- a/src/main/java/org/warp/cowdb/IDatabase.java +++ b/src/main/java/org/warp/cowdb/IDatabase.java @@ -5,6 +5,4 @@ import java.io.IOException; public interface IDatabase { void close() throws IOException; - IDataInitializer getDataInitializer(); - IObjectsIO getObjectsIO(); } diff --git a/src/main/java/org/warp/cowdb/IDatabaseTools.java b/src/main/java/org/warp/cowdb/IDatabaseTools.java new file mode 100644 index 0000000..0876f01 --- /dev/null +++ b/src/main/java/org/warp/cowdb/IDatabaseTools.java @@ -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(); +} diff --git a/src/main/java/org/warp/cowdb/IObjectsIO.java b/src/main/java/org/warp/cowdb/IObjectsIO.java index 39212a6..8e18994 100644 --- a/src/main/java/org/warp/cowdb/IObjectsIO.java +++ b/src/main/java/org/warp/cowdb/IObjectsIO.java @@ -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(); } diff --git a/src/main/java/org/warp/cowdb/IReferencesMetadata.java b/src/main/java/org/warp/cowdb/IReferencesMetadata.java index 8b3d248..2a40a52 100644 --- a/src/main/java/org/warp/cowdb/IReferencesMetadata.java +++ b/src/main/java/org/warp/cowdb/IReferencesMetadata.java @@ -28,4 +28,6 @@ public interface IReferencesMetadata { * Close file */ void close() throws IOException; + + long getFirstFreeReference(); } diff --git a/src/main/java/org/warp/cowdb/database/DatabaseBlocksIO.java b/src/main/java/org/warp/cowdb/database/DatabaseBlocksIO.java new file mode 100644 index 0000000..0abca87 --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseBlocksIO.java @@ -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() { + + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseBlocksMetadata.java b/src/main/java/org/warp/cowdb/database/DatabaseBlocksMetadata.java new file mode 100644 index 0000000..7981dac --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseBlocksMetadata.java @@ -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(); + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseDataInitializer.java b/src/main/java/org/warp/cowdb/database/DatabaseDataInitializer.java new file mode 100644 index 0000000..1fb0fdf --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseDataInitializer.java @@ -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 setterMethods = new LinkedHashMap<>(); + Map 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); + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseEnhancedObjectUpgrader.java b/src/main/java/org/warp/cowdb/database/DatabaseEnhancedObjectUpgrader.java new file mode 100644 index 0000000..63121b0 --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseEnhancedObjectUpgrader.java @@ -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> enhancedClassType) throws IOException { + return objectsIO.loadData(type, fieldRefs[id], enhancedClassType); + } + + @Override + @SuppressWarnings("unchecked") + public Object getMethod(int id, DBDataType type, Supplier> enhancedClassType) throws IOException { + return objectsIO.loadData(type, methodRefs[id], enhancedClassType); + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseFileIO.java b/src/main/java/org/warp/cowdb/database/DatabaseFileIO.java new file mode 100644 index 0000000..7304b12 --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseFileIO.java @@ -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(); + } + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseObjectsIO.java b/src/main/java/org/warp/cowdb/database/DatabaseObjectsIO.java new file mode 100644 index 0000000..00fd440 --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseObjectsIO.java @@ -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 loadEnhancedObject(long reference, Class 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> returnType) throws IOException { + switch (propertyType) { + case ENHANCED_OBJECT: + return loadEnhancedObject(dataReference, (Class) 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"); + } + } + + 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 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 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 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 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 setterMethods = new LinkedHashMap<>(); + Map 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 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); + } + + 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); + } + } + + 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 toInstance(Class 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 preloadEnhancedObject(Class 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; + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseReferencesIO.java b/src/main/java/org/warp/cowdb/database/DatabaseReferencesIO.java new file mode 100644 index 0000000..24cbd77 --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseReferencesIO.java @@ -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); + } +} diff --git a/src/main/java/org/warp/cowdb/database/DatabaseReferencesMetadata.java b/src/main/java/org/warp/cowdb/database/DatabaseReferencesMetadata.java new file mode 100644 index 0000000..09ee1d1 --- /dev/null +++ b/src/main/java/org/warp/cowdb/database/DatabaseReferencesMetadata.java @@ -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; + } +} diff --git a/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java b/src/main/java/org/warp/cowdb/database/EnhancedObjectFullInfo.java similarity index 83% rename from src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java rename to src/main/java/org/warp/cowdb/database/EnhancedObjectFullInfo.java index 6cac504..fc2aa67 100644 --- a/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java +++ b/src/main/java/org/warp/cowdb/database/EnhancedObjectFullInfo.java @@ -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(); diff --git a/src/main/java/org/warp/cowdb/lists/CowList.java b/src/main/java/org/warp/cowdb/lists/CowList.java index b5a3d64..9cf6bc3 100644 --- a/src/main/java/org/warp/cowdb/lists/CowList.java +++ b/src/main/java/org/warp/cowdb/lists/CowList.java @@ -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 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 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 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 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); diff --git a/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java b/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java index 9fb879c..8fc4fa5 100644 --- a/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java +++ b/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java @@ -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 extends CowList super(); } - public EnhancedObjectCowList(IDatabase database, Class type) throws IOException { - super(database); + public EnhancedObjectCowList(IDatabaseTools databaseTools, Class 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); } } diff --git a/src/main/java/org/warp/cowdb/lists/ObjectCowList.java b/src/main/java/org/warp/cowdb/lists/ObjectCowList.java index ebdc58e..697b0ed 100644 --- a/src/main/java/org/warp/cowdb/lists/ObjectCowList.java +++ b/src/main/java/org/warp/cowdb/lists/ObjectCowList.java @@ -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 extends CowList { 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); } } diff --git a/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java b/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java index 2b2c344..42b988e 100644 --- a/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java +++ b/src/test/java/org/warp/jcwdb/tests/EnhancedClassUpdate.java @@ -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); diff --git a/src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java b/src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java index 438132e..7fe7b50 100644 --- a/src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java +++ b/src/test/java/org/warp/jcwdb/tests/MultipleEnhancedObjects.java @@ -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) diff --git a/src/test/java/org/warp/jcwdb/tests/OldClass.java b/src/test/java/org/warp/jcwdb/tests/OldClass.java index 1254909..2f7d943 100644 --- a/src/test/java/org/warp/jcwdb/tests/OldClass.java +++ b/src/test/java/org/warp/jcwdb/tests/OldClass.java @@ -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); + } } diff --git a/src/test/java/org/warp/jcwdb/tests/Performance.java b/src/test/java/org/warp/jcwdb/tests/Performance.java new file mode 100644 index 0000000..a2aa29c --- /dev/null +++ b/src/test/java/org/warp/jcwdb/tests/Performance.java @@ -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 = new VariableWrapper<>(null); + final VariableWrapper simpleEnhancedObjectContainer = new VariableWrapper<>(null); + testS("ObjectCowList creation", 3000, () -> { + regenDb(); + preloadedListContainer.var = db.loadRoot(PreloadedListContainer.class, PreloadedListContainer::new); + }, () -> preloadedListContainer.var.list = new ObjectCowList<>(db), () -> {}); + testS("ObjectCowList: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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 list; + + @DBField(id = 1, type = DBDataType.ENHANCED_OBJECT) + public EnhancedObjectCowList 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 getList() { + return getProperty(); + } + + @DBPropertySetter(id = 1, type = DBDataType.ENHANCED_OBJECT) + public void setList(ObjectCowList 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 object; + + @DBField(id = 1, type = DBDataType.INTEGER) + public int integerNumber; + + @DBField(id = 2, type = DBDataType.LONG) + public long longNumber; + } +} diff --git a/src/test/java/org/warp/jcwdb/tests/V2Class.java b/src/test/java/org/warp/jcwdb/tests/V2Class.java index ce6ceb0..de51c79 100644 --- a/src/test/java/org/warp/jcwdb/tests/V2Class.java +++ b/src/test/java/org/warp/jcwdb/tests/V2Class.java @@ -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) { diff --git a/src/test/java/org/warp/jcwdb/utils/NTestUtils.java b/src/test/java/org/warp/jcwdb/utils/NTestUtils.java index 07baa75..e920403 100644 --- a/src/test/java/org/warp/jcwdb/utils/NTestUtils.java +++ b/src/test/java/org/warp/jcwdb/utils/NTestUtils.java @@ -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)