From 41b2bb77a29c2e29460a3dd79912982ec3a34169 Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Fri, 18 Jan 2019 02:35:55 +0100 Subject: [PATCH] Finished new implementation with annotations --- pom.xml | 162 ++++--- .../java/org/warp/jcwdb/FileAllocator.java | 15 +- .../java/org/warp/jcwdb/FileIndexManager.java | 120 ++--- .../java/org/warp/jcwdb/IndexManager.java | 1 - src/main/java/org/warp/jcwdb/ann/DBClass.java | 9 - .../org/warp/jcwdb/ann/DBDBObjectList.java | 35 ++ .../java/org/warp/jcwdb/ann/DBDataType.java | 3 +- src/main/java/org/warp/jcwdb/ann/DBList.java | 171 +++++++ .../java/org/warp/jcwdb/ann/DBObject.java | 211 ++++++--- .../jcwdb/ann/DBObjectIndicesManager.java | 19 +- .../java/org/warp/jcwdb/ann/DBObjectList.java | 25 + .../java/org/warp/jcwdb/ann/Database.java | 275 ----------- .../org/warp/jcwdb/ann/DatabaseManager.java | 447 ++++++++++++++++++ .../java/org/warp/jcwdb/ann/JCWDatabase.java | 32 ++ .../warp/jcwdb/ann/exampleimpl/Class1.java | 36 +- .../warp/jcwdb/ann/exampleimpl/Class2.java | 40 ++ .../jcwdb/ann/exampleimpl/ClassWithList.java | 48 ++ .../warp/jcwdb/ann/exampleimpl/IntClass.java | 27 ++ .../org/warp/jcwdb/ann/exampleimpl/Main.java | 67 ++- .../ann/exampleimpl/MainSingleClass.java | 52 ++ .../jcwdb/ann/exampleimpl/SingleClass.java | 68 +++ src/test/java/org/warp/jcwdb/AppTest.java | 53 +++ 22 files changed, 1402 insertions(+), 514 deletions(-) delete mode 100644 src/main/java/org/warp/jcwdb/ann/DBClass.java create mode 100644 src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java create mode 100644 src/main/java/org/warp/jcwdb/ann/DBList.java create mode 100644 src/main/java/org/warp/jcwdb/ann/DBObjectList.java delete mode 100644 src/main/java/org/warp/jcwdb/ann/Database.java create mode 100644 src/main/java/org/warp/jcwdb/ann/DatabaseManager.java create mode 100644 src/main/java/org/warp/jcwdb/ann/JCWDatabase.java create mode 100644 src/main/java/org/warp/jcwdb/ann/exampleimpl/Class2.java create mode 100644 src/main/java/org/warp/jcwdb/ann/exampleimpl/ClassWithList.java create mode 100644 src/main/java/org/warp/jcwdb/ann/exampleimpl/IntClass.java create mode 100644 src/main/java/org/warp/jcwdb/ann/exampleimpl/MainSingleClass.java create mode 100644 src/main/java/org/warp/jcwdb/ann/exampleimpl/SingleClass.java diff --git a/pom.xml b/pom.xml index bc00df0..e46f8ab 100644 --- a/pom.xml +++ b/pom.xml @@ -1,86 +1,94 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - org.warp - jcwdb - 1.0-SNAPSHOT + org.warp + jcwdb + 1.1-SNAPSHOT - jcwdb - - http://www.example.com + jcwdb + + http://www.example.com - - UTF-8 - 11 - 11 - + + UTF-8 + 10 + 10 + - - - junit - junit - 4.11 - test - - - it.unimi.dsi - fastutil - 8.2.2 - - - com.esotericsoftware - kryo - 5.0.0-RC1 - - - net.openhft - zero-allocation-hashing - 0.8 - - - - org.apache.commons - commons-lang3 - 3.8.1 - - + + + sonatype-snapshots + sonatype snapshots repo + https://oss.sonatype.org/content/repositories/snapshots + + - - - - - maven-clean-plugin - 3.0.0 - - - - maven-resources-plugin - 3.0.2 - - - maven-compiler-plugin - 3.7.0 - - - maven-surefire-plugin - 2.22.1 - - - maven-jar-plugin - 3.0.2 - - - maven-install-plugin - 2.5.2 - - - maven-deploy-plugin - 2.8.2 - - - - + + + junit + junit + 4.11 + test + + + it.unimi.dsi + fastutil + 8.2.2 + + + com.esotericsoftware + kryo + 5.0.0-RC2-SNAPSHOT + + + net.openhft + zero-allocation-hashing + 0.8 + + + + org.apache.commons + commons-lang3 + 3.8.1 + + + + + + + + maven-clean-plugin + 3.0.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.7.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + diff --git a/src/main/java/org/warp/jcwdb/FileAllocator.java b/src/main/java/org/warp/jcwdb/FileAllocator.java index 88f04d5..8809afd 100644 --- a/src/main/java/org/warp/jcwdb/FileAllocator.java +++ b/src/main/java/org/warp/jcwdb/FileAllocator.java @@ -29,7 +29,7 @@ public class FileAllocator implements AutoCloseable { this.fileSize = this.dataFileChannel.size(); } - public FileAllocator(SeekableByteChannel dataFileChannel, long fileSize, Long2IntMap freeBytes) throws IOException { + public FileAllocator(SeekableByteChannel dataFileChannel, long fileSize, Long2IntMap freeBytes) { this.dataFileChannel = dataFileChannel; this.fileSize = fileSize; this.freeBytes.putAll(freeBytes); @@ -44,16 +44,20 @@ public class FileAllocator implements AutoCloseable { public long allocate(int size) { checkClosed(); synchronized (allocateLock) { - long offset = allocateIntoUnusedParts(size); - if (offset == -1) { - return allocateToEnd(size); - } else { + 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> sorted = freeBytes.entrySet().stream() .sorted(Collections.reverseOrder(Map.Entry.comparingByValue())); @@ -104,6 +108,7 @@ public class FileAllocator implements AutoCloseable { */ 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); diff --git a/src/main/java/org/warp/jcwdb/FileIndexManager.java b/src/main/java/org/warp/jcwdb/FileIndexManager.java index 1aea0fd..3f52372 100644 --- a/src/main/java/org/warp/jcwdb/FileIndexManager.java +++ b/src/main/java/org/warp/jcwdb/FileIndexManager.java @@ -4,7 +4,7 @@ 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.Database; +import org.warp.jcwdb.ann.DatabaseManager; import java.io.IOException; import java.nio.ByteBuffer; @@ -13,14 +13,14 @@ import java.nio.channels.SeekableByteChannel; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.function.Consumer; public class FileIndexManager implements IndexManager { + public static final boolean ALWAYS_ALLOCATE_NEW = false; 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(Integer.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(); @@ -36,19 +36,17 @@ public class FileIndexManager implements IndexManager { /** * Edit this using editIndex() */ - private final LongSet dirtyLoadedIndices, flushingAllowedIndices, removedIndices; + 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(); - flushingAllowedIndices = new LongOpenHashSet(); removedIndices = new LongOpenHashSet(); } else { loadedIndices = new Long2ObjectLinkedOpenHashMap<>(); dirtyLoadedIndices = new LongLinkedOpenHashSet(); - flushingAllowedIndices = new LongLinkedOpenHashSet(); removedIndices = new LongLinkedOpenHashSet(); } if (Files.notExists(dataFile)) { @@ -59,53 +57,57 @@ public class FileIndexManager implements IndexManager { } dataFileChannel = Files.newByteChannel(dataFile, StandardOpenOption.READ, StandardOpenOption.WRITE); metadataFileChannel = Files.newByteChannel(metadataFile, StandardOpenOption.READ, StandardOpenOption.WRITE); - fileAllocator = createFileAllocator(dataFileChannel, metadataFileChannel.position(0)); metadataFileChannelSize = metadataFileChannel.size(); + fileAllocator = createFileAllocator(dataFileChannel, metadataFileChannel.position(0)); firstAllocableIndex = getMetadataFileChannelSize() / (long) IndexDetails.TOTAL_BYTES; - if (firstAllocableIndex == 0) { - firstAllocableIndex = 1; - } } - private long getMetadataFileChannelSize() throws IOException { + private long getMetadataFileChannelSize() { return metadataFileChannelSize; } private FileAllocator createFileAllocator(final SeekableByteChannel dataFileChannel, final SeekableByteChannel metadataFileChannel) throws IOException { - Long2IntMap freeBytes = new Long2IntRBTreeMap(); - Long2IntMap usedBytes = new Long2IntRBTreeMap(); - long firstOffset = 0; - while (metadataFileChannel.position() + IndexDetails.TOTAL_BYTES <= getMetadataFileChannelSize()) { - IndexDetails indexDetails = readIndexDetailsAt(metadataFileChannel); - if (indexDetails != null) { - long offset = indexDetails.getOffset(); - usedBytes.put(offset, indexDetails.getSize()); - if (offset < firstOffset) { - firstOffset = offset; + 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 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(); + long previousEntryOffset = 0; + long previousEntrySize = 0; + ObjectIterator 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))); + if (previousEntryOffset + previousEntrySize < entryOffset) { + freeBytes.put(previousEntryOffset + previousEntrySize, (int) (entryOffset - (previousEntryOffset + previousEntrySize))); + } + + previousEntryOffset = entryOffset; + previousEntrySize = entrySize; } - previousEntryOffset = entryOffset; - previousEntrySize = entrySize; + final long fileSize = previousEntryOffset + previousEntrySize; + + return new FileAllocator(dataFileChannel, fileSize, freeBytes); } - - final long fileSize = previousEntryOffset + previousEntrySize; - - return new FileAllocator(dataFileChannel, fileSize, freeBytes); } @Override @@ -121,7 +123,7 @@ public class FileIndexManager implements IndexManager { public IndexDetails set(long index, int size, DBWriter data) throws IOException { checkClosed(); IndexDetails indexDetails = getIndexMetadataUnsafe(index); - if (indexDetails == null || indexDetails.getSize() < size) { + if (ALWAYS_ALLOCATE_NEW || indexDetails == null || indexDetails.getSize() < size) { // Allocate new space IndexDetails newDetails = allocateAndWrite(index, size, data); if (indexDetails != null) { @@ -144,16 +146,6 @@ public class FileIndexManager implements IndexManager { } } - @Override - public void setFlushingAllowed(long index, boolean isUnloadingAllowed) { - checkClosed(); - if (isUnloadingAllowed) { - flushingAllowedIndices.add(index); - } else { - flushingAllowedIndices.remove(index); - } - } - @Override public long add(int size) { checkClosed(); @@ -192,7 +184,6 @@ public class FileIndexManager implements IndexManager { throw new IOException("Unable to write " + size + " in a space of " + indexDetails.getSize()); } final long offset = indexDetails.getOffset(); - final Output o = new Output(Channels.newOutputStream(dataFileChannel.position(offset)), size); data.write(o); o.flush(); @@ -214,7 +205,6 @@ public class FileIndexManager implements IndexManager { } synchronized (indicesMapsAccessLock) { dirtyLoadedIndices.remove(index); - flushingAllowedIndices.remove(index); loadedIndices.remove(index); removedIndices.add(index); } @@ -225,7 +215,6 @@ public class FileIndexManager implements IndexManager { synchronized (indicesMapsAccessLock) { removedIndices.remove(index); dirtyLoadedIndices.remove(index); - flushingAllowedIndices.remove(index); loadedIndices.remove(index); } // Update indices metadata @@ -238,7 +227,6 @@ public class FileIndexManager implements IndexManager { if (dirtyLoadedIndices.contains(index)) { indexDetails = loadedIndices.get(index); dirtyLoadedIndices.remove(index); - flushingAllowedIndices.remove(index); } } if (isDirty) { @@ -302,7 +290,6 @@ public class FileIndexManager implements IndexManager { synchronized (indicesMapsAccessLock) { loadedIndices.put(index, details); dirtyLoadedIndices.add(index); - flushingAllowedIndices.remove(index); } } @@ -311,7 +298,6 @@ public class FileIndexManager implements IndexManager { long newIndex = firstAllocableIndex++; loadedIndices.put(newIndex, indexDetails); dirtyLoadedIndices.add(newIndex); - flushingAllowedIndices.remove(newIndex); removedIndices.remove(newIndex); return newIndex; } @@ -442,21 +428,17 @@ public class FileIndexManager implements IndexManager { long lastIndex = -2; synchronized (indicesMapsAccessLock) { for (long index : dirtyLoadedIndices) { - if (!flushingAllowedIndices.contains(index)) { - 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++; + 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(); - dirtyLoadedIndices.addAll(flushingAllowedIndices); - flushingAllowedIndices.clear(); } return flushedIndices; } @@ -484,12 +466,12 @@ public class FileIndexManager implements IndexManager { long removedIndices = 0; LongArrayList toUnload = new LongArrayList(); synchronized (indicesMapsAccessLock) { - if (loadedIndices.size() > Database.MAX_LOADED_INDICES) { + 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 < Database.MAX_LOADED_INDICES * 3l / 2l) { + if (count < DatabaseManager.MAX_LOADED_INDICES * 3l / 2l) { break; } toUnload.add(loadedIndex); diff --git a/src/main/java/org/warp/jcwdb/IndexManager.java b/src/main/java/org/warp/jcwdb/IndexManager.java index 21f4320..be8fc37 100644 --- a/src/main/java/org/warp/jcwdb/IndexManager.java +++ b/src/main/java/org/warp/jcwdb/IndexManager.java @@ -13,7 +13,6 @@ public interface IndexManager extends Cleanable { 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 setFlushingAllowed(long index, boolean isUnloadingAllowed); void delete(long index) throws IOException; boolean has(long index); void close() throws IOException; diff --git a/src/main/java/org/warp/jcwdb/ann/DBClass.java b/src/main/java/org/warp/jcwdb/ann/DBClass.java deleted file mode 100644 index 1829d2f..0000000 --- a/src/main/java/org/warp/jcwdb/ann/DBClass.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.warp.jcwdb.ann; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) -public @interface DBClass { - int classTypeId(); -} diff --git a/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java b/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java new file mode 100644 index 0000000..d22a5ca --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DBDBObjectList.java @@ -0,0 +1,35 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; +import java.util.LinkedList; + +public class DBDBObjectList extends DBList { + + @DBField(id = 2, type = DBDataType.OBJECT) + public Class type; + + public DBDBObjectList(JCWDatabase db, Class type) { + super(db, 10); + this.type = type; + } + + public DBDBObjectList(JCWDatabase db, Class type, int maxLoadedItems) { + super(db, maxLoadedItems); + this.type = type; + } + + public DBDBObjectList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + @Override + protected T loadObjectFromDatabase(long uid) throws IOException { + return database.loadDBObject(type, uid); + } + + @Override + protected long saveObjectToDatabase(long uid, T value) throws IOException { + database.writeObjectProperty(uid, DBDataType.DATABASE_OBJECT, value); + return uid; + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/DBDataType.java b/src/main/java/org/warp/jcwdb/ann/DBDataType.java index 8e2dab0..0ced52c 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBDataType.java +++ b/src/main/java/org/warp/jcwdb/ann/DBDataType.java @@ -3,5 +3,6 @@ package org.warp.jcwdb.ann; public enum DBDataType { DATABASE_OBJECT, OBJECT, - INTEGER + INTEGER, + UID_LIST } \ No newline at end of file diff --git a/src/main/java/org/warp/jcwdb/ann/DBList.java b/src/main/java/org/warp/jcwdb/ann/DBList.java new file mode 100644 index 0000000..685798c --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DBList.java @@ -0,0 +1,171 @@ +package org.warp.jcwdb.ann; + +import it.unimi.dsi.fastutil.longs.LongArrayList; + +import java.io.IOError; +import java.io.IOException; +import java.util.LinkedList; +import java.util.StringJoiner; + +public abstract class DBList extends DBObject { + @DBField(id = 0, type = DBDataType.INTEGER) + private int maxLoadedItems; + + @DBField(id = 1, type = DBDataType.UID_LIST) + private LongArrayList itemIndices; + + public DBList(JCWDatabase db, int maxLoadedItems) { + super(db); + this.maxLoadedItems = maxLoadedItems; + this.itemIndices = new LongArrayList(); + } + + public DBList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + public int getMaxLoadedItems() { + return maxLoadedItems; + } + + public void add(T obj) { + try { + itemIndices.add(saveObjectToDatabase(database.allocateNullValue(), obj)); + } catch (IOException e) { + throw new IOError(e); + } + } + + public int size() { + return this.itemIndices.size(); + } + + private T loadObjectAt(int i) throws IOException { + return loadObjectFromDatabase(itemIndices.getLong(i)); + } + + protected abstract T loadObjectFromDatabase(long uid) throws IOException; + + protected abstract long saveObjectToDatabase(long uid, T value) throws IOException; + + public DBListIterator iterator() { + return new DBListIterator<>() { + private int position = itemIndices.size(); + private LinkedList cachedItems; + + private int objectToSavePosition; + private T objectToSave; + + @Override + public boolean hasNext() { + if (position > 0) { + return true; + } else { + try { + saveObservedObject(); + } catch (IOException e) { + throw new IOError(e); + } + return false; + } + } + + @Override + public T next() { + position--; + if (position < 0) { + throw new NullPointerException("Position < 0"); + } + if (cachedItems == null || cachedItems.size() == 0) { + try { + cachedItems = fillCache(position); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + try { + return switchObservedObject(position, cachedItems.removeFirst()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void remove() { + try { + saveObservedObject(); + } catch (IOException e) { + throw new RuntimeException(e); + } + itemIndices.removeLong(position); + } + + @Override + public void insert(T obj) { + try { + itemIndices.add(position, saveObjectToDatabase(database.allocateNullValue(), obj)); + } catch (IOException e) { + throw new IOError(e); + } + } + + @Override + public void set(T obj) { + try { + itemIndices.set(position, saveObjectToDatabase(database.allocateNullValue(), obj)); + } catch (IOException e) { + throw new IOError(e); + } + } + + private LinkedList fillCache(int position) throws IOException { + LinkedList cachedItems = new LinkedList<>(); + int firstObjectIndex = position; + int lastObjectIndex = firstObjectIndex - maxLoadedItems; + if (lastObjectIndex < 0) { + lastObjectIndex = 0; + } + for (int i = firstObjectIndex; i >= lastObjectIndex; i--) { + cachedItems.addLast(loadObjectAt(i)); + } + return cachedItems; + } + + private void saveObservedObject() throws IOException { + if (objectToSave != null) { + itemIndices.set(objectToSavePosition, saveObjectToDatabase(database.allocateNullValue(), objectToSave)); + objectToSave = null; + objectToSavePosition = 0; + } + } + + private T switchObservedObject(int position, T obj) throws IOException { + saveObservedObject(); + objectToSave = obj; + objectToSavePosition = position; + return obj; + } + }; + } + + public interface DBListIterator { + boolean hasNext(); + + T next(); + + void remove(); + + void insert(T obj); + + void set(T obj); + } + + @Override + public String toString() { + return new StringJoiner(", ", DBList.class.getSimpleName() + "[", "]") + .add("size=" + size()) + .add("maxLoadedItems=" + maxLoadedItems) + .add("itemIndices=" + (itemIndices.size() < 100 ? itemIndices : "["+itemIndices.size()+" items]")) + .toString(); + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/DBObject.java b/src/main/java/org/warp/jcwdb/ann/DBObject.java index f6e58f1..da5a3ae 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBObject.java +++ b/src/main/java/org/warp/jcwdb/ann/DBObject.java @@ -1,12 +1,16 @@ package org.warp.jcwdb.ann; +import org.apache.commons.lang3.reflect.FieldUtils; + +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.util.LinkedHashMap; +import java.util.Map; public abstract class DBObject { - private final Database database; - private final long uid; + protected final DatabaseManager database; private Field[] fields; private DBDataType[] fieldTypes; private long[] fieldUIDs; @@ -17,17 +21,103 @@ public abstract class DBObject { private long[] propertyUIDs; private boolean[] loadedProperties; private Object[] loadedPropertyValues; + private Map setterMethods; + private Map getterMethods; + private final Object fieldsAccessLock = new Object(); + private final Object propertiesAccessLock = new Object(); - public DBObject(Database database) { - this.database = database; - this.uid = database.newDBObject(this); - database.preloadDBObject(this); + public DBObject(JCWDatabase database) { + this.database = database.getDatabaseManager(); + try { + initializeDBObject(); + } catch (IOException e) { + throw new RuntimeException(e); + } } - public DBObject(Database database, long uid) { - this.database = database; - this.uid = uid; - database.preloadDBObject(this); + private void initializeDBObject() throws IOException { + initializeDBObjectFields(); + initializeDBObjectProperties(); + } + + private void initializeDBObjectFields() throws IOException { + // Declare the variables needed to get the biggest field Id + Field[] unorderedFields = database.getFields(this); + // Find the biggest field Id + int biggestFieldId = database.getBiggestFieldId(unorderedFields); + + // Allocate new UIDs + fieldUIDs = database.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(); + database.loadField(this, field, fieldType, fieldUIDs[fieldId]); + fields[fieldId] = field; + orderedFieldTypes[fieldId] = fieldType; + } + // Set fields metadata + setFields(fields, orderedFieldTypes, fieldUIDs); + } + + private void initializeDBObjectProperties() { + // Declare the variables needed to get the biggest property Id + Method[] unorderedPropertyGetters = database.getPropertyGetters(this); + Method[] unorderedPropertySetters = database.getPropertySetters(this); + + // Find the biggest property Id + int biggestGetter = database.getBiggestPropertyGetterId(unorderedPropertyGetters); + int biggestSetter = database.getBiggestPropertySetterId(unorderedPropertySetters); + int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter; + + // Allocate new UIDs + propertyUIDs = database.allocateNewUIDs(biggestPropertyId + 1); + + for (Method property : unorderedPropertySetters) { + DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestPropertyId) { + biggestPropertyId = propertyId; + } + } + + // Declare the other variables + DBDataType[] propertyTypes = new DBDataType[biggestPropertyId + 1]; + Method[] propertyGetters = new Method[biggestPropertyId + 1]; + Method[] propertySetters = new Method[biggestPropertyId + 1]; + Map setterMethods = new LinkedHashMap<>(); + Map getterMethods = new LinkedHashMap<>(); + + // Load the properties metadata + for (Method property : unorderedPropertyGetters) { + DBPropertyGetter propertyAnnotation = property.getAnnotation(DBPropertyGetter.class); + int propertyId = propertyAnnotation.id(); + DBDataType propertyType = propertyAnnotation.type(); + propertyTypes[propertyId] = propertyType; + propertyGetters[propertyId] = property; + getterMethods.put(property.getName(), propertyAnnotation); + } + for (Method property : unorderedPropertySetters) { + DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class); + int propertyId = propertyAnnotation.id(); + DBDataType propertyType = propertyAnnotation.type(); + propertyTypes[propertyId] = propertyType; + propertySetters[propertyId] = property; + setterMethods.put(property.getName(), propertyAnnotation); + } + // Set properties metadata + setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs, setterMethods, getterMethods); + } + + public DBObject(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + this.database = database.getDatabaseManager(); + this.database.preloadDBObject(this, objectInfo); } public T getProperty() { @@ -36,71 +126,86 @@ public abstract class DBObject { try { int propertyId = stackFrame.getDeclaringClass().getDeclaredMethod(stackFrame.getMethodName()).getAnnotation(DBPropertyGetter.class).id(); return getProperty(propertyId); - } catch (NoSuchMethodException e) { + } catch (IOException | NoSuchMethodException e) { throw new RuntimeException(e); } } void setLoadedProperty(int propertyId, T value) { loadedPropertyValues[propertyId] = value; + loadedProperties[propertyId] = true; } @SuppressWarnings("unchecked") - private T getProperty(int propertyId) { - if (!loadedProperties[propertyId]) { - try { - database.loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUIDs[propertyId]); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new RuntimeException(e); + private T getProperty(int propertyId) throws IOException { + synchronized (propertiesAccessLock) { + if (!loadedProperties[propertyId]) { + long propertyUID = propertyUIDs[propertyId]; + database.loadProperty(this, propertyId, propertyGetters[propertyId], propertyTypes[propertyId], propertyUID); } + return (T) loadedPropertyValues[propertyId]; } - return (T) loadedPropertyValues[propertyId]; } public void setProperty(T value) { StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); StackWalker.StackFrame stackFrame = walker.walk(f -> f.skip(1).findFirst().orElse(null)); - try { - int propertyId = stackFrame.getDeclaringClass().getDeclaredMethod(stackFrame.getMethodName(), value.getClass()).getAnnotation(DBPropertySetter.class).id(); - setProperty(propertyId, value); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); + DBPropertySetter propertyAnnotation = setterMethods.get(stackFrame.getMethodName()); + setProperty(propertyAnnotation.id(), propertyAnnotation.type(), value); + } + + public void setProperty(int propertyId, DBDataType propertyType, T value) { + synchronized (propertiesAccessLock) { + loadedPropertyValues[propertyId] = value; + loadedProperties[propertyId] = true; } } - public void setProperty(int propertyId, T value) { - loadedPropertyValues[propertyId] = value; - loadedProperties[propertyId] = true; - } - - public void close() { - database.saveObject(this); + public void writeToDisk(long uid) { + //System.err.println("Saving object " + uid + ":" + this); + try { + synchronized (propertiesAccessLock) { + synchronized (fieldsAccessLock) { + database.writeObjectInfo(uid, fieldUIDs, propertyUIDs); + } + } + synchronized (fieldsAccessLock) { + for (int i = 0; i < fieldUIDs.length; i++) { + try { + database.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.writeObjectProperty(propertyUIDs[i], propertyTypes[i], loadedPropertyValues[i]); + } + } + } catch (IOException e) { + throw new IOError(e); + } } public final void setFields(Field[] fields, DBDataType[] fieldTypes, long[] fieldUIDs) { - this.fields = fields; - this.fieldTypes = fieldTypes; - this.fieldUIDs = fieldUIDs; + synchronized (fieldsAccessLock) { + this.fields = fields; + this.fieldTypes = fieldTypes; + this.fieldUIDs = fieldUIDs; + } } - public final void setProperties(Method[] propertyGetters, Method[] propertySetters, DBDataType[] propertyTypes, long[] propertyUIDs) { - 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]; - } - - public final long getUID() { - return uid; - } - - public long[] getAllFieldUIDs() { - return fieldUIDs; - } - - public long[] getAllPropertyUIDs() { - return propertyUIDs; + public final void setProperties(Method[] propertyGetters, Method[] propertySetters, DBDataType[] propertyTypes, long[] propertyUIDs, Map setterMethods, Map 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; + } } } diff --git a/src/main/java/org/warp/jcwdb/ann/DBObjectIndicesManager.java b/src/main/java/org/warp/jcwdb/ann/DBObjectIndicesManager.java index ca1f368..4005946 100644 --- a/src/main/java/org/warp/jcwdb/ann/DBObjectIndicesManager.java +++ b/src/main/java/org/warp/jcwdb/ann/DBObjectIndicesManager.java @@ -8,12 +8,18 @@ import java.io.IOException; public class DBObjectIndicesManager { private final FileIndexManager indices; - public DBObjectIndicesManager(FileIndexManager indices) { + DBObjectIndicesManager(FileIndexManager indices) { this.indices = indices; } public long allocate(int fieldsCount, int propertiesCount) { - return indices.add(calculateObjectSize(fieldsCount, 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 { @@ -23,7 +29,7 @@ public class DBObjectIndicesManager { for (int i = 0; i < fields.length; i++) { w.writeLong(fields[i]); } - for (int i = 0; i < fields.length; i++) { + for (int i = 0; i < properties.length; i++) { w.writeLong(properties[i]); } }); @@ -31,6 +37,9 @@ public class DBObjectIndicesManager { 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)) { @@ -46,6 +55,10 @@ public class DBObjectIndicesManager { }); } + public boolean has(long uid) { + return indices.has(uid); + } + private int calculateObjectSize(long[] fields, long[] properties) { return calculateObjectSize(fields.length, properties.length); } diff --git a/src/main/java/org/warp/jcwdb/ann/DBObjectList.java b/src/main/java/org/warp/jcwdb/ann/DBObjectList.java new file mode 100644 index 0000000..1fa796e --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DBObjectList.java @@ -0,0 +1,25 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; + +public class DBObjectList extends DBList { + + public DBObjectList(JCWDatabase db, int maxLoadedItems) { + super(db, maxLoadedItems); + } + + public DBObjectList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + @Override + protected T loadObjectFromDatabase(long uid) throws IOException { + return database.loadObject(uid); + } + + @Override + protected long saveObjectToDatabase(long uid, T value) throws IOException { + database.writeObjectProperty(uid, DBDataType.OBJECT, value); + return uid; + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/Database.java b/src/main/java/org/warp/jcwdb/ann/Database.java deleted file mode 100644 index a652706..0000000 --- a/src/main/java/org/warp/jcwdb/ann/Database.java +++ /dev/null @@ -1,275 +0,0 @@ -package org.warp.jcwdb.ann; - -import com.esotericsoftware.kryo.Kryo; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.apache.commons.lang3.reflect.MethodUtils; -import org.warp.jcwdb.FileIndexManager; - -import java.io.IOError; -import java.io.IOException; -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.util.LinkedList; - -public class Database { - - public static final long MAX_LOADED_INDICES = 100000; - private final DBObjectIndicesManager objectIndicesManager; - private final FileIndexManager indices; - private final LinkedList> loadedObjects = new LinkedList<>(); - private static final Kryo kryo = new Kryo(); - - public Database(Path dataFile, Path metadataFile) throws IOException { - this.indices = new FileIndexManager(dataFile, metadataFile); - this.objectIndicesManager = new DBObjectIndicesManager(this.indices); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - Database.this.close(); - } catch (Exception e) { - e.printStackTrace(); - } - })); - } - - private void close() throws IOException { - indices.close(); - for (WeakReference loadedObjectReference : loadedObjects) { - DBObject loadedObject = loadedObjectReference.get(); - if (loadedObject != null) { - loadedObject.close(); - } - } - } - - public void preloadDBObject(DBObject obj) { - DBObjectIndicesManager.DBObjectInfo UIDs = readUIDs(obj.getUID()); - - preloadDBObjectFields(obj, UIDs.getFields()); - preloadDBObjectProperties(obj, UIDs.getProperties()); - } - - private void preloadDBObjectFields(DBObject obj, long[] fieldUIDs) { - // 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(); - try { - loadField(obj, field, fieldType, fieldUIDs[fieldId]); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new RuntimeException(e); - } - fields[fieldId] = field; - orderedFieldTypes[fieldId] = fieldType; - } - // Set fields metadata - obj.setFields(fields, orderedFieldTypes, fieldUIDs); - } - - private void preloadDBObjectProperties(DBObject obj, long[] propertyUIDs) { - // Declare the variables needed to get the biggest property Id - Method[] unorderedPropertyGetters = getPropertyGetters(obj); - Method[] unorderedPropertySetters = getPropertySetters(obj); - - // Find the biggest property Id - int biggestGetter = getBiggestPropertyGetterId(unorderedPropertyGetters); - int biggestSetter = getBiggestPropertySetterId(unorderedPropertySetters); - int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter; - - for (Method property : unorderedPropertySetters) { - DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class); - int propertyId = fieldAnnotation.id(); - if (propertyId > biggestPropertyId) { - biggestPropertyId = propertyId; - } - } - - // Declare the other variables - DBDataType[] propertyTypes = new DBDataType[biggestPropertyId + 1]; - Method[] propertyGetters = new Method[biggestPropertyId + 1]; - Method[] propertySetters = new Method[biggestPropertyId + 1]; - - // 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; - } - for (Method property : unorderedPropertySetters) { - DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class); - int propertyId = propertyAnnotation.id(); - DBDataType propertyType = propertyAnnotation.type(); - propertyTypes[propertyId] = propertyType; - propertySetters[propertyId] = property; - } - // Set properties metadata - obj.setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs); - } - - private Method[] getPropertyGetters(DBObject obj) { - return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertyGetter.class); - } - - private Method[] getPropertySetters(DBObject obj) { - return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertySetter.class); - } - - private Field[] getFields(DBObject obj) { - return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class); - } - - private int getBiggestPropertyGetterId(Method[] unorderedPropertyGetters) { - int biggestPropertyId = -1; - for (Method property : unorderedPropertyGetters) { - DBPropertyGetter fieldAnnotation = property.getAnnotation(DBPropertyGetter.class); - int propertyId = fieldAnnotation.id(); - if (propertyId > biggestPropertyId) { - biggestPropertyId = propertyId; - } - } - return biggestPropertyId; - } - - private int getBiggestPropertySetterId(Method[] unorderedPropertySetters) { - int biggestPropertyId = -1; - for (Method property : unorderedPropertySetters) { - DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class); - int propertyId = fieldAnnotation.id(); - if (propertyId > biggestPropertyId) { - biggestPropertyId = propertyId; - } - } - return biggestPropertyId; - } - - - private int getBiggestFieldId(Field[] unorderedFields) { - int biggestFieldId = -1; - for (Field field : unorderedFields) { - DBField fieldAnnotation = field.getAnnotation(DBField.class); - int propertyId = fieldAnnotation.id(); - if (propertyId > biggestFieldId) { - biggestFieldId = propertyId; - } - } - return biggestFieldId; - } - - public void loadProperty(DBObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - switch (propertyType) { - case DATABASE_OBJECT: - DBObject fieldDBObjectValue = (DBObject) property.getDeclaringClass().getConstructor(Database.class, long.class).newInstance(this, propertyUID); - obj.setLoadedProperty(propertyId, fieldDBObjectValue); - break; - case OBJECT: - Object fieldObjectValue = loadObject(propertyUID); - obj.setLoadedProperty(propertyId, fieldObjectValue); - break; - case INTEGER: - int fieldIntValue = loadInt(propertyUID); - obj.setLoadedProperty(propertyId, fieldIntValue); - break; - default: - throw new NullPointerException("Unknown Field Type"); - } - } - - public void loadField(DBObject obj, Field field, DBDataType fieldType, long fieldUID) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - switch (fieldType) { - case DATABASE_OBJECT: - DBObject fieldDBObjectValue = (DBObject) field.getDeclaringClass().getConstructor(Database.class, long.class).newInstance(this, fieldUID); - field.set(obj, fieldDBObjectValue); - break; - case OBJECT: - Object fieldObjectValue = loadObject(fieldUID); - field.set(obj, fieldObjectValue); - break; - case INTEGER: - int fieldIntValue = loadInt(fieldUID); - field.setInt(obj, fieldIntValue); - break; - default: - throw new NullPointerException("Unknown Field Type"); - } - } - - @SuppressWarnings("unchecked") - public T loadObject(long uid) { - try { - return (T) indices.get(uid, (i, size) -> size == 0 ? null : kryo.readClassAndObject(i)); - } catch (IOException ex) { - throw new IOError(ex); - } - } - - public int loadInt(long uid) { - try { - return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readInt()); - } catch (IOException ex) { - throw new IOError(ex); - } - } - - public void watchObject(DBObject obj) { - loadedObjects.add(new WeakReference<>(obj)); - } - - /** - * - * @param uid - * @return - */ - private DBObjectIndicesManager.DBObjectInfo readUIDs(long uid) { - try { - return objectIndicesManager.get(uid); - } catch (IOException e) { - throw new IOError(e); - } - } - - public void saveObject(DBObject obj) { - System.out.println("Saving object " + obj.getUID()); - try { - objectIndicesManager.set(obj.getUID(), obj.getAllFieldUIDs(), obj.getAllPropertyUIDs()); - } catch (IOException e) { - throw new IOError(e); - } - } - - public long newDBObject(DBObject obj) { - int fieldsCount = getBiggestFieldId(getFields(obj)) + 1; - int biggestGetter = getBiggestPropertyGetterId(getPropertyGetters(obj)); - int biggestSetter = getBiggestPropertySetterId(getPropertySetters(obj)); - int propertiesCount = (biggestGetter > biggestSetter ? biggestGetter : biggestSetter) + 1; - - long uid = objectIndicesManager.allocate(fieldsCount, propertiesCount); - long[] fields = new long[fieldsCount]; - for (int i = 0; i < fieldsCount; i++) { - fields[i] = indices.add(0); - } - long[] properties = new long[propertiesCount]; - for (int i = 0; i < propertiesCount; i++) { - properties[i] = indices.add(0); - } - try { - objectIndicesManager.set(uid, fields, properties); - } catch (IOException e) { - throw new IOError(e); - } - return uid; - } -} diff --git a/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java b/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java new file mode 100644 index 0000000..7a986bd --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/DatabaseManager.java @@ -0,0 +1,447 @@ +package org.warp.jcwdb.ann; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.Registration; +import com.esotericsoftware.kryo.io.Output; +import it.unimi.dsi.fastutil.booleans.BooleanArrayList; +import it.unimi.dsi.fastutil.bytes.ByteArrayList; +import it.unimi.dsi.fastutil.chars.CharArrayList; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.warp.jcwdb.Cleanable; +import org.warp.jcwdb.Cleaner; +import org.warp.jcwdb.FileIndexManager; +import org.warp.jcwdb.ann.exampleimpl.ClassWithList; + +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.*; + +public class DatabaseManager implements Cleanable { + + public static final long MAX_LOADED_INDICES = 100000; + private final DBObjectIndicesManager objectIndicesManager; + private final FileIndexManager indices; + private final Cleaner cleaner; + private final JCWDatabase jcwDatabase; + private DBObject loadedRootObject = null; + private final Kryo kryo = new Kryo(); + private volatile boolean closed; + + DatabaseManager(JCWDatabase jcwDatabase, Path dataFile, Path metadataFile) throws IOException { + this.jcwDatabase = jcwDatabase; + kryo.setRegistrationRequired(true); + registerDefaultClasses(); + this.indices = new FileIndexManager(dataFile, metadataFile); + if (!indices.has(0)) { + allocateNullValue(); + } + this.objectIndicesManager = new DBObjectIndicesManager(this.indices); + this.cleaner = new Cleaner(this); + this.cleaner.start(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + DatabaseManager.this.close(); + } catch (Exception e) { + e.printStackTrace(); + } + })); + } + + 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++); + } + + @SuppressWarnings("unchecked") + public T loadRoot(Class rootType) throws IOException { + if (loadedRootObject != null) { + throw new RuntimeException("Root already set!"); + } + if (isDBObjectNull(0)) { + try { + T root = rootType.getConstructor(JCWDatabase.class).newInstance(this.jcwDatabase); + loadedRootObject = root; + return root; + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IOError(e); + } + } else { + T root = (T) loadDBObject(rootType, 0); + loadedRootObject = root; + return root; + } + } + + public void close() throws IOException { + if (!closed) { + closed = true; + DatabaseManager.this.cleaner.stop(); + if (loadedRootObject != null) { + loadedRootObject.writeToDisk(0); + } + indices.close(); + } + } + + public void preloadDBObject(DBObject obj, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + preloadDBObjectFields(obj, objectInfo.getFields()); + preloadDBObjectProperties(obj, objectInfo.getProperties()); + } + + private void preloadDBObjectFields(DBObject obj, long[] fieldUIDs) throws IOException { + // Declare the variables needed to get the biggest field Id + Field[] unorderedFields = getFields(obj); + // Find the biggest field Id + int biggestFieldId = getBiggestFieldId(unorderedFields); + + // Declare the other variables + Field[] fields = new Field[biggestFieldId + 1]; + DBDataType[] orderedFieldTypes = new DBDataType[biggestFieldId + 1]; + + // Load all fields metadata and load them + for (Field field : unorderedFields) { + DBField fieldAnnotation = field.getAnnotation(DBField.class); + int fieldId = fieldAnnotation.id(); + DBDataType fieldType = fieldAnnotation.type(); + loadField(obj, field, fieldType, fieldUIDs[fieldId]); + fields[fieldId] = field; + orderedFieldTypes[fieldId] = fieldType; + } + // Set fields metadata + obj.setFields(fields, orderedFieldTypes, fieldUIDs); + } + + private void preloadDBObjectProperties(DBObject obj, long[] propertyUIDs) { + // Declare the variables needed to get the biggest property Id + Method[] unorderedPropertyGetters = getPropertyGetters(obj); + Method[] unorderedPropertySetters = getPropertySetters(obj); + + // Find the biggest property Id + int biggestGetter = getBiggestPropertyGetterId(unorderedPropertyGetters); + int biggestSetter = getBiggestPropertySetterId(unorderedPropertySetters); + int biggestPropertyId = biggestGetter > biggestSetter ? biggestGetter : biggestSetter; + + for (Method property : unorderedPropertySetters) { + DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestPropertyId) { + biggestPropertyId = propertyId; + } + } + + // Declare the other variables + DBDataType[] propertyTypes = new DBDataType[biggestPropertyId + 1]; + Method[] propertyGetters = new Method[biggestPropertyId + 1]; + Method[] propertySetters = new Method[biggestPropertyId + 1]; + Map setterMethods = new LinkedHashMap<>(); + Map getterMethods = new LinkedHashMap<>(); + + // Load the properties metadata + for (Method property : unorderedPropertyGetters) { + DBPropertyGetter propertyAnnotation = property.getAnnotation(DBPropertyGetter.class); + int propertyId = propertyAnnotation.id(); + DBDataType propertyType = propertyAnnotation.type(); + propertyTypes[propertyId] = propertyType; + propertyGetters[propertyId] = property; + getterMethods.put(property.getName(), propertyAnnotation); + } + for (Method property : unorderedPropertySetters) { + DBPropertySetter propertyAnnotation = property.getAnnotation(DBPropertySetter.class); + int propertyId = propertyAnnotation.id(); + DBDataType propertyType = propertyAnnotation.type(); + propertyTypes[propertyId] = propertyType; + propertySetters[propertyId] = property; + setterMethods.put(property.getName(), propertyAnnotation); + } + // Set properties metadata + obj.setProperties(propertyGetters, propertySetters, propertyTypes, propertyUIDs, setterMethods, getterMethods); + } + + Method[] getPropertyGetters(DBObject obj) { + return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertyGetter.class); + } + + Method[] getPropertySetters(DBObject obj) { + return MethodUtils.getMethodsWithAnnotation(obj.getClass(), DBPropertySetter.class); + } + + protected Field[] getFields(DBObject obj) { + return FieldUtils.getFieldsWithAnnotation(obj.getClass(), DBField.class); + } + + int getBiggestPropertyGetterId(Method[] unorderedPropertyGetters) { + int biggestPropertyId = -1; + for (Method property : unorderedPropertyGetters) { + DBPropertyGetter fieldAnnotation = property.getAnnotation(DBPropertyGetter.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestPropertyId) { + biggestPropertyId = propertyId; + } + } + return biggestPropertyId; + } + + int getBiggestPropertySetterId(Method[] unorderedPropertySetters) { + int biggestPropertyId = -1; + for (Method property : unorderedPropertySetters) { + DBPropertySetter fieldAnnotation = property.getAnnotation(DBPropertySetter.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestPropertyId) { + biggestPropertyId = propertyId; + } + } + return biggestPropertyId; + } + + + protected int getBiggestFieldId(Field[] unorderedFields) { + int biggestFieldId = -1; + for (Field field : unorderedFields) { + DBField fieldAnnotation = field.getAnnotation(DBField.class); + int propertyId = fieldAnnotation.id(); + if (propertyId > biggestFieldId) { + biggestFieldId = propertyId; + } + } + return biggestFieldId; + } + + @SuppressWarnings("unchecked") + public void loadProperty(DBObject obj, int propertyId, Method property, DBDataType propertyType, long propertyUID) throws IOException { + switch (propertyType) { + case DATABASE_OBJECT: + DBObject fieldDBObjectValue = loadDBObject((Class) property.getReturnType(), propertyUID); + //System.err.println("Loading prop. DBObj " + propertyUID + ":" + fieldDBObjectValue); + obj.setLoadedProperty(propertyId, fieldDBObjectValue); + break; + case OBJECT: + Object fieldObjectValue = loadObject(propertyUID); + //System.err.println("Loading prop. Obj " + propertyUID + ":" + fieldObjectValue); + obj.setLoadedProperty(propertyId, fieldObjectValue); + break; + case UID_LIST: + LongArrayList fieldListObjectValue = loadListObject(propertyUID); + //System.err.println("Loading prop. LOb " + propertyUID + ":" + fieldListObjectValue); + obj.setLoadedProperty(propertyId, fieldListObjectValue); + break; + case INTEGER: + int fieldIntValue = loadInt(propertyUID); + //System.err.println("Loading prop. Int " + propertyUID + ":" + fieldIntValue); + obj.setLoadedProperty(propertyId, fieldIntValue); + break; + default: + throw new NullPointerException("Unknown Field Type"); + } + } + + @SuppressWarnings("unchecked") + public void loadField(DBObject obj, Field field, DBDataType fieldType, long fieldUID) throws IOException { + try { + switch (fieldType) { + case DATABASE_OBJECT: + DBObject fieldDBObjectValue = loadDBObject((Class) field.getType(), fieldUID); + //System.err.println("Loading field DBObj " + fieldUID + ":" + fieldDBObjectValue); + FieldUtils.writeField(field, obj, fieldDBObjectValue, true); + break; + case OBJECT: + Object fieldObjectValue = loadObject(fieldUID); + //System.err.println("Loading field Obj " + fieldUID + ":" + fieldObjectValue); + FieldUtils.writeField(field, obj, fieldObjectValue, true); + break; + case UID_LIST: + LongArrayList fieldListObjectValue = loadListObject(fieldUID); + //System.err.println("Loading field LOb " + fieldUID + ":" + fieldObjectValue); + FieldUtils.writeField(field, obj, fieldListObjectValue, true); + break; + case INTEGER: + int fieldIntValue = loadInt(fieldUID); + //System.err.println("Loading field Int " + fieldUID + ":" + fieldIntValue); + FieldUtils.writeField(field, obj, fieldIntValue, true); + break; + default: + throw new NullPointerException("Unknown Field Type"); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public T loadDBObject(Class type, long propertyUID) throws IOException { + try { + DBObjectIndicesManager.DBObjectInfo objectInfo = readUIDs(propertyUID); + if (objectInfo == null) return null; + return type.getDeclaredConstructor(JCWDatabase.class, DBObjectIndicesManager.DBObjectInfo.class).newInstance(jcwDatabase, objectInfo); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new IOException(e); + } + } + + private boolean isDBObjectNull(long uid) { + try { + return !objectIndicesManager.has(uid) || objectIndicesManager.get(uid) == null; + } catch (IOException ex) { + throw new IOError(ex); + } + } + + @SuppressWarnings("unchecked") + public T loadObject(long uid) throws IOException { + return indices.get(uid, (i, size) -> size == 0 ? null : (T) kryo.readClassAndObject(i)); + } + + @SuppressWarnings("unchecked") + private LongArrayList loadListObject(long uid) throws IOException { + return indices.get(uid, (i, size) -> { + if (size == 0) return null; + LongArrayList list = new LongArrayList(); + int listSize = i.readVarInt(true); + for (int li = 0; li < listSize; li++) { + list.add(i.readVarLong(true)); + } + return list; + }); + } + + public int loadInt(long uid) throws IOException { + return indices.get(uid, (i, size) -> size == 0 ? 0 : i.readInt()); + } + + /** + * + * @param uid + * @return + */ + public DBObjectIndicesManager.DBObjectInfo readUIDs(long uid) { + try { + return objectIndicesManager.get(uid); + } catch (IOException e) { + throw new IOError(e); + } + } + + public boolean exists(long uid) { + return objectIndicesManager.has(uid); + } + + public void writeObjectInfo(long uid, long[] fieldUIDs, long[] propertyUIDs) throws IOException { + //System.err.println("Saving obj. " + uid); + this.objectIndicesManager.set(uid, fieldUIDs, propertyUIDs); + } + + private void writeObjectInfoNull(long uid) throws IOException { + this.objectIndicesManager.setNull(uid); + } + + public void writeObjectProperty(long uid, DBDataType propertyType, T loadedPropertyValue) throws IOException { + switch (propertyType) { + case INTEGER: + indices.set(uid, Integer.BYTES, (o) -> o.writeInt((int) loadedPropertyValue)); + //System.err.println("Saving prop. Int " + uid + ":" + loadedPropertyValue); + break; + case OBJECT: + Output baosOutput = new Output(new ByteArrayOutputStream()); + kryo.writeClassAndObject(baosOutput, loadedPropertyValue); + //System.err.println("Saving prop. Obj " + uid + ":" + loadedPropertyValue); + byte[] out = baosOutput.toBytes(); + indices.set(uid, out.length, o -> o.write(out, 0, out.length)); + break; + case UID_LIST: + LongArrayList list = (LongArrayList) loadedPropertyValue; + final int listSize = list.size(); + Output baosListOutput = new Output(Long.BYTES * 100, Long.BYTES * listSize); + baosListOutput.writeVarInt(listSize, true); + for (int i = 0; i < listSize; i++) { + baosListOutput.writeVarLong(list.getLong(i), true); + } + //System.err.println("Saving prop. LOb " + uid + ":" + loadedPropertyValue); + byte[] outList = baosListOutput.toBytes(); + indices.set(uid, outList.length, o -> o.write(outList, 0, outList.length)); + break; + case DATABASE_OBJECT: + //System.err.println("Saving prop. DBObj " + uid + ":" + loadedPropertyValue); + if (loadedPropertyValue == null) { + writeObjectInfoNull(uid); + } else { + ((DBObject) loadedPropertyValue).writeToDisk(uid); + } + break; + } + } + + public void registerClass(Class clazz, int id) { + kryo.register(clazz, 100 + id); + } + + public long allocateNullValue() { + return indices.add(0); + } + + @Override + public long clean() { + return 0;//indices.clean(); + } + + public long[] allocateNewUIDs(int quantity) { + long[] ids = new long[quantity]; + for (int i = 0; i < quantity; i++) { + ids[i] = allocateNullValue(); + } + return ids; + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java b/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java new file mode 100644 index 0000000..0d25ca2 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/JCWDatabase.java @@ -0,0 +1,32 @@ +package org.warp.jcwdb.ann; + +import java.io.IOException; +import java.nio.file.Path; + +public class JCWDatabase { + private final DatabaseManager database; + + public JCWDatabase(Path dataFile, Path metadataFile) throws IOException { + this.database = new DatabaseManager(this, dataFile, metadataFile); + } + + public T loadRoot(Class rootClass) { + try { + return database.loadRoot(rootClass); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + DatabaseManager getDatabaseManager() { + return database; + } + + public void registerClass(Class clazz, int id) { + database.registerClass(clazz, id); + } + + public void close() throws IOException { + database.close(); + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class1.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class1.java index 5622816..3687822 100644 --- a/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class1.java +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class1.java @@ -2,14 +2,17 @@ package org.warp.jcwdb.ann.exampleimpl; import org.warp.jcwdb.ann.*; -@DBClass(classTypeId = 0) +import java.io.IOException; +import java.util.StringJoiner; + public class Class1 extends DBObject { - public Class1(Database database) { + + public Class1(JCWDatabase database) { super(database); } - public Class1(Database database, long uid) { - super(database, uid); + public Class1(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); } @DBField(id = 0, type = DBDataType.OBJECT) @@ -18,6 +21,9 @@ public class Class1 extends DBObject { @DBField(id = 1, type = DBDataType.INTEGER) public int value2; + @DBField(id = 2, type = DBDataType.INTEGER) + public int value3; + @DBPropertyGetter(id = 0, type = DBDataType.OBJECT) public String getValue3() { return getProperty(); @@ -37,4 +43,26 @@ public class Class1 extends DBObject { public void setValue4(Class1 value) { setProperty(value); } + + @DBPropertyGetter(id = 2, type = DBDataType.OBJECT) + public String getValueStr() { + return getProperty(); + } + + @DBPropertySetter(id = 2, type = DBDataType.OBJECT) + public void setValueStr(String value) { + setProperty(value); + } + + @Override + public String toString() { + return new StringJoiner(", ", Class1.class.getSimpleName() + "[", "]") + .add("value1='" + value1 + "'") + .add("value2=" + value2) + .add("value3=" + value3) + .add("getValue3=" + getValue3()) + .add("getValue4=" + getValue4()) + .add("getValueStr=" + getValueStr()) + .toString(); + } } diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class2.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class2.java new file mode 100644 index 0000000..4b92731 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/Class2.java @@ -0,0 +1,40 @@ +package org.warp.jcwdb.ann.exampleimpl; + +import org.warp.jcwdb.ann.*; + +import java.io.IOException; +import java.util.StringJoiner; + +public class Class2 extends Class1 { + + //@DBField(id = 3, type = DBDataType.DATABASE_OBJECT) + //public DBDBObjectList value4; + + @DBField(id = 3, type = DBDataType.DATABASE_OBJECT) + public Class1 value4; + + @DBField(id = 4, type = DBDataType.INTEGER) + public int value5; + + public Class2(JCWDatabase database) { + super(database); + } + + public Class2(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + @Override + public String toString() { + return new StringJoiner(", ", Class1.class.getSimpleName() + "[", "]") + .add("value1='" + value1 + "'") + .add("value2=" + value2) + .add("value3=" + value3) + .add("value4=" + value4) + .add("value5=" + value5) + .add("getValue3=" + getValue3()) + .add("getValue4=" + getValue4()) + .add("getValueStr=" + getValueStr()) + .toString(); + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/ClassWithList.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/ClassWithList.java new file mode 100644 index 0000000..6e76677 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/ClassWithList.java @@ -0,0 +1,48 @@ +package org.warp.jcwdb.ann.exampleimpl; + +import org.warp.jcwdb.ann.*; + +import java.io.IOError; +import java.io.IOException; +import java.util.StringJoiner; + +public class ClassWithList extends DBObject { + + public ClassWithList(JCWDatabase database) { + super(database); + } + + public ClassWithList(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + @DBField(id = 0, type = DBDataType.DATABASE_OBJECT) + public DBDBObjectList valueList; + + + @DBPropertySetter(id = 0, type = DBDataType.DATABASE_OBJECT) + public void setList(DBDBObjectList value) { + setProperty(value); + } + + @DBPropertyGetter(id = 0, type = DBDataType.DATABASE_OBJECT) + public DBDBObjectList getList() { + return getProperty(); + } + + @Override + public String toString() { + DBDBObjectList list; + boolean listErrored = false; + try { + list = getList(); + } catch (IOError | Exception ex) { + list = null; + listErrored = true; + } + return new StringJoiner(", ", ClassWithList.class.getSimpleName() + "[", "]") + .add("valueList=" + valueList) + .add("getList=" + (listErrored ? "{error}" : (list == null ? list : list.size() < 100 ? list : "[" + list.size() + " items])"))) + .toString(); + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/IntClass.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/IntClass.java new file mode 100644 index 0000000..cf19761 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/IntClass.java @@ -0,0 +1,27 @@ +package org.warp.jcwdb.ann.exampleimpl; + +import org.warp.jcwdb.ann.*; + +import java.io.IOException; +import java.util.StringJoiner; + +public class IntClass extends DBObject { + + public IntClass(JCWDatabase database) { + super(database); + } + + public IntClass(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + @DBField(id = 0, type = DBDataType.INTEGER) + public int value; + + @Override + public String toString() { + return new StringJoiner(", ", IntClass.class.getSimpleName() + "[", "]") + .add("value=" + value) + .toString(); + } +} diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/Main.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/Main.java index 259169f..2e07a5b 100644 --- a/src/main/java/org/warp/jcwdb/ann/exampleimpl/Main.java +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/Main.java @@ -1,6 +1,7 @@ package org.warp.jcwdb.ann.exampleimpl; -import org.warp.jcwdb.ann.Database; +import org.warp.jcwdb.ann.DBDBObjectList; +import org.warp.jcwdb.ann.JCWDatabase; import java.io.IOException; import java.nio.file.Paths; @@ -8,25 +9,57 @@ import java.nio.file.Paths; public class Main { public static void main(String[] args) throws IOException { - Database db = new Database(Paths.get("database_temp.db"), Paths.get("database_temp.idx")); - Class1 class1 = new Class1(db, 0); - class1.value1 = "ciao"; + long t0 = System.currentTimeMillis(); + JCWDatabase db = new JCWDatabase(Paths.get("N:\\TimedTemp\\database_temp.db"), Paths.get("N:\\TimedTemp\\database_temp.idx")); + db.registerClass(Class1.class, 0); + db.registerClass(Class2.class, 1); + Class2 class1 = db.loadRoot(Class2.class); + long t1 = System.currentTimeMillis(); + System.err.println("Loading took " + (t1-t0)/1000d + " seconds"); + t0 = System.currentTimeMillis(); + System.err.println("[MAIN] class1="+class1); + class1.value1 = "ciaoooooooooooooooooooooo"; class1.value2 = 3; - System.out.println("value3="+class1.getValue3()); + class1.value5 = 5; + System.err.println("[MAIN] value3="+class1.getValue3()); class1.setValue3("Ciao 3"); - System.out.println("value3="+class1.getValue3()); + System.err.println("[MAIN] value3="+class1.getValue3()); - Class1 nested = new Class1(db); - class1.setValue4(nested); - nested.setValue3("Ciao nested 3"); - try { - class1.close(); - nested.close(); - System.out.println(class1.getValue4().getValue3()); - } catch (Exception ex) { - ex.printStackTrace(); - } catch (Throwable throwable) { - throwable.printStackTrace(); + System.err.println("[MAIN] propString="+class1.getValueStr()); + class1.setValueStr("Ciao String"); + System.err.println("[MAIN] propString="+class1.getValueStr()); + + System.err.println("[MAIN] getValue4="+class1.getValue4()); + t1 = System.currentTimeMillis(); + System.err.println("Post-loading took " + (t1-t0)/1000d + " seconds"); + t0 = System.currentTimeMillis(); + for (int i = 0; i < 200; i++) { + Class1 nested; + if ((nested = class1.getValue4()) == null) { + //System.err.println("[MAIN] Created nested class"); + class1.setValue4(nested = new Class1(db)); + } + nested.getValue3(); + //System.err.println("[MAIN] value4="+class1.getValue4()); + //System.err.println("[MAIN] nested value3="+nested.getValue3()); + nested.setValue3("Ciao nested 3"); + //System.err.println("[MAIN] nested value3=" + class1.getValue4().getValue3()); } + t1 = System.currentTimeMillis(); + System.err.println("Took " + (t1-t0)/1000d + " seconds"); + + + /* + if (class1.value4 == null) { + class1.value4 = new DBDBObjectList<>(db, 100, Class1.class); + } + for (int i = 0; i < 15; i++) { + Class1 c1 = new Class1(db); + c1.value1 = "" + i; + c1.value2 = i; + c1.setValueStr("" + i); + class1.value4.add(c1); + }*/ + class1.value4 = new Class1(db); } } \ No newline at end of file diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/MainSingleClass.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/MainSingleClass.java new file mode 100644 index 0000000..0cd1961 --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/MainSingleClass.java @@ -0,0 +1,52 @@ +package org.warp.jcwdb.ann.exampleimpl; + +import org.warp.jcwdb.ann.DBDBObjectList; +import org.warp.jcwdb.ann.DBList; +import org.warp.jcwdb.ann.JCWDatabase; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class MainSingleClass { + + public static void main(String[] args) throws IOException { + long t0 = System.currentTimeMillis(); + Files.deleteIfExists(Paths.get("N:\\TimedTemp\\database_t.db")); + Files.deleteIfExists(Paths.get("N:\\TimedTemp\\database_t.idx")); + for (int i = 0; i < 18; i++) { + doIt(); + } + } + + public static void doIt() throws IOException { + System.err.println("doIt"); + JCWDatabase db = new JCWDatabase(Paths.get("N:\\TimedTemp\\database_t.db"), Paths.get("N:\\TimedTemp\\database_t.idx")); + db.registerClass(ClassWithList.class, 0); + db.registerClass(IntClass.class, 1); + ClassWithList classWithList = db.loadRoot(ClassWithList.class); + System.err.println("[MAIN init] classWithList="+classWithList); + + if (classWithList.valueList == null) { + System.out.println("Get list was null"); + classWithList.valueList = new DBDBObjectList<>(db, IntClass.class, 10000); + } + DBDBObjectList list = classWithList.valueList; + + for (int i = 0; i < 1000000; i++) { + IntClass intClass = new IntClass(db); + intClass.value = i+0xFF00; + //System.err.println("[WRITE]" + intClass.value); + list.add(intClass); + } + + DBList.DBListIterator it = list.iterator(); + while (it.hasNext()) { + IntClass intClass = it.next(); + //System.err.println("[READ]" + intClass.value); + } + + System.err.println("[MAIN end.] singleClass="+classWithList); + db.close(); + } +} \ No newline at end of file diff --git a/src/main/java/org/warp/jcwdb/ann/exampleimpl/SingleClass.java b/src/main/java/org/warp/jcwdb/ann/exampleimpl/SingleClass.java new file mode 100644 index 0000000..f35e5da --- /dev/null +++ b/src/main/java/org/warp/jcwdb/ann/exampleimpl/SingleClass.java @@ -0,0 +1,68 @@ +package org.warp.jcwdb.ann.exampleimpl; + +import org.warp.jcwdb.ann.*; + +import java.io.IOException; +import java.util.StringJoiner; + +public class SingleClass extends DBObject { + + @DBField(id = 0, type = DBDataType.INTEGER) + public int valueInt; + + @DBField(id = 1, type = DBDataType.OBJECT) + public String valueObj; + + @DBField(id = 2, type = DBDataType.DATABASE_OBJECT) + public IntClass valueDB; + + @DBPropertySetter(id = 0, type = DBDataType.INTEGER) + public void setInt(int value) { + setProperty(value); + } + + @DBPropertySetter(id = 1, type = DBDataType.OBJECT) + public void setObj(String value) { + setProperty(value); + } + + @DBPropertySetter(id = 2, type = DBDataType.DATABASE_OBJECT) + public void setDB(IntClass value) { + setProperty(value); + } + + @DBPropertyGetter(id = 0, type = DBDataType.INTEGER) + public int getInt() { + return getProperty(); + } + + @DBPropertyGetter(id = 1, type = DBDataType.OBJECT) + public String getObj() { + return getProperty(); + } + + @DBPropertyGetter(id = 2, type = DBDataType.DATABASE_OBJECT) + public IntClass getDB() { + return getProperty(); + } + + public SingleClass(JCWDatabase database) { + super(database); + } + + public SingleClass(JCWDatabase database, DBObjectIndicesManager.DBObjectInfo objectInfo) throws IOException { + super(database, objectInfo); + } + + @Override + public String toString() { + return new StringJoiner(", ", SingleClass.class.getSimpleName() + "[", "]") + .add("valueInt=" + valueInt) + .add("valueObj='" + valueObj + "'") + .add("valueDB=" + valueDB) + .add("getInt=" + getInt()) + .add("getObj='" + getObj() + "'") + .add("getDB=" + getDB()) + .toString(); + } +} diff --git a/src/test/java/org/warp/jcwdb/AppTest.java b/src/test/java/org/warp/jcwdb/AppTest.java index 662c139..7e58457 100644 --- a/src/test/java/org/warp/jcwdb/AppTest.java +++ b/src/test/java/org/warp/jcwdb/AppTest.java @@ -1,6 +1,13 @@ package org.warp.jcwdb; import org.junit.Test; +import org.warp.jcwdb.ann.JCWDatabase; +import org.warp.jcwdb.ann.exampleimpl.IntClass; +import org.warp.jcwdb.ann.exampleimpl.SingleClass; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; import static org.junit.Assert.assertTrue; @@ -15,4 +22,50 @@ public class AppTest { public void shouldAnswerWithTrue() { assertTrue(true); } + + @Test + public void simpleClassTest() throws IOException { + long t0 = System.currentTimeMillis(); + Files.deleteIfExists(Paths.get("N:\\TimedTemp\\database_s.db")); + Files.deleteIfExists(Paths.get("N:\\TimedTemp\\database_s.idx")); + for (int i = 0; i < 3; i++) { + doSingleClassTest(); + } + } + + public static void doSingleClassTest() throws IOException { + System.err.println("doSingleClassTest"); + JCWDatabase db = new JCWDatabase(Paths.get("N:\\TimedTemp\\database_s.db"), Paths.get("N:\\TimedTemp\\database_s.idx")); + db.registerClass(SingleClass.class, 0); + db.registerClass(IntClass.class, 1); + SingleClass singleClass = db.loadRoot(SingleClass.class); + System.err.println("[MAIN init] singleClass="+singleClass); + + singleClass.getInt(); + singleClass.setInt((int) (Math.random() * 100)); + + singleClass.getObj(); + singleClass.setObj(String.valueOf(Math.random() * 100)); + + if (singleClass.getDB() == null) { + System.out.println("Get db was null"); + singleClass.setDB(new IntClass(db)); + } + IntClass intClass = singleClass.getDB(); + intClass.value = (int) (Math.random() * 100); + + singleClass.valueInt = (int) (Math.random() * 100); + + singleClass.valueObj = String.valueOf(Math.random() * 100); + + if (singleClass.valueDB == null) { + System.out.println("Value db was null"); + singleClass.valueDB = new IntClass(db); + } + singleClass.valueDB.value = (int) (Math.random() * 100); + + + System.err.println("[MAIN end.] singleClass="+singleClass); + db.close(); + } }