diff --git a/pom.xml b/pom.xml index 6451ac9..378d8aa 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.warp jcwdb - 1.2-SNAPSHOT + 1.3 jcwdb 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..97ba2dc --- /dev/null +++ b/src/main/java/org/warp/cowdb/Database.java @@ -0,0 +1,839 @@ +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.longs.LongArrayList; +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.LinkedHashMap; +import java.util.Map; +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; + } + + 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; + } + + @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) 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 IOError(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)); + } + + 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 metaFileChannelSize; + private long firstFreeReference; + + private DatabaseReferencesMetadata(Path refMetaFile) throws IOException { + metaFileChannel = Files.newByteChannel(refMetaFile, StandardOpenOption.READ, StandardOpenOption.WRITE); + metaFileChannelSize = metaFileChannel.size(); + firstFreeReference = metaFileChannelSize / REF_META_BYTES_COUNT; + } + + @Override + public long getReference(long reference) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(REF_META_BYTES_COUNT); + if (reference >= firstFreeReference || reference * REF_META_BYTES_COUNT > metaFileChannelSize) { + 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..8cd28ea --- /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; + + public 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; + } + + public long[] getFieldReferences() { + return fieldReferences; + } + + public DBDataType[] getFieldTypes() { + return fieldTypes; + } + + public Field[] getFields() { + return fields; + } + + public long[] getPropertyReferences() { + return propertyReferences; + } + + public DBDataType[] getPropertyTypes() { + return propertyTypes; + } + + public 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..ac0cb9c --- /dev/null +++ b/src/main/java/org/warp/cowdb/IObjectsIO.java @@ -0,0 +1,109 @@ +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; + +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; +} 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/jcwdb/Cleaner.java b/src/main/java/org/warp/jcwdb/Cleaner.java index 321e40a..58d792e 100644 --- a/src/main/java/org/warp/jcwdb/Cleaner.java +++ b/src/main/java/org/warp/jcwdb/Cleaner.java @@ -1,11 +1,5 @@ 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; diff --git a/src/main/java/org/warp/jcwdb/FileAllocator.java b/src/main/java/org/warp/jcwdb/FileAllocator.java index 8809afd..afa9649 100644 --- a/src/main/java/org/warp/jcwdb/FileAllocator.java +++ b/src/main/java/org/warp/jcwdb/FileAllocator.java @@ -1,11 +1,10 @@ package org.warp.jcwdb; -import it.unimi.dsi.fastutil.longs.*; -import it.unimi.dsi.fastutil.objects.ObjectBidirectionalIterator; +import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; +import it.unimi.dsi.fastutil.longs.Long2IntMap; import java.io.IOException; import java.nio.channels.SeekableByteChannel; -import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Optional; diff --git a/src/main/java/org/warp/jcwdb/FileIndexManager.java b/src/main/java/org/warp/jcwdb/FileIndexManager.java index 3f52372..4a089cd 100644 --- a/src/main/java/org/warp/jcwdb/FileIndexManager.java +++ b/src/main/java/org/warp/jcwdb/FileIndexManager.java @@ -7,6 +7,7 @@ import it.unimi.dsi.fastutil.objects.ObjectIterator; import org.warp.jcwdb.ann.DatabaseManager; import java.io.IOException; +import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.SeekableByteChannel; @@ -15,7 +16,7 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; public class FileIndexManager implements IndexManager { - public static final boolean ALWAYS_ALLOCATE_NEW = false; + public static final boolean ALWAYS_ALLOCATE_NEW = true; private final SeekableByteChannel dataFileChannel, metadataFileChannel; private volatile long metadataFileChannelSize; private final FileAllocator fileAllocator; @@ -114,7 +115,7 @@ public class FileIndexManager implements IndexManager { public T get(long index, DBReader reader) throws IOException { checkClosed(); IndexDetails details = getIndexMetadata(index); - Input i = new Input(Channels.newInputStream(dataFileChannel.position(details.getOffset()))); + Input i = new Input(Channels.newInputStream(dataFileChannel.position(details.getOffset())), details.getSize()); T result = reader.read(i, details.getSize()); return result; } @@ -184,8 +185,10 @@ public class FileIndexManager implements IndexManager { throw new IOException("Unable to write " + size + " in a space of " + indexDetails.getSize()); } final long offset = indexDetails.getOffset(); - final Output o = new Output(Channels.newOutputStream(dataFileChannel.position(offset)), size); + OutputStream os = Channels.newOutputStream(dataFileChannel.position(offset)); + final Output o = new Output(os, size); data.write(o); + os.flush(); o.flush(); } diff --git a/src/main/java/org/warp/jcwdb/IndexDetails.java b/src/main/java/org/warp/jcwdb/IndexDetails.java index 382eb49..d85aeb2 100644 --- a/src/main/java/org/warp/jcwdb/IndexDetails.java +++ b/src/main/java/org/warp/jcwdb/IndexDetails.java @@ -1,7 +1,5 @@ package org.warp.jcwdb; -import java.util.Objects; - public class IndexDetails { /** * The bitmask is used to determine if an index has been deleted diff --git a/src/main/java/org/warp/jcwdb/IndexManager.java b/src/main/java/org/warp/jcwdb/IndexManager.java index be8fc37..d5983b0 100644 --- a/src/main/java/org/warp/jcwdb/IndexManager.java +++ b/src/main/java/org/warp/jcwdb/IndexManager.java @@ -1,11 +1,6 @@ package org.warp.jcwdb; -import com.esotericsoftware.kryo.io.Output; - import java.io.IOException; -import java.util.function.BiConsumer; -import java.util.function.BiPredicate; -import java.util.function.Consumer; public interface IndexManager extends Cleanable { T get(long index, DBReader reader) throws IOException; diff --git a/src/main/java/org/warp/jcwdb/Saveable.java b/src/main/java/org/warp/jcwdb/Saveable.java index b8fcffb..b910fc5 100644 --- a/src/main/java/org/warp/jcwdb/Saveable.java +++ b/src/main/java/org/warp/jcwdb/Saveable.java @@ -1,7 +1,5 @@ package org.warp.jcwdb; -import java.io.IOException; - public interface Saveable { void save(); void saveAndFlush(); diff --git a/src/main/java/org/warp/jcwdb/ann/ConsumerWithIO.java b/src/main/java/org/warp/jcwdb/ann/ConsumerWithIO.java new file mode 100644 index 0000000..3bda55b --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/ConsumerWithIO.java @@ -0,0 +1,15 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; +import java.util.Objects; + +@FunctionalInterface +public interface ConsumerWithIO { + + void accept(T t) throws IOException; + + default ConsumerWithIO andThen(ConsumerWithIO after) { + Objects.requireNonNull(after); + return (T t) -> { accept(t); after.accept(t); }; + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/DBArrayList.java b/src/main/java/org/warp/jcwdb/ann/DBArrayList.java index 366024f..02907df 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBArrayList.java +++ b/src/main/java/org/warp/jcwdb/ann/DBArrayList.java @@ -13,13 +13,17 @@ public abstract class DBArrayList extends DBObject { @DBField(id = 0, type = DBDataType.UID_LIST) private LongArrayList indices; - public DBArrayList(JCWDatabase database) { - super(database); - indices = new LongArrayList(); + public DBArrayList() { + super(); } - public DBArrayList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); + public DBArrayList(JCWDatabase database) throws IOException { + super(database); + } + + @Override + public void initialize() { + indices = new LongArrayList(); } public T get(int index) { @@ -34,7 +38,7 @@ public abstract class DBArrayList extends DBObject { } public void add(T value) { - long uid = databaseManager.allocateNullValue(); + long uid = database.getDataLoader().allocateNullValue(); synchronized (indicesAccessLock) { indices.add(uid); try { @@ -52,7 +56,7 @@ public abstract class DBArrayList extends DBObject { } public void set(int index, T value) { - long uid = databaseManager.allocateNullValue(); + long uid = database.getDataLoader().allocateNullValue(); synchronized (indicesAccessLock) { indices.set(index, uid); try { @@ -64,7 +68,7 @@ public abstract class DBArrayList extends DBObject { } public void add(int index, T value) { - long uid = databaseManager.allocateNullValue(); + long uid = database.getDataLoader().allocateNullValue(); synchronized (indicesAccessLock) { indices.add(index, uid); try { diff --git a/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java b/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java index 71d7dac..a96af45 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java +++ b/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java @@ -7,22 +7,22 @@ public class DBDBObjectList extends DBArrayList { @DBField(id = 1, type = DBDataType.OBJECT) private Class type; - public DBDBObjectList(JCWDatabase database, Class type) { + public DBDBObjectList() { + super(); + } + + public DBDBObjectList(JCWDatabase database, Class type) throws IOException { super(database); this.type = type; } - public DBDBObjectList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); - } - @Override protected T loadItem(long uid) throws IOException { - return databaseManager.loadDBObject(type, uid); + return database.getDataLoader().loadDBObject(type, uid); } @Override protected void writeItemToDisk(long uid, T item) throws IOException { - databaseManager.writeObjectProperty(uid, DBDataType.DATABASE_OBJECT, item); + database.getDataLoader().writeObjectProperty(uid, DBDataType.DATABASE_OBJECT, item); } } diff --git a/src/main/java/org/warp/jcwdb/ann/DBDataType.java b/src/main/java/org/warp/jcwdb/ann/DBDataType.java index 9e8907b..24eabb0 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBDataType.java +++ b/src/main/java/org/warp/jcwdb/ann/DBDataType.java @@ -9,5 +9,6 @@ public enum DBDataType { CHAR, INTEGER, LONG, - UID_LIST + UID_LIST, + REFERENCES_LIST } \ No newline at end of file diff --git a/src/main/java/org/warp/jcwdb/ann/DBObject.java b/src/main/java/org/warp/jcwdb/ann/DBObject.java index e70b17a..e68c9cf 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBObject.java +++ b/src/main/java/org/warp/jcwdb/ann/DBObject.java @@ -1,6 +1,6 @@ package org.warp.jcwdb.ann; -import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; import java.io.IOError; import java.io.IOException; @@ -10,8 +10,7 @@ import java.util.LinkedHashMap; import java.util.Map; public abstract class DBObject { - protected final JCWDatabase database; - protected final DatabaseManager databaseManager; + protected JCWDatabase database; private Field[] fields; private DBDataType[] fieldTypes; private long[] fieldUIDs; @@ -27,101 +26,17 @@ public abstract class DBObject { private final Object fieldsAccessLock = new Object(); private final Object propertiesAccessLock = new Object(); - public DBObject(JCWDatabase database) { + public DBObject() { + + } + + public DBObject(JCWDatabase database) throws IOException { this.database = database; - this.databaseManager = database.getDatabaseManager(); - try { - initializeDBObject(); - } catch (IOException e) { - throw new RuntimeException(e); - } + database.initializeDBObject(this); } - public DBObject(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - this.database = database; - this.databaseManager = database.getDatabaseManager(); - this.databaseManager.preloadDBObject(this, objectInfo); - } + public abstract void initialize() throws IOException; - private void initializeDBObject() throws IOException { - initializeDBObjectFields(); - initializeDBObjectProperties(); - } - - private void initializeDBObjectFields() throws IOException { - // Declare the variables needed to get the biggest field Id - Field[] unorderedFields = databaseManager.getFields(this); - // Find the biggest field Id - int biggestFieldId = databaseManager.getBiggestFieldId(unorderedFields); - - // Allocate new UIDs - fieldUIDs = databaseManager.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(); - databaseManager.loadField(this, field, fieldType, fieldUIDs[fieldId]); - fields[fieldId] = field; - orderedFieldTypes[fieldId] = fieldType; - } - // Set fields metadata - setFields(fields, orderedFieldTypes, fieldUIDs); - } - - private void initializeDBObjectProperties() { - // Declare the variables needed to get the biggest property Id - Method[] unorderedPropertyGetters = databaseManager.getPropertyGetters(this); - Method[] unorderedPropertySetters = databaseManager.getPropertySetters(this); - - // Find the biggest property Id - int biggestGetter = databaseManager.getBiggestPropertyGetterId(unorderedPropertyGetters); - int biggestSetter = databaseManager.getBiggestPropertySetterId(unorderedPropertySetters); - int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter; - - // Allocate new UIDs - propertyUIDs = databaseManager.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 - setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs, setterMethods, getterMethods); - } public T getProperty() { StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); @@ -144,7 +59,7 @@ public abstract class DBObject { synchronized (propertiesAccessLock) { if (!loadedProperties[propertyId]) { long propertyUID = propertyUIDs[propertyId]; - databaseManager.loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID); + database.getDataLoader().loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID); } return (T) loadedPropertyValues[propertyId]; } @@ -169,13 +84,13 @@ public abstract class DBObject { try { synchronized (propertiesAccessLock) { synchronized (fieldsAccessLock) { - databaseManager.writeObjectInfo(uid, fieldUIDs, propertyUIDs); + database.getDataLoader().writeObjectInfo(uid, fieldUIDs, propertyUIDs); } } synchronized (fieldsAccessLock) { for (int i = 0; i < fieldUIDs.length; i++) { try { - databaseManager.writeObjectProperty(fieldUIDs[i], fieldTypes[i], fields[i].get(this)); + database.getDataLoader().writeObjectProperty(fieldUIDs[i], fieldTypes[i], fields[i].get(this)); } catch (IllegalAccessException e) { throw new IOError(e); } @@ -183,7 +98,7 @@ public abstract class DBObject { } synchronized (propertiesAccessLock) { for (int i = 0; i < propertyUIDs.length; i++) { - databaseManager.writeObjectProperty(propertyUIDs[i], propertyTypes[i], loadedPropertyValues[i]); + database.getDataLoader().writeObjectProperty(propertyUIDs[i], propertyTypes[i], loadedPropertyValues[i]); } } } catch (IOException e) { @@ -211,4 +126,12 @@ public abstract class DBObject { this.getterMethods = getterMethods; } } + + Method[] getPropertyGetters() { + return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertyGetter.class); + } + + public Method[] getPropertySetters() { + return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertySetter.class); + } } diff --git a/src/main/java/org/warp/jcwdb/ann/DBObjectList.java b/src/main/java/org/warp/jcwdb/ann/DBObjectList.java index 44a41a0..0063fee 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBObjectList.java +++ b/src/main/java/org/warp/jcwdb/ann/DBObjectList.java @@ -3,21 +3,22 @@ package org.warp.jcwdb.ann; import java.io.IOException; public class DBObjectList extends DBArrayList { - public DBObjectList(JCWDatabase database) { - super(database); + + public DBObjectList() { + } - public DBObjectList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); + public DBObjectList(JCWDatabase database) throws IOException { + super(database); } @Override public T loadItem(long uid) throws IOException { - return databaseManager.loadObject(uid); + return database.getDataLoader().loadObject(uid); } @Override public void writeItemToDisk(long uid, T item) throws IOException { - databaseManager.writeObjectProperty(uid, DBDataType.OBJECT, item); + database.getDataLoader().writeObjectProperty(uid, DBDataType.OBJECT, item); } } diff --git a/src/main/java/org/warp/jcwdb/ann/DataInitializer.java b/src/main/java/org/warp/jcwdb/ann/DataInitializer.java new file mode 100644 index 0000000..d980a93 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DataInitializer.java @@ -0,0 +1,96 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.Map; + +public class DataInitializer { + private final DataLoader dataLoader; + + public DataInitializer(DataLoader dataLoader) { + this.dataLoader = dataLoader; + } + + public void initializeDBObject(DBObject obj) throws IOException { + initializeDBObjectFields(obj); + initializeDBObjectProperties(obj); + obj.initialize(); + } + + private void initializeDBObjectFields(DBObject obj) throws IOException { + // Declare the variables needed to get the biggest field Id + Field[] unorderedFields = dataLoader.getFields(obj); + // Find the biggest field Id + int biggestFieldId = dataLoader.getBiggestFieldId(unorderedFields); + + // Allocate new UIDs + long[] fieldUIDs = dataLoader.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(); + dataLoader.loadField(obj, field, fieldType, fieldUIDs[fieldId]); + fields[fieldId] = field; + orderedFieldTypes[fieldId] = fieldType; + } + // Set fields metadata + obj.setFields(fields, orderedFieldTypes, fieldUIDs); + } + + private void initializeDBObjectProperties(DBObject obj) { + // 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 = dataLoader.getBiggestPropertyGetterId(unorderedPropertyGetters); + int biggestSetter = dataLoader.getBiggestPropertySetterId(unorderedPropertySetters); + int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter; + + // Allocate new UIDs + long[] propertyUIDs = dataLoader.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/jcwdb/ann/DataLoader.java b/src/main/java/org/warp/jcwdb/ann/DataLoader.java new file mode 100644 index 0000000..2acd136 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DataLoader.java @@ -0,0 +1,645 @@ +package org.warp.jcwdb.ann; + +import com.esotericsoftware.kryo.Kryo; +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.apache.commons.lang3.reflect.MethodUtils; +import org.warp.jcwdb.FileIndexManager; + +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.file.Path; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class DataLoader { + + private final Kryo kryo = new Kryo(); + private final DBObjectIndicesManager objectIndicesManager; + private final FileIndexManager indices; + private final Object indicesAccessLock = new Object(); + private volatile boolean closed; + /** + * DO NOT USE + */ + private JCWDatabase databaseInstance; + + public DataLoader(JCWDatabase databaseInstance, Path dataFile, Path metadataFile, boolean registrationRequired) throws IOException { + synchronized (indicesAccessLock) { + this.databaseInstance = databaseInstance; + this.indices = new FileIndexManager(dataFile, metadataFile); + if (!indices.has(0)) { + allocateNullValue(); + } + this.objectIndicesManager = new DBObjectIndicesManager(this.indices); + kryo.setRegistrationRequired(registrationRequired); + registerDefaultClasses(); + } + } + + private void registerDefaultClasses() { + 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++); + } + + public void close() throws IOException { + synchronized (indicesAccessLock) { + if (!closed) { + closed = true; + indices.close(); + } + } + } + + public void preloadDBObject(DBObject obj, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + synchronized (indicesAccessLock) { + preloadDBObjectFields(obj, objectInfo.getFields()); + preloadDBObjectProperties(obj, objectInfo.getProperties()); + } + } + + T loadRoot(Class rootType, SupplierWithIO ifAbsent) throws IOException { + synchronized (indicesAccessLock) { + if (isDBObjectNull(0)) { + return ifAbsent.getWithIO(); + } else { + return loadDBObject(rootType, 0); + } + } + } + + private T instantiateDBObject(Class type) throws IOException { + synchronized (indicesAccessLock) { + try { + T obj = type.getConstructor().newInstance(); + obj.database = databaseInstance; + 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 void preloadDBObjectFields(DBObject obj, long[] fieldUIDs) throws IOException { + synchronized (indicesAccessLock) { + // 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, fieldUIDs[fieldId]); + fields[fieldId] = field; + orderedFieldTypes[fieldId] = fieldType; + } + // Set fields metadata + obj.setFields(fields, orderedFieldTypes, fieldUIDs); + } + } + + private void preloadDBObjectProperties(DBObject obj, long[] propertyUIDs) { + synchronized (indicesAccessLock) { + // 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, propertyUIDs, setterMethods, getterMethods); + } + } + + + protected Field[] getFields(DBObject obj) { + synchronized (indicesAccessLock) { + return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class); + } + } + + + int getBiggestPropertyGetterId(Method[] unorderedPropertyGetters) { + synchronized (indicesAccessLock) { + 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) { + synchronized (indicesAccessLock) { + int biggestPropertyId = -1; + for (Method property : unorderedPropertySetters) { + DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestPropertyId) { + biggestPropertyId = propertyId; + } + } + return biggestPropertyId; + } + } + + + protected int getBiggestFieldId(Field[] unorderedFields) { + synchronized (indicesAccessLock) { + int biggestFieldId = -1; + for (Field field : unorderedFields) { + DBField fieldAnnotation = field.getAnnotation(DBField.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestFieldId) { + biggestFieldId = propertyId; + } + } + return biggestFieldId; + } + } + + public void loadProperty(DBObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException { + synchronized (indicesAccessLock) { + loadData(propertyType, propertyUID, property::getReturnType, (data) -> { + synchronized (indicesAccessLock) { + obj.setLoadedProperty(propertyId, data); + } + }); + } + } + + public void loadField(DBObject obj, Field field, DBDataType fieldType, long fieldUID) throws IOException { + synchronized (indicesAccessLock) { + loadData(fieldType, fieldUID, field::getType, (data) -> { + synchronized (indicesAccessLock) { + 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); + } + } + }); + } + } + + @SuppressWarnings("unchecked") + private void loadData(DBDataType propertyType, long dataUID, Supplier> returnType, ConsumerWithIO result) throws IOException { + synchronized (indicesAccessLock) { + switch (propertyType) { + case DATABASE_OBJECT: + DBObject fieldDBObjectValue = loadDBObject((Class) returnType.get(), dataUID); + //System.err.println("Loading data DBObj " + dataUID + ":" + fieldDBObjectValue); + result.accept(fieldDBObjectValue); + return; + case OBJECT: + Object fieldObjectValue = loadObject(dataUID); + //System.err.println("Loading data Obj " + dataUID + ":" + fieldObjectValue); + result.accept(fieldObjectValue); + return; + case UID_LIST: + LongArrayList fieldListObjectValue = loadListObject(dataUID); + //System.err.println("Loading data LOb " + dataUID + ":" + fieldListObjectValue); + result.accept(fieldListObjectValue); + return; + case BOOLEAN: + boolean fieldBooleanValue = loadBoolean(dataUID); + //System.err.println("Loading data Boo " + dataUID + ":" + fieldBooleanValue); + result.accept(fieldBooleanValue); + return; + case BYTE: + byte fieldByteValue = loadByte(dataUID); + //System.err.println("Loading data Byt " + dataUID + ":" + fieldByteValue); + result.accept(fieldByteValue); + return; + case SHORT: + short fieldShortValue = loadShort(dataUID); + //System.err.println("Loading data Shr " + dataUID + ":" + fieldShortValue); + result.accept(fieldShortValue); + return; + case CHAR: + char fieldCharValue = loadChar(dataUID); + //System.err.println("Loading data Chr " + dataUID + ":" + fieldCharValue); + result.accept(fieldCharValue); + return; + case INTEGER: + int fieldIntValue = loadInt(dataUID); + //System.err.println("Loading data Int " + dataUID + ":" + fieldIntValue); + result.accept(fieldIntValue); + return; + case LONG: + long fieldLongValue = loadLong(dataUID); + //System.err.println("Loading data Lng " + dataUID + ":" + fieldLongValue); + result.accept(fieldLongValue); + return; + default: + throw new NullPointerException("Unknown data type"); + } + } + } + + + public T loadDBObject(Class type, long propertyUID) throws IOException { + synchronized (indicesAccessLock) { + DBObjectIndicesManager.DBObjectInfo objectInfo = readUIDs(propertyUID); + if (objectInfo == null) return null; + T obj = instantiateDBObject(type); + preloadDBObject(obj, objectInfo); + return obj; + } + } + + private boolean isDBObjectNull(long uid) { + synchronized (indicesAccessLock) { + try { + return !objectIndicesManager.has(uid) || objectIndicesManager.get(uid) == null; + } catch (IOException ex) { + throw new IOError(ex); + } + } + } + + @SuppressWarnings("unchecked") + public T loadObject(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + return (T) kryo.readClassAndObject(i); + } else { + return null; + } + } + }); + } + } + + private LongArrayList loadListObject(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + LongArrayList list = new LongArrayList(); + int listSize = i.readVarInt(true); + for (int li = 0; li < listSize; li++) { + list.add(i.readVarLong(true)); + } + return list; + } else { + return null; + } + } + }); + } + } + + public boolean loadBoolean(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + return i.readBoolean(); + } else { + return false; + } + } + }); + } + } + + public byte loadByte(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + return i.readByte(); + } else { + return (byte) 0; + } + } + }); + } + } + + public short loadShort(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + return i.readShort(); + } else { + return (short) 0; + } + } + }); + } + } + + public char loadChar(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + return i.readChar(); + } else { + return (char) 0; + } + } + }); + } + } + + + public int loadInt(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + if (size != 0) { + return i.readInt(); + } else { + return 0; + } + }); + } + } + + public long loadLong(long uid) throws IOException { + synchronized (indicesAccessLock) { + return indices.get(uid, (i, size) -> { + synchronized (indicesAccessLock) { + if (size != 0) { + return i.readLong(); + } else { + return 0L; + } + } + }); + } + } + public boolean exists(long uid) { + synchronized (indicesAccessLock) { + return objectIndicesManager.has(uid); + } + } + + public void writeObjectInfo(long uid, long[] fieldUIDs, long[] propertyUIDs) throws IOException { + synchronized (indicesAccessLock) { + //System.err.println("Saving obj. " + uid); + this.objectIndicesManager.set(uid, fieldUIDs, propertyUIDs); + } + } + + private void writeObjectInfoNull(long uid) throws IOException { + synchronized (indicesAccessLock) { + this.objectIndicesManager.setNull(uid); + } + } + + /** + * + * @param uid + * @return + */ + public DBObjectIndicesManager.DBObjectInfo readUIDs(long uid) { + synchronized (indicesAccessLock) { + try { + return objectIndicesManager.get(uid); + } catch (IOException e) { + throw new IOError(e); + } + } + } + + + public void writeObjectProperty(long uid, DBDataType propertyType, T loadedPropertyValue) throws IOException { + synchronized (indicesAccessLock) { + switch (propertyType) { + case BOOLEAN: + indices.set(uid, 1, (o) -> { + synchronized (indicesAccessLock) { + o.writeBoolean(loadedPropertyValue == null ? false : (boolean) loadedPropertyValue); + } + }); + //System.err.println("Saving data Boo " + uid + ":" + loadedPropertyValue); + break; + case BYTE: + indices.set(uid, Byte.BYTES, (o) -> { + synchronized (indicesAccessLock) { + o.writeByte(loadedPropertyValue == null ? 0 : (byte) loadedPropertyValue); + } + }); + //System.err.println("Saving data Byt " + uid + ":" + loadedPropertyValue); + break; + case SHORT: + indices.set(uid, Short.BYTES, (o) -> { + synchronized (indicesAccessLock) { + o.writeShort(loadedPropertyValue == null ? 0 : (short) loadedPropertyValue); + } + }); + //System.err.println("Saving data Shr " + uid + ":" + loadedPropertyValue); + break; + case CHAR: + indices.set(uid, Character.BYTES, (o) -> { + synchronized (indicesAccessLock) { + o.writeChar(loadedPropertyValue == null ? 0 : (char) loadedPropertyValue); + } + }); + //System.err.println("Saving data Chr " + uid + ":" + loadedPropertyValue); + break; + case INTEGER: + indices.set(uid, Integer.BYTES, (o) -> { + synchronized (indicesAccessLock) { + o.writeInt(loadedPropertyValue == null ? 0 : (int) loadedPropertyValue); + } + }); + //System.err.println("Saving data Int " + uid + ":" + loadedPropertyValue); + break; + case LONG: + indices.set(uid, Long.BYTES, (o) -> { + synchronized (indicesAccessLock) { + o.writeLong(loadedPropertyValue == null ? 0 : (long) loadedPropertyValue); + } + }); + //System.err.println("Saving data Lng " + uid + ":" + loadedPropertyValue); + break; + case OBJECT: + Output baosOutput = new Output(new ByteArrayOutputStream()); + kryo.writeClassAndObject(baosOutput, loadedPropertyValue); + //System.err.println("Saving data Obj " + uid + ":" + loadedPropertyValue); + if (loadedPropertyValue instanceof Class) { + System.out.println(); + } + byte[] out = baosOutput.toBytes(); + indices.set(uid, out.length, o -> { + synchronized (indicesAccessLock) { + o.write(out, 0, out.length); + } + }); + break; + case UID_LIST: + if (loadedPropertyValue == null) { + indices.set(uid, 0, (o) -> { + }); + } else { + LongArrayList list = (LongArrayList) loadedPropertyValue; + final int listSize = list.size(); + Output baosListOutput = new Output(Long.BYTES * 100, Long.BYTES * (listSize > 100 ? listSize : 100)); + baosListOutput.writeVarInt(listSize, true); + for (int i = 0; i < listSize; i++) { + baosListOutput.writeVarLong(list.getLong(i), true); + } + //System.err.println("Saving data LOb " + uid + ":" + loadedPropertyValue); + byte[] outList = baosListOutput.toBytes(); + indices.set(uid, outList.length, o -> { + synchronized (indicesAccessLock) { + o.write(outList, 0, outList.length); + } + }); + } + break; + case DATABASE_OBJECT: + //System.err.println("Saving data DBObj " + uid + ":" + loadedPropertyValue); + if (loadedPropertyValue == null) { + writeObjectInfoNull(uid); + } else { + ((DBObject) loadedPropertyValue).writeToDisk(uid); + } + break; + } + } + } + + public void registerClass(Class clazz, int id) { + synchronized (indicesAccessLock) { + kryo.register(clazz, 100 + id); + } + } + + public long allocateNullValue() { + synchronized (indicesAccessLock) { + return indices.add(0); + } + } + + public long[] allocateNewUIDs(int quantity) { + synchronized (indicesAccessLock) { + long[] ids = new long[quantity]; + for (int i = 0; i < quantity; i++) { + ids[i] = allocateNullValue(); + } + return ids; + } + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java b/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java index 84a0cb4..875b61f 100644 --- a/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java +++ b/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java @@ -1,50 +1,21 @@ package org.warp.jcwdb.ann; -import com.esotericsoftware.kryo.Kryo; -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.apache.commons.lang3.reflect.MethodUtils; import org.warp.jcwdb.Cleanable; import org.warp.jcwdb.Cleaner; -import org.warp.jcwdb.FileIndexManager; -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.file.Path; -import java.util.*; -import java.util.function.Consumer; import java.util.function.Supplier; public class DatabaseManager implements Cleanable { public static final long MAX_LOADED_INDICES = 100000; - private final DBObjectIndicesManager objectIndicesManager; - private final FileIndexManager indices; private final Cleaner cleaner; - private final JCWDatabase jcwDatabase; + private final DataLoader dataLoader; private DBObject loadedRootObject = null; - private final Kryo kryo = new Kryo(); private volatile boolean closed; - DatabaseManager(JCWDatabase jcwDatabase, Path dataFile, Path metadataFile, boolean registrationRequired) throws IOException { - this.jcwDatabase = jcwDatabase; - kryo.setRegistrationRequired(registrationRequired); - registerDefaultClasses(); - this.indices = new FileIndexManager(dataFile, metadataFile); - if (!indices.has(0)) { - allocateNullValue(); - } - this.objectIndicesManager = new DBObjectIndicesManager(this.indices); + DatabaseManager(DataLoader dataLoader) { + this.dataLoader = dataLoader; this.cleaner = new Cleaner(this); this.cleaner.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -56,79 +27,13 @@ public class DatabaseManager implements Cleanable { })); } - private void registerDefaultClasses() { - 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++); - } - - @SuppressWarnings("unchecked") - public T loadRoot(Class rootType) throws IOException { + public T loadRoot(Class rootType, SupplierWithIO ifAbsent) throws IOException { if (loadedRootObject != null) { throw new RuntimeException("Root already set!"); } - if (isDBObjectNull(0)) { - try { - T root = rootType.getConstructor(JCWDatabase.class).newInstance(this.jcwDatabase); - loadedRootObject = root; - return root; - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new IOError(e); - } - } else { - T root = (T) loadDBObject(rootType, 0); - loadedRootObject = root; - return root; - } + T root = dataLoader.loadRoot(rootType, ifAbsent); + loadedRootObject = root; + return root; } public void close() throws IOException { @@ -138,367 +43,12 @@ public class DatabaseManager implements Cleanable { if (loadedRootObject != null) { loadedRootObject.writeToDisk(0); } - indices.close(); + dataLoader.close(); } } - public void preloadDBObject(DBObject obj, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - preloadDBObjectFields(obj, objectInfo.getFields()); - preloadDBObjectProperties(obj, objectInfo.getProperties()); - } - - private void preloadDBObjectFields(DBObject obj, long[] fieldUIDs) 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, fieldUIDs[fieldId]); - fields[fieldId] = field; - orderedFieldTypes[fieldId] = fieldType; - } - // Set fields metadata - obj.setFields(fields, orderedFieldTypes, fieldUIDs); - } - - private void preloadDBObjectProperties(DBObject obj, long[] propertyUIDs) { - // Declare the variables needed to get the biggest property Id - Method[] unorderedPropertyGetters = getPropertyGetters(obj); - Method[] unorderedPropertySetters = getPropertySetters(obj); - - // 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, propertyUIDs, setterMethods, getterMethods); - } - - Method[] getPropertyGetters(DBObject obj) { - return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertyGetter.class); - } - - Method[] getPropertySetters(DBObject obj) { - return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertySetter.class); - } - - protected Field[] getFields(DBObject obj) { - return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class); - } - - 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; - } - - - protected 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; - } - - public void loadProperty(DBObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException { - loadData(propertyType, propertyUID, property::getReturnType, (data) -> obj.setLoadedProperty(propertyId, data)); - } - - public void loadField(DBObject obj, Field field, DBDataType fieldType, long fieldUID) throws IOException { - loadData(fieldType, fieldUID, field::getType, (data) -> { - try { - FieldUtils.writeField(field, obj, data, true); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - }); - } - - @SuppressWarnings("unchecked") - private void loadData(DBDataType propertyType, long dataUID, Supplier> returnType, Consumer result) throws IOException { - switch (propertyType) { - case DATABASE_OBJECT: - DBObject fieldDBObjectValue = loadDBObject((Class) returnType.get(), dataUID); - //System.err.println("Loading data DBObj " + dataUID + ":" + fieldDBObjectValue); - result.accept(fieldDBObjectValue); - return; - case OBJECT: - Object fieldObjectValue = loadObject(dataUID); - //System.err.println("Loading data Obj " + dataUID + ":" + fieldObjectValue); - result.accept(fieldObjectValue); - return; - case UID_LIST: - LongArrayList fieldListObjectValue = loadListObject(dataUID); - //System.err.println("Loading data LOb " + dataUID + ":" + fieldListObjectValue); - result.accept(fieldListObjectValue); - return; - case BOOLEAN: - boolean fieldBooleanValue = loadBoolean(dataUID); - //System.err.println("Loading data Boo " + dataUID + ":" + fieldBooleanValue); - result.accept(fieldBooleanValue); - return; - case BYTE: - byte fieldByteValue = loadByte(dataUID); - //System.err.println("Loading data Byt " + dataUID + ":" + fieldByteValue); - result.accept(fieldByteValue); - return; - case SHORT: - short fieldShortValue = loadShort(dataUID); - //System.err.println("Loading data Shr " + dataUID + ":" + fieldShortValue); - result.accept(fieldShortValue); - return; - case CHAR: - char fieldCharValue = loadChar(dataUID); - //System.err.println("Loading data Chr " + dataUID + ":" + fieldCharValue); - result.accept(fieldCharValue); - return; - case INTEGER: - int fieldIntValue = loadInt(dataUID); - //System.err.println("Loading data Int " + dataUID + ":" + fieldIntValue); - result.accept(fieldIntValue); - return; - case LONG: - long fieldLongValue = loadLong(dataUID); - //System.err.println("Loading data Lng " + dataUID + ":" + fieldLongValue); - result.accept(fieldLongValue); - return; - default: - throw new NullPointerException("Unknown data type"); - } - } - - public T loadDBObject(Class type, long propertyUID) throws IOException { - try { - DBObjectIndicesManager.DBObjectInfo objectInfo = readUIDs(propertyUID); - if (objectInfo == null) return null; - return type.getDeclaredConstructor(JCWDatabase.class, DBObjectIndicesManager.DBObjectInfo.class).newInstance(jcwDatabase, objectInfo); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new IOException(e); - } - } - - private boolean isDBObjectNull(long uid) { - try { - return !objectIndicesManager.has(uid) || objectIndicesManager.get(uid) == null; - } catch (IOException ex) { - throw new IOError(ex); - } - } - - @SuppressWarnings("unchecked") - public T loadObject(long uid) throws IOException { - return indices.get(uid, (i, size) -> size == 0 ? null : (T) kryo.readClassAndObject(i)); - } - - private LongArrayList loadListObject(long uid) throws IOException { - return indices.get(uid, (i, size) -> { - if (size == 0) return null; - LongArrayList list = new LongArrayList(); - int listSize = i.readVarInt(true); - for (int li = 0; li < listSize; li++) { - list.add(i.readVarLong(true)); - } - return list; - }); - } - - public boolean loadBoolean(long uid) throws IOException { - return indices.get(uid, (i, size) -> size != 0 && i.readBoolean()); - } - - public byte loadByte(long uid) throws IOException { - return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readByte()); - } - - public short loadShort(long uid) throws IOException { - return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readShort()); - } - - public char loadChar(long uid) throws IOException { - return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readChar()); - } - - - public int loadInt(long uid) throws IOException { - return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readInt()); - } - - public long loadLong(long uid) throws IOException { - return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readLong()); - } - - /** - * - * @param uid - * @return - */ - public DBObjectIndicesManager.DBObjectInfo readUIDs(long uid) { - try { - return objectIndicesManager.get(uid); - } catch (IOException e) { - throw new IOError(e); - } - } - - public boolean exists(long uid) { - return objectIndicesManager.has(uid); - } - - public void writeObjectInfo(long uid, long[] fieldUIDs, long[] propertyUIDs) throws IOException { - //System.err.println("Saving obj. " + uid); - this.objectIndicesManager.set(uid, fieldUIDs, propertyUIDs); - } - - private void writeObjectInfoNull(long uid) throws IOException { - this.objectIndicesManager.setNull(uid); - } - - public void writeObjectProperty(long uid, DBDataType propertyType, T loadedPropertyValue) throws IOException { - switch (propertyType) { - case BOOLEAN: - indices.set(uid, 1, (o) -> o.writeBoolean(loadedPropertyValue == null ? false : (boolean) loadedPropertyValue)); - //System.err.println("Saving data Boo " + uid + ":" + loadedPropertyValue); - break; - case BYTE: - indices.set(uid, Byte.BYTES, (o) -> o.writeByte(loadedPropertyValue == null ? 0 : (byte) loadedPropertyValue)); - //System.err.println("Saving data Byt " + uid + ":" + loadedPropertyValue); - break; - case SHORT: - indices.set(uid, Short.BYTES, (o) -> o.writeShort(loadedPropertyValue == null ? 0 : (short) loadedPropertyValue)); - //System.err.println("Saving data Shr " + uid + ":" + loadedPropertyValue); - break; - case CHAR: - indices.set(uid, Character.BYTES, (o) -> o.writeChar(loadedPropertyValue == null ? 0 : (char) loadedPropertyValue)); - //System.err.println("Saving data Chr " + uid + ":" + loadedPropertyValue); - break; - case INTEGER: - indices.set(uid, Integer.BYTES, (o) -> o.writeInt(loadedPropertyValue == null ? 0 : (int) loadedPropertyValue)); - //System.err.println("Saving data Int " + uid + ":" + loadedPropertyValue); - break; - case LONG: - indices.set(uid, Long.BYTES, (o) -> o.writeLong(loadedPropertyValue == null ? 0 : (long) loadedPropertyValue)); - //System.err.println("Saving data Lng " + uid + ":" + loadedPropertyValue); - break; - case OBJECT: - Output baosOutput = new Output(new ByteArrayOutputStream()); - kryo.writeClassAndObject(baosOutput, loadedPropertyValue); - //System.err.println("Saving data Obj " + uid + ":" + loadedPropertyValue); - if (loadedPropertyValue instanceof Class) { - System.out.println(); - } - byte[] out = baosOutput.toBytes(); - indices.set(uid, out.length, o -> o.write(out, 0, out.length)); - break; - case UID_LIST: - if (loadedPropertyValue == null) { - indices.set(uid, 0, (o) -> {}); - } else { - LongArrayList list = (LongArrayList) loadedPropertyValue; - final int listSize = list.size(); - Output baosListOutput = new Output(Long.BYTES * 100, Long.BYTES * (listSize > 100 ? listSize : 100)); - baosListOutput.writeVarInt(listSize, true); - for (int i = 0; i < listSize; i++) { - baosListOutput.writeVarLong(list.getLong(i), true); - } - //System.err.println("Saving data LOb " + uid + ":" + loadedPropertyValue); - byte[] outList = baosListOutput.toBytes(); - indices.set(uid, outList.length, o -> o.write(outList, 0, outList.length)); - } - break; - case DATABASE_OBJECT: - //System.err.println("Saving data DBObj " + uid + ":" + loadedPropertyValue); - if (loadedPropertyValue == null) { - writeObjectInfoNull(uid); - } else { - ((DBObject) loadedPropertyValue).writeToDisk(uid); - } - break; - } - } - - public void registerClass(Class clazz, int id) { - kryo.register(clazz, 100 + id); - } - - public long allocateNullValue() { - return indices.add(0); - } - @Override public long clean() { return 0;//indices.clean(); } - - public long[] allocateNewUIDs(int quantity) { - long[] ids = new long[quantity]; - for (int i = 0; i < quantity; i++) { - ids[i] = allocateNullValue(); - } - return ids; - } } diff --git a/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java b/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java index 4e0854d..fbde022 100644 --- a/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java +++ b/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java @@ -1,35 +1,55 @@ package org.warp.jcwdb.ann; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; +import java.util.function.Supplier; public class JCWDatabase { private final DatabaseManager database; + private final DataLoader dataLoader; + private final DataInitializer dataInitializer; public JCWDatabase(Path dataFile, Path metadataFile, boolean registrationRequired) throws IOException { - this.database = new DatabaseManager(this, dataFile, metadataFile, registrationRequired); + this.dataLoader = new DataLoader(this, dataFile, metadataFile, registrationRequired); + this.dataInitializer = new DataInitializer(dataLoader); + this.database = new DatabaseManager(dataLoader); } - public T loadRoot(Class rootClass) { - try { - return database.loadRoot(rootClass); - } catch (IOException e) { - throw new RuntimeException(e); - } + public T loadRoot(Class rootClass) throws IOException { + return loadRoot(rootClass, () -> { + try { + return rootClass.getConstructor(JCWDatabase.class).newInstance(this); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IOException(e); + } + }); } - DatabaseManager getDatabaseManager() { - return database; + public T loadRoot(Class rootClass, SupplierWithIO ifAbsent) throws IOException { + return database.loadRoot(rootClass, ifAbsent); } public void registerClass(Class clazz, int id) { if (id < 0) { throw new IllegalArgumentException(); } - database.registerClass(clazz, id); + dataLoader.registerClass(clazz, id); } public void close() throws IOException { database.close(); } + + public DataLoader getDataLoader() { + return dataLoader; + } + + public DataInitializer getDataInitializer() { + return dataInitializer; + } + + public void initializeDBObject(DBObject dbObject) throws IOException { + dataInitializer.initializeDBObject(dbObject); + } } diff --git a/src/main/java/org/warp/jcwdb/ann/RunnableWithIO.java b/src/main/java/org/warp/jcwdb/ann/RunnableWithIO.java new file mode 100644 index 0000000..6f3215d --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/RunnableWithIO.java @@ -0,0 +1,19 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; + +@FunctionalInterface +public interface RunnableWithIO { + /** + * When an object implementing interface Runnable is used + * to create a thread, starting the thread causes the object's + * run method to be called in that separately executing + * thread. + *

+ * The general contract of the method run is that it may + * take any action whatsoever. + * + * @see java.lang.Thread#run() + */ + public abstract void run() throws IOException; +} diff --git a/src/main/java/org/warp/jcwdb/ann/SupplierWithIO.java b/src/main/java/org/warp/jcwdb/ann/SupplierWithIO.java new file mode 100644 index 0000000..2e8fa67 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/SupplierWithIO.java @@ -0,0 +1,8 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; + +@FunctionalInterface +public interface SupplierWithIO { + public T getWithIO() throws IOException; +} \ No newline at end of file diff --git a/src/test/java/org/warp/jcwdb/tests/DBDBObjectListTests.java b/src/test/java/org/warp/jcwdb/tests/DBDBObjectListTests.java index 60eaaa3..f362f50 100644 --- a/src/test/java/org/warp/jcwdb/tests/DBDBObjectListTests.java +++ b/src/test/java/org/warp/jcwdb/tests/DBDBObjectListTests.java @@ -20,6 +20,7 @@ public class DBDBObjectListTests { db.get().registerClass(TestUtils.class, 0); db.get().registerClass(TestUtils.RootClass.class, 1); db.get().registerClass(Class.class, 2); + System.out.println("Loading root"); root = db.get().loadRoot(RootWithList.class); }); root.list = new DBDBObjectList<>(db.get(), TestUtils.RootClass.class); @@ -37,7 +38,7 @@ public class DBDBObjectListTests { } @Test - public void shouldMatchList() { + public void shouldMatchList() throws IOException { checkEmptyList(); assertEquals(200, root.list.size()); for (int i = 0; i < 200; i++) { @@ -45,7 +46,7 @@ public class DBDBObjectListTests { } } - private void checkEmptyList() { + private void checkEmptyList() throws IOException { DBObjectList list = new DBObjectList<>(db.get()); assertEquals(null, list.getLast()); assertEquals(0, list.size()); @@ -63,15 +64,20 @@ public class DBDBObjectListTests { public static class RootWithList extends DBObject { - public RootWithList(JCWDatabase database) { + @DBField(id = 0, type = DBDataType.DATABASE_OBJECT) + public DBDBObjectList list; + + public RootWithList() { + super(); + } + + public RootWithList(JCWDatabase database) throws IOException { super(database); } - public RootWithList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); + @Override + public void initialize() throws IOException { + list = new DBDBObjectList<>(database, TestUtils.RootClass.class); } - - @DBField(id = 0, type = DBDataType.DATABASE_OBJECT) - public DBDBObjectList list; } } diff --git a/src/test/java/org/warp/jcwdb/tests/DBMultipleDBObjects.java b/src/test/java/org/warp/jcwdb/tests/DBMultipleDBObjects.java index c0e0aa1..dc378fe 100644 --- a/src/test/java/org/warp/jcwdb/tests/DBMultipleDBObjects.java +++ b/src/test/java/org/warp/jcwdb/tests/DBMultipleDBObjects.java @@ -43,20 +43,20 @@ public class DBMultipleDBObjects { public static class RootTwoClasses extends DBObject { - public RootTwoClasses(JCWDatabase database) { - super(database); - } - - public RootTwoClasses(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); - } - @DBField(id = 0, type = DBDataType.DATABASE_OBJECT) public TestUtils.RootClass class1; @DBField(id = 1, type = DBDataType.DATABASE_OBJECT) public TestUtils.RootClass class2; + public RootTwoClasses() { + super(); + } + + public RootTwoClasses(JCWDatabase database) throws IOException { + super(database); + } + @DBPropertyGetter(id = 0, type = DBDataType.DATABASE_OBJECT) public TestUtils.RootClass getClass3() { return getProperty(); @@ -76,5 +76,10 @@ public class DBMultipleDBObjects { public void setClass4(TestUtils.RootClass value) { setProperty(value); } + + @Override + public void initialize() throws IOException { + + } } } diff --git a/src/test/java/org/warp/jcwdb/tests/DBNestedDBObjects.java b/src/test/java/org/warp/jcwdb/tests/DBNestedDBObjects.java index 73160df..5ad71a9 100644 --- a/src/test/java/org/warp/jcwdb/tests/DBNestedDBObjects.java +++ b/src/test/java/org/warp/jcwdb/tests/DBNestedDBObjects.java @@ -3,12 +3,9 @@ package org.warp.jcwdb.tests; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.warp.jcwdb.ann.*; import org.warp.jcwdb.utils.NestedClass; import org.warp.jcwdb.utils.TestUtils; -import java.io.IOException; - import static org.junit.Assert.*; public class DBNestedDBObjects { diff --git a/src/test/java/org/warp/jcwdb/tests/DBRootCreation.java b/src/test/java/org/warp/jcwdb/tests/DBRootCreation.java index ff962d0..4b2523b 100644 --- a/src/test/java/org/warp/jcwdb/tests/DBRootCreation.java +++ b/src/test/java/org/warp/jcwdb/tests/DBRootCreation.java @@ -10,7 +10,7 @@ import org.warp.jcwdb.utils.TestUtils; import java.io.IOException; -import static org.junit.Assert.*; +import static org.junit.Assert.assertTrue; public class DBRootCreation { private TestUtils.WrappedDb db; @@ -21,7 +21,7 @@ public class DBRootCreation { } @Test - public void shouldCreateRoot() { + public void shouldCreateRoot() throws IOException { RootClass root = db.get().loadRoot(RootClass.class); assertTrue(root.test()); } @@ -33,16 +33,17 @@ public class DBRootCreation { public static class RootClass extends DBObject { - public RootClass(JCWDatabase database) { + public RootClass(JCWDatabase database) throws IOException { super(database); } - public RootClass(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); - } - public boolean test() { return true; } + + @Override + public void initialize() throws IOException { + + } } } diff --git a/src/test/java/org/warp/jcwdb/tests/NDBMultipleEnhancedObjects.java b/src/test/java/org/warp/jcwdb/tests/NDBMultipleEnhancedObjects.java new file mode 100644 index 0000000..82abb62 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/tests/NDBMultipleEnhancedObjects.java @@ -0,0 +1,90 @@ +package org.warp.jcwdb.tests; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +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.jcwdb.ann.DBPropertyGetter; +import org.warp.jcwdb.ann.DBPropertySetter; +import org.warp.jcwdb.utils.NTestUtils; + +import java.io.IOException; + +public class NDBMultipleEnhancedObjects { + private NTestUtils.WrappedDb db; + private RootTwoClasses root; + + @Before + public void setUp() throws Exception { + db = NTestUtils.wrapDb().create((db) -> { + root = db.get().loadRoot(RootTwoClasses.class); + }); + root.class1 = new NTestUtils.RootClass(db.get()); + db.setRootClassValues(root.class1); + root.class2 = new NTestUtils.RootClass(db.get()); + db.setRootClassValues(root.class2); + root.setClass3(new NTestUtils.RootClass(db.get())); + db.setRootClassValues(root.getClass3()); + root.setClass4(new NTestUtils.RootClass(db.get())); + db.setRootClassValues(root.getClass4()); + db.closeAndReopen(); + } + + @Test + public void shouldMatchMultipleEnhancedObjects() { + db.testRootClassValues(root.class1); + db.testRootClassValues(root.class2); + db.testRootClassValues(root.getClass3()); + db.testRootClassValues(root.getClass4()); + } + + @After + public void tearDown() throws Exception { + db.delete(); + } + + public static class RootTwoClasses extends EnhancedObject { + + @DBField(id = 0, type = DBDataType.DATABASE_OBJECT) + public NTestUtils.RootClass class1; + + @DBField(id = 1, type = DBDataType.DATABASE_OBJECT) + public NTestUtils.RootClass class2; + + public RootTwoClasses() { + super(); + } + + public RootTwoClasses(IDatabase database) throws IOException { + super(database); + } + + @DBPropertyGetter(id = 0, type = DBDataType.DATABASE_OBJECT) + public NTestUtils.RootClass getClass3() { + return getProperty(); + } + + @DBPropertySetter(id = 0, type = DBDataType.DATABASE_OBJECT) + public void setClass3(NTestUtils.RootClass value) { + setProperty(value); + } + + @DBPropertyGetter(id = 1, type = DBDataType.DATABASE_OBJECT) + public NTestUtils.RootClass getClass4() { + return getProperty(); + } + + @DBPropertySetter(id = 1, type = DBDataType.DATABASE_OBJECT) + public void setClass4(NTestUtils.RootClass value) { + setProperty(value); + } + + @Override + public void initialize() throws IOException { + + } + } +} diff --git a/src/test/java/org/warp/jcwdb/utils/ConsumerWithIO.java b/src/test/java/org/warp/jcwdb/utils/ConsumerWithIO.java new file mode 100644 index 0000000..0b85738 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/utils/ConsumerWithIO.java @@ -0,0 +1,32 @@ +package org.warp.jcwdb.utils; + +import java.io.IOException; +import java.util.Objects; + +@FunctionalInterface +public interface ConsumerWithIO { + + /** + * Performs this operation on the given argument. + * + * @param t the input argument + */ + void accept(T t) throws IOException; + + /** + * Returns a composed {@code Consumer} that performs, in sequence, this + * operation followed by the {@code after} operation. If performing either + * operation throws an exception, it is relayed to the caller of the + * composed operation. If performing this operation throws an exception, + * the {@code after} operation will not be performed. + * + * @param after the operation to perform after this operation + * @return a composed {@code Consumer} that performs in sequence this + * operation followed by the {@code after} operation + * @throws NullPointerException if {@code after} is null + */ + default ConsumerWithIO andThen(ConsumerWithIO after) { + Objects.requireNonNull(after); + return (T t) -> { accept(t); after.accept(t); }; + } +} diff --git a/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java b/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java new file mode 100644 index 0000000..55f76d6 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/utils/NSimplestClass.java @@ -0,0 +1,29 @@ +package org.warp.jcwdb.utils; + +import org.warp.cowdb.Database; +import org.warp.cowdb.EnhancedObject; +import org.warp.jcwdb.ann.DBDataType; +import org.warp.jcwdb.ann.DBField; +import org.warp.jcwdb.ann.DBObject; +import org.warp.jcwdb.ann.JCWDatabase; + +import java.io.IOException; + +public class NSimplestClass extends EnhancedObject { + + @DBField(id = 0, type = DBDataType.BOOLEAN) + public boolean field1; + + public NSimplestClass() { + + } + + public NSimplestClass(Database database) throws IOException { + super(database); + } + + @Override + public void initialize() throws IOException { + field1 = true; + } +} diff --git a/src/test/java/org/warp/jcwdb/utils/NTestUtils.java b/src/test/java/org/warp/jcwdb/utils/NTestUtils.java new file mode 100644 index 0000000..ffbae03 --- /dev/null +++ b/src/test/java/org/warp/jcwdb/utils/NTestUtils.java @@ -0,0 +1,383 @@ +package org.warp.jcwdb.utils; + +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.jcwdb.ann.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Comparator; + +import static org.junit.Assert.*; + +public class NTestUtils { + public static WrappedDb wrapDb() { + return new WrappedDb(); + } + + public static class WrappedDb { + + private Database db; + private Path tempDir; + private RunnableWithIO r; + + private WrappedDb() { + + } + + public WrappedDb create() throws IOException { + tempDir = Files.createTempDirectory("tests-"); + db = openDatabase(); + if (r != null) { + r.run(); + } + return this; + } + + public WrappedDb create(ConsumerWithIO r) throws IOException { + this.r = () -> r.accept(WrappedDb.this); + this.create(); + return this; + } + + private Database openDatabase() throws IOException { + return new Database(tempDir.resolve(Paths.get("data.db")), tempDir.resolve(Paths.get("blocks.dat")), tempDir.resolve(Paths.get("references.dat"))); + } + + public void delete() throws IOException { + db.close(); + deleteDir(tempDir); + } + + public Database get() { + return db; + } + + private void deleteDir(Path p) throws IOException { + Files.walk(p) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + + public void closeAndReopen() throws IOException { + db.close(); + db = openDatabase(); + r.run(); + } + + public void setRootClassValues(RootClass root) throws IOException { + setRootClassFields(root); + setRootClassProperties(root); + } + + public void setRootClassFields(RootClass root) throws IOException { + root.field1 = true; + root.field2 = 2; + root.field3 = 3; + root.field4 = 4; + root.field5 = 5; + root.field6 = 6; + root.field7 = "Test"; + root.field8 = new LongArrayList(); + root.field8.add(0); + root.field8.add(1); + root.field8.add(2); + root.field8.add(Long.MAX_VALUE/2); + root.field8.add(Long.MIN_VALUE/2); + root.field8.add(Long.MAX_VALUE); + root.field8.add(Long.MIN_VALUE); + root.field9 = new NSimplestClass(db); + root.field9.field1 = true; + + } + + public void setRootClassProperties(RootClass root) throws IOException { + root.set1(true); + root.set2((byte)2); + root.set3((short)3); + root.set4((char)4); + root.set5(5); + root.set6(6); + root.set7("Test"); + LongArrayList lArrayList = new LongArrayList(); + lArrayList.add(0); + lArrayList.add(1); + lArrayList.add(2); + lArrayList.add(Long.MAX_VALUE/2); + lArrayList.add(Long.MIN_VALUE/2); + lArrayList.add(Long.MAX_VALUE); + lArrayList.add(Long.MIN_VALUE); + root.set8(lArrayList); + NSimplestClass simplestClass9 = new NSimplestClass(db); + simplestClass9.field1 = true; + root.set9(simplestClass9); + } + + public void testRootClassValues(RootClass root) { + testRootClassFields(root); + testRootClassProperties(root); + } + + public void testRootClassFields(RootClass root) { + shouldGetFieldBoolean(root); + shouldGetFieldByte(root); + shouldGetFieldShort(root); + shouldGetFieldCharacter(root); + shouldGetFieldInteger(root); + shouldGetFieldLong(root); + shouldGetFieldObject(root); + shouldGetFieldUID(root); + shouldGetFieldDBObject(root); + } + + public void testRootClassProperties(RootClass root) { + shouldGetPropertyBoolean(root); + shouldGetPropertyByte(root); + shouldGetPropertyShort(root); + shouldGetPropertyCharacter(root); + shouldGetPropertyInteger(root); + shouldGetPropertyLong(root); + shouldGetPropertyObject(root); + shouldGetPropertyUID(root); + shouldGetPropertyDBObject(root); + } + + + private void shouldGetFieldBoolean(RootClass root) { + assertTrue(root.field1); + } + + private void shouldGetPropertyBoolean(RootClass root) { + assertTrue(root.get1()); + } + + private void shouldGetFieldByte(RootClass root) { + assertEquals(2, root.field2); + } + + private void shouldGetPropertyByte(RootClass root) { + assertEquals(2, root.get2()); + } + + private void shouldGetFieldShort(RootClass root) { + assertEquals(3, root.field3); + } + + private void shouldGetPropertyShort(RootClass root) { + assertEquals(3, root.get3()); + } + + private void shouldGetFieldCharacter(RootClass root) { + assertEquals(4, root.field4); + } + + private void shouldGetPropertyCharacter(RootClass root) { + assertEquals(4, root.get4()); + } + + private void shouldGetFieldInteger(RootClass root) { + assertEquals(5, root.field5); + } + + private void shouldGetPropertyInteger(RootClass root) { + assertEquals(5, root.get5()); + } + + private void shouldGetFieldLong(RootClass root) { + assertEquals(6, root.field6); + } + + private void shouldGetPropertyLong(RootClass root) { + assertEquals(6, root.get6()); + } + + private void shouldGetFieldObject(RootClass root) { + shouldGetObject(root.field7); + } + + private void shouldGetPropertyObject(RootClass root) { + shouldGetObject(root.get7()); + } + + private void shouldGetFieldDBObject(RootClass root) { + assertTrue(root.field9.field1); + } + + private void shouldGetPropertyDBObject(RootClass root) { + assertTrue(root.get9().field1); + } + + private void shouldGetObject(String val) { + assertNotNull(val); + assertEquals("Test", val); + } + + private void shouldGetDBObject(SimplestClass val) { + assertNotNull(val); + assertTrue(val.field1); + } + + private void shouldGetFieldUID(RootClass root) { + shouldGetUID(root.field8); + } + + private void shouldGetPropertyUID(RootClass root) { + shouldGetUID(root.get8()); + } + + private void shouldGetUID(LongArrayList val) { + assertNotNull(val); + assertEquals(7, val.size()); + assertEquals(0, val.getLong(0)); + assertEquals(val.getLong(5), Long.MAX_VALUE); + assertEquals(val.getLong(6), Long.MIN_VALUE); + } + + public void onLoad(RunnableWithIO r) { + this.r = r; + } + } + + public static class RootClass extends EnhancedObject { + + @DBField(id = 0, type = DBDataType.BOOLEAN) + public boolean field1; + + @DBField(id = 1, type = DBDataType.BYTE) + public byte field2; + + @DBField(id = 2, type = DBDataType.SHORT) + public short field3; + + @DBField(id = 3, type = DBDataType.CHAR) + public char field4; + + @DBField(id = 4, type = DBDataType.INTEGER) + public int field5; + + @DBField(id = 5, type = DBDataType.LONG) + public long field6; + + @DBField(id = 6, type = DBDataType.OBJECT) + public String field7; + + @DBField(id = 7, type = DBDataType.REFERENCES_LIST) + public LongArrayList field8; + + @DBField(id = 8, type = DBDataType.DATABASE_OBJECT) + public NSimplestClass field9; + + public RootClass() { + + } + + public RootClass(IDatabase database) throws IOException { + super(database); + } + + @DBPropertyGetter(id = 0, type = DBDataType.BOOLEAN) + public boolean get1() { + return getProperty(); + } + + @DBPropertyGetter(id = 1, type = DBDataType.BYTE) + public byte get2() { + return getProperty(); + } + + @DBPropertyGetter(id = 2, type = DBDataType.SHORT) + public short get3() { + return getProperty(); + } + + @DBPropertyGetter(id = 3, type = DBDataType.CHAR) + public char get4() { + return getProperty(); + } + + @DBPropertyGetter(id = 4, type = DBDataType.INTEGER) + public int get5() { + return getProperty(); + } + + @DBPropertyGetter(id = 5, type = DBDataType.LONG) + public long get6() { + return getProperty(); + } + + @DBPropertyGetter(id = 6, type = DBDataType.OBJECT) + public String get7() { + return getProperty(); + } + + @DBPropertyGetter(id = 7, type = DBDataType.REFERENCES_LIST) + public LongArrayList get8() { + return getProperty(); + } + + @DBPropertyGetter(id = 8, type = DBDataType.DATABASE_OBJECT) + public NSimplestClass get9() { + return getProperty(); + } + + @DBPropertySetter(id = 0, type = DBDataType.BOOLEAN) + public void set1(boolean val) { + setProperty(val); + } + + @DBPropertySetter(id = 1, type = DBDataType.BYTE) + public void set2(byte val) { + setProperty(val); + } + + @DBPropertySetter(id = 2, type = DBDataType.SHORT) + public void set3(short val) { + setProperty(val); + } + + @DBPropertySetter(id = 3, type = DBDataType.CHAR) + public void set4(char val) { + setProperty(val); + } + + @DBPropertySetter(id = 4, type = DBDataType.INTEGER) + public void set5(int val) { + setProperty(val); + } + + @DBPropertySetter(id = 5, type = DBDataType.LONG) + public void set6(long val) { + setProperty(val); + } + + @DBPropertySetter(id = 6, type = DBDataType.OBJECT) + public void set7(String val) { + setProperty(val); + } + + @DBPropertySetter(id = 7, type = DBDataType.REFERENCES_LIST) + public void set8(LongArrayList val) { + setProperty(val); + } + + @DBPropertySetter(id = 8, type = DBDataType.DATABASE_OBJECT) + public void set9(NSimplestClass val) { + setProperty(val); + } + + public boolean test() { + return true; + } + + @Override + public void initialize() throws IOException { + + } + } +} diff --git a/src/test/java/org/warp/jcwdb/utils/NestedClass.java b/src/test/java/org/warp/jcwdb/utils/NestedClass.java index 7a53bb3..042cbd4 100644 --- a/src/test/java/org/warp/jcwdb/utils/NestedClass.java +++ b/src/test/java/org/warp/jcwdb/utils/NestedClass.java @@ -12,6 +12,14 @@ public class NestedClass extends DBObject { @DBField(id = 1, type = DBDataType.DATABASE_OBJECT) public NestedClass child; + public NestedClass() { + + } + + public NestedClass(JCWDatabase database) throws IOException { + super(database); + } + @DBPropertySetter(id = 0, type = DBDataType.DATABASE_OBJECT) public void setValue(NestedClass value) { setProperty(value); @@ -22,11 +30,8 @@ public class NestedClass extends DBObject { return getProperty(); } - public NestedClass(JCWDatabase database) { - super(database); - } + @Override + public void initialize() throws IOException { - public NestedClass(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); } } diff --git a/src/test/java/org/warp/jcwdb/utils/SimplestClass.java b/src/test/java/org/warp/jcwdb/utils/SimplestClass.java index e73e55d..2791395 100644 --- a/src/test/java/org/warp/jcwdb/utils/SimplestClass.java +++ b/src/test/java/org/warp/jcwdb/utils/SimplestClass.java @@ -9,12 +9,16 @@ public class SimplestClass extends DBObject { @DBField(id = 0, type = DBDataType.BOOLEAN) public boolean field1; - public SimplestClass(JCWDatabase database) { - super(database); - field1 = true; + public SimplestClass() { + } - public SimplestClass(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); + public SimplestClass(JCWDatabase database) throws IOException { + super(database); + } + + @Override + public void initialize() throws IOException { + field1 = true; } } diff --git a/src/test/java/org/warp/jcwdb/utils/TestUtils.java b/src/test/java/org/warp/jcwdb/utils/TestUtils.java index 5f8c632..ed622e0 100644 --- a/src/test/java/org/warp/jcwdb/utils/TestUtils.java +++ b/src/test/java/org/warp/jcwdb/utils/TestUtils.java @@ -12,7 +12,6 @@ import java.util.Comparator; import java.util.function.Consumer; import static org.junit.Assert.*; -import static org.junit.Assert.assertEquals; public class TestUtils { public static WrappedDb wrapDb() { @@ -23,7 +22,7 @@ public class TestUtils { private JCWDatabase db; private Path tempDir; - private Runnable r; + private RunnableWithIO r; private WrappedDb() { @@ -38,7 +37,7 @@ public class TestUtils { return this; } - public WrappedDb create(Consumer r) throws IOException { + public WrappedDb create(ConsumerWithIO r) throws IOException { this.r = () -> r.accept(WrappedDb.this); this.create(); return this; @@ -70,12 +69,12 @@ public class TestUtils { r.run(); } - public void setRootClassValues(RootClass root) { + public void setRootClassValues(RootClass root) throws IOException { setRootClassFields(root); setRootClassProperties(root); } - public void setRootClassFields(RootClass root) { + public void setRootClassFields(RootClass root) throws IOException { root.field1 = true; root.field2 = 2; root.field3 = 3; @@ -96,7 +95,7 @@ public class TestUtils { } - public void setRootClassProperties(RootClass root) { + public void setRootClassProperties(RootClass root) throws IOException { root.set1(true); root.set2((byte)2); root.set3((short)3); @@ -238,7 +237,7 @@ public class TestUtils { assertEquals(val.getLong(6), Long.MIN_VALUE); } - public void onLoad(Runnable r) { + public void onLoad(RunnableWithIO r) { this.r = r; } } @@ -272,6 +271,14 @@ public class TestUtils { @DBField(id = 8, type = DBDataType.DATABASE_OBJECT) public SimplestClass field9; + public RootClass() { + + } + + public RootClass(JCWDatabase database) throws IOException { + super(database); + } + @DBPropertyGetter(id = 0, type = DBDataType.BOOLEAN) public boolean get1() { return getProperty(); @@ -362,16 +369,13 @@ public class TestUtils { setProperty(val); } - public RootClass(JCWDatabase database) { - super(database); - } - - public RootClass(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { - super(database, objectInfo); - } - public boolean test() { return true; } + + @Override + public void initialize() throws IOException { + + } } }