strangedb/src/main/java/it/cavallium/strangedb/java/database/DatabaseJava.java

197 lines
7.5 KiB
Java

package it.cavallium.strangedb.java.database;
import it.cavallium.strangedb.database.DatabaseCore;
import it.cavallium.strangedb.database.references.ReferenceInfo;
import it.cavallium.strangedb.java.objects.EnhancedObject;
import it.cavallium.strangedb.functionalinterfaces.FunctionWithIO;
import it.cavallium.strangedb.java.objects.EnhancedObjectIndices;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import static it.cavallium.strangedb.database.references.DatabaseReferencesMetadata.*;
import static it.cavallium.strangedb.java.database.DatabaseObjectsIO.ENHANCED_OBJECT_METADATA_CLEANER;
import static it.cavallium.strangedb.java.database.DatabaseObjectsIO.REFERENCES_LIST_CLEANER;
public class DatabaseJava extends DatabaseCore implements IDatabaseTools {
private final IDatabaseTools databaseTools;
private final DatabaseObjectsIO objectsIO;
private EnhancedObject loadedRootObject;
private boolean hasLoadedRootObject;
public DatabaseJava(Path dataFile, Path blocksMetaFile, Path referencesMetaFile) throws IOException {
super(dataFile, blocksMetaFile, referencesMetaFile);
this.databaseTools = this;
this.objectsIO = new DatabaseObjectsIO(databaseTools, referencesIO);
this.hasLoadedRootObject = false;
}
@Override
public synchronized void close() throws IOException {
if (this.closed) {
throw new IOException("The database has been already closed!");
}
if (hasLoadedRootObject) {
this.objectsIO.setEnhancedObject(0, loadedRootObject);
}
super.close();
}
public <T extends EnhancedObject> T loadRoot(FunctionWithIO<IDatabaseTools, T> ifAbsent) throws IOException {
if (hasLoadedRootObject) {
throw new RuntimeException("Root already set!");
}
T root;
if (referencesMetadata.getFirstFreeReference() > 0) {
root = objectsIO.loadEnhancedObject(0);
} else {
if (objectsIO.newNullObject() != 0) {
throw new IOException("Can't allocate root!");
} else {
root = ifAbsent.apply(DatabaseJava.this);
objectsIO.setEnhancedObject(0, root);
}
}
loadedRootObject = root;
hasLoadedRootObject = true;
return root;
}
public <T extends EnhancedObject> T loadRoot(FunctionWithIO<IDatabaseTools, T> ifAbsent, Class<?> forcedClassType) throws IOException {
if (hasLoadedRootObject) {
throw new RuntimeException("Root already set!");
}
T root;
if (referencesMetadata.getFirstFreeReference() > 0) {
root = objectsIO.loadEnhancedObject(0, forcedClassType);
} else {
if (objectsIO.newNullObject() != 0) {
throw new IOException("Can't allocate root!");
} else {
root = ifAbsent.apply(DatabaseJava.this);
objectsIO.setEnhancedObject(0, root);
}
}
loadedRootObject = root;
hasLoadedRootObject = true;
return root;
}
@Override
public void closeAndClean() throws IOException {
if (!this.closed) {
this.close();
}
Path newDataFile = dataFile.resolveSibling("compressed-data-file.tmp");
Path newBlocksFile = blocksMetaFile.resolveSibling("compressed-blocks-file.tmp");
Path newReferencesFile = referencesMetaFile.resolveSibling("compressed-references-file.tmp");
Path backupDataFile = dataFile.resolveSibling("backup-data.db.bak");
Path backupBlocksFile = blocksMetaFile.resolveSibling("backup-blocks.dat.bak");
Path backupReferencesFile = referencesMetaFile.resolveSibling("backup-references.dat.bak");
Files.copy(dataFile, backupDataFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
Files.copy(blocksMetaFile, backupBlocksFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
Files.copy(referencesMetaFile, backupReferencesFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
Files.move(dataFile, newDataFile, StandardCopyOption.REPLACE_EXISTING);
Files.move(blocksMetaFile, newBlocksFile, StandardCopyOption.REPLACE_EXISTING);
Files.move(referencesMetaFile, newReferencesFile, StandardCopyOption.REPLACE_EXISTING);
DatabaseJava databaseToClean = instantiateNewDatabase(newDataFile, newBlocksFile, newReferencesFile);
DatabaseJava newDatabase = instantiateNewDatabase(dataFile, blocksMetaFile, referencesMetaFile);
long firstFreeReference = databaseToClean.referencesMetadata.getFirstFreeReference();
long referencesCount = 0;
long blocksCount = databaseToClean.blocksMetadata.getTotalBlocksCount();
long writtenReferences = 0;
long writtenBlocks = 0;
LongArrayList idsToKeep = new LongArrayList();
cleanRef(databaseToClean, idsToKeep, 0);
for (int referenceID = 0; referenceID < firstFreeReference; referenceID++) {
try {
ReferenceInfo ref = databaseToClean.referencesMetadata.getCleanReference(referenceID);
if (!NONEXISTENT_REFERENCE_INFO.equals(ref)) {
referencesCount++;
if (idsToKeep.contains(referenceID)) {
ByteBuffer buffer = databaseToClean.referencesIO.readFromReference(referenceID);
newDatabase.referencesIO.writeToReference(referenceID, ref.getCleanerId(), buffer.limit(), buffer);
writtenReferences++;
if (buffer.limit() > 0) {
writtenBlocks++;
}
}
}
} catch (IOException ex) {
System.out.println("Error while reading reference " + referenceID + ". References written: " + writtenReferences);
}
}
System.out.println("[Java Cleaner] References written: " + writtenReferences + ". Removed " + (blocksCount - writtenBlocks) + " blocks. Removed " + (referencesCount - writtenReferences) + " references.");
databaseToClean.close();
newDatabase.close();
Files.deleteIfExists(newDataFile);
Files.deleteIfExists(newBlocksFile);
Files.deleteIfExists(newReferencesFile);
}
private void cleanRef(DatabaseJava db, LongArrayList idsToKeep, long ref) throws IOException {
idsToKeep.add(ref);
ReferenceInfo refInfo = db.referencesMetadata.getCleanReference(ref);
if (!NONEXISTENT_REFERENCE_INFO.equals(refInfo)) {
switch (refInfo.getCleanerId()) {
case ENHANCED_OBJECT_METADATA_CLEANER: {
EnhancedObjectIndices enhancedObjectUids = db.objectsIO.loadEnhancedObjectUids(ref);
for (long fieldUid : enhancedObjectUids.fieldUids) {
cleanRef(db, idsToKeep, fieldUid);
}
for (long propUid : enhancedObjectUids.propertyUids) {
cleanRef(db, idsToKeep, propUid);
}
cleanRef(db, idsToKeep, enhancedObjectUids.nativeDataUid);
break;
}
case ERRORED_CLEANER: {
System.err.println("Errored cleaner found! Skipping...");
break;
}
case REFERENCES_LIST_CLEANER: {
LongArrayList refList = db.objectsIO.loadReferencesList(ref);
for (long elemRef : refList) {
cleanRef(db, idsToKeep, elemRef);
}
break;
}
case BLANK_DATA_CLEANER: {
break;
}
default: {
System.err.println("Unrecognized cleaner found! Skipping...");
}
}
}
}
public void registerClass(Class<?> type, int id) {
this.objectsIO.registerClass(type, id);
}
protected DatabaseJava instantiateNewDatabase(Path dataFile, Path blocksMetaFile, Path referencesMetaFile) throws IOException {
DatabaseJava newDatabaseJava = new DatabaseJava(dataFile, blocksMetaFile, referencesMetaFile);
this.getObjectsIO().getRegisteredClasses().forEach(newDatabaseJava::registerClass);
return newDatabaseJava;
}
@Override
public void initializeEnhancedObject(EnhancedObject enhancedObject) throws IOException {
this.objectsIO.getDataInitializer().initializeDbObject(enhancedObject);
}
@Override
public DatabaseObjectsIO getObjectsIO() {
return objectsIO;
}
}