diff --git a/pom.xml b/pom.xml
index ba64ed1..378d8aa 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,80 +1,100 @@
- 4.0.0
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
- org.warp
- jcwdb
- 1.0-SNAPSHOT
+ org.warp
+ jcwdb
+ 1.3
- jcwdb
-
- http://www.example.com
+ jcwdb
+
+ http://www.example.com
-
- UTF-8
- 11
- 11
-
+
+ UTF-8
+ 10
+ 10
+
-
-
- junit
- junit
- 4.11
- test
-
-
- it.unimi.dsi
- fastutil
- 8.2.2
-
-
- com.esotericsoftware
- kryo
- 5.0.0-RC1
-
-
- net.openhft
- zero-allocation-hashing
- 0.8
-
-
+
+
+ sonatype-snapshots
+ sonatype snapshots repo
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
-
-
-
-
- maven-clean-plugin
- 3.0.0
-
-
-
- maven-resources-plugin
- 3.0.2
-
-
- maven-compiler-plugin
- 3.7.0
-
-
- maven-surefire-plugin
- 2.22.1
-
-
- maven-jar-plugin
- 3.0.2
-
-
- maven-install-plugin
- 2.5.2
-
-
- maven-deploy-plugin
- 2.8.2
-
-
-
-
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ it.unimi.dsi
+ fastutil
+ 8.2.2
+
+
+ com.esotericsoftware
+ kryo
+ 5.0.0-RC1
+
+
+ net.openhft
+ zero-allocation-hashing
+ 0.8
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.5
+
+
+ org.jetbrains
+ annotations
+ RELEASE
+ compile
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.0.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.7.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+
diff --git a/src/main/java/org/warp/cowdb/BlockInfo.java b/src/main/java/org/warp/cowdb/BlockInfo.java
new file mode 100644
index 0000000..02b301b
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/BlockInfo.java
@@ -0,0 +1,44 @@
+package org.warp.cowdb;
+
+import java.util.Objects;
+import java.util.StringJoiner;
+
+public class BlockInfo {
+ private final long index;
+ private final int size;
+
+ public BlockInfo(long index, int size) {
+ this.index = index;
+ this.size = size;
+ }
+
+ public long getIndex() {
+ return index;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BlockInfo blockInfo = (BlockInfo) o;
+ return index == blockInfo.index &&
+ size == blockInfo.size;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(index, size);
+ }
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", BlockInfo.class.getSimpleName() + "[", "]")
+ .add("index=" + index)
+ .add("size=" + size)
+ .toString();
+ }
+}
diff --git a/src/main/java/org/warp/cowdb/ByteBufferBackedInputStream.java b/src/main/java/org/warp/cowdb/ByteBufferBackedInputStream.java
new file mode 100644
index 0000000..2c339a0
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/ByteBufferBackedInputStream.java
@@ -0,0 +1,33 @@
+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
new file mode 100644
index 0000000..795607b
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/Database.java
@@ -0,0 +1,906 @@
+package org.warp.cowdb;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.ByteBufferInput;
+import com.esotericsoftware.kryo.io.ByteBufferInputStream;
+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 java.io.ByteArrayOutputStream;
+import java.io.IOError;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.nio.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;
+import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_INFO;
+
+public class Database implements IDatabase {
+
+ private final DatabaseFileIO fileIO;
+ private final DatabaseBlocksIO blocksIO;
+ private final DatabaseBlocksMetadata blocksMetadata;
+ private final DatabaseReferencesIO referencesIO;
+ private final DatabaseReferencesMetadata referencesMetadata;
+ private final DatabaseObjectsIO objectsIO;
+ private final DatabaseDataInitializer dataInitializer;
+ private EnhancedObject loadedRootObject;
+
+ public Database(Path dataFile, Path blocksMetaFile, Path referencesMetaFile) throws IOException {
+ if (Files.notExists(dataFile)) {
+ Files.createFile(dataFile);
+ }
+ if (Files.notExists(blocksMetaFile)) {
+ Files.createFile(blocksMetaFile);
+ }
+ if (Files.notExists(referencesMetaFile)) {
+ Files.createFile(referencesMetaFile);
+ }
+ 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);
+ this.dataInitializer = new DatabaseDataInitializer(objectsIO);
+ }
+
+ @Override
+ public IDataInitializer getDataInitializer() {
+ return dataInitializer;
+ }
+
+ @Override
+ public IObjectsIO getObjectsIO() {
+ return objectsIO;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.objectsIO.setEnhancedObject(0, loadedRootObject);
+ this.referencesMetadata.close();
+ this.blocksMetadata.close();
+ this.fileIO.close();
+ }
+
+ public T loadRoot(Class type) throws IOException {
+ return loadRoot(type, () -> {
+ T obj = objectsIO.instantiateEnhancedObject(type);
+ dataInitializer.initializeDBObject(obj);
+ return obj;
+ });
+ }
+
+ public T loadRoot(Class type, SupplierWithIO ifAbsent) throws IOException {
+ if (loadedRootObject != null) {
+ throw new RuntimeException("Root already set!");
+ }
+ T root;
+ if (referencesMetadata.firstFreeReference > 0) {
+ root = objectsIO.loadEnhancedObject(0, type);
+ } else {
+ if (objectsIO.newNullObject() != 0) {
+ throw new IOException("Can't allocate root!");
+ } else {
+ root = ifAbsent.getWithIO();
+ objectsIO.setEnhancedObject(0, root);
+ }
+ }
+ loadedRootObject = root;
+ return root;
+ }
+
+ protected void registerClass(Class> type, int id) {
+ 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);
+ obj.initialize();
+ }
+
+ 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);
+ }
+ }
+
+ public static class DatabaseObjectsIO implements IObjectsIO {
+
+ private final IDatabase database;
+ private final DatabaseReferencesIO referencesIO;
+
+ private Kryo kryo = new Kryo();
+
+ private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) {
+ this.database = database;
+ this.referencesIO = referencesIO;
+ 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 {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return null;
+ }
+ 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, fieldRefs, methodRefs);
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private Object loadData(DBDataType propertyType, long dataReference, Supplier> returnType) throws IOException {
+ switch (propertyType) {
+ case DATABASE_OBJECT:
+ return loadEnhancedObject(dataReference, (Class extends EnhancedObject>) returnType.get());
+ case OBJECT:
+ return loadObject(dataReference);
+ case REFERENCES_LIST:
+ return loadReferencesList(dataReference);
+ case BOOLEAN:
+ return loadBoolean(dataReference);
+ case BYTE:
+ return loadByte(dataReference);
+ case SHORT:
+ return loadShort(dataReference);
+ case CHAR:
+ return loadChar(dataReference);
+ case INTEGER:
+ return loadInt(dataReference);
+ case LONG:
+ return loadLong(dataReference);
+ default:
+ throw new NullPointerException("Unknown data type");
+ }
+ }
+
+ private 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 DATABASE_OBJECT:
+ setEnhancedObject(reference, (EnhancedObject) loadedPropertyValue);
+ break;
+ }
+ }
+
+ @Override
+ public void setEnhancedObject(long reference, T value) throws IOException {
+ if (value != null) {
+ EnhancedObjectFullInfo objectFullInfo = value.getAllInfo();
+ int totalSize = Integer.BYTES * 2 + objectFullInfo.getFieldReferences().length * Long.BYTES + objectFullInfo.getPropertyReferences().length * Long.BYTES;
+ ByteBuffer buffer = ByteBuffer.allocate(totalSize);
+ 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]);
+ }
+ for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) {
+ try {
+ setData(objectFullInfo.getFieldReferences()[i], objectFullInfo.getFieldTypes()[i], objectFullInfo.getFields()[i].get(value));
+ } catch (IllegalAccessException e) {
+ throw new IOException(e);
+ }
+ }
+ for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
+ setData(objectFullInfo.getPropertyReferences()[i], objectFullInfo.getPropertyTypes()[i], objectFullInfo.getLoadedPropertyValues()[i]);
+ }
+ buffer.flip();
+ referencesIO.writeToReference(reference, totalSize, buffer);
+ } else {
+ referencesIO.writeToReference(reference, 0, null);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T loadObject(long reference) throws IOException {
+ 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 {
+ 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 {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return false;
+ }
+ return buffer.get() == 1;
+ }
+
+ @Override
+ public byte loadByte(long reference) throws IOException {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return 0;
+ }
+ return buffer.get();
+ }
+
+ @Override
+ public short loadShort(long reference) throws IOException {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return 0;
+ }
+ return buffer.getShort();
+ }
+
+ @Override
+ public char loadChar(long reference) throws IOException {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return 0;
+ }
+ return buffer.getChar();
+ }
+
+ @Override
+ public int loadInt(long reference) throws IOException {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return 0;
+ }
+ return buffer.getInt();
+ }
+
+ @Override
+ public long loadLong(long reference) throws IOException {
+ ByteBuffer buffer = referencesIO.readFromReference(reference);
+ if (buffer.limit() == 0) {
+ return 0;
+ }
+ return buffer.getLong();
+ }
+
+ @Override
+ public void setObject(long reference, T value) throws IOException {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
+ buffer.putLong(value);
+ buffer.flip();
+ referencesIO.writeToReference(reference, Long.BYTES, buffer);
+ }
+
+ @Override
+ public long newNullObject() throws IOException {
+ return referencesIO.allocateReference();
+ }
+
+ @Override
+ public void loadProperty(EnhancedObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException {
+ 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 instantiateEnhancedObject(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, long[] fieldRefs, long[] methodRefs) throws IOException {
+ T obj = instantiateEnhancedObject(objectType);
+ 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();
+ return buffer.getLong();
+ }
+
+ @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
new file mode 100644
index 0000000..83be611
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/EnhancedObject.java
@@ -0,0 +1,101 @@
+package org.warp.cowdb;
+
+import org.apache.commons.lang3.reflect.MethodUtils;
+import org.warp.jcwdb.ann.DBDataType;
+import org.warp.jcwdb.ann.DBPropertyGetter;
+import org.warp.jcwdb.ann.DBPropertySetter;
+
+import java.io.IOError;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+public abstract class EnhancedObject {
+ protected IDatabase database;
+ private Field[] fields;
+ private DBDataType[] fieldTypes;
+ private long[] fieldReferences;
+ private Method[] propertyGetters;
+ private Method[] propertySetters;
+ private DBDataType[] propertyTypes;
+ private long[] propertyReferences;
+ private boolean[] loadedProperties;
+ private Object[] loadedPropertyValues;
+ private Map setterMethods;
+ private Map getterMethods;
+
+ public EnhancedObject() {
+
+ }
+
+ public EnhancedObject(IDatabase database) throws IOException {
+ this.database = database;
+ database.getDataInitializer().initializeDBObject(this);
+ }
+
+ public abstract void initialize() throws IOException;
+
+
+ public void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldReferences) {
+ this.fields = fields;
+ this.fieldTypes = fieldTypes;
+ this.fieldReferences = fieldReferences;
+ }
+
+ public Method[] getPropertyGetters() {
+ return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertyGetter.class);
+ }
+
+ public Method[] getPropertySetters() {
+ return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertySetter.class);
+ }
+
+ public void setProperties(Method[] propertyGetters, Method[] propertySetters, DBDataType[] propertyTypes, long[] propertyReferences, Map setterMethods, Map getterMethods) {
+ this.propertyGetters = propertyGetters;
+ this.propertySetters = propertySetters;
+ this.propertyTypes = propertyTypes;
+ this.propertyReferences = propertyReferences;
+ this.loadedProperties = new boolean[this.propertyReferences.length];
+ this.loadedPropertyValues = new Object[this.propertyReferences.length];
+ this.setterMethods = setterMethods;
+ this.getterMethods = getterMethods;
+ }
+
+ public EnhancedObjectFullInfo getAllInfo() {
+ return new EnhancedObjectFullInfo(fieldReferences, fieldTypes, fields, propertyReferences, propertyTypes, loadedPropertyValues);
+ }
+
+
+ public T getProperty() {
+ StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+ StackWalker.StackFrame stackFrame = walker.walk(f -> f.skip(1).findFirst().orElse(null));
+ try {
+ int propertyId = stackFrame.getDeclaringClass().getDeclaredMethod(stackFrame.getMethodName()).getAnnotation(DBPropertyGetter.class).id();
+ return getProperty(propertyId);
+ } catch (IOException | NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private T getProperty(int propertyId) throws IOException {
+ if (!loadedProperties[propertyId]) {
+ long propertyUID = propertyReferences[propertyId];
+ database.getObjectsIO().loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID);
+ }
+ return (T) loadedPropertyValues[propertyId];
+ }
+
+ public void setProperty(T value) {
+ StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+ StackWalker.StackFrame stackFrame = walker.walk(f -> f.skip(1).findFirst().orElse(null));
+ DBPropertySetter propertyAnnotation = setterMethods.get(stackFrame.getMethodName());
+ setProperty(propertyAnnotation.id(), value);
+ }
+
+ public void setProperty(int propertyId, T value) {
+ loadedPropertyValues[propertyId] = value;
+ loadedProperties[propertyId] = true;
+ }
+}
diff --git a/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java b/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java
new file mode 100644
index 0000000..6a1ce2c
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/EnhancedObjectFullInfo.java
@@ -0,0 +1,47 @@
+package org.warp.cowdb;
+
+import org.warp.jcwdb.ann.DBDataType;
+
+import java.lang.reflect.Field;
+
+public class EnhancedObjectFullInfo {
+ private final long[] fieldReferences;
+ private final DBDataType[] fieldTypes;
+ private final Field[] fields;
+ private final long[] propertyReferences;
+ private final DBDataType[] propertyTypes;
+ private final Object[] loadedPropertyValues;
+
+ EnhancedObjectFullInfo(long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) {
+ this.fieldReferences = fieldReferences;
+ this.fieldTypes = fieldTypes;
+ this.fields = fields;
+ this.propertyReferences = propertyReferences;
+ this.propertyTypes = propertyTypes;
+ this.loadedPropertyValues = loadedPropertyValues;
+ }
+
+ long[] getFieldReferences() {
+ return fieldReferences;
+ }
+
+ DBDataType[] getFieldTypes() {
+ return fieldTypes;
+ }
+
+ public Field[] getFields() {
+ return fields;
+ }
+
+ long[] getPropertyReferences() {
+ return propertyReferences;
+ }
+
+ DBDataType[] getPropertyTypes() {
+ return propertyTypes;
+ }
+
+ Object[] getLoadedPropertyValues() {
+ return loadedPropertyValues;
+ }
+}
diff --git a/src/main/java/org/warp/cowdb/IBlocksIO.java b/src/main/java/org/warp/cowdb/IBlocksIO.java
new file mode 100644
index 0000000..0fc0a14
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IBlocksIO.java
@@ -0,0 +1,28 @@
+package org.warp.cowdb;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+public interface IBlocksIO {
+ /**
+ * Allocate a block
+ * @param size block size
+ * @param data block data
+ * @return the block id
+ */
+ long newBlock(int size, ByteBuffer data) throws IOException;
+
+ /**
+ * Read a block
+ * @param blockId block id
+ * @return block data
+ */
+ ByteBuffer readBlock(long blockId) throws IOException;
+
+ /**
+ * Close file
+ */
+ void close();
+}
diff --git a/src/main/java/org/warp/cowdb/IBlocksMetadata.java b/src/main/java/org/warp/cowdb/IBlocksMetadata.java
new file mode 100644
index 0000000..12a6443
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IBlocksMetadata.java
@@ -0,0 +1,44 @@
+package org.warp.cowdb;
+
+import java.io.IOException;
+
+public interface IBlocksMetadata {
+ long EMPTY_BLOCK_ID = -1;
+ BlockInfo EMPTY_BLOCK_INFO = new BlockInfo(0, 0);
+
+ /**
+ * Get block info
+ * @param blockId block id
+ * @return block metadata
+ */
+ BlockInfo getBlockInfo(long blockId) throws IOException;
+
+ /**
+ * New empty block info
+ * @return block id
+ */
+ default long newBlock() {
+ return EMPTY_BLOCK_ID;
+ }
+
+ /**
+ * Set block info
+ * @param index block index
+ * @param size block size
+ * @return block id
+ */
+ long newBlock(long index, int size) throws IOException;
+
+ /**
+ * Set block info
+ * @param blockInfo block info
+ * @return block id
+ */
+ default long newBlock(BlockInfo blockInfo) throws IOException {
+ return this.newBlock(blockInfo.getIndex(), blockInfo.getSize());
+ }
+ /**
+ * Close file
+ */
+ void close() throws IOException;
+}
diff --git a/src/main/java/org/warp/cowdb/IDataInitializer.java b/src/main/java/org/warp/cowdb/IDataInitializer.java
new file mode 100644
index 0000000..371d5ea
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IDataInitializer.java
@@ -0,0 +1,7 @@
+package org.warp.cowdb;
+
+import java.io.IOException;
+
+public interface IDataInitializer {
+ void initializeDBObject(EnhancedObject obj) throws IOException;
+}
diff --git a/src/main/java/org/warp/cowdb/IDatabase.java b/src/main/java/org/warp/cowdb/IDatabase.java
new file mode 100644
index 0000000..2e87d39
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IDatabase.java
@@ -0,0 +1,10 @@
+package org.warp.cowdb;
+
+import java.io.IOException;
+
+public interface IDatabase {
+
+ void close() throws IOException;
+ IDataInitializer getDataInitializer();
+ IObjectsIO getObjectsIO();
+}
diff --git a/src/main/java/org/warp/cowdb/IFileIO.java b/src/main/java/org/warp/cowdb/IFileIO.java
new file mode 100644
index 0000000..31bda13
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IFileIO.java
@@ -0,0 +1,35 @@
+package org.warp.cowdb;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+
+public interface IFileIO {
+ /**
+ * Read *length* bytes in position *index*
+ * @param index index
+ * @param length length
+ * @return bytes
+ */
+ ByteBuffer readAt(long index, int length) throws IOException;
+
+ /**
+ * Write *length* bytes in position *index*
+ * @param index index
+ * @param length length
+ * @param data bytes
+ */
+ void writeAt(long index, int length, ByteBuffer data) throws IOException;
+
+ /**
+ * Write *length* bytes in position *index*
+ * @param length length
+ * @param data bytes
+ * @return index
+ */
+ long writeAtEnd(int length, ByteBuffer data) throws IOException;
+
+ /**
+ * Close the file
+ */
+ void close() throws IOException;
+}
diff --git a/src/main/java/org/warp/cowdb/IObjectsIO.java b/src/main/java/org/warp/cowdb/IObjectsIO.java
new file mode 100644
index 0000000..39212a6
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IObjectsIO.java
@@ -0,0 +1,111 @@
+package org.warp.cowdb;
+
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import org.warp.jcwdb.ann.DBDataType;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+public interface IObjectsIO {
+ T loadEnhancedObject(long reference, Class objectType) throws IOException;
+
+ T loadObject(long reference) throws IOException;
+
+ LongArrayList loadReferencesList(long reference) throws IOException;
+
+ boolean loadBoolean(long reference) throws IOException;
+
+ byte loadByte(long reference) throws IOException;
+
+ short loadShort(long reference) throws IOException;
+
+ char loadChar(long reference) throws IOException;
+
+ int loadInt(long reference) throws IOException;
+
+ long loadLong(long reference) throws IOException;
+
+ void setEnhancedObject(long reference, T value) throws IOException;
+
+ void setObject(long reference, T value) throws IOException;
+
+ void setReferencesList(long reference, LongArrayList value) throws IOException;
+
+ void setBoolean(long reference, boolean value) throws IOException;
+
+ void setByte(long reference, byte value) throws IOException;
+
+ void setShort(long reference, short value) throws IOException;
+
+ void setChar(long reference, char value) throws IOException;
+
+ void setInt(long reference, int value) throws IOException;
+
+ void setLong(long reference, long value) throws IOException;
+
+ long newNullObject() throws IOException;
+
+ default long newEnhancedObject(T value) throws IOException {
+ long reference = newNullObject();
+ if (value != null) {
+ setEnhancedObject(reference, value);
+ }
+ return reference;
+ }
+
+ default long newObject(T value) throws IOException {
+ long reference = newNullObject();
+ if (value != null) {
+ setObject(reference, value);
+ }
+ return reference;
+ }
+
+ default long newReferencesList(LongArrayList value) throws IOException {
+ long reference = newNullObject();
+ if (value != null) {
+ setReferencesList(reference, value);
+ }
+ return reference;
+ }
+
+ default long newBoolean(boolean value) throws IOException {
+ long reference = newNullObject();
+ setBoolean(reference, value);
+ return reference;
+ }
+
+ default long newByte(byte value) throws IOException {
+ long reference = newNullObject();
+ setByte(reference, value);
+ return reference;
+ }
+
+ default long newShort(short value) throws IOException {
+ long reference = newNullObject();
+ setShort(reference, value);
+ return reference;
+ }
+
+ default long newChar(char value) throws IOException {
+ long reference = newNullObject();
+ setChar(reference, value);
+ return reference;
+ }
+
+ default long newInt(int value) throws IOException {
+ long reference = newNullObject();
+ setInt(reference, value);
+ return reference;
+ }
+
+ default long newLong(long value) throws IOException {
+ long reference = newNullObject();
+ setLong(reference, value);
+ return reference;
+ }
+
+ void loadProperty(EnhancedObject enhancedObject, int propertyId, Method propertyGetter, DBDataType propertyType, long propertyUID) throws IOException;
+
+ void registerClass(Class> type, int id);
+}
diff --git a/src/main/java/org/warp/cowdb/IReferencesIO.java b/src/main/java/org/warp/cowdb/IReferencesIO.java
new file mode 100644
index 0000000..d89d3e9
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IReferencesIO.java
@@ -0,0 +1,37 @@
+package org.warp.cowdb;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+public interface IReferencesIO {
+ /**
+ * Allocate a new empty reference
+ * @return the new reference
+ */
+ long allocateReference() throws IOException;
+
+ /**
+ * Allocate a new reference with that data
+ * @param size data size
+ * @param data bytes
+ * @return the new reference
+ */
+ long allocateReference(int size, ByteBuffer data) throws IOException;
+
+ /**
+ * Write some data to the reference
+ * @param reference reference
+ * @param size data size
+ * @param data bytes
+ */
+ void writeToReference(long reference, int size, ByteBuffer data) throws IOException;
+
+ /**
+ * Read data from the reference
+ * @param reference reference
+ * @return bytes
+ */
+ ByteBuffer readFromReference(long reference) throws IOException;
+}
diff --git a/src/main/java/org/warp/cowdb/IReferencesMetadata.java b/src/main/java/org/warp/cowdb/IReferencesMetadata.java
new file mode 100644
index 0000000..8b3d248
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/IReferencesMetadata.java
@@ -0,0 +1,31 @@
+package org.warp.cowdb;
+
+import java.io.IOException;
+
+public interface IReferencesMetadata {
+ /**
+ * Get block of reference
+ * @param reference reference
+ * @return block id
+ */
+ long getReference(long reference) throws IOException;
+
+ /**
+ * Allocate a block for a new reference
+ * @param blockId block id
+ * @return reference
+ */
+ long newReference(long blockId) throws IOException;
+
+ /**
+ * Change reference size
+ * @param reference reference
+ * @param blockId block id
+ */
+ void editReference(long reference, long blockId) throws IOException;
+
+ /**
+ * Close file
+ */
+ void close() throws IOException;
+}
diff --git a/src/main/java/org/warp/cowdb/lists/CowList.java b/src/main/java/org/warp/cowdb/lists/CowList.java
new file mode 100644
index 0000000..fcba1d4
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/lists/CowList.java
@@ -0,0 +1,101 @@
+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 java.io.IOException;
+import java.util.StringJoiner;
+
+public abstract class CowList extends EnhancedObject {
+
+ private final Object indicesAccessLock = new Object();
+
+ @DBField(id = 0, type = DBDataType.REFERENCES_LIST)
+ private LongArrayList indices;
+
+ public CowList() {
+
+ }
+
+ public CowList(IDatabase database) throws IOException {
+ super(database);
+ }
+
+ @Override
+ public void initialize() throws IOException {
+ indices = new LongArrayList();
+ }
+
+ public T get(int index) throws IOException {
+ synchronized (indicesAccessLock) {
+ long uid = indices.getLong(index);
+ return loadItem(uid);
+ }
+ }
+
+ public void add(T value) throws IOException {
+ long uid = database.getObjectsIO().newNullObject();
+ synchronized (indicesAccessLock) {
+ indices.add(uid);
+ writeItemToDisk(uid, value);
+ }
+ }
+
+ public void update(int index, T value) throws IOException {
+ synchronized (indicesAccessLock) {
+ set(index, value);
+ }
+ }
+
+ public void set(int index, T value) throws IOException {
+ long uid = database.getObjectsIO().newNullObject();
+ synchronized (indicesAccessLock) {
+ indices.set(index, uid);
+ writeItemToDisk(uid, value);
+ }
+ }
+
+ public void add(int index, T value) throws IOException {
+ long uid = database.getObjectsIO().newNullObject();
+ synchronized (indicesAccessLock) {
+ indices.add(index, uid);
+ writeItemToDisk(uid, value);
+ }
+ }
+
+ public T getLast() throws IOException {
+ synchronized (indicesAccessLock) {
+ if (indices.size() > 0) {
+ return get(indices.size() - 1);
+ } else {
+ return null;
+ }
+ }
+ }
+
+ public boolean isEmpty() {
+ synchronized (indicesAccessLock) {
+ return indices.size() <= 0;
+ }
+ }
+
+ public int size() {
+ synchronized (indicesAccessLock) {
+ return indices.size();
+ }
+ }
+
+ protected abstract T loadItem(long uid) throws IOException;
+
+ protected abstract void writeItemToDisk(long uid, T item) throws IOException;
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", CowList.class.getSimpleName() + "[", "]")
+ .add(indices.size() + " items")
+ .toString();
+ }
+}
diff --git a/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java b/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java
new file mode 100644
index 0000000..e35f22f
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/lists/EnhancedObjectCowList.java
@@ -0,0 +1,33 @@
+package org.warp.cowdb.lists;
+
+import org.warp.cowdb.EnhancedObject;
+import org.warp.cowdb.IDatabase;
+import org.warp.jcwdb.ann.DBDataType;
+import org.warp.jcwdb.ann.DBField;
+
+import java.io.IOException;
+
+public class EnhancedObjectCowList extends CowList {
+
+ @DBField(id = 1, type = DBDataType.OBJECT)
+ private Class type;
+
+ public EnhancedObjectCowList() {
+ super();
+ }
+
+ public EnhancedObjectCowList(IDatabase database, Class type) throws IOException {
+ super(database);
+ this.type = type;
+ }
+
+ @Override
+ protected T loadItem(long uid) throws IOException {
+ return database.getObjectsIO().loadEnhancedObject(uid, type);
+ }
+
+ @Override
+ protected void writeItemToDisk(long uid, T item) throws IOException {
+ database.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
new file mode 100644
index 0000000..8902c27
--- /dev/null
+++ b/src/main/java/org/warp/cowdb/lists/ObjectCowList.java
@@ -0,0 +1,29 @@
+package org.warp.cowdb.lists;
+
+import org.warp.cowdb.EnhancedObject;
+import org.warp.cowdb.IDatabase;
+import org.warp.jcwdb.ann.DBDataType;
+import org.warp.jcwdb.ann.DBField;
+
+import java.io.IOException;
+
+public class ObjectCowList extends CowList {
+
+ public ObjectCowList() {
+ super();
+ }
+
+ public ObjectCowList(IDatabase database) throws IOException {
+ super(database);
+ }
+
+ @Override
+ protected T loadItem(long uid) throws IOException {
+ return database.getObjectsIO().loadObject(uid);
+ }
+
+ @Override
+ protected void writeItemToDisk(long uid, T item) throws IOException {
+ database.getObjectsIO().setObject(uid, item);
+ }
+}
diff --git a/src/main/java/org/warp/jcwdb/CacheIndexManager.java b/src/main/java/org/warp/jcwdb/CacheIndexManager.java
deleted file mode 100644
index 6ab7543..0000000
--- a/src/main/java/org/warp/jcwdb/CacheIndexManager.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package org.warp.jcwdb;
-
-import java.io.IOException;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-public class CacheIndexManager implements IndexManager {
-
- public CacheIndexManager() {
- }
-
- @Override
- public T get(long index, DBReader reader) {
- // TODO: implement
- return null;
- }
-
- @Override
- public int getType(long index) {
- // TODO: implement
- return 0;
- }
-
- @Override
- public long getHash(long index) {
- // TODO: implement
- return 0;
- }
-
- @Override
- public long add(DBDataOutput writer) {
- // TODO: implement
- return 0;
- }
-
- @Override
- public FullIndexDetails addAndGetDetails(DBDataOutput writer) {
- // TODO: implement
- return null;
- }
-
- @Override
- public IndexDetails set(long index, DBDataOutput writer) {
- // TODO: implement
- return null;
- }
-
- @Override
- public void setFlushingAllowed(long index, boolean isUnloadingAllowed) {
- // TODO: implement
- }
-
- @Override
- public void delete(long index) {
- // TODO: implement
- }
-
- @Override
- public boolean has(long index) {
- // TODO: implement
- return false;
- }
-
- @Override
- public void close() {
- // TODO: implement
- }
-
- @Override
- public long clean() {
- return 0;
- }
-}
diff --git a/src/main/java/org/warp/jcwdb/Castable.java b/src/main/java/org/warp/jcwdb/Castable.java
deleted file mode 100644
index 23e04ed..0000000
--- a/src/main/java/org/warp/jcwdb/Castable.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package org.warp.jcwdb;
-
-public interface Castable {
- T cast();
-}
diff --git a/src/main/java/org/warp/jcwdb/Cleanable.java b/src/main/java/org/warp/jcwdb/Cleanable.java
deleted file mode 100644
index 57af24c..0000000
--- a/src/main/java/org/warp/jcwdb/Cleanable.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.warp.jcwdb;
-
-public interface Cleanable {
- /**
- * Clean the object
- * @return the approximated number of cleaned items
- */
- public long clean();
-}
diff --git a/src/main/java/org/warp/jcwdb/Cleaner.java b/src/main/java/org/warp/jcwdb/Cleaner.java
deleted file mode 100644
index 321e40a..0000000
--- a/src/main/java/org/warp/jcwdb/Cleaner.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package org.warp.jcwdb;
-
-import java.io.IOException;
-import java.lang.ref.WeakReference;
-import java.nio.channels.ClosedChannelException;
-
-import it.unimi.dsi.fastutil.longs.Long2ObjectMap.Entry;
-
-public class Cleaner {
-
- public static final boolean DISABLE_CLEANER = false;
- public static final boolean ENABLE_CLEANER_LOGGING = false;
- private static final double MAXIMUM_SLEEP_INTERVAL = 8d * 1000d; // 8 seconds
- private static final double MINIMUM_SLEEP_INTERVAL = 1d * 1000d; // 1 second
- private static final double NORMAL_REMOVED_ITEMS = 2500l;
- private static final double REMOVED_ITEMS_RATIO = 2.5d; // 250%
-
- private final Cleanable[] objectsToClean;
- private final Thread cleanerThread;
- private int sleepInterval = (int) MINIMUM_SLEEP_INTERVAL;
- private volatile boolean stopRequest = false;
-
- public Cleaner(Cleanable... objectsToClean) {
- this.objectsToClean = objectsToClean;
- this.cleanerThread = new Thread(new CleanLoop());
- this.cleanerThread.setName("Cleaner thread");
- this.cleanerThread.setDaemon(true);
- }
-
- public void start() {
- if (!DISABLE_CLEANER) {
- this.cleanerThread.start();
- }
- }
-
- /**
- * Clean
- * @return number of removed items
- */
- private long clean() {
- long cleanedItems = 0;
- for (Cleanable cleanable : objectsToClean) {
- cleanedItems += cleanable.clean();
- }
- //System.gc();
- return cleanedItems;
- }
-
- public void stop() {
- if (cleanerThread != null) {
- stopRequest = true;
- while (cleanerThread.isAlive()) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
-
- private class CleanLoop implements Runnable {
-
- @Override
- public void run() {
- while(!stopRequest) {
- try {
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Waiting " + sleepInterval + "ms.");
- sleepFor(sleepInterval);
- final long time1 = System.currentTimeMillis();
- final double removedItems = clean();
- final long time2 = System.currentTimeMillis();
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] CLEAN_TIME " + (time2 - time1));
- double suggestedExecutionTimeByItemsCalculations = (sleepInterval + MAXIMUM_SLEEP_INTERVAL) / 2;
-
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] REMOVED_ITEMS: " + removedItems);
- if (removedItems > 0) {
- final double removedItemsRatio = removedItems / NORMAL_REMOVED_ITEMS;
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] REMOVED_ITEMS_RATIO: " + removedItemsRatio);
- if (removedItemsRatio < 1d / REMOVED_ITEMS_RATIO || removedItemsRatio >= REMOVED_ITEMS_RATIO) {
- suggestedExecutionTimeByItemsCalculations = sleepInterval / removedItemsRatio;
- }
- }
-
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Items: SUGGESTING SLEEP_INTERVAL FROM " + sleepInterval + "ms TO " + suggestedExecutionTimeByItemsCalculations + "ms");
-
- double newSleepInterval = suggestedExecutionTimeByItemsCalculations;
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Total: SUGGESTING SLEEP_INTERVAL FROM " + sleepInterval + "ms TO " + newSleepInterval + "ms");
- if (newSleepInterval > MAXIMUM_SLEEP_INTERVAL) {
- sleepInterval = (int) MAXIMUM_SLEEP_INTERVAL;
- } else if (newSleepInterval < MINIMUM_SLEEP_INTERVAL) {
- sleepInterval = (int) MINIMUM_SLEEP_INTERVAL;
- } else {
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] CHANGED SLEEP_INTERVAL FROM " + sleepInterval + "ms TO " + newSleepInterval + "ms");
- sleepInterval = (int) newSleepInterval;
- }
-
-
- if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Cleaned " + removedItems + " items.");
- }catch (InterruptedException e) {
-
- }
- }
- }
-
- private void sleepFor(int sleepInterval) throws InterruptedException {
- int lastI = (int) Math.ceil(((double) sleepInterval) / 1000d);
- for (int i = 0; i < lastI; i++) {
- if (stopRequest) {
- return;
- }
- if (i == lastI) {
- Thread.sleep(sleepInterval % 1000);
- } else {
- Thread.sleep(lastI);
- }
- Thread.sleep(sleepInterval);
- }
- }
-
- }
-}
diff --git a/src/main/java/org/warp/jcwdb/DBDataOutput.java b/src/main/java/org/warp/jcwdb/DBDataOutput.java
deleted file mode 100644
index 6af18e9..0000000
--- a/src/main/java/org/warp/jcwdb/DBDataOutput.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.warp.jcwdb;
-
-public interface DBDataOutput {
- int getSize();
- int getType();
- long calculateHash();
- DBWriter getWriter();
-
- static DBDataOutput create(DBWriter writer, int type, int size, long hash) {
- return new DBDataOutput() {
-
- @Override
- public int getSize() {
- return size;
- }
-
- @Override
- public int getType() {
- return type;
- }
-
- @Override
- public long calculateHash() {
- return hash;
- }
-
- @Override
- public DBWriter getWriter() {
- return writer;
- }
-
- };
- }
-}
diff --git a/src/main/java/org/warp/jcwdb/DBGenericObjectParser.java b/src/main/java/org/warp/jcwdb/DBGenericObjectParser.java
deleted file mode 100644
index 5e6f9ae..0000000
--- a/src/main/java/org/warp/jcwdb/DBGenericObjectParser.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package org.warp.jcwdb;
-
-import java.io.ByteArrayOutputStream;
-
-import com.esotericsoftware.kryo.Kryo;
-import com.esotericsoftware.kryo.io.Output;
-
-import net.openhft.hashing.LongHashFunction;
-
-public class DBGenericObjectParser extends DBTypeParserImpl