Skip to content

Commit

Permalink
FFDB-007: add PersistableFileTable implementation (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
godcrampy authored Jan 6, 2024
1 parent c7311b6 commit c7615c1
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 2 deletions.
5 changes: 5 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.5.0</version>
</dependency>
</dependencies>

<build>
Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/sahilbondre/firefly/filetable/FilePointer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.sahilbondre.firefly.filetable;

import java.util.Objects;

public class FilePointer {
private String fileName;
private long offset;

public FilePointer(String fileName, long offset) {
this.fileName = fileName;
this.offset = offset;
}

public FilePointer() {
}

public String getFileName() {
return fileName;
}

public void setFileName(String fileName) {
this.fileName = fileName;
}

public long getOffset() {
return offset;
}

public void setOffset(long offset) {
this.offset = offset;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FilePointer that = (FilePointer) o;
return offset == that.offset && fileName.equals(that.fileName);
}

@Override
public int hashCode() {
return Objects.hash(fileName, offset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.sahilbondre.firefly.filetable;

public class InvalidFileTableException extends RuntimeException {
public InvalidFileTableException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.sahilbondre.firefly.filetable;

import java.io.FileNotFoundException;

public interface PersistableFileTable {
void put(byte[] key, FilePointer value);

FilePointer get(byte[] key);

void saveToDisk(String filePath) throws FileNotFoundException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.sahilbondre.firefly.filetable;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class SerializedPersistableFileTable implements PersistableFileTable, Serializable {

private static final Kryo kryo = new Kryo();

private final Map<String, FilePointer> table;

public SerializedPersistableFileTable() {
kryo.register(SerializedPersistableFileTable.class);
kryo.register(HashMap.class);
kryo.register(FilePointer.class);
this.table = new HashMap<>();
}

public static SerializedPersistableFileTable fromEmpty() {
return new SerializedPersistableFileTable();
}

public static SerializedPersistableFileTable fromFile(String filePath) throws FileNotFoundException, KryoException {
try (Input input = new Input(new FileInputStream(filePath))) {
return kryo.readObject(input, SerializedPersistableFileTable.class);
} catch (KryoException e) {
throw new InvalidFileTableException("Failed to load FileTable from disk: " + e.getMessage());
}
}

@Override
public void put(byte[] key, FilePointer value) {
if (key != null && value != null) {
table.put(new String(key), value);
}
}

@Override
public FilePointer get(byte[] key) {
if (key != null) {
return table.get(new String(key));
}
return null;
}

@Override
public void saveToDisk(String filePath) throws FileNotFoundException {
Output output = new Output(new FileOutputStream(filePath));
kryo.writeObject(output, this);
output.close();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.sahilbondre.firefly.log;

import com.sahilbondre.firefly.filetable.FilePointer;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
Expand Down Expand Up @@ -28,10 +30,11 @@ public String getFilePath() {
}

@Override
public void append(byte[] message) throws IOException {
public FilePointer append(byte[] message) throws IOException {
fileChannel.position(fileChannel.size());
ByteBuffer buffer = ByteBuffer.wrap(message);
fileChannel.write(buffer);
return new FilePointer(filePath, fileChannel.size() - message.length);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.sahilbondre.firefly.log;

import com.sahilbondre.firefly.filetable.FilePointer;

import java.io.IOException;

public interface RandomAccessLog {
long size() throws IOException;

String getFilePath();

void append(byte[] message) throws IOException;
FilePointer append(byte[] message) throws IOException;

byte[] read(long offset, long length) throws IOException, InvalidRangeException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.sahilbondre.firefly.filetable;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class SerializedPersistableFileTableTest {

private static final String TEST_FILE_PATH = "src/test/resources/map";
private SerializedPersistableFileTable fileTable;

@BeforeEach
void setUp() throws IOException {
Files.deleteIfExists(Paths.get(TEST_FILE_PATH));
fileTable = SerializedPersistableFileTable.fromEmpty();
}

@AfterEach
void tearDown() throws IOException {
Files.deleteIfExists(Paths.get(TEST_FILE_PATH));
}

@Test
void given_KeyValue_When_PuttingAndGet_Then_RetrievedValueMatches() {
// Given
byte[] key = "testKey".getBytes();
FilePointer expectedValue = new FilePointer("test.txt", 42);

// When
fileTable.put(key, new FilePointer("test.txt", 42));
FilePointer retrievedValue = fileTable.get(key);

// Then
assertEquals(expectedValue, retrievedValue);
}

@Test
void given_NullKey_When_PuttingAndGet_Then_RetrievedValueIsNull() {
// Given
FilePointer value = new FilePointer("test.txt", 42);

// When
fileTable.put(null, value);
FilePointer retrievedValue = fileTable.get(null);

// Then
assertNull(retrievedValue);
}

@Test
void given_NullValue_When_PuttingAndGet_Then_RetrievedValueIsNull() {
// Given
byte[] key = "testKey".getBytes();

// When
fileTable.put(key, null);
FilePointer retrievedValue = fileTable.get(key);

// Then
assertNull(retrievedValue);
}

@Test
void given_KeyValue_When_SavingToDiskAndLoadingFromFile_Then_RetrievedValueMatches() throws FileNotFoundException {
// Given
byte[] key = "testKey".getBytes();
FilePointer value = new FilePointer("test.txt", 42);

// When
fileTable.put(key, value);
fileTable.saveToDisk(TEST_FILE_PATH);
SerializedPersistableFileTable loadedFileTable = SerializedPersistableFileTable.fromFile(TEST_FILE_PATH);
FilePointer retrievedValue = loadedFileTable.get(key);

// Then
assertEquals(value, retrievedValue);
}

@Test
void given_NonexistentFile_When_LoadingFromFile_Then_FileNotFoundExceptionIsThrown() {
// When
// Then
assertThrows(FileNotFoundException.class,
() -> SerializedPersistableFileTable.fromFile(TEST_FILE_PATH));
}

@Test
void given_CorruptedFile_When_LoadingFromFile_Then_InvalidFileTableExceptionIsThrown() throws IOException {
// Given
// Create a corrupted file by writing invalid data
Path filePath = Paths.get(TEST_FILE_PATH);
Files.write(filePath, List.of("Invalid Data"));

// Then
assertThrows(InvalidFileTableException.class,
() -> SerializedPersistableFileTable.fromFile(TEST_FILE_PATH));
}
}

0 comments on commit c7615c1

Please sign in to comment.