Added CowList
This commit is contained in:
parent
a772ac5431
commit
b06ee07ad2
@ -5,7 +5,12 @@ import com.esotericsoftware.kryo.io.ByteBufferInput;
|
||||
import com.esotericsoftware.kryo.io.ByteBufferInputStream;
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import it.unimi.dsi.fastutil.booleans.BooleanArrayList;
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||
import it.unimi.dsi.fastutil.chars.CharArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.shorts.ShortArrayList;
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.warp.jcwdb.ann.*;
|
||||
|
||||
@ -20,8 +25,7 @@ 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.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.warp.cowdb.IBlocksMetadata.EMPTY_BLOCK_ID;
|
||||
@ -102,6 +106,10 @@ public class Database implements IDatabase {
|
||||
return root;
|
||||
}
|
||||
|
||||
protected void registerClass(Class<?> type, int id) {
|
||||
this.objectsIO.registerClass(type, id);
|
||||
}
|
||||
|
||||
public static class DatabaseDataInitializer implements IDataInitializer {
|
||||
|
||||
private final DatabaseObjectsIO objectsIO;
|
||||
@ -203,6 +211,59 @@ public class Database implements IDatabase {
|
||||
private DatabaseObjectsIO(IDatabase database, DatabaseReferencesIO referencesIO) {
|
||||
this.database = database;
|
||||
this.referencesIO = referencesIO;
|
||||
kryo.setRegistrationRequired(false);
|
||||
int id = -90;
|
||||
registerClass(boolean[].class, id++);
|
||||
registerClass(byte[].class, id++);
|
||||
registerClass(short[].class, id++);
|
||||
registerClass(char[].class, id++);
|
||||
registerClass(int[].class, id++);
|
||||
registerClass(long[].class, id++);
|
||||
registerClass(Boolean[].class, id++);
|
||||
registerClass(Byte[].class, id++);
|
||||
registerClass(Short[].class, id++);
|
||||
registerClass(Character[].class, id++);
|
||||
registerClass(Integer[].class, id++);
|
||||
registerClass(Long[].class, id++);
|
||||
registerClass(String.class, id++);
|
||||
registerClass(String[].class, id++);
|
||||
registerClass(Boolean.class, id++);
|
||||
registerClass(Byte.class, id++);
|
||||
registerClass(Short.class, id++);
|
||||
registerClass(Character.class, id++);
|
||||
registerClass(Integer.class, id++);
|
||||
registerClass(Class.class, id++);
|
||||
registerClass(Object.class, id++);
|
||||
registerClass(Object[].class, id++);
|
||||
registerClass(Long.class, id++);
|
||||
registerClass(String.class, id++);
|
||||
registerClass(String[].class, id++);
|
||||
registerClass(boolean[][].class, id++);
|
||||
registerClass(byte[][].class, id++);
|
||||
registerClass(short[][].class, id++);
|
||||
registerClass(char[][].class, id++);
|
||||
registerClass(int[][].class, id++);
|
||||
registerClass(long[][].class, id++);
|
||||
registerClass(String[][].class, id++);
|
||||
registerClass(List.class, id++);
|
||||
registerClass(ArrayList.class, id++);
|
||||
registerClass(LinkedList.class, id++);
|
||||
registerClass(Set.class, id++);
|
||||
registerClass(HashSet.class, id++);
|
||||
registerClass(LinkedHashSet.class, id++);
|
||||
registerClass(Map.class, id++);
|
||||
registerClass(HashMap.class, id++);
|
||||
registerClass(LinkedHashMap.class, id++);
|
||||
registerClass(TreeMap.class, id++);
|
||||
registerClass(BooleanArrayList.class, id++);
|
||||
registerClass(ByteArrayList.class, id++);
|
||||
registerClass(ShortArrayList.class, id++);
|
||||
registerClass(CharArrayList.class, id++);
|
||||
registerClass(IntArrayList.class, id++);
|
||||
registerClass(LongArrayList.class, id++);
|
||||
registerClass(TreeSet.class, id++);
|
||||
registerClass(SortedSet.class, id++);
|
||||
registerClass(SortedMap.class, id++);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -301,7 +362,7 @@ public class Database implements IDatabase {
|
||||
try {
|
||||
setData(objectFullInfo.getFieldReferences()[i], objectFullInfo.getFieldTypes()[i], objectFullInfo.getFields()[i].get(value));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOError(e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < objectFullInfo.getPropertyReferences().length; i++) {
|
||||
@ -483,6 +544,14 @@ public class Database implements IDatabase {
|
||||
obj.setProperty(propertyId, loadData(propertyType, propertyUID, property::getReturnType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerClass(Class<?> type, int id) {
|
||||
if (id < -100) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
kryo.register(type, 100 + id);
|
||||
}
|
||||
|
||||
private <T extends EnhancedObject> void preloadEnhancedObjectProperties(T obj, long[] propertyReferences) {
|
||||
// Declare the variables needed to get the biggest property Id
|
||||
Method[] unorderedPropertyGetters = obj.getPropertyGetters();
|
||||
@ -671,19 +740,17 @@ public class Database implements IDatabase {
|
||||
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;
|
||||
firstFreeReference = metaFileChannel.size() / REF_META_BYTES_COUNT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getReference(long reference) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(REF_META_BYTES_COUNT);
|
||||
if (reference >= firstFreeReference || reference * REF_META_BYTES_COUNT > metaFileChannelSize) {
|
||||
if (reference >= firstFreeReference) {
|
||||
return EMPTY_BLOCK_ID;
|
||||
}
|
||||
SeekableByteChannel currentFileChannel = metaFileChannel.position(reference * REF_META_BYTES_COUNT);
|
||||
|
@ -12,7 +12,7 @@ public class EnhancedObjectFullInfo {
|
||||
private final DBDataType[] propertyTypes;
|
||||
private final Object[] loadedPropertyValues;
|
||||
|
||||
public EnhancedObjectFullInfo(long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) {
|
||||
EnhancedObjectFullInfo(long[] fieldReferences, DBDataType[] fieldTypes, Field[] fields, long[] propertyReferences, DBDataType[] propertyTypes, Object[] loadedPropertyValues) {
|
||||
this.fieldReferences = fieldReferences;
|
||||
this.fieldTypes = fieldTypes;
|
||||
this.fields = fields;
|
||||
@ -21,11 +21,11 @@ public class EnhancedObjectFullInfo {
|
||||
this.loadedPropertyValues = loadedPropertyValues;
|
||||
}
|
||||
|
||||
public long[] getFieldReferences() {
|
||||
long[] getFieldReferences() {
|
||||
return fieldReferences;
|
||||
}
|
||||
|
||||
public DBDataType[] getFieldTypes() {
|
||||
DBDataType[] getFieldTypes() {
|
||||
return fieldTypes;
|
||||
}
|
||||
|
||||
@ -33,15 +33,15 @@ public class EnhancedObjectFullInfo {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public long[] getPropertyReferences() {
|
||||
long[] getPropertyReferences() {
|
||||
return propertyReferences;
|
||||
}
|
||||
|
||||
public DBDataType[] getPropertyTypes() {
|
||||
DBDataType[] getPropertyTypes() {
|
||||
return propertyTypes;
|
||||
}
|
||||
|
||||
public Object[] getLoadedPropertyValues() {
|
||||
Object[] getLoadedPropertyValues() {
|
||||
return loadedPropertyValues;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import org.warp.jcwdb.ann.DBDataType;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
interface IObjectsIO {
|
||||
public interface IObjectsIO {
|
||||
<T extends EnhancedObject> T loadEnhancedObject(long reference, Class<T> objectType) throws IOException;
|
||||
|
||||
<T> T loadObject(long reference) throws IOException;
|
||||
@ -106,4 +106,6 @@ interface IObjectsIO {
|
||||
}
|
||||
|
||||
void loadProperty(EnhancedObject enhancedObject, int propertyId, Method propertyGetter, DBDataType propertyType, long propertyUID) throws IOException;
|
||||
|
||||
void registerClass(Class<?> type, int id);
|
||||
}
|
||||
|
@ -1,85 +1,72 @@
|
||||
package org.warp.jcwdb.ann;
|
||||
package org.warp.cowdb.lists;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public abstract class DBArrayList<T> extends DBObject {
|
||||
public abstract class CowList<T> extends EnhancedObject {
|
||||
|
||||
private final Object indicesAccessLock = new Object();
|
||||
|
||||
@DBField(id = 0, type = DBDataType.UID_LIST)
|
||||
@DBField(id = 0, type = DBDataType.REFERENCES_LIST)
|
||||
private LongArrayList indices;
|
||||
|
||||
public DBArrayList() {
|
||||
super();
|
||||
public CowList() {
|
||||
|
||||
}
|
||||
|
||||
public DBArrayList(JCWDatabase database) throws IOException {
|
||||
public CowList(IDatabase database) throws IOException {
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
public void initialize() throws IOException {
|
||||
indices = new LongArrayList();
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
public T get(int index) throws IOException {
|
||||
synchronized (indicesAccessLock) {
|
||||
try {
|
||||
long uid = indices.getLong(index);
|
||||
return loadItem(uid);
|
||||
} catch (IOException e) {
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add(T value) {
|
||||
long uid = database.getDataLoader().allocateNullValue();
|
||||
public void add(T value) throws IOException {
|
||||
long uid = database.getObjectsIO().newNullObject();
|
||||
synchronized (indicesAccessLock) {
|
||||
indices.add(uid);
|
||||
try {
|
||||
writeItemToDisk(uid, value);
|
||||
} catch (IOException e) {
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void update(int index, T value) {
|
||||
public void update(int index, T value) throws IOException {
|
||||
synchronized (indicesAccessLock) {
|
||||
set(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(int index, T value) {
|
||||
long uid = database.getDataLoader().allocateNullValue();
|
||||
public void set(int index, T value) throws IOException {
|
||||
long uid = database.getObjectsIO().newNullObject();
|
||||
synchronized (indicesAccessLock) {
|
||||
indices.set(index, uid);
|
||||
try {
|
||||
writeItemToDisk(uid, value);
|
||||
} catch (IOException e) {
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add(int index, T value) {
|
||||
long uid = database.getDataLoader().allocateNullValue();
|
||||
public void add(int index, T value) throws IOException {
|
||||
long uid = database.getObjectsIO().newNullObject();
|
||||
synchronized (indicesAccessLock) {
|
||||
indices.add(index, uid);
|
||||
try {
|
||||
writeItemToDisk(uid, value);
|
||||
} catch (IOException e) {
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T getLast() {
|
||||
public T getLast() throws IOException {
|
||||
synchronized (indicesAccessLock) {
|
||||
if (indices.size() > 0) {
|
||||
return get(indices.size() - 1);
|
||||
@ -107,7 +94,7 @@ public abstract class DBArrayList<T> extends DBObject {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", DBArrayList.class.getSimpleName() + "[", "]")
|
||||
return new StringJoiner(", ", CowList.class.getSimpleName() + "[", "]")
|
||||
.add(indices.size() + " items")
|
||||
.toString();
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.warp.cowdb.lists;
|
||||
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class EnhancedObjectCowList<T extends EnhancedObject> extends CowList<T> {
|
||||
|
||||
@DBField(id = 1, type = DBDataType.OBJECT)
|
||||
private Class<T> type;
|
||||
|
||||
public EnhancedObjectCowList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public EnhancedObjectCowList(IDatabase database, Class<T> type) throws IOException {
|
||||
super(database);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T loadItem(long uid) throws IOException {
|
||||
return database.getObjectsIO().loadEnhancedObject(uid, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeItemToDisk(long uid, T item) throws IOException {
|
||||
database.getObjectsIO().setEnhancedObject(uid, item);
|
||||
}
|
||||
}
|
29
src/main/java/org/warp/cowdb/lists/ObjectCowList.java
Normal file
29
src/main/java/org/warp/cowdb/lists/ObjectCowList.java
Normal file
@ -0,0 +1,29 @@
|
||||
package org.warp.cowdb.lists;
|
||||
|
||||
import org.warp.cowdb.EnhancedObject;
|
||||
import org.warp.cowdb.IDatabase;
|
||||
import org.warp.jcwdb.ann.DBDataType;
|
||||
import org.warp.jcwdb.ann.DBField;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ObjectCowList<T> extends CowList<T> {
|
||||
|
||||
public ObjectCowList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ObjectCowList(IDatabase database) throws IOException {
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T loadItem(long uid) throws IOException {
|
||||
return database.getObjectsIO().loadObject(uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeItemToDisk(long uid, T item) throws IOException {
|
||||
database.getObjectsIO().setObject(uid, item);
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public interface Castable {
|
||||
<T> T cast();
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public interface Cleanable {
|
||||
/**
|
||||
* Clean the object
|
||||
* @return the approximated number of cleaned items
|
||||
*/
|
||||
public long clean();
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public class Cleaner {
|
||||
|
||||
public static final boolean DISABLE_CLEANER = false;
|
||||
public static final boolean ENABLE_CLEANER_LOGGING = false;
|
||||
private static final double MAXIMUM_SLEEP_INTERVAL = 8d * 1000d; // 8 seconds
|
||||
private static final double MINIMUM_SLEEP_INTERVAL = 1d * 1000d; // 1 second
|
||||
private static final double NORMAL_REMOVED_ITEMS = 2500l;
|
||||
private static final double REMOVED_ITEMS_RATIO = 2.5d; // 250%
|
||||
|
||||
private final Cleanable[] objectsToClean;
|
||||
private final Thread cleanerThread;
|
||||
private int sleepInterval = (int) MINIMUM_SLEEP_INTERVAL;
|
||||
private volatile boolean stopRequest = false;
|
||||
|
||||
public Cleaner(Cleanable... objectsToClean) {
|
||||
this.objectsToClean = objectsToClean;
|
||||
this.cleanerThread = new Thread(new CleanLoop());
|
||||
this.cleanerThread.setName("Cleaner thread");
|
||||
this.cleanerThread.setDaemon(true);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!DISABLE_CLEANER) {
|
||||
this.cleanerThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean
|
||||
* @return number of removed items
|
||||
*/
|
||||
private long clean() {
|
||||
long cleanedItems = 0;
|
||||
for (Cleanable cleanable : objectsToClean) {
|
||||
cleanedItems += cleanable.clean();
|
||||
}
|
||||
//System.gc();
|
||||
return cleanedItems;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (cleanerThread != null) {
|
||||
stopRequest = true;
|
||||
while (cleanerThread.isAlive()) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CleanLoop implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while(!stopRequest) {
|
||||
try {
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Waiting " + sleepInterval + "ms.");
|
||||
sleepFor(sleepInterval);
|
||||
final long time1 = System.currentTimeMillis();
|
||||
final double removedItems = clean();
|
||||
final long time2 = System.currentTimeMillis();
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] CLEAN_TIME " + (time2 - time1));
|
||||
double suggestedExecutionTimeByItemsCalculations = (sleepInterval + MAXIMUM_SLEEP_INTERVAL) / 2;
|
||||
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] REMOVED_ITEMS: " + removedItems);
|
||||
if (removedItems > 0) {
|
||||
final double removedItemsRatio = removedItems / NORMAL_REMOVED_ITEMS;
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] REMOVED_ITEMS_RATIO: " + removedItemsRatio);
|
||||
if (removedItemsRatio < 1d / REMOVED_ITEMS_RATIO || removedItemsRatio >= REMOVED_ITEMS_RATIO) {
|
||||
suggestedExecutionTimeByItemsCalculations = sleepInterval / removedItemsRatio;
|
||||
}
|
||||
}
|
||||
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Items: SUGGESTING SLEEP_INTERVAL FROM " + sleepInterval + "ms TO " + suggestedExecutionTimeByItemsCalculations + "ms");
|
||||
|
||||
double newSleepInterval = suggestedExecutionTimeByItemsCalculations;
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Total: SUGGESTING SLEEP_INTERVAL FROM " + sleepInterval + "ms TO " + newSleepInterval + "ms");
|
||||
if (newSleepInterval > MAXIMUM_SLEEP_INTERVAL) {
|
||||
sleepInterval = (int) MAXIMUM_SLEEP_INTERVAL;
|
||||
} else if (newSleepInterval < MINIMUM_SLEEP_INTERVAL) {
|
||||
sleepInterval = (int) MINIMUM_SLEEP_INTERVAL;
|
||||
} else {
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] CHANGED SLEEP_INTERVAL FROM " + sleepInterval + "ms TO " + newSleepInterval + "ms");
|
||||
sleepInterval = (int) newSleepInterval;
|
||||
}
|
||||
|
||||
|
||||
if (ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] Cleaned " + removedItems + " items.");
|
||||
}catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sleepFor(int sleepInterval) throws InterruptedException {
|
||||
int lastI = (int) Math.ceil(((double) sleepInterval) / 1000d);
|
||||
for (int i = 0; i < lastI; i++) {
|
||||
if (stopRequest) {
|
||||
return;
|
||||
}
|
||||
if (i == lastI) {
|
||||
Thread.sleep(sleepInterval % 1000);
|
||||
} else {
|
||||
Thread.sleep(lastI);
|
||||
}
|
||||
Thread.sleep(sleepInterval);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
|
||||
public interface DBReader<T> {
|
||||
T read(Input i, int size);
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
|
||||
public interface DBWriter {
|
||||
void write(Output o);
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
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.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class FileAllocator implements AutoCloseable {
|
||||
private static final int MAXIMUM_UNALLOCATED_ENTRIES = 50000;
|
||||
|
||||
private final SeekableByteChannel dataFileChannel;
|
||||
private volatile long fileSize;
|
||||
private volatile boolean closed;
|
||||
private final Object closeLock = new Object();
|
||||
private final Object allocateLock = new Object();
|
||||
/**
|
||||
* index -> free space size
|
||||
*/
|
||||
private final Long2IntMap freeBytes = new Long2IntLinkedOpenHashMap();
|
||||
|
||||
public FileAllocator(SeekableByteChannel dataFileChannel) throws IOException {
|
||||
this.dataFileChannel = dataFileChannel;
|
||||
this.fileSize = this.dataFileChannel.size();
|
||||
}
|
||||
|
||||
public FileAllocator(SeekableByteChannel dataFileChannel, long fileSize, Long2IntMap freeBytes) {
|
||||
this.dataFileChannel = dataFileChannel;
|
||||
this.fileSize = fileSize;
|
||||
this.freeBytes.putAll(freeBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: not implemented
|
||||
*
|
||||
* @param size
|
||||
* @return offset
|
||||
*/
|
||||
public long allocate(int size) {
|
||||
checkClosed();
|
||||
synchronized (allocateLock) {
|
||||
long offset;
|
||||
if ((offset = allocateIntoUnusedParts(size)) != -1) {
|
||||
if (offset + size > fileSize) {
|
||||
fileSize = offset + size;
|
||||
}
|
||||
return offset;
|
||||
} else {
|
||||
return allocateToEnd(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private long allocateIntoUnusedParts(int size) {
|
||||
if (FileIndexManager.ALWAYS_ALLOCATE_NEW) return -1;
|
||||
Stream<Map.Entry<Long,Integer>> sorted =
|
||||
freeBytes.entrySet().stream()
|
||||
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
|
||||
final VariableWrapper<Long> holeOffset = new VariableWrapper<>(-1L);
|
||||
final VariableWrapper<Integer> holeSize = new VariableWrapper<>(0);
|
||||
sorted.anyMatch((entry) -> {
|
||||
int currentHoleSize = entry.getValue();
|
||||
if (currentHoleSize < size) {
|
||||
return true;
|
||||
}
|
||||
holeOffset.var = entry.getKey();
|
||||
holeSize.var = currentHoleSize;
|
||||
return false;
|
||||
});
|
||||
if (holeOffset.var != -1L) {
|
||||
freeBytes.remove(holeOffset.var);
|
||||
if (holeSize.var > size) {
|
||||
freeBytes.put(holeOffset.var + size, holeSize.var - size);
|
||||
}
|
||||
}
|
||||
return holeOffset.var;
|
||||
}
|
||||
|
||||
private long allocateToEnd(int size) {
|
||||
long allocatedOffset = fileSize;
|
||||
fileSize += size;
|
||||
return allocatedOffset;
|
||||
}
|
||||
|
||||
|
||||
public void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
synchronized (closeLock) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees the unused bytes
|
||||
*
|
||||
* @param startPosition
|
||||
* @param length
|
||||
*/
|
||||
public void markFree(long startPosition, int length) {
|
||||
checkClosed();
|
||||
if (FileIndexManager.ALWAYS_ALLOCATE_NEW) return;
|
||||
|
||||
if (freeBytes.containsKey(startPosition + length)) {
|
||||
int secondLength = freeBytes.remove(startPosition + length);
|
||||
freeBytes.put(startPosition, length + secondLength);
|
||||
} else {
|
||||
boolean addedToList = false;
|
||||
for (Long2IntMap.Entry entry : freeBytes.long2IntEntrySet()) {
|
||||
if (entry.getLongKey() + entry.getIntValue() == startPosition) {
|
||||
freeBytes.put(entry.getLongKey(), entry.getIntValue() + length);
|
||||
addedToList = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!addedToList && length > 0) {
|
||||
freeBytes.put(startPosition, length);
|
||||
}
|
||||
}
|
||||
|
||||
if (startPosition + length >= fileSize) {
|
||||
fileSize = startPosition;
|
||||
}
|
||||
|
||||
// Remove the smallest hole in the file
|
||||
if (freeBytes.size() > MAXIMUM_UNALLOCATED_ENTRIES) {
|
||||
Stream<Map.Entry<Long,Integer>> sorted =
|
||||
freeBytes.entrySet().stream()
|
||||
.sorted(Map.Entry.comparingByValue());
|
||||
Optional<Map.Entry<Long, Integer>> first = sorted.findFirst();
|
||||
if (first.isPresent()) {
|
||||
freeBytes.remove(first.get().getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkClosed() {
|
||||
if (closed) {
|
||||
throw new RuntimeException("Index Manager is closed.");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,495 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
import com.esotericsoftware.kryo.io.Input;
|
||||
import com.esotericsoftware.kryo.io.Output;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
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;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
public class FileIndexManager implements IndexManager {
|
||||
public static final boolean ALWAYS_ALLOCATE_NEW = true;
|
||||
private final SeekableByteChannel dataFileChannel, metadataFileChannel;
|
||||
private volatile long metadataFileChannelSize;
|
||||
private final FileAllocator fileAllocator;
|
||||
private final ByteBuffer metadataByteBuffer = ByteBuffer.allocateDirect(IndexDetails.TOTAL_BYTES);
|
||||
private final ByteBuffer maskByteBuffer = ByteBuffer.allocateDirect(Long.BYTES);
|
||||
private volatile boolean closed;
|
||||
private final Object closeLock = new Object();
|
||||
private final Object metadataByteBufferLock = new Object();
|
||||
private final Object maskByteBufferLock = new Object();
|
||||
private final Object indicesMapsAccessLock = new Object();
|
||||
|
||||
/**
|
||||
* Edit this using editIndex()
|
||||
* Get using getIndexMetadata()
|
||||
* This hashmap must contain all indices.
|
||||
*/
|
||||
private final Long2ObjectMap<IndexDetails> loadedIndices;
|
||||
/**
|
||||
* Edit this using editIndex()
|
||||
*/
|
||||
private final LongSet dirtyLoadedIndices, removedIndices;
|
||||
private long firstAllocableIndex;
|
||||
|
||||
public FileIndexManager(Path dataFile, Path metadataFile) throws IOException {
|
||||
if (Cleaner.DISABLE_CLEANER) {
|
||||
loadedIndices = new Long2ObjectOpenHashMap<>();
|
||||
dirtyLoadedIndices = new LongOpenHashSet();
|
||||
removedIndices = new LongOpenHashSet();
|
||||
} else {
|
||||
loadedIndices = new Long2ObjectLinkedOpenHashMap<>();
|
||||
dirtyLoadedIndices = new LongLinkedOpenHashSet();
|
||||
removedIndices = new LongLinkedOpenHashSet();
|
||||
}
|
||||
if (Files.notExists(dataFile)) {
|
||||
Files.createFile(dataFile);
|
||||
}
|
||||
if (Files.notExists(metadataFile)) {
|
||||
Files.createFile(metadataFile);
|
||||
}
|
||||
dataFileChannel = Files.newByteChannel(dataFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
metadataFileChannel = Files.newByteChannel(metadataFile, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||
metadataFileChannelSize = metadataFileChannel.size();
|
||||
fileAllocator = createFileAllocator(dataFileChannel, metadataFileChannel.position(0));
|
||||
firstAllocableIndex = getMetadataFileChannelSize() / (long) IndexDetails.TOTAL_BYTES;
|
||||
}
|
||||
|
||||
private long getMetadataFileChannelSize() {
|
||||
return metadataFileChannelSize;
|
||||
}
|
||||
|
||||
private FileAllocator createFileAllocator(final SeekableByteChannel dataFileChannel, final SeekableByteChannel metadataFileChannel) throws IOException {
|
||||
if (ALWAYS_ALLOCATE_NEW) {
|
||||
return new FileAllocator(dataFileChannel);
|
||||
} else {
|
||||
Long2IntMap freeBytes = new Long2IntRBTreeMap();
|
||||
Long2IntMap usedBytes = new Long2IntRBTreeMap();
|
||||
long firstOffset = 0;
|
||||
metadataFileChannel.position(0);
|
||||
while (metadataFileChannel.position() + IndexDetails.TOTAL_BYTES <= getMetadataFileChannelSize()) {
|
||||
IndexDetails indexDetails = readIndexDetailsAt(metadataFileChannel);
|
||||
if (indexDetails != null) {
|
||||
long offset = indexDetails.getOffset();
|
||||
if (!usedBytes.containsKey(offset) || indexDetails.getSize() > usedBytes.get(offset)) {
|
||||
usedBytes.put(offset, indexDetails.getSize());
|
||||
}
|
||||
if (offset < firstOffset) {
|
||||
firstOffset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long previousEntryOffset = 0;
|
||||
long previousEntrySize = 0;
|
||||
ObjectIterator<Long2IntMap.Entry> it = usedBytes.long2IntEntrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
final Long2IntMap.Entry entry = it.next();
|
||||
final long entryOffset = entry.getLongKey();
|
||||
final long entrySize = entry.getIntValue();
|
||||
it.remove();
|
||||
|
||||
if (previousEntryOffset + previousEntrySize < entryOffset) {
|
||||
freeBytes.put(previousEntryOffset + previousEntrySize, (int) (entryOffset - (previousEntryOffset + previousEntrySize)));
|
||||
}
|
||||
|
||||
previousEntryOffset = entryOffset;
|
||||
previousEntrySize = entrySize;
|
||||
}
|
||||
|
||||
final long fileSize = previousEntryOffset + previousEntrySize;
|
||||
|
||||
return new FileAllocator(dataFileChannel, fileSize, freeBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(long index, DBReader<T> reader) throws IOException {
|
||||
checkClosed();
|
||||
IndexDetails details = getIndexMetadata(index);
|
||||
Input i = new Input(Channels.newInputStream(dataFileChannel.position(details.getOffset())), details.getSize());
|
||||
T result = reader.read(i, details.getSize());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexDetails set(long index, int size, DBWriter data) throws IOException {
|
||||
checkClosed();
|
||||
IndexDetails indexDetails = getIndexMetadataUnsafe(index);
|
||||
if (ALWAYS_ALLOCATE_NEW || indexDetails == null || indexDetails.getSize() < size) {
|
||||
// Allocate new space
|
||||
IndexDetails newDetails = allocateAndWrite(index, size, data);
|
||||
if (indexDetails != null) {
|
||||
// Mark free the old bytes
|
||||
fileAllocator.markFree(indexDetails.getOffset(), indexDetails.getSize());
|
||||
}
|
||||
return newDetails;
|
||||
} else {
|
||||
// Check if size changed
|
||||
if (size < indexDetails.getSize()) {
|
||||
// Mark free the unused bytes
|
||||
fileAllocator.markFree(indexDetails.getOffset() + size, size);
|
||||
}
|
||||
// Update index details
|
||||
indexDetails = editIndex(index, indexDetails, indexDetails.getOffset(), size);
|
||||
// Write data
|
||||
writeExact(indexDetails, size, data);
|
||||
// Before returning, return IndexDetails
|
||||
return indexDetails;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long add(int size) {
|
||||
checkClosed();
|
||||
final long offset = fileAllocator.allocate(size);
|
||||
final IndexDetails indexDetails = new IndexDetails(offset, size);
|
||||
final long index = createIndexMetadata(indexDetails);
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long add(int size, DBWriter data) throws IOException {
|
||||
checkClosed();
|
||||
final long offset = fileAllocator.allocate(size);
|
||||
final IndexDetails indexDetails = new IndexDetails(offset, size);
|
||||
final long index = createIndexMetadata(indexDetails);
|
||||
writeExact(indexDetails, size, data);
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FullIndexDetails addAndGetDetails(int size, DBWriter data) throws IOException {
|
||||
checkClosed();
|
||||
final long offset = fileAllocator.allocate(size);
|
||||
final IndexDetails indexDetails = new IndexDetails(offset, size);
|
||||
final long index = createIndexMetadata(indexDetails);
|
||||
writeExact(indexDetails, size, data);
|
||||
return new FullIndexDetails(index, indexDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the data at index.
|
||||
* The input size must be equal to the index size!
|
||||
*/
|
||||
private void writeExact(final IndexDetails indexDetails, int size, DBWriter data) throws IOException {
|
||||
if (indexDetails.getSize() != size) {
|
||||
throw new IOException("Unable to write " + size + " in a space of " + indexDetails.getSize());
|
||||
}
|
||||
final long offset = indexDetails.getOffset();
|
||||
OutputStream os = Channels.newOutputStream(dataFileChannel.position(offset));
|
||||
final Output o = new Output(os, size);
|
||||
data.write(o);
|
||||
os.flush();
|
||||
o.flush();
|
||||
}
|
||||
|
||||
private IndexDetails allocateAndWrite(final long index, int size, DBWriter w) throws IOException {
|
||||
final long offset = fileAllocator.allocate(size);
|
||||
IndexDetails details = editIndex(index, offset, size);
|
||||
writeExact(details, size, w);
|
||||
return details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(long index) throws IOException {
|
||||
checkClosed();
|
||||
IndexDetails indexDetails = getIndexMetadataUnsafe(index);
|
||||
if (indexDetails != null) {
|
||||
fileAllocator.markFree(indexDetails.getOffset(), indexDetails.getSize());
|
||||
}
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
dirtyLoadedIndices.remove(index);
|
||||
loadedIndices.remove(index);
|
||||
removedIndices.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void flushAndUnload(long index) throws IOException {
|
||||
if (removedIndices.contains(index)) {
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
removedIndices.remove(index);
|
||||
dirtyLoadedIndices.remove(index);
|
||||
loadedIndices.remove(index);
|
||||
}
|
||||
// Update indices metadata
|
||||
SeekableByteChannel metadata = metadataFileChannel.position(index * IndexDetails.TOTAL_BYTES);
|
||||
eraseIndexDetails(metadata);
|
||||
}
|
||||
boolean isDirty = false;
|
||||
IndexDetails indexDetails = null;
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
if (dirtyLoadedIndices.contains(index)) {
|
||||
indexDetails = loadedIndices.get(index);
|
||||
dirtyLoadedIndices.remove(index);
|
||||
}
|
||||
}
|
||||
if (isDirty) {
|
||||
// Update indices metadata
|
||||
long position = index * IndexDetails.TOTAL_BYTES;
|
||||
resizeMetadataFileChannel(position);
|
||||
SeekableByteChannel metadata = metadataFileChannel.position(position);
|
||||
writeIndexDetails(metadata, indexDetails);
|
||||
}
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
loadedIndices.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(long index) {
|
||||
checkClosed();
|
||||
try {
|
||||
return getIndexMetadataUnsafe(index) != null;
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit index data if a change is detected
|
||||
* @param index
|
||||
* @param oldData Old index data to check
|
||||
* @param offset offset
|
||||
* @param size size
|
||||
* @return
|
||||
*/
|
||||
private IndexDetails editIndex(long index, IndexDetails oldData, long offset, int size) {
|
||||
if (oldData.getOffset() != offset || oldData.getSize() != size) {
|
||||
return editIndex(index, offset, size);
|
||||
} else {
|
||||
return oldData;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit index data
|
||||
* @param index
|
||||
* @param offset
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
private IndexDetails editIndex(long index, long offset, int size) {
|
||||
IndexDetails indexDetails = new IndexDetails(offset, size);
|
||||
editIndex(index, indexDetails);
|
||||
return indexDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit index data
|
||||
* @param index
|
||||
* @param details
|
||||
*/
|
||||
private void editIndex(long index, IndexDetails details) {
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
loadedIndices.put(index, details);
|
||||
dirtyLoadedIndices.add(index);
|
||||
}
|
||||
}
|
||||
|
||||
private long createIndexMetadata(IndexDetails indexDetails) {
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
long newIndex = firstAllocableIndex++;
|
||||
loadedIndices.put(newIndex, indexDetails);
|
||||
dirtyLoadedIndices.add(newIndex);
|
||||
removedIndices.remove(newIndex);
|
||||
return newIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private IndexDetails getIndexMetadataUnsafe(long index) throws IOException {
|
||||
// Return index details if loaded
|
||||
IndexDetails details;
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
details = loadedIndices.getOrDefault(index, null);
|
||||
}
|
||||
if (details != null) return details;
|
||||
|
||||
// Try to load the details from file
|
||||
final long metadataPosition = index * IndexDetails.TOTAL_BYTES;
|
||||
if (metadataPosition + IndexDetails.TOTAL_BYTES > getMetadataFileChannelSize()) {
|
||||
// Avoid underflow exception
|
||||
return null;
|
||||
}
|
||||
SeekableByteChannel currentMetadataFileChannel = metadataFileChannel.position(metadataPosition);
|
||||
IndexDetails indexDetails = readIndexDetailsAt(currentMetadataFileChannel);
|
||||
|
||||
if (indexDetails != null) {
|
||||
editIndex(index, indexDetails);
|
||||
return indexDetails;
|
||||
}
|
||||
|
||||
// No results found. Returning null
|
||||
return null;
|
||||
}
|
||||
|
||||
private IndexDetails readIndexDetailsAt(SeekableByteChannel currentMetadataFileChannel) throws IOException {
|
||||
IndexDetails indexDetails = null;
|
||||
synchronized (metadataByteBufferLock) {
|
||||
metadataByteBuffer.rewind();
|
||||
currentMetadataFileChannel.read(metadataByteBuffer);
|
||||
metadataByteBuffer.rewind();
|
||||
// If it's not deleted continue
|
||||
final long offset = metadataByteBuffer.getLong();
|
||||
if (offset >= 0) { // If it's < 0 it means that the index has been deleted
|
||||
final int size = metadataByteBuffer.getInt();
|
||||
indexDetails = new IndexDetails(offset, size);
|
||||
}
|
||||
}
|
||||
return indexDetails;
|
||||
}
|
||||
|
||||
private IndexDetails getIndexMetadata(long index) throws IOException {
|
||||
IndexDetails details = getIndexMetadataUnsafe(index);
|
||||
if (details == null)
|
||||
throw new IOException("Index " + index + " not found");
|
||||
else
|
||||
return details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
synchronized (closeLock) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
}
|
||||
|
||||
// Update indices metadata
|
||||
flushAllFlushableIndices();
|
||||
|
||||
// Remove removed indices
|
||||
removeRemovedIndices();
|
||||
fileAllocator.close();
|
||||
}
|
||||
|
||||
private void writeIndexDetails(SeekableByteChannel position, IndexDetails indexDetails) throws IOException {
|
||||
synchronized (metadataByteBufferLock) {
|
||||
final int size = indexDetails.getSize();
|
||||
final long offset = indexDetails.getOffset();
|
||||
metadataByteBuffer.rewind();
|
||||
metadataByteBuffer.putLong(offset);
|
||||
metadataByteBuffer.putInt(size);
|
||||
metadataByteBuffer.rewind();
|
||||
position.write(metadataByteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void eraseIndexDetails(SeekableByteChannel position) throws IOException {
|
||||
synchronized (maskByteBufferLock) {
|
||||
maskByteBuffer.rewind();
|
||||
maskByteBuffer.putLong(-1); // -1 = deleted
|
||||
maskByteBuffer.rewind();
|
||||
position.write(maskByteBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkClosed() {
|
||||
if (closed) {
|
||||
throw new RuntimeException("Index Manager is closed.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clean() {
|
||||
long cleaned = 0;
|
||||
long tim1 = System.currentTimeMillis();
|
||||
try {
|
||||
cleaned += flushAllFlushableIndices();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
long tim2 = System.currentTimeMillis();
|
||||
try {
|
||||
cleaned += removeRemovedIndices();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
long tim3 = System.currentTimeMillis();
|
||||
cleaned += cleanExtraIndices();
|
||||
long tim4 = System.currentTimeMillis();
|
||||
if (Cleaner.ENABLE_CLEANER_LOGGING) System.out.println("[CLEANER] FileIndexManager CLEAN_TIME: " + (tim2-tim1) + "," + (tim3-tim2) + "," + (tim4-tim3));
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
private long flushAllFlushableIndices() throws IOException {
|
||||
long flushedIndices = 0;
|
||||
SeekableByteChannel metadata = metadataFileChannel;
|
||||
long lastIndex = -2;
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
for (long index : dirtyLoadedIndices) {
|
||||
IndexDetails indexDetails = loadedIndices.get(index);
|
||||
long position = index * IndexDetails.TOTAL_BYTES;
|
||||
resizeMetadataFileChannel(position);
|
||||
if (index - lastIndex != 1) {
|
||||
metadata = metadata.position(position);
|
||||
}
|
||||
writeIndexDetails(metadata, indexDetails);
|
||||
lastIndex = index;
|
||||
flushedIndices++;
|
||||
}
|
||||
dirtyLoadedIndices.clear();
|
||||
}
|
||||
return flushedIndices;
|
||||
}
|
||||
|
||||
private void resizeMetadataFileChannel(long position) {
|
||||
if (position + IndexDetails.TOTAL_BYTES > metadataFileChannelSize) {
|
||||
metadataFileChannelSize = position + IndexDetails.TOTAL_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
private long removeRemovedIndices() throws IOException {
|
||||
SeekableByteChannel metadata = metadataFileChannel;
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
long removed = this.removedIndices.size();
|
||||
for (long index : this.removedIndices) {
|
||||
metadata = metadata.position(index * IndexDetails.TOTAL_BYTES);
|
||||
eraseIndexDetails(metadata);
|
||||
}
|
||||
this.removedIndices.clear();
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
private long cleanExtraIndices() {
|
||||
long removedIndices = 0;
|
||||
LongArrayList toUnload = new LongArrayList();
|
||||
synchronized (indicesMapsAccessLock) {
|
||||
if (loadedIndices.size() > DatabaseManager.MAX_LOADED_INDICES) {
|
||||
long count = loadedIndices.size();
|
||||
LongIterator it = loadedIndices.keySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
long loadedIndex = it.nextLong();
|
||||
if (count < DatabaseManager.MAX_LOADED_INDICES * 3l / 2l) {
|
||||
break;
|
||||
}
|
||||
toUnload.add(loadedIndex);
|
||||
removedIndices++;
|
||||
count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (long index : toUnload.elements()) {
|
||||
try {
|
||||
flushAndUnload(index);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return removedIndices;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public class FullIndexDetails extends IndexDetails {
|
||||
private final long index;
|
||||
|
||||
public FullIndexDetails(long index, IndexDetails details) {
|
||||
super(details);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public long getIndex() {
|
||||
return index;
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public class IndexDetails {
|
||||
/**
|
||||
* The bitmask is used to determine if an index has been deleted
|
||||
*/
|
||||
public static final int OFFSET_BYTES = Long.BYTES;
|
||||
public static final int DATA_SIZE_BYTES = Integer.BYTES;
|
||||
public static final int TOTAL_BYTES = OFFSET_BYTES + DATA_SIZE_BYTES;
|
||||
private final long offset;
|
||||
private final int size;
|
||||
|
||||
public IndexDetails(long offset, int size) {
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public IndexDetails(IndexDetails indexDetails) {
|
||||
this.offset = indexDetails.offset;
|
||||
this.size = indexDetails.size;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (int) (offset ^ (offset >>> 32));
|
||||
result = prime * result + size;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
IndexDetails other = (IndexDetails) obj;
|
||||
if (offset != other.offset)
|
||||
return false;
|
||||
if (size != other.size)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexDetails [offset=" + offset + ", size=" + size + "]";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface IndexManager extends Cleanable {
|
||||
<T> T get(long index, DBReader<T> reader) throws IOException;
|
||||
long add(int size);
|
||||
long add(int size, DBWriter writer) throws IOException;
|
||||
FullIndexDetails addAndGetDetails(int size, DBWriter writer) throws IOException;
|
||||
IndexDetails set(long index, int size, DBWriter writer) throws IOException;
|
||||
void delete(long index) throws IOException;
|
||||
boolean has(long index);
|
||||
void close() throws IOException;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public class NoParserFoundException extends NullPointerException {
|
||||
|
||||
public NoParserFoundException(String string) {
|
||||
super(string);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 701010818132241139L;
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
package org.warp.jcwdb;
|
||||
|
||||
public interface Saveable {
|
||||
void save();
|
||||
void saveAndFlush();
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package org.warp.jcwdb.ann;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DBDBObjectList<T extends DBObject> extends DBArrayList<T> {
|
||||
|
||||
@DBField(id = 1, type = DBDataType.OBJECT)
|
||||
private Class<T> type;
|
||||
|
||||
public DBDBObjectList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DBDBObjectList(JCWDatabase database, Class<T> type) throws IOException {
|
||||
super(database);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T loadItem(long uid) throws IOException {
|
||||
return database.getDataLoader().loadDBObject(type, uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeItemToDisk(long uid, T item) throws IOException {
|
||||
database.getDataLoader().writeObjectProperty(uid, DBDataType.DATABASE_OBJECT, item);
|
||||
}
|
||||
}
|
@ -9,6 +9,5 @@ public enum DBDataType {
|
||||
CHAR,
|
||||
INTEGER,
|
||||
LONG,
|
||||
UID_LIST,
|
||||
REFERENCES_LIST
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package org.warp.jcwdb.ann;
|
||||
|
||||
import org.apache.commons.lang3.reflect.MethodUtils;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class DBObject {
|
||||
protected JCWDatabase database;
|
||||
private Field[] fields;
|
||||
private DBDataType[] fieldTypes;
|
||||
private long[] fieldUIDs;
|
||||
|
||||
private Method[] propertyGetters;
|
||||
private Method[] propertySetters;
|
||||
private DBDataType[] propertyTypes;
|
||||
private long[] propertyUIDs;
|
||||
private boolean[] loadedProperties;
|
||||
private Object[] loadedPropertyValues;
|
||||
private Map<String, DBPropertySetter> setterMethods;
|
||||
private Map<String, DBPropertyGetter> getterMethods;
|
||||
private final Object fieldsAccessLock = new Object();
|
||||
private final Object propertiesAccessLock = new Object();
|
||||
|
||||
public DBObject() {
|
||||
|
||||
}
|
||||
|
||||
public DBObject(JCWDatabase database) throws IOException {
|
||||
this.database = database;
|
||||
database.initializeDBObject(this);
|
||||
}
|
||||
|
||||
public abstract void initialize() throws IOException;
|
||||
|
||||
|
||||
public <T> 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);
|
||||
}
|
||||
}
|
||||
|
||||
<T> void setLoadedProperty(int propertyId, T value) {
|
||||
loadedPropertyValues[propertyId] = value;
|
||||
loadedProperties[propertyId] = true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T getProperty(int propertyId) throws IOException {
|
||||
synchronized (propertiesAccessLock) {
|
||||
if (!loadedProperties[propertyId]) {
|
||||
long propertyUID = propertyUIDs[propertyId];
|
||||
database.getDataLoader().loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID);
|
||||
}
|
||||
return (T) loadedPropertyValues[propertyId];
|
||||
}
|
||||
}
|
||||
|
||||
public <T> 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(), propertyAnnotation.type(), value);
|
||||
}
|
||||
|
||||
public <T> void setProperty(int propertyId, DBDataType propertyType, T value) {
|
||||
synchronized (propertiesAccessLock) {
|
||||
loadedPropertyValues[propertyId] = value;
|
||||
loadedProperties[propertyId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void writeToDisk(long uid) {
|
||||
//System.err.println("Saving object " + uid + ":" + this);
|
||||
try {
|
||||
synchronized (propertiesAccessLock) {
|
||||
synchronized (fieldsAccessLock) {
|
||||
database.getDataLoader().writeObjectInfo(uid, fieldUIDs, propertyUIDs);
|
||||
}
|
||||
}
|
||||
synchronized (fieldsAccessLock) {
|
||||
for (int i = 0; i < fieldUIDs.length; i++) {
|
||||
try {
|
||||
database.getDataLoader().writeObjectProperty(fieldUIDs[i], fieldTypes[i], fields[i].get(this));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
synchronized (propertiesAccessLock) {
|
||||
for (int i = 0; i < propertyUIDs.length; i++) {
|
||||
database.getDataLoader().writeObjectProperty(propertyUIDs[i], propertyTypes[i], loadedPropertyValues[i]);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new IOError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public final void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldUIDs) {
|
||||
synchronized (fieldsAccessLock) {
|
||||
this.fields = fields;
|
||||
this.fieldTypes = fieldTypes;
|
||||
this.fieldUIDs = fieldUIDs;
|
||||
}
|
||||
}
|
||||
|
||||
public final void setProperties(Method[] propertyGetters, Method[] propertySetters, DBDataType[] propertyTypes, long[] propertyUIDs, Map<String, DBPropertySetter> setterMethods, Map<String, DBPropertyGetter> getterMethods) {
|
||||
synchronized (propertiesAccessLock) {
|
||||
this.propertyGetters = propertyGetters;
|
||||
this.propertySetters = propertySetters;
|
||||
this.propertyTypes = propertyTypes;
|
||||
this.propertyUIDs = propertyUIDs;
|
||||
this.loadedProperties = new boolean[this.propertyUIDs.length];
|
||||
this.loadedPropertyValues = new Object[this.propertyUIDs.length];
|
||||
this.setterMethods = setterMethods;
|
||||
this.getterMethods = getterMethods;
|
||||
}
|
||||
}
|
||||
|
||||
Method[] getPropertyGetters() {
|
||||
return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertyGetter.class);
|
||||
}
|
||||
|
||||
public Method[] getPropertySetters() {
|
||||
return MethodUtils.getMethodsWithAnnotation(this.getClass(), DBPropertySetter.class);
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package org.warp.jcwdb.ann;
|
||||
|
||||
import org.warp.jcwdb.FileIndexManager;
|
||||
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
|
||||
public class DBObjectIndicesManager {
|
||||
private final FileIndexManager indices;
|
||||
|
||||
DBObjectIndicesManager(FileIndexManager indices) {
|
||||
this.indices = indices;
|
||||
}
|
||||
|
||||
public long allocate(int fieldsCount, int propertiesCount) {
|
||||
long uid = indices.add(calculateObjectSize(fieldsCount, propertiesCount));
|
||||
//System.err.println("ALLOCATED UID " + uid);
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setNull(long uid) throws IOException {
|
||||
indices.set(uid, 0, (w) -> w.write(new byte[0]));
|
||||
}
|
||||
|
||||
public void set(long uid, long[] fields, long[] properties) throws IOException {
|
||||
indices.set(uid, calculateObjectSize(fields, properties), (w) -> {
|
||||
w.writeInt(fields.length);
|
||||
w.writeInt(properties.length);
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
w.writeLong(fields[i]);
|
||||
}
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
w.writeLong(properties[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public DBObjectInfo get(long uid) throws IOException {
|
||||
return indices.get(uid, (i, size) -> {
|
||||
if (size < Integer.BYTES * 2) {
|
||||
return null;
|
||||
}
|
||||
long[] indices = new long[i.readInt()];
|
||||
long[] properties = new long[i.readInt()];
|
||||
if (size != calculateObjectSize(indices, properties)) {
|
||||
throw new IOError(new IOException("The size of the object is different!"));
|
||||
}
|
||||
for (int indicesI = 0; indicesI < indices.length; indicesI++) {
|
||||
indices[indicesI] = i.readLong();
|
||||
}
|
||||
for (int propertiesI = 0; propertiesI < properties.length; propertiesI++) {
|
||||
properties[propertiesI] = i.readLong();
|
||||
}
|
||||
return new DBObjectInfo(indices, properties);
|
||||
});
|
||||
}
|
||||
|
||||
public boolean has(long uid) {
|
||||
return indices.has(uid);
|
||||
}
|
||||
|
||||
private int calculateObjectSize(long[] fields, long[] properties) {
|
||||
return calculateObjectSize(fields.length, properties.length);
|
||||
}
|
||||
|
||||
private int calculateObjectSize(int fieldsCount, int propertiesCount) {
|
||||
return Integer.BYTES * 2 + (fieldsCount + propertiesCount) * Long.BYTES;
|
||||
}
|
||||
|
||||
public class DBObjectInfo {
|
||||
private final long[] fields;
|
||||
private final long[] properties;
|
||||
|
||||
public DBObjectInfo(long[] fields, long[] properties) {
|
||||
this.fields = fields;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public long[] getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public long[] getProperties() {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package org.warp.jcwdb.ann;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DBObjectList<T> extends DBArrayList<T> {
|
||||
|
||||
public DBObjectList() {
|
||||
|
||||
}
|
||||
|
||||
public DBObjectList(JCWDatabase database) throws IOException {
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T loadItem(long uid) throws IOException {
|
||||
return database.getDataLoader().loadObject(uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeItemToDisk(long uid, T item) throws IOException {
|
||||
database.getDataLoader().writeObjectProperty(uid, DBDataType.OBJECT, item);
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
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<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);
|
||||
}
|
||||
}
|
@ -1,645 +0,0 @@
|
||||
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 extends DBObject> T loadRoot(Class<T> rootType, SupplierWithIO<T> ifAbsent) throws IOException {
|
||||
synchronized (indicesAccessLock) {
|
||||
if (isDBObjectNull(0)) {
|
||||
return ifAbsent.getWithIO();
|
||||
} else {
|
||||
return loadDBObject(rootType, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends DBObject> T instantiateDBObject(Class<T> 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<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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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<Class<?>> returnType, ConsumerWithIO result) throws IOException {
|
||||
synchronized (indicesAccessLock) {
|
||||
switch (propertyType) {
|
||||
case DATABASE_OBJECT:
|
||||
DBObject fieldDBObjectValue = loadDBObject((Class<? extends DBObject>) 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 extends DBObject> T loadDBObject(Class<T> 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> 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 <T> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package org.warp.jcwdb.ann;
|
||||
|
||||
import org.warp.jcwdb.Cleanable;
|
||||
import org.warp.jcwdb.Cleaner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class DatabaseManager implements Cleanable {
|
||||
|
||||
public static final long MAX_LOADED_INDICES = 100000;
|
||||
private final Cleaner cleaner;
|
||||
private final DataLoader dataLoader;
|
||||
private DBObject loadedRootObject = null;
|
||||
private volatile boolean closed;
|
||||
|
||||
DatabaseManager(DataLoader dataLoader) {
|
||||
this.dataLoader = dataLoader;
|
||||
this.cleaner = new Cleaner(this);
|
||||
this.cleaner.start();
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
DatabaseManager.this.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public <T extends DBObject> T loadRoot(Class<T> rootType, SupplierWithIO<T> ifAbsent) throws IOException {
|
||||
if (loadedRootObject != null) {
|
||||
throw new RuntimeException("Root already set!");
|
||||
}
|
||||
T root = dataLoader.loadRoot(rootType, ifAbsent);
|
||||
loadedRootObject = root;
|
||||
return root;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (!closed) {
|
||||
closed = true;
|
||||
DatabaseManager.this.cleaner.stop();
|
||||
if (loadedRootObject != null) {
|
||||
loadedRootObject.writeToDisk(0);
|
||||
}
|
||||
dataLoader.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long clean() {
|
||||
return 0;//indices.clean();
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
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.dataLoader = new DataLoader(this, dataFile, metadataFile, registrationRequired);
|
||||
this.dataInitializer = new DataInitializer(dataLoader);
|
||||
this.database = new DatabaseManager(dataLoader);
|
||||
}
|
||||
|
||||
public <T extends DBObject> T loadRoot(Class<T> rootClass) throws IOException {
|
||||
return loadRoot(rootClass, () -> {
|
||||
try {
|
||||
return rootClass.getConstructor(JCWDatabase.class).newInstance(this);
|
||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public <T extends DBObject> T loadRoot(Class<T> rootClass, SupplierWithIO<T> ifAbsent) throws IOException {
|
||||
return database.loadRoot(rootClass, ifAbsent);
|
||||
}
|
||||
|
||||
public void registerClass(Class<?> clazz, int id) {
|
||||
if (id < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
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.TestUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DBDBObjectListTests {
|
||||
private TestUtils.WrappedDb db;
|
||||
private RootWithList root;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = TestUtils.wrapDb().create((db) -> {
|
||||
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);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
TestUtils.RootClass rootClass = new TestUtils.RootClass(db.get());
|
||||
db.setRootClassValues(rootClass);
|
||||
root.list.add(rootClass);
|
||||
}
|
||||
db.closeAndReopen();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
TestUtils.RootClass rootClass = new TestUtils.RootClass(db.get());
|
||||
db.setRootClassValues(rootClass);
|
||||
root.list.add(rootClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchList() throws IOException {
|
||||
checkEmptyList();
|
||||
assertEquals(200, root.list.size());
|
||||
for (int i = 0; i < 200; i++) {
|
||||
db.testRootClassValues(root.list.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEmptyList() throws IOException {
|
||||
DBObjectList<String> list = new DBObjectList<>(db.get());
|
||||
assertEquals(null, list.getLast());
|
||||
assertEquals(0, list.size());
|
||||
assertTrue(list.isEmpty());
|
||||
list.add("1");
|
||||
assertEquals(1, list.size());
|
||||
assertEquals("1", list.getLast());
|
||||
assertFalse(list.isEmpty());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
db.delete();
|
||||
}
|
||||
|
||||
public static class RootWithList extends DBObject {
|
||||
|
||||
@DBField(id = 0, type = DBDataType.DATABASE_OBJECT)
|
||||
public DBDBObjectList<TestUtils.RootClass> list;
|
||||
|
||||
public RootWithList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public RootWithList(JCWDatabase database) throws IOException {
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws IOException {
|
||||
list = new DBDBObjectList<>(database, TestUtils.RootClass.class);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
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.TestUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class DBMultipleDBObjects {
|
||||
private TestUtils.WrappedDb db;
|
||||
private RootTwoClasses root;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = TestUtils.wrapDb().create((db) -> {
|
||||
root = db.get().loadRoot(RootTwoClasses.class);
|
||||
});
|
||||
root.class1 = new TestUtils.RootClass(db.get());
|
||||
db.setRootClassValues(root.class1);
|
||||
root.class2 = new TestUtils.RootClass(db.get());
|
||||
db.setRootClassValues(root.class2);
|
||||
root.setClass3(new TestUtils.RootClass(db.get()));
|
||||
db.setRootClassValues(root.getClass3());
|
||||
root.setClass4(new TestUtils.RootClass(db.get()));
|
||||
db.setRootClassValues(root.getClass4());
|
||||
db.closeAndReopen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchMultipleObjects() {
|
||||
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 DBObject {
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@DBPropertySetter(id = 0, type = DBDataType.DATABASE_OBJECT)
|
||||
public void setClass3(TestUtils.RootClass value) {
|
||||
setProperty(value);
|
||||
}
|
||||
|
||||
@DBPropertyGetter(id = 1, type = DBDataType.DATABASE_OBJECT)
|
||||
public TestUtils.RootClass getClass4() {
|
||||
return getProperty();
|
||||
}
|
||||
|
||||
@DBPropertySetter(id = 1, type = DBDataType.DATABASE_OBJECT)
|
||||
public void setClass4(TestUtils.RootClass value) {
|
||||
setProperty(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
package org.warp.jcwdb.tests;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.warp.jcwdb.utils.NestedClass;
|
||||
import org.warp.jcwdb.utils.TestUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DBNestedDBObjects {
|
||||
private TestUtils.WrappedDb db;
|
||||
private NestedClass root;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = TestUtils.wrapDb().create((db) -> {
|
||||
root = db.get().loadRoot(NestedClass.class);
|
||||
});
|
||||
root.child = new NestedClass(db.get());
|
||||
root.child.child = new NestedClass(db.get());
|
||||
root.child.child.child = new NestedClass(db.get());
|
||||
root.child.child.child.child = new NestedClass(db.get());
|
||||
root.child.child.child.child.isFinal = true;
|
||||
|
||||
NestedClass nestedClass0 = new NestedClass(db.get());
|
||||
nestedClass0.isFinal = true;
|
||||
NestedClass nestedClass1 = new NestedClass(db.get());
|
||||
nestedClass1.setValue(nestedClass0);
|
||||
NestedClass nestedClass2 = new NestedClass(db.get());
|
||||
nestedClass2.setValue(nestedClass1);
|
||||
NestedClass nestedClass3 = new NestedClass(db.get());
|
||||
nestedClass3.setValue(nestedClass2);
|
||||
NestedClass nestedClass4 = new NestedClass(db.get());
|
||||
nestedClass4.setValue(nestedClass3);
|
||||
root.setValue(nestedClass4);
|
||||
|
||||
db.closeAndReopen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchNestedObjects() {
|
||||
assertNotNull(root);
|
||||
assertNotNull(root.child);
|
||||
assertNotNull(root.child.child);
|
||||
assertNotNull(root.child.child.child);
|
||||
assertNotNull(root.child.child.child.child);
|
||||
assertTrue(root.child.child.child.child.isFinal);
|
||||
assertNotNull(root.getValue());
|
||||
assertFalse(root.isFinal);
|
||||
assertNotNull(root.getValue().getValue());
|
||||
assertFalse(root.getValue().isFinal);
|
||||
assertNotNull(root.getValue().getValue().getValue());
|
||||
assertFalse(root.getValue().getValue().isFinal);
|
||||
assertNotNull(root.getValue().getValue().getValue().getValue());
|
||||
assertFalse(root.getValue().getValue().getValue().isFinal);
|
||||
assertNotNull(root.getValue().getValue().getValue().getValue().getValue());
|
||||
assertFalse(root.getValue().getValue().getValue().getValue().isFinal);
|
||||
assertTrue(root.getValue().getValue().getValue().getValue().getValue().isFinal);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
db.delete();
|
||||
}
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package org.warp.jcwdb.tests;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.warp.jcwdb.ann.DBObject;
|
||||
import org.warp.jcwdb.ann.DBObjectIndicesManager;
|
||||
import org.warp.jcwdb.ann.JCWDatabase;
|
||||
import org.warp.jcwdb.utils.TestUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DBRootCreation {
|
||||
private TestUtils.WrappedDb db;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = TestUtils.wrapDb().create();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateRoot() throws IOException {
|
||||
RootClass root = db.get().loadRoot(RootClass.class);
|
||||
assertTrue(root.test());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
db.delete();
|
||||
}
|
||||
|
||||
public static class RootClass extends DBObject {
|
||||
|
||||
public RootClass(JCWDatabase database) throws IOException {
|
||||
super(database);
|
||||
}
|
||||
|
||||
public boolean test() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.warp.jcwdb.tests;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.warp.jcwdb.utils.TestUtils;
|
||||
|
||||
public class DBRootFields {
|
||||
private TestUtils.WrappedDb db;
|
||||
private TestUtils.RootClass root;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = TestUtils.wrapDb().create((db) -> {
|
||||
root = db.get().loadRoot(TestUtils.RootClass.class);
|
||||
});
|
||||
db.setRootClassFields(root);
|
||||
db.closeAndReopen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchAllFields() {
|
||||
db.testRootClassFields(root);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
db.delete();
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
package org.warp.jcwdb.tests;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.warp.jcwdb.utils.TestUtils;
|
||||
|
||||
public class DBRootProperties {
|
||||
private TestUtils.WrappedDb db;
|
||||
private TestUtils.RootClass root;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
db = TestUtils.wrapDb().create((db) -> {
|
||||
root = db.get().loadRoot(TestUtils.RootClass.class);
|
||||
});
|
||||
db.setRootClassProperties(root);
|
||||
db.closeAndReopen();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMatchAllProperties() {
|
||||
db.testRootClassProperties(root);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
db.delete();
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ 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;
|
||||
|
||||
|
@ -218,7 +218,7 @@ public class NTestUtils {
|
||||
assertEquals("Test", val);
|
||||
}
|
||||
|
||||
private void shouldGetDBObject(SimplestClass val) {
|
||||
private void shouldGetDBObject(NSimplestClass val) {
|
||||
assertNotNull(val);
|
||||
assertTrue(val.field1);
|
||||
}
|
||||
|
@ -1,37 +0,0 @@
|
||||
package org.warp.jcwdb.utils;
|
||||
|
||||
import org.warp.jcwdb.ann.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NestedClass extends DBObject {
|
||||
|
||||
@DBField(id = 0, type = DBDataType.BOOLEAN)
|
||||
public boolean isFinal;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@DBPropertyGetter(id = 0, type = DBDataType.DATABASE_OBJECT)
|
||||
public NestedClass getValue() {
|
||||
return getProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws IOException {
|
||||
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package org.warp.jcwdb.utils;
|
||||
|
||||
import org.warp.jcwdb.ann.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SimplestClass extends DBObject {
|
||||
|
||||
@DBField(id = 0, type = DBDataType.BOOLEAN)
|
||||
public boolean field1;
|
||||
|
||||
public SimplestClass() {
|
||||
|
||||
}
|
||||
|
||||
public SimplestClass(JCWDatabase database) throws IOException {
|
||||
super(database);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws IOException {
|
||||
field1 = true;
|
||||
}
|
||||
}
|
@ -1,381 +0,0 @@
|
||||
package org.warp.jcwdb.utils;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
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 java.util.function.Consumer;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class TestUtils {
|
||||
public static WrappedDb wrapDb() {
|
||||
return new WrappedDb();
|
||||
}
|
||||
|
||||
public static class WrappedDb {
|
||||
|
||||
private JCWDatabase 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<WrappedDb> r) throws IOException {
|
||||
this.r = () -> r.accept(WrappedDb.this);
|
||||
this.create();
|
||||
return this;
|
||||
}
|
||||
|
||||
private JCWDatabase openDatabase() throws IOException {
|
||||
return new JCWDatabase(tempDir.resolve(Paths.get("data.db")), tempDir.resolve(Paths.get("indices.idx")), true);
|
||||
}
|
||||
|
||||
public void delete() throws IOException {
|
||||
db.close();
|
||||
deleteDir(tempDir);
|
||||
}
|
||||
|
||||
public JCWDatabase 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 SimplestClass(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);
|
||||
SimplestClass simplestClass9 = new SimplestClass(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 DBObject {
|
||||
|
||||
@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.UID_LIST)
|
||||
public LongArrayList field8;
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@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.UID_LIST)
|
||||
public LongArrayList get8() {
|
||||
return getProperty();
|
||||
}
|
||||
|
||||
@DBPropertyGetter(id = 8, type = DBDataType.DATABASE_OBJECT)
|
||||
public SimplestClass 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.UID_LIST)
|
||||
public void set8(LongArrayList val) {
|
||||
setProperty(val);
|
||||
}
|
||||
|
||||
@DBPropertySetter(id = 8, type = DBDataType.DATABASE_OBJECT)
|
||||
public void set9(SimplestClass val) {
|
||||
setProperty(val);
|
||||
}
|
||||
|
||||
public boolean test() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() throws IOException {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user