2 * Sixth Data. Copyright ©2012-2018, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of version 3 of the GNU Lesser General Public License
6 * or later as published by the Free Software Foundation.
9 package eu.svjatoslav.sixth.data.store.file;
11 import eu.svjatoslav.sixth.data.store.DataStore;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.io.RandomAccessFile;
17 import java.util.List;
20 * DataStore backed by single filesystem file.
22 public class FileDataStore implements DataStore {
24 final MetaData metaData = new MetaData(this);
25 final EntryAllocationTable entryAllocationTable = new EntryAllocationTable(this);
26 RandomAccessFile randomAccessFile;
28 public FileDataStore(final File backingFile) throws IOException {
29 if (backingFile.exists())
30 initializeFromExistingFile(backingFile);
32 initializeNewFile(backingFile);
36 public synchronized void close() throws IOException {
37 metaData.writeFileHeader();
38 randomAccessFile.close();
42 public synchronized int createRecord(final byte[] value) throws IOException {
44 if (metaData.entriesTableNeedsIncreasing())
45 increaseEntriesTable();
47 final int newEntryId = entryAllocationTable.getNewUnusedEntryId();
49 final long currentLocation = metaData
50 .allocateStorageSpace(value.length);
52 final EntryRecord record = new EntryRecord(newEntryId, currentLocation,
55 metaData.increaseUsedEntriesCount();
59 randomAccessFile.seek(currentLocation);
60 randomAccessFile.write(value);
62 // update header to increase crash resilience
63 metaData.writeFileHeader();
68 public synchronized void deleteRecord(final int id) throws IOException {
69 final EntryRecord entryRecord = new EntryRecord(this, id);
71 if (!entryRecord.isUsed())
72 throw new RuntimeException("Record already does not exist!");
75 entryRecord.save(this);
76 metaData.decreaseUsedEntriesCount();
78 // update header to increase crash resilience
79 metaData.writeFileHeader();
82 public long getDefragmentationStartAddress(final int allowedFragmentationPercent,
83 final List<EntryRecord> allEntryRecords) {
85 final VacuumContext context = new VacuumContext(this,
86 metaData.allocateStorageSpace(0), allowedFragmentationPercent);
88 for (int i = allEntryRecords.size() - 1; i >= 0; i--) {
89 final EntryRecord entryRecord = allEntryRecords.get(i);
90 context.analyzeEntry(entryRecord);
92 context.analyzeStartOfDataArea();
94 return context.defragmentationStartAddress;
97 public void increaseEntriesTable() throws IOException {
98 final List<EntryRecord> allEntryRecords = entryAllocationTable
99 .loadAllEntryRecords();
101 final int newEntriesTableSize = metaData.getEntriesTableSize() * 2;
103 final long dataEvacuationTreshold = metaData
104 .getEntriesStorageAreaStart(newEntriesTableSize);
106 metaData.ensureMinimumCurrentLocation(dataEvacuationTreshold);
108 for (final EntryRecord record : allEntryRecords)
109 if (record.location < dataEvacuationTreshold) {
111 final long newEntryLocation = metaData
112 .allocateStorageSpace(record.length);
114 // read record content
115 final byte[] entryContent = new byte[record.length];
117 randomAccessFile.seek(record.location);
118 randomAccessFile.readFully(entryContent);
120 // write record content to new location
121 randomAccessFile.seek(newEntryLocation);
122 randomAccessFile.write(entryContent);
124 // update record header
125 record.location = newEntryLocation;
129 entryAllocationTable.enlarge(newEntriesTableSize);
130 System.out.println("Entries table increased.");
132 // update header to increase crash resilience
133 metaData.writeFileHeader();
136 public void initializeFromExistingFile(final File backingFile)
139 randomAccessFile = new RandomAccessFile(backingFile, "rw");
141 metaData.readFileHeader();
143 } catch (final FileNotFoundException e) {
144 throw new RuntimeException(e);
148 public void initializeNewFile(final File backingFile) throws IOException {
150 randomAccessFile = new RandomAccessFile(backingFile, "rw");
151 } catch (final FileNotFoundException e) {
152 throw new RuntimeException(e);
155 metaData.initializeNewFile();
157 entryAllocationTable.initializeNewFile();
160 public int readInt(final long position) throws IOException {
161 randomAccessFile.seek(position);
162 return randomAccessFile.readInt();
165 public long readLong(final long position) throws IOException {
166 randomAccessFile.seek(position);
167 return randomAccessFile.readLong();
171 public synchronized byte[] readRecord(final int id) throws IOException {
173 final EntryRecord entryRecord = new EntryRecord(this, id);
175 if (!entryRecord.isUsed())
176 throw new RuntimeException("Entity record by id: " + id
177 + " does not exist.");
179 final byte[] result = new byte[entryRecord.length];
181 randomAccessFile.seek(entryRecord.location);
182 randomAccessFile.readFully(result);
187 public long relocateEntry(final EntryRecord record, final long newLocation)
190 if (record.location != newLocation) {
191 System.out.println("Relocating record " + record.id + " from: "
192 + record.location + " to: " + newLocation);
194 final byte[] result = new byte[record.length];
196 randomAccessFile.seek(record.location);
197 randomAccessFile.readFully(result);
199 randomAccessFile.seek(newLocation);
200 randomAccessFile.write(result);
202 record.location = newLocation;
206 return newLocation + record.length;
210 public synchronized void updateRecord(final int id, final byte[] value)
213 final EntryRecord entryRecord = new EntryRecord(this, id);
215 if (!entryRecord.isUsed())
216 throw new RuntimeException("Entry record is empty!");
218 final int newDataSize = value.length;
220 if (entryRecord.length >= newDataSize) {
221 // update record in place
222 if (entryRecord.length != newDataSize) {
223 entryRecord.length = newDataSize;
224 entryRecord.save(this);
227 // save record to the new location
228 entryRecord.location = metaData.allocateStorageSpace(newDataSize);
229 entryRecord.length = newDataSize;
230 entryRecord.save(this);
233 randomAccessFile.seek(entryRecord.location);
234 randomAccessFile.write(value);
236 // update header to increase crash resilience
237 metaData.writeFileHeader();
241 * Defragment empty space.
243 * @param allowedFragmentationPercent allowed maximum percentage of free space, relative to total
245 * @throws IOException
247 public void vacuum(final int allowedFragmentationPercent) throws IOException {
249 final List<EntryRecord> allEntryRecords = entryAllocationTable
250 .loadAllEntryRecords();
252 final long defragmentationStart = getDefragmentationStartAddress(
253 allowedFragmentationPercent, allEntryRecords);
255 long nextFreeSpace = defragmentationStart;
257 for (final EntryRecord record : allEntryRecords) {
258 if (record.location < defragmentationStart)
261 nextFreeSpace = relocateEntry(record, nextFreeSpace);
265 metaData.setCurrentLocation(nextFreeSpace);
267 // update header to increase crash resilience
268 metaData.writeFileHeader();
271 public void writeInt(final long position, final int value)
273 randomAccessFile.seek(position);
274 randomAccessFile.writeInt(value);
277 public void writeLong(final long position, final long value)
279 randomAccessFile.seek(position);
280 randomAccessFile.writeLong(value);