2 * Sixth - System for data storage, computation, exploration and interaction.
3 * Copyright ©2012-2016, Svjatoslav Agejenko, svjatoslav@svjatoslav.eu
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of version 3 of the GNU Lesser General Public License
7 * or later as published by the Free Software Foundation.
10 package eu.svjatoslav.sixth.data.store.file;
12 import eu.svjatoslav.sixth.data.store.DataStore;
15 import java.io.FileNotFoundException;
16 import java.io.IOException;
17 import java.io.RandomAccessFile;
18 import java.util.List;
21 * DataStore backed by single filesystem file.
23 public class FileDataStore implements DataStore {
25 final MetaData metaData = new MetaData(this);
26 final EntryAllocationTable entryAllocationTable = new EntryAllocationTable(this);
27 RandomAccessFile randomAccessFile;
29 public FileDataStore(final File backingFile) throws IOException {
30 if (backingFile.exists())
31 initializeFromExistingFile(backingFile);
33 initializeNewFile(backingFile);
37 public synchronized void close() throws IOException {
38 metaData.writeFileHeader();
39 randomAccessFile.close();
43 public synchronized int createRecord(final byte[] value) throws IOException {
45 if (metaData.entriesTableNeedsIncreasing())
46 increaseEntriesTable();
48 final int newEntryId = entryAllocationTable.getNewUnusedEntryId();
50 final long currentLocation = metaData
51 .allocateStorageSpace(value.length);
53 final EntryRecord record = new EntryRecord(newEntryId, currentLocation,
56 metaData.increaseUsedEntriesCount();
60 randomAccessFile.seek(currentLocation);
61 randomAccessFile.write(value);
63 // update header to increase crash resilience
64 metaData.writeFileHeader();
69 public synchronized void deleteRecord(final int id) throws IOException {
70 final EntryRecord entryRecord = new EntryRecord(this, id);
72 if (!entryRecord.isUsed())
73 throw new RuntimeException("Record already does not exist!");
76 entryRecord.save(this);
77 metaData.decreaseUsedEntriesCount();
79 // update header to increase crash resilience
80 metaData.writeFileHeader();
83 public long getDefragmentationStartAddress(final int allowedFragmentationPercent,
84 final List<EntryRecord> allEntryRecords) {
86 final VacuumContext context = new VacuumContext(this,
87 metaData.allocateStorageSpace(0), allowedFragmentationPercent);
89 for (int i = allEntryRecords.size() - 1; i >= 0; i--) {
90 final EntryRecord entryRecord = allEntryRecords.get(i);
91 context.analyzeEntry(entryRecord);
93 context.analyzeStartOfDataArea();
95 return context.defragmentationStartAddress;
98 public void increaseEntriesTable() throws IOException {
99 final List<EntryRecord> allEntryRecords = entryAllocationTable
100 .loadAllEntryRecords();
102 final int newEntriesTableSize = metaData.getEntriesTableSize() * 2;
104 final long dataEvacuationTreshold = metaData
105 .getEntriesStorageAreaStart(newEntriesTableSize);
107 metaData.ensureMinimumCurrentLocation(dataEvacuationTreshold);
109 for (final EntryRecord record : allEntryRecords)
110 if (record.location < dataEvacuationTreshold) {
112 final long newEntryLocation = metaData
113 .allocateStorageSpace(record.length);
115 // read record content
116 final byte[] entryContent = new byte[record.length];
118 randomAccessFile.seek(record.location);
119 randomAccessFile.readFully(entryContent);
121 // write record content to new location
122 randomAccessFile.seek(newEntryLocation);
123 randomAccessFile.write(entryContent);
125 // update record header
126 record.location = newEntryLocation;
130 entryAllocationTable.enlarge(newEntriesTableSize);
131 System.out.println("Entries table increased.");
133 // update header to increase crash resilience
134 metaData.writeFileHeader();
137 public void initializeFromExistingFile(final File backingFile)
140 randomAccessFile = new RandomAccessFile(backingFile, "rw");
142 metaData.readFileHeader();
144 } catch (final FileNotFoundException e) {
145 throw new RuntimeException(e);
149 public void initializeNewFile(final File backingFile) throws IOException {
151 randomAccessFile = new RandomAccessFile(backingFile, "rw");
152 } catch (final FileNotFoundException e) {
153 throw new RuntimeException(e);
156 metaData.initializeNewFile();
158 entryAllocationTable.initializeNewFile();
161 public int readInt(final long position) throws IOException {
162 randomAccessFile.seek(position);
163 return randomAccessFile.readInt();
166 public long readLong(final long position) throws IOException {
167 randomAccessFile.seek(position);
168 return randomAccessFile.readLong();
172 public synchronized byte[] readRecord(final int id) throws IOException {
174 final EntryRecord entryRecord = new EntryRecord(this, id);
176 if (!entryRecord.isUsed())
177 throw new RuntimeException("Entity record by id: " + id
178 + " does not exist.");
180 final byte[] result = new byte[entryRecord.length];
182 randomAccessFile.seek(entryRecord.location);
183 randomAccessFile.readFully(result);
188 public long relocateEntry(final EntryRecord record, final long newLocation)
191 if (record.location != newLocation) {
192 System.out.println("Relocating record " + record.id + " from: "
193 + record.location + " to: " + newLocation);
195 final byte[] result = new byte[record.length];
197 randomAccessFile.seek(record.location);
198 randomAccessFile.readFully(result);
200 randomAccessFile.seek(newLocation);
201 randomAccessFile.write(result);
203 record.location = newLocation;
207 return newLocation + record.length;
211 public synchronized void updateRecord(final int id, final byte[] value)
214 final EntryRecord entryRecord = new EntryRecord(this, id);
216 if (!entryRecord.isUsed())
217 throw new RuntimeException("Entry record is empty!");
219 final int newDataSize = value.length;
221 if (entryRecord.length >= newDataSize) {
222 // update record in place
223 if (entryRecord.length != newDataSize) {
224 entryRecord.length = newDataSize;
225 entryRecord.save(this);
228 // save record to the new location
229 entryRecord.location = metaData.allocateStorageSpace(newDataSize);
230 entryRecord.length = newDataSize;
231 entryRecord.save(this);
234 randomAccessFile.seek(entryRecord.location);
235 randomAccessFile.write(value);
237 // update header to increase crash resilience
238 metaData.writeFileHeader();
242 * Defragment empty space.
244 * @param allowedFragmentationPercent allowed maximum percentage of free space, relative to total
246 * @throws IOException
248 public void vacuum(final int allowedFragmentationPercent) throws IOException {
250 final List<EntryRecord> allEntryRecords = entryAllocationTable
251 .loadAllEntryRecords();
253 final long defragmentationStart = getDefragmentationStartAddress(
254 allowedFragmentationPercent, allEntryRecords);
256 long nextFreeSpace = defragmentationStart;
258 for (final EntryRecord record : allEntryRecords) {
259 if (record.location < defragmentationStart)
262 nextFreeSpace = relocateEntry(record, nextFreeSpace);
266 metaData.setCurrentLocation(nextFreeSpace);
268 // update header to increase crash resilience
269 metaData.writeFileHeader();
272 public void writeInt(final long position, final int value)
274 randomAccessFile.seek(position);
275 randomAccessFile.writeInt(value);
278 public void writeLong(final long position, final long value)
280 randomAccessFile.seek(position);
281 randomAccessFile.writeLong(value);