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

141 lines
5.4 KiB
Java

package it.cavallium.strangedb.database;
import it.cavallium.strangedb.functionalinterfaces.FunctionWithIO;
import it.cavallium.strangedb.EnhancedObject;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public class Database implements IDatabase, IDatabaseTools {
private final IDatabaseTools databaseTools;
private final DatabaseFileIO fileIO;
private final DatabaseBlocksIO blocksIO;
private final DatabaseBlocksMetadata blocksMetadata;
private final DatabaseReferencesIO referencesIO;
private final DatabaseReferencesMetadata referencesMetadata;
private final DatabaseObjectsIO objectsIO;
private final Path dataFile;
private final Path blocksMetaFile;
private final Path referencesMetaFile;
private EnhancedObject loadedRootObject;
private volatile boolean closed;
public Database(Path dataFile, Path blocksMetaFile, Path referencesMetaFile) throws IOException {
if (Files.notExists(dataFile)) {
Files.createFile(dataFile);
}
if (Files.notExists(blocksMetaFile)) {
Files.createFile(blocksMetaFile);
}
if (Files.notExists(referencesMetaFile)) {
Files.createFile(referencesMetaFile);
}
this.dataFile = dataFile;
this.blocksMetaFile = blocksMetaFile;
this.referencesMetaFile = referencesMetaFile;
this.databaseTools = this;
this.fileIO = new DatabaseFileIO(dataFile);
this.blocksMetadata = new DatabaseBlocksMetadata(blocksMetaFile);
this.blocksIO = new DatabaseBlocksIO(fileIO, blocksMetadata);
this.referencesMetadata = new DatabaseReferencesMetadata(referencesMetaFile);
this.referencesIO = new DatabaseReferencesIO(blocksIO, referencesMetadata);
this.objectsIO = new DatabaseObjectsIO(databaseTools, referencesIO);
}
@Override
public synchronized void close() throws IOException {
if (this.closed) {
throw new IOException("The database has been already closed!");
}
this.objectsIO.setEnhancedObject(0, loadedRootObject);
this.referencesMetadata.close();
this.blocksMetadata.close();
this.fileIO.close();
this.closed = true;
}
@Override
public boolean isClosed() {
return closed;
}
@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);
Database databaseToClean = new Database(newDataFile, newBlocksFile, newReferencesFile);
Database newDatabase = new Database(dataFile, blocksMetaFile, referencesMetaFile);
long referencesCount = databaseToClean.referencesMetadata.getFirstFreeReference();
long blocksCount = databaseToClean.blocksMetadata.getTotalBlocksCount();
long writtenReferences = 0;
for (int referenceID = 0; referenceID < referencesCount; referenceID++) {
try {
ByteBuffer buffer = databaseToClean.referencesIO.readFromReference(referenceID);
newDatabase.referencesIO.writeToReference(referenceID, buffer.limit(), buffer);
writtenReferences++;
} catch (IOException ex) {
System.out.println("Error while reading reference " + referenceID + ". References written: " + writtenReferences);
}
}
System.out.println("References written: " + writtenReferences + ". Removed " + (blocksCount - writtenReferences) + " blocks. Removed " + (referencesCount - writtenReferences) + " references.");
databaseToClean.close();
newDatabase.close();
Files.deleteIfExists(newDataFile);
Files.deleteIfExists(newBlocksFile);
Files.deleteIfExists(newReferencesFile);
}
public <T extends EnhancedObject> T loadRoot(Class<T> type, FunctionWithIO<IDatabaseTools, T> ifAbsent) throws IOException {
if (loadedRootObject != null) {
throw new RuntimeException("Root already set!");
}
T root;
if (referencesMetadata.getFirstFreeReference() > 0) {
root = objectsIO.loadEnhancedObject(0, type);
} else {
if (objectsIO.newNullObject() != 0) {
throw new IOException("Can't allocate root!");
} else {
root = ifAbsent.apply(Database.this);
objectsIO.setEnhancedObject(0, root);
}
}
loadedRootObject = root;
return root;
}
protected void registerClass(Class<?> type, int id) {
this.objectsIO.registerClass(type, id);
}
@Override
public void initializeEnhancedObject(EnhancedObject enhancedObject) throws IOException {
this.objectsIO.getDataInitializer().initializeDbObject(enhancedObject);
}
@Override
public IObjectsIO getObjectsIO() {
return objectsIO;
}
}