2019-01-31 20:05:23 +01:00
|
|
|
package org.warp.cowdb;
|
|
|
|
|
|
|
|
import com.esotericsoftware.kryo.Kryo;
|
|
|
|
import com.esotericsoftware.kryo.io.Input;
|
|
|
|
import com.esotericsoftware.kryo.io.Output;
|
2019-02-01 00:04:51 +01:00
|
|
|
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;
|
2019-01-31 20:05:23 +01:00
|
|
|
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
2019-02-01 00:04:51 +01:00
|
|
|
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
2019-01-31 20:05:23 +01:00
|
|
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
|
|
|
import org.warp.jcwdb.ann.*;
|
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
|
|
import java.lang.reflect.Method;
|
|
|
|
import java.nio.ByteBuffer;
|
|
|
|
import java.nio.channels.SeekableByteChannel;
|
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.nio.file.StandardOpenOption;
|
2019-02-01 00:04:51 +01:00
|
|
|
import java.util.*;
|
2019-01-31 20:05:23 +01:00
|
|
|
import java.util.function.Supplier;
|
|
|
|
|
|
|
|
import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_ID;
|
|
|
|
|
|
|
|
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 extends EnhancedObject> T loadRoot(Class<T> type) throws IOException {
|
|
|
|
return loadRoot(type, () -> {
|
|
|
|
T obj = objectsIO.instantiateEnhancedObject(type);
|
|
|
|
dataInitializer.initializeDBObject(obj);
|
|
|
|
return obj;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public <T extends EnhancedObject> T loadRoot(Class<T> type, SupplierWithIO<T> 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;
|
|
|
|
}
|
|
|
|
|
2019-02-01 00:04:51 +01:00
|
|
|
protected void registerClass(Class<?> type, int id) {
|
|
|
|
this.objectsIO.registerClass(type, id);
|
|
|
|
}
|
|
|
|
|
2019-01-31 20:05:23 +01:00
|
|
|
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<String, DBPropertySetter> setterMethods = new LinkedHashMap<>();
|
|
|
|
Map<String, DBPropertyGetter> getterMethods = new LinkedHashMap<>();
|
|
|
|
|
|
|
|
// Load the properties metadata
|
|
|
|
for (Method property : unorderedPropertyGetters) {
|
|
|
|
DBPropertyGetter propertyAnnotation = property.getAnnotation(DBPropertyGetter.class);
|
|
|
|
int propertyId = propertyAnnotation.id();
|
|
|
|
DBDataType propertyType = propertyAnnotation.type();
|
|
|
|
propertyTypes[propertyId] = propertyType;
|
|
|
|
propertyGetters[propertyId] = property;
|
|
|
|
getterMethods.put(property.getName(), propertyAnnotation);
|
|
|
|
}
|
|
|
|
for (Method property : unorderedPropertySetters) {
|
|
|
|
DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
|
|
int propertyId = propertyAnnotation.id();
|
|
|
|
DBDataType propertyType = propertyAnnotation.type();
|
|
|
|
propertyTypes[propertyId] = propertyType;
|
|
|
|
propertySetters[propertyId] = property;
|
|
|
|
setterMethods.put(property.getName(), propertyAnnotation);
|
|
|
|
}
|
|
|
|
// Set properties metadata
|
|
|
|
obj.setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs, setterMethods, getterMethods);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class DatabaseObjectsIO implements IObjectsIO {
|
|
|
|
|
|
|
|
private final IDatabase database;
|
|
|
|
private final DatabaseReferencesIO referencesIO;
|
|
|
|
|
2019-02-05 17:56:28 +01:00
|
|
|
private final Object accessLock = new Object();
|
|
|
|
|
2019-01-31 20:05:23 +01:00
|
|
|
private Kryo kryo = new Kryo();
|
|
|
|
|
|
|
|
private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) {
|
|
|
|
this.database = database;
|
|
|
|
this.referencesIO = referencesIO;
|
2019-02-01 00:04:51 +01:00
|
|
|
kryo.setRegistrationRequired(false);
|
|
|
|
int id = -90;
|
|
|
|
registerClass(boolean[].class, id++);
|
|
|
|
registerClass(byte[].class, id++);
|
|
|
|
registerClass(short[].class, id++);
|
|
|
|
registerClass(char[].class, id++);
|
|
|
|
registerClass(int[].class, id++);
|
|
|
|
registerClass(long[].class, id++);
|
|
|
|
registerClass(Boolean[].class, id++);
|
|
|
|
registerClass(Byte[].class, id++);
|
|
|
|
registerClass(Short[].class, id++);
|
|
|
|
registerClass(Character[].class, id++);
|
|
|
|
registerClass(Integer[].class, id++);
|
|
|
|
registerClass(Long[].class, id++);
|
|
|
|
registerClass(String.class, id++);
|
|
|
|
registerClass(String[].class, id++);
|
|
|
|
registerClass(Boolean.class, id++);
|
|
|
|
registerClass(Byte.class, id++);
|
|
|
|
registerClass(Short.class, id++);
|
|
|
|
registerClass(Character.class, id++);
|
|
|
|
registerClass(Integer.class, id++);
|
|
|
|
registerClass(Class.class, id++);
|
|
|
|
registerClass(Object.class, id++);
|
|
|
|
registerClass(Object[].class, id++);
|
|
|
|
registerClass(Long.class, id++);
|
|
|
|
registerClass(String.class, id++);
|
|
|
|
registerClass(String[].class, id++);
|
|
|
|
registerClass(boolean[][].class, id++);
|
|
|
|
registerClass(byte[][].class, id++);
|
|
|
|
registerClass(short[][].class, id++);
|
|
|
|
registerClass(char[][].class, id++);
|
|
|
|
registerClass(int[][].class, id++);
|
|
|
|
registerClass(long[][].class, id++);
|
|
|
|
registerClass(String[][].class, id++);
|
|
|
|
registerClass(List.class, id++);
|
|
|
|
registerClass(ArrayList.class, id++);
|
|
|
|
registerClass(LinkedList.class, id++);
|
|
|
|
registerClass(Set.class, id++);
|
|
|
|
registerClass(HashSet.class, id++);
|
|
|
|
registerClass(LinkedHashSet.class, id++);
|
|
|
|
registerClass(Map.class, id++);
|
|
|
|
registerClass(HashMap.class, id++);
|
|
|
|
registerClass(LinkedHashMap.class, id++);
|
|
|
|
registerClass(TreeMap.class, id++);
|
|
|
|
registerClass(BooleanArrayList.class, id++);
|
|
|
|
registerClass(ByteArrayList.class, id++);
|
|
|
|
registerClass(ShortArrayList.class, id++);
|
|
|
|
registerClass(CharArrayList.class, id++);
|
|
|
|
registerClass(IntArrayList.class, id++);
|
|
|
|
registerClass(LongArrayList.class, id++);
|
|
|
|
registerClass(TreeSet.class, id++);
|
|
|
|
registerClass(SortedSet.class, id++);
|
|
|
|
registerClass(SortedMap.class, id++);
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public <T extends EnhancedObject> T loadEnhancedObject(long reference, Class<T> objectType) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
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);
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
private Object loadData(DBDataType propertyType, long dataReference, Supplier<Class<?>> returnType) throws IOException {
|
|
|
|
switch (propertyType) {
|
2019-02-05 17:56:28 +01:00
|
|
|
case ENHANCED_OBJECT:
|
2019-01-31 20:05:23 +01:00
|
|
|
return loadEnhancedObject(dataReference, (Class<? extends EnhancedObject>) returnType.get());
|
|
|
|
case OBJECT:
|
|
|
|
return loadObject(dataReference);
|
|
|
|
case REFERENCES_LIST:
|
|
|
|
return loadReferencesList(dataReference);
|
|
|
|
case BOOLEAN:
|
|
|
|
return loadBoolean(dataReference);
|
|
|
|
case BYTE:
|
|
|
|
return loadByte(dataReference);
|
|
|
|
case SHORT:
|
|
|
|
return loadShort(dataReference);
|
|
|
|
case CHAR:
|
|
|
|
return loadChar(dataReference);
|
|
|
|
case INTEGER:
|
|
|
|
return loadInt(dataReference);
|
|
|
|
case LONG:
|
|
|
|
return loadLong(dataReference);
|
|
|
|
default:
|
|
|
|
throw new NullPointerException("Unknown data type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private <T> void setData(long reference, DBDataType propertyType, T loadedPropertyValue) throws IOException {
|
|
|
|
switch (propertyType) {
|
|
|
|
case BOOLEAN:
|
|
|
|
setBoolean(reference, loadedPropertyValue != null && (boolean) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case BYTE:
|
|
|
|
setByte(reference, loadedPropertyValue == null ? 0 : (byte) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case SHORT:
|
|
|
|
setShort(reference, loadedPropertyValue == null ? 0 : (short) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case CHAR:
|
|
|
|
setChar(reference, loadedPropertyValue == null ? 0 : (char) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case INTEGER:
|
|
|
|
setInt(reference, loadedPropertyValue == null ? 0 : (int) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case LONG:
|
|
|
|
setLong(reference, loadedPropertyValue == null ? 0 : (long) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case OBJECT:
|
|
|
|
setObject(reference, loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
case REFERENCES_LIST:
|
|
|
|
setReferencesList(reference, (LongArrayList) loadedPropertyValue);
|
|
|
|
break;
|
2019-02-05 17:56:28 +01:00
|
|
|
case ENHANCED_OBJECT:
|
2019-01-31 20:05:23 +01:00
|
|
|
setEnhancedObject(reference, (EnhancedObject) loadedPropertyValue);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public <T extends EnhancedObject> void setEnhancedObject(long reference, T value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
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]);
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
2019-02-05 17:56:28 +01:00
|
|
|
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
|
|
|
|
buffer.putLong(objectFullInfo.getPropertyReferences()[i]);
|
|
|
|
}
|
|
|
|
for (int i = 0; i < objectFullInfo.getFieldReferences().length; i++) {
|
|
|
|
try {
|
|
|
|
setData(objectFullInfo.getFieldReferences()[i], objectFullInfo.getFieldTypes()[i], objectFullInfo.getFields()[i].get(value));
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw new IOException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
|
|
|
|
setData(objectFullInfo.getPropertyReferences()[i], objectFullInfo.getPropertyTypes()[i], objectFullInfo.getLoadedPropertyValues()[i]);
|
|
|
|
}
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, totalSize, buffer);
|
|
|
|
} else {
|
|
|
|
referencesIO.writeToReference(reference, 0, null);
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
@Override
|
|
|
|
public <T> T loadObject(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
buffer.rewind();
|
|
|
|
byte[] data = buffer.array();
|
|
|
|
return (T) kryo.readClassAndObject(new Input(data));
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public LongArrayList loadReferencesList(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
int itemsCount = buffer.getInt();
|
|
|
|
LongArrayList arrayList = new LongArrayList();
|
|
|
|
for (int i = 0; i < itemsCount; i++) {
|
|
|
|
arrayList.add(buffer.getLong());
|
|
|
|
}
|
|
|
|
return arrayList;
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean loadBoolean(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return buffer.get() == 1;
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public byte loadByte(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return buffer.get();
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public short loadShort(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return buffer.getShort();
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public char loadChar(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return buffer.getChar();
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int loadInt(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return buffer.getInt();
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long loadLong(long reference) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = referencesIO.readFromReference(reference);
|
|
|
|
if (buffer.limit() == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return buffer.getLong();
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public <T> void setObject(long reference, T value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
if (value != null) {
|
|
|
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
|
|
|
Output output = new Output(outputStream);
|
|
|
|
kryo.writeClassAndObject(output, value);
|
|
|
|
output.flush();
|
|
|
|
byte[] data = outputStream.toByteArray();
|
|
|
|
ByteBuffer dataByteBuffer = ByteBuffer.wrap(data);
|
|
|
|
referencesIO.writeToReference(reference, data.length, dataByteBuffer);
|
|
|
|
} else {
|
|
|
|
referencesIO.writeToReference(reference, 0, null);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setReferencesList(long reference, LongArrayList value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
if (value != null) {
|
|
|
|
int items = value.size();
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES * items + Integer.BYTES);
|
|
|
|
buffer.putInt(items);
|
|
|
|
for (int i = 0; i < items; i++) {
|
|
|
|
buffer.putLong(value.getLong(i));
|
|
|
|
}
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, buffer.limit(), buffer);
|
|
|
|
} else {
|
|
|
|
referencesIO.writeToReference(reference, 0, null);
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setBoolean(long reference, boolean value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES);
|
|
|
|
buffer.put(value ? (byte) 1 : (byte) 0);
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, Byte.BYTES, buffer);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setByte(long reference, byte value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Byte.BYTES);
|
|
|
|
buffer.put(value);
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, Byte.BYTES, buffer);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setShort(long reference, short value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Short.BYTES);
|
|
|
|
buffer.putShort(value);
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, Short.BYTES, buffer);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setChar(long reference, char value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Character.BYTES);
|
|
|
|
buffer.putChar(value);
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, Character.BYTES, buffer);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setInt(long reference, int value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
|
|
|
|
buffer.putInt(value);
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, Integer.BYTES, buffer);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void setLong(long reference, long value) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
|
|
|
|
buffer.putLong(value);
|
|
|
|
buffer.flip();
|
|
|
|
referencesIO.writeToReference(reference, Long.BYTES, buffer);
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long newNullObject() throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
return referencesIO.allocateReference();
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void loadProperty(EnhancedObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException {
|
2019-02-05 17:56:28 +01:00
|
|
|
synchronized (accessLock) {
|
|
|
|
obj.setProperty(propertyId, loadData(propertyType, propertyUID, property::getReturnType));
|
|
|
|
}
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
2019-02-01 00:04:51 +01:00
|
|
|
@Override
|
|
|
|
public void registerClass(Class<?> type, int id) {
|
|
|
|
if (id < -100) {
|
|
|
|
throw new IllegalArgumentException();
|
|
|
|
}
|
|
|
|
kryo.register(type, 100 + id);
|
|
|
|
}
|
|
|
|
|
2019-01-31 20:05:23 +01:00
|
|
|
private <T extends EnhancedObject> void preloadEnhancedObjectProperties(T obj, long[] propertyReferences) {
|
|
|
|
// Declare the variables needed to get the biggest property Id
|
|
|
|
Method[] unorderedPropertyGetters = obj.getPropertyGetters();
|
|
|
|
Method[] unorderedPropertySetters = obj.getPropertySetters();
|
|
|
|
|
|
|
|
// Find the biggest property Id
|
|
|
|
int biggestGetter = getBiggestPropertyGetterId(unorderedPropertyGetters);
|
|
|
|
int biggestSetter = getBiggestPropertySetterId(unorderedPropertySetters);
|
|
|
|
int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter;
|
|
|
|
|
|
|
|
for (Method property : unorderedPropertySetters) {
|
|
|
|
DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
|
|
int propertyId = fieldAnnotation.id();
|
|
|
|
if (propertyId > biggestPropertyId) {
|
|
|
|
biggestPropertyId = propertyId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Declare the other variables
|
|
|
|
DBDataType[] propertyTypes = new DBDataType[biggestPropertyId + 1];
|
|
|
|
Method[] propertyGetters = new Method[biggestPropertyId + 1];
|
|
|
|
Method[] propertySetters = new Method[biggestPropertyId + 1];
|
|
|
|
Map<String, DBPropertySetter> setterMethods = new LinkedHashMap<>();
|
|
|
|
Map<String, DBPropertyGetter> getterMethods = new LinkedHashMap<>();
|
|
|
|
|
|
|
|
// Load the properties metadata
|
|
|
|
for (Method property : unorderedPropertyGetters) {
|
|
|
|
DBPropertyGetter propertyAnnotation = property.getAnnotation(DBPropertyGetter.class);
|
|
|
|
int propertyId = propertyAnnotation.id();
|
|
|
|
DBDataType propertyType = propertyAnnotation.type();
|
|
|
|
propertyTypes[propertyId] = propertyType;
|
|
|
|
propertyGetters[propertyId] = property;
|
|
|
|
getterMethods.put(property.getName(), propertyAnnotation);
|
|
|
|
}
|
|
|
|
for (Method property : unorderedPropertySetters) {
|
|
|
|
DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
|
|
int propertyId = propertyAnnotation.id();
|
|
|
|
DBDataType propertyType = propertyAnnotation.type();
|
|
|
|
propertyTypes[propertyId] = propertyType;
|
|
|
|
propertySetters[propertyId] = property;
|
|
|
|
setterMethods.put(property.getName(), propertyAnnotation);
|
|
|
|
}
|
|
|
|
// Set properties metadata
|
|
|
|
obj.setProperties(propertyGetters, propertySetters, propertyTypes, propertyReferences, setterMethods, getterMethods);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getBiggestPropertyGetterId(Method[] unorderedPropertyGetters) {
|
|
|
|
int biggestPropertyId = -1;
|
|
|
|
for (Method property : unorderedPropertyGetters) {
|
|
|
|
DBPropertyGetter fieldAnnotation = property.getAnnotation(DBPropertyGetter.class);
|
|
|
|
int propertyId = fieldAnnotation.id();
|
|
|
|
if (propertyId > biggestPropertyId) {
|
|
|
|
biggestPropertyId = propertyId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return biggestPropertyId;
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getBiggestPropertySetterId(Method[] unorderedPropertySetters) {
|
|
|
|
int biggestPropertyId = -1;
|
|
|
|
for (Method property : unorderedPropertySetters) {
|
|
|
|
DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class);
|
|
|
|
int propertyId = fieldAnnotation.id();
|
|
|
|
if (propertyId > biggestPropertyId) {
|
|
|
|
biggestPropertyId = propertyId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return biggestPropertyId;
|
|
|
|
}
|
|
|
|
|
|
|
|
private <T extends EnhancedObject> void preloadEnhancedObjectFields(T obj, long[] fieldReferences) throws IOException {
|
|
|
|
// Declare the variables needed to get the biggest field Id
|
|
|
|
Field[] unorderedFields = getFields(obj);
|
|
|
|
// Find the biggest field Id
|
|
|
|
int biggestFieldId = getBiggestFieldId(unorderedFields);
|
|
|
|
|
|
|
|
// Declare the other variables
|
|
|
|
Field[] fields = new Field[biggestFieldId + 1];
|
|
|
|
DBDataType[] orderedFieldTypes = new DBDataType[biggestFieldId + 1];
|
|
|
|
|
|
|
|
// Load all fields metadata and load them
|
|
|
|
for (Field field : unorderedFields) {
|
|
|
|
DBField fieldAnnotation = field.getAnnotation(DBField.class);
|
|
|
|
int fieldId = fieldAnnotation.id();
|
|
|
|
DBDataType fieldType = fieldAnnotation.type();
|
|
|
|
loadField(obj, field, fieldType, fieldReferences[fieldId]);
|
|
|
|
fields[fieldId] = field;
|
|
|
|
orderedFieldTypes[fieldId] = fieldType;
|
|
|
|
}
|
|
|
|
// Set fields metadata
|
|
|
|
obj.setFields(fields, orderedFieldTypes, fieldReferences);
|
|
|
|
}
|
|
|
|
|
|
|
|
private <T extends EnhancedObject> void loadField(T obj, Field field, DBDataType fieldType, long fieldReference) throws IOException {
|
|
|
|
Object data = loadData(fieldType, fieldReference, field::getType);
|
|
|
|
try {
|
|
|
|
if (fieldType == DBDataType.OBJECT && data != null) {
|
|
|
|
if (!field.getType().isInstance(data)) {
|
|
|
|
throw new IOException("There is an attempt to load an object of type " + data.getClass() + " into a field of type " + field.getType());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FieldUtils.writeField(field, obj, data, true);
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private <T extends EnhancedObject> Field[] getFields(T obj) {
|
|
|
|
return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class);
|
|
|
|
}
|
|
|
|
|
|
|
|
private int getBiggestFieldId(Field[] unorderedFields) {
|
|
|
|
int biggestFieldId = -1;
|
|
|
|
for (Field field : unorderedFields) {
|
|
|
|
DBField fieldAnnotation = field.getAnnotation(DBField.class);
|
|
|
|
int propertyId = fieldAnnotation.id();
|
|
|
|
if (propertyId > biggestFieldId) {
|
|
|
|
biggestFieldId = propertyId;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return biggestFieldId;
|
|
|
|
}
|
|
|
|
|
|
|
|
private <T extends EnhancedObject> T instantiateEnhancedObject(Class<T> type) throws IOException {
|
|
|
|
try {
|
|
|
|
T obj = type.getConstructor().newInstance();
|
|
|
|
obj.database = database;
|
|
|
|
return obj;
|
|
|
|
} catch (NoSuchMethodException e) {
|
|
|
|
throw new IOException("You must declare a public empty constructor in class " + type + ": public " + type.getSimpleName() + "()", e);
|
|
|
|
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
|
|
|
throw new IOException(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private <T extends EnhancedObject> T preloadEnhancedObject(Class<T> objectType, long[] fieldRefs, long[] methodRefs) throws IOException {
|
|
|
|
T obj = instantiateEnhancedObject(objectType);
|
|
|
|
preloadEnhancedObjectFields(obj, fieldRefs);
|
|
|
|
preloadEnhancedObjectProperties(obj, methodRefs);
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
public long[] allocateNewUIDs(int quantity) throws IOException {
|
|
|
|
long[] ids = new long[quantity];
|
|
|
|
for (int i = 0; i < quantity; i++) {
|
|
|
|
ids[i] = newNullObject();
|
|
|
|
}
|
|
|
|
return ids;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class DatabaseReferencesIO implements IReferencesIO {
|
|
|
|
|
|
|
|
private final DatabaseBlocksIO blocksIO;
|
|
|
|
private final DatabaseReferencesMetadata referencesMetadata;
|
|
|
|
|
|
|
|
public DatabaseReferencesIO(DatabaseBlocksIO blocksIO, DatabaseReferencesMetadata referencesMetadata) {
|
|
|
|
this.blocksIO = blocksIO;
|
|
|
|
this.referencesMetadata = referencesMetadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long allocateReference() throws IOException {
|
|
|
|
return referencesMetadata.newReference(EMPTY_BLOCK_ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long allocateReference(int size, ByteBuffer data) throws IOException {
|
|
|
|
long blockId = (size == 0) ? EMPTY_BLOCK_ID : blocksIO.newBlock(size, data);
|
|
|
|
return referencesMetadata.newReference(blockId);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void writeToReference(long reference, int size, ByteBuffer data) throws IOException {
|
|
|
|
long blockId = (size == 0) ? EMPTY_BLOCK_ID : blocksIO.newBlock(size, data);
|
|
|
|
referencesMetadata.editReference(reference, blockId);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ByteBuffer readFromReference(long reference) throws IOException {
|
|
|
|
long blockId = referencesMetadata.getReference(reference);
|
|
|
|
return blocksIO.readBlock(blockId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class DatabaseReferencesMetadata implements IReferencesMetadata {
|
|
|
|
private final SeekableByteChannel metaFileChannel;
|
|
|
|
private final int REF_META_BYTES_COUNT = Long.BYTES;
|
|
|
|
private long firstFreeReference;
|
|
|
|
|
|
|
|
private DatabaseReferencesMetadata(Path refMetaFile) throws IOException {
|
|
|
|
metaFileChannel = Files.newByteChannel(refMetaFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
2019-02-01 00:04:51 +01:00
|
|
|
firstFreeReference = metaFileChannel.size() / REF_META_BYTES_COUNT;
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public long getReference(long reference) throws IOException {
|
|
|
|
ByteBuffer buffer = ByteBuffer.allocate(REF_META_BYTES_COUNT);
|
2019-02-01 00:04:51 +01:00
|
|
|
if (reference >= firstFreeReference) {
|
2019-01-31 20:05:23 +01:00
|
|
|
return EMPTY_BLOCK_ID;
|
|
|
|
}
|
|
|
|
SeekableByteChannel currentFileChannel = metaFileChannel.position(reference * REF_META_BYTES_COUNT);
|
|
|
|
currentFileChannel.read(buffer);
|
|
|
|
buffer.flip();
|
2019-02-05 17:56:28 +01:00
|
|
|
long block = buffer.getLong();
|
|
|
|
if (buffer.limit() == 0 || block == 0xFFFFFFFFFFFFFFFFL) {
|
|
|
|
return EMPTY_BLOCK_ID;
|
|
|
|
}
|
|
|
|
return block;
|
2019-01-31 20:05:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|