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