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 loadRoot(Class type, FunctionWithIO 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; } }