2 * Sixth Data. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
7 package eu.svjatoslav.sixth.data.store.file;
9 import eu.svjatoslav.sixth.data.store.DataStore;
12 import java.io.FileNotFoundException;
13 import java.io.IOException;
14 import java.io.RandomAccessFile;
15 import java.util.List;
18 * DataStore backed by single filesystem file.
20 public class FileDataStore implements DataStore {
22 final MetaData metaData = new MetaData(this);
23 final EntryAllocationTable entryAllocationTable = new EntryAllocationTable(this);
24 RandomAccessFile randomAccessFile;
26 public FileDataStore(final File backingFile) throws IOException {
27 if (backingFile.exists())
28 initializeFromExistingFile(backingFile);
30 initializeNewFile(backingFile);
34 public synchronized void close() throws IOException {
35 metaData.writeFileHeader();
36 randomAccessFile.close();
40 public synchronized int createRecord(final byte[] value) throws IOException {
42 if (metaData.entriesTableNeedsIncreasing())
43 increaseEntriesTable();
45 final int newEntryId = entryAllocationTable.getNewUnusedEntryId();
47 final long currentLocation = metaData
48 .allocateStorageSpace(value.length);
50 final EntryRecord record = new EntryRecord(newEntryId, currentLocation,
53 metaData.increaseUsedEntriesCount();
57 randomAccessFile.seek(currentLocation);
58 randomAccessFile.write(value);
60 // update header to increase crash resilience
61 metaData.writeFileHeader();
66 public synchronized void deleteRecord(final int id) throws IOException {
67 final EntryRecord entryRecord = new EntryRecord(this, id);
69 if (!entryRecord.isUsed())
70 throw new RuntimeException("Record already does not exist!");
73 entryRecord.save(this);
74 metaData.decreaseUsedEntriesCount();
76 // update header to increase crash resilience
77 metaData.writeFileHeader();
80 public long getDefragmentationStartAddress(final int allowedFragmentationPercent,
81 final List<EntryRecord> allEntryRecords) {
83 final VacuumContext context = new VacuumContext(this,
84 metaData.allocateStorageSpace(0), allowedFragmentationPercent);
86 for (int i = allEntryRecords.size() - 1; i >= 0; i--) {
87 final EntryRecord entryRecord = allEntryRecords.get(i);
88 context.analyzeEntry(entryRecord);
90 context.analyzeStartOfDataArea();
92 return context.defragmentationStartAddress;
95 public void increaseEntriesTable() throws IOException {
96 final List<EntryRecord> allEntryRecords = entryAllocationTable
97 .loadAllEntryRecords();
99 final int newEntriesTableSize = metaData.getEntriesTableSize() * 2;
101 final long dataEvacuationTreshold = metaData
102 .getEntriesStorageAreaStart(newEntriesTableSize);
104 metaData.ensureMinimumCurrentLocation(dataEvacuationTreshold);
106 for (final EntryRecord record : allEntryRecords)
107 if (record.location < dataEvacuationTreshold) {
109 final long newEntryLocation = metaData
110 .allocateStorageSpace(record.length);
112 // read record content
113 final byte[] entryContent = new byte[record.length];
115 randomAccessFile.seek(record.location);
116 randomAccessFile.readFully(entryContent);
118 // write record content to new location
119 randomAccessFile.seek(newEntryLocation);
120 randomAccessFile.write(entryContent);
122 // update record header
123 record.location = newEntryLocation;
127 entryAllocationTable.enlarge(newEntriesTableSize);
128 System.out.println("Entries table increased.");
130 // update header to increase crash resilience
131 metaData.writeFileHeader();
134 public void initializeFromExistingFile(final File backingFile)
137 randomAccessFile = new RandomAccessFile(backingFile, "rw");
139 metaData.readFileHeader();
141 } catch (final FileNotFoundException e) {
142 throw new RuntimeException(e);
146 public void initializeNewFile(final File backingFile) throws IOException {
148 randomAccessFile = new RandomAccessFile(backingFile, "rw");
149 } catch (final FileNotFoundException e) {
150 throw new RuntimeException(e);
153 metaData.initializeNewFile();
155 entryAllocationTable.initializeNewFile();
158 public int readInt(final long position) throws IOException {
159 randomAccessFile.seek(position);
160 return randomAccessFile.readInt();
163 public long readLong(final long position) throws IOException {
164 randomAccessFile.seek(position);
165 return randomAccessFile.readLong();
169 public synchronized byte[] readRecord(final int id) throws IOException {
171 final EntryRecord entryRecord = new EntryRecord(this, id);
173 if (!entryRecord.isUsed())
174 throw new RuntimeException("Entity record by id: " + id
175 + " does not exist.");
177 final byte[] result = new byte[entryRecord.length];
179 randomAccessFile.seek(entryRecord.location);
180 randomAccessFile.readFully(result);
185 public long relocateEntry(final EntryRecord record, final long newLocation)
188 if (record.location != newLocation) {
189 System.out.println("Relocating record " + record.id + " from: "
190 + record.location + " to: " + newLocation);
192 final byte[] result = new byte[record.length];
194 randomAccessFile.seek(record.location);
195 randomAccessFile.readFully(result);
197 randomAccessFile.seek(newLocation);
198 randomAccessFile.write(result);
200 record.location = newLocation;
204 return newLocation + record.length;
208 public synchronized void updateRecord(final int id, final byte[] value)
211 final EntryRecord entryRecord = new EntryRecord(this, id);
213 if (!entryRecord.isUsed())
214 throw new RuntimeException("Entry record is empty!");
216 final int newDataSize = value.length;
218 if (entryRecord.length >= newDataSize) {
219 // update record in place
220 if (entryRecord.length != newDataSize) {
221 entryRecord.length = newDataSize;
222 entryRecord.save(this);
225 // save record to the new location
226 entryRecord.location = metaData.allocateStorageSpace(newDataSize);
227 entryRecord.length = newDataSize;
228 entryRecord.save(this);
231 randomAccessFile.seek(entryRecord.location);
232 randomAccessFile.write(value);
234 // update header to increase crash resilience
235 metaData.writeFileHeader();
239 * Defragment empty space.
241 * @param allowedFragmentationPercent allowed maximum percentage of free space, relative to total
243 * @throws IOException
245 public void vacuum(final int allowedFragmentationPercent) throws IOException {
247 final List<EntryRecord> allEntryRecords = entryAllocationTable
248 .loadAllEntryRecords();
250 final long defragmentationStart = getDefragmentationStartAddress(
251 allowedFragmentationPercent, allEntryRecords);
253 long nextFreeSpace = defragmentationStart;
255 for (final EntryRecord record : allEntryRecords) {
256 if (record.location < defragmentationStart)
259 nextFreeSpace = relocateEntry(record, nextFreeSpace);
263 metaData.setCurrentLocation(nextFreeSpace);
265 // update header to increase crash resilience
266 metaData.writeFileHeader();
269 public void writeInt(final long position, final int value)
271 randomAccessFile.seek(position);
272 randomAccessFile.writeInt(value);
275 public void writeLong(final long position, final long value)
277 randomAccessFile.seek(position);
278 randomAccessFile.writeLong(value);