diff --git a/.all-contributorsrc b/.all-contributorsrc
index df0e170f068f..ccc740df15e1 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -3773,6 +3773,33 @@
"contributions": [
"code"
]
+ },
+ {
+ "login": "SandhyaDevadiga",
+ "name": "Sandhya",
+ "avatar_url": "https://avatars.githubusercontent.com/u/128058717?v=4",
+ "profile": "https://github.com/SandhyaDevadiga",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "devikae",
+ "name": "dev-ikae",
+ "avatar_url": "https://avatars.githubusercontent.com/u/98100047?v=4",
+ "profile": "https://github.com/devikae",
+ "contributions": [
+ "code"
+ ]
+ },
+ {
+ "login": "zanarellidev",
+ "name": "zanarelli",
+ "avatar_url": "https://avatars.githubusercontent.com/u/268068569?v=4",
+ "profile": "https://github.com/zanarellidev",
+ "contributions": [
+ "translation"
+ ]
}
],
"contributorsPerLine": 6,
diff --git a/README.md b/README.md
index f0e461c0fb76..eb821177e73e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[](https://sonarcloud.io/dashboard?id=iluwatar_java-design-patterns)
[](https://gitter.im/iluwatar/java-design-patterns?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[](#contributors-)
+[](#contributors-)
@@ -613,6 +613,11 @@ This project is licensed under the terms of the MIT license.
{
+
+ private static final int THRESHOLD = 1000;
+ private final long[] numbers;
+ private final int start;
+ private final int end;
+
+ @Override
+ protected Long compute() {
+ int length = end - start;
+
+ if (length <= THRESHOLD) {
+ // Base case: sum directly
+ long sum = 0;
+ for (int i = start; i < end; i++) {
+ sum += numbers[i];
+ }
+ return sum;
+ }
+
+ // Fork: split into two halves
+ int mid = start + length / 2;
+ SumTask leftTask = new SumTask(numbers, start, mid);
+ SumTask rightTask = new SumTask(numbers, mid, end);
+
+ leftTask.fork(); // run left half asynchronously
+ long rightResult = rightTask.compute(); // compute right half here
+ long leftResult = leftTask.join(); // wait for left half
+
+ // Join: combine results
+ return leftResult + rightResult;
+ }
+}
+```
+
+The `ForkJoinSumCalculator` provides a clean API:
+
+```java
+public class ForkJoinSumCalculator {
+
+ private final ForkJoinPool pool;
+
+ public ForkJoinSumCalculator() {
+ this.pool = ForkJoinPool.commonPool();
+ }
+
+ public long calculateSum(long[] numbers) {
+ SumTask task = new SumTask(numbers, 0, numbers.length);
+ return pool.invoke(task);
+ }
+}
+```
+
+Running the example in `App`:
+
+```java
+long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
+ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
+long result = calculator.calculateSum(numbers);
+System.out.println("Fork/Join sum: " + result);
+```
+
+Program output:
+
+```
+Fork/Join sum: 50000005000000
+Expected sum: 50000005000000
+Correct: true
+Time taken: 45 ms
+Available processors: 8
+```
+
+## When to Use the Fork/Join Pattern in Java
+
+* When you have a large, CPU-intensive task that can be divided into independent subtasks.
+* When the subtasks are roughly the same size and don't depend on each other.
+* When you want to utilize multiple CPU cores without manually managing threads.
+* When the problem naturally fits a divide-and-conquer strategy (e.g., sorting, searching,
+ numerical computation).
+
+## When NOT to Use Fork/Join
+
+* For I/O-bound tasks (network calls, file reads) — use virtual threads or async I/O instead.
+* When subtasks are too small — the overhead of forking exceeds the benefit.
+* When tasks have dependencies on each other and cannot run independently.
+
+## Benefits and Trade-offs of Fork/Join Pattern
+
+Benefits:
+
+* Maximizes CPU utilization through work-stealing algorithm.
+* Scales automatically with the number of available processors.
+* Built into Java's standard library (`java.util.concurrent`) — no external dependencies.
+* Clean recursive decomposition makes the code readable and maintainable.
+
+Trade-offs:
+
+* Overhead from task creation and thread management for very small problems.
+* Requires tasks to be independent — shared mutable state introduces bugs.
+* Choosing an appropriate threshold requires tuning for optimal performance.
+* Debugging parallel code is inherently harder than sequential code.
+
+## Related Java Design Patterns
+
+* [Divide and Conquer](https://java-design-patterns.com/patterns/divide-and-conquer/):
+ Fork/Join is the parallel execution variant of the classic divide-and-conquer strategy.
+* [Thread Pool](https://java-design-patterns.com/patterns/thread-pool/): Fork/Join uses a
+ specialized pool with work-stealing semantics.
+
+## References
+
+* [Java Documentation for ForkJoinPool](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ForkJoinPool.html)
+* [Java Concurrency in Practice — Brian Goetz](https://amzn.to/4aRMruW)
+* [Java Documentation for RecursiveTask](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/RecursiveTask.html)
diff --git a/fork-join/pom.xml b/fork-join/pom.xml
new file mode 100644
index 000000000000..23958727728a
--- /dev/null
+++ b/fork-join/pom.xml
@@ -0,0 +1,50 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+
+ fork-join
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.forkjoin.App
+
+
+
+
+
+
+
+
+
+
diff --git a/fork-join/src/main/java/com/iluwatar/forkjoin/App.java b/fork-join/src/main/java/com/iluwatar/forkjoin/App.java
new file mode 100644
index 000000000000..a53bdcec6de7
--- /dev/null
+++ b/fork-join/src/main/java/com/iluwatar/forkjoin/App.java
@@ -0,0 +1,43 @@
+package com.iluwatar.forkjoin;
+
+import java.util.stream.LongStream;
+
+/**
+ * The Fork/Join pattern is a concurrency design pattern that splits a large task into smaller
+ * subtasks (fork), processes them in parallel, and then combines the results (join).
+ *
+ * In Java, this pattern is implemented using {@link java.util.concurrent.ForkJoinPool} and
+ * {@link java.util.concurrent.RecursiveTask}. Worker threads in the pool use a work-stealing
+ * algorithm — idle threads take tasks from busy threads — maximizing CPU utilization.
+ *
+ *
In this example, we demonstrate the pattern by computing the sum of a large array in parallel.
+ * The array is recursively split in half until each piece is small enough to sum directly, then
+ * results are combined back up.
+ */
+public final class App {
+
+ private App() {}
+
+ /**
+ * @param args command line arguments, not used
+ */
+ public static void main(String[] args) {
+ // Create an array of 10 million numbers: [1, 2, 3, ..., 10_000_000]
+ long[] numbers = LongStream.rangeClosed(1, 10_000_000).toArray();
+
+ // Calculate sum using Fork/Join
+ ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
+ long startTime = System.currentTimeMillis();
+ long result = calculator.calculateSum(numbers);
+ long endTime = System.currentTimeMillis();
+
+ // The expected sum of 1 to N is N*(N+1)/2
+ long expected = 10_000_000L * 10_000_001L / 2;
+
+ System.out.println("Fork/Join sum: " + result);
+ System.out.println("Expected sum: " + expected);
+ System.out.println("Correct: " + (result == expected));
+ System.out.println("Time taken: " + (endTime - startTime) + " ms");
+ System.out.println("Available processors: " + Runtime.getRuntime().availableProcessors());
+ }
+}
diff --git a/fork-join/src/main/java/com/iluwatar/forkjoin/ForkJoinSumCalculator.java b/fork-join/src/main/java/com/iluwatar/forkjoin/ForkJoinSumCalculator.java
new file mode 100644
index 000000000000..0e9dff32cda8
--- /dev/null
+++ b/fork-join/src/main/java/com/iluwatar/forkjoin/ForkJoinSumCalculator.java
@@ -0,0 +1,44 @@
+package com.iluwatar.forkjoin;
+
+import java.util.concurrent.ForkJoinPool;
+
+/**
+ * ForkJoinSumCalculator provides a convenient API to sum an array of numbers using the Fork/Join
+ * framework. It creates a {@link ForkJoinPool}, submits a {@link SumTask}, and returns the computed
+ * sum.
+ *
+ *
The pool manages a set of worker threads that process subtasks in parallel. Idle threads can
+ * "steal" work from busy threads, maximizing CPU utilization.
+ */
+public class ForkJoinSumCalculator {
+
+ private final ForkJoinPool pool;
+
+ /** Creates a calculator using the common ForkJoinPool (uses all available CPU cores). */
+ public ForkJoinSumCalculator() {
+ this.pool = ForkJoinPool.commonPool();
+ }
+
+ /**
+ * Creates a calculator with a specific number of threads.
+ *
+ * @param parallelism the number of worker threads to use
+ */
+ public ForkJoinSumCalculator(int parallelism) {
+ this.pool = new ForkJoinPool(parallelism);
+ }
+
+ /**
+ * Calculates the sum of all elements in the array using Fork/Join parallelism.
+ *
+ * @param numbers the array of numbers to sum
+ * @return the total sum of all elements
+ */
+ public long calculateSum(long[] numbers) {
+ if (numbers == null || numbers.length == 0) {
+ return 0;
+ }
+ SumTask task = new SumTask(numbers, 0, numbers.length);
+ return pool.invoke(task);
+ }
+}
diff --git a/fork-join/src/main/java/com/iluwatar/forkjoin/SumTask.java b/fork-join/src/main/java/com/iluwatar/forkjoin/SumTask.java
new file mode 100644
index 000000000000..acbd5f0516b7
--- /dev/null
+++ b/fork-join/src/main/java/com/iluwatar/forkjoin/SumTask.java
@@ -0,0 +1,93 @@
+package com.iluwatar.forkjoin;
+
+import java.util.concurrent.RecursiveTask;
+
+/**
+ * SumTask demonstrates the Fork/Join pattern by recursively splitting an array summation problem
+ * into smaller subtasks until each subtask is small enough to compute directly.
+ *
+ *
How it works: If the portion of the array is smaller than THRESHOLD, sum it in a simple loop.
+ * Otherwise, split the array in half, fork one half to run in parallel, compute the other half in
+ * the current thread, and then join the results. This approach utilizes multiple CPU cores to
+ * perform the summation significantly faster than a single-threaded loop for large arrays.
+ */
+public class SumTask extends RecursiveTask {
+
+ /**
+ * If the number of elements to process is at or below this threshold, the task computes the sum
+ * directly instead of splitting further.
+ */
+ private static final int THRESHOLD = 1000;
+
+ private final long[] numbers;
+ private final int start;
+ private final int end;
+
+ /**
+ * Creates a task to sum elements of the given array from index {@code start} (inclusive) to index
+ * {@code end} (exclusive).
+ *
+ * @param numbers the array of numbers to sum
+ * @param start the starting index (inclusive)
+ * @param end the ending index (exclusive)
+ */
+ public SumTask(long[] numbers, int start, int end) {
+ if (start > end) {
+ throw new IllegalArgumentException(
+ "start (" + start + ") must not be greater than end (" + end + ")");
+ }
+ this.numbers = numbers;
+ this.start = start;
+ this.end = end;
+ }
+
+ /**
+ * The main computation method. This is where the fork/join magic happens.
+ *
+ * @return the sum of elements from start to end
+ */
+ @Override
+ protected Long compute() {
+ int length = end - start;
+
+ // BASE CASE: if the chunk is small enough, just sum directly
+ if (length <= THRESHOLD) {
+ return computeDirectly();
+ }
+
+ // FORK: split the task into two halves
+ int mid = start + length / 2;
+
+ // Create subtask for the left half
+ SumTask leftTask = new SumTask(numbers, start, mid);
+
+ // Create subtask for the right half
+ SumTask rightTask = new SumTask(numbers, mid, end);
+
+ // Fork the left task — it will run in a separate thread
+ leftTask.fork();
+
+ // Compute the right task in the current thread (no need to fork both)
+ long rightResult = rightTask.compute();
+
+ // JOIN: wait for the left task to finish and get its result
+ long leftResult = leftTask.join();
+
+ // Combine the results from both halves
+ return leftResult + rightResult;
+ }
+
+ /**
+ * Computes the sum directly using a simple loop. This is used when the chunk size is at or below
+ * the threshold — no further splitting needed.
+ *
+ * @return the sum of elements in the range [start, end)
+ */
+ private long computeDirectly() {
+ long sum = 0;
+ for (int i = start; i < end; i++) {
+ sum += numbers[i];
+ }
+ return sum;
+ }
+}
diff --git a/fork-join/src/test/java/com/iluwatar/forkjoin/AppTest.java b/fork-join/src/test/java/com/iluwatar/forkjoin/AppTest.java
new file mode 100644
index 000000000000..c66f8610cc55
--- /dev/null
+++ b/fork-join/src/test/java/com/iluwatar/forkjoin/AppTest.java
@@ -0,0 +1,11 @@
+package com.iluwatar.forkjoin;
+
+import org.junit.jupiter.api.Test;
+
+class AppTest {
+
+ @Test
+ void shouldExecuteWithoutException() {
+ App.main(new String[] {});
+ }
+}
diff --git a/fork-join/src/test/java/com/iluwatar/forkjoin/ForkJoinSumCalculatorTest.java b/fork-join/src/test/java/com/iluwatar/forkjoin/ForkJoinSumCalculatorTest.java
new file mode 100644
index 000000000000..6af56dfc38e1
--- /dev/null
+++ b/fork-join/src/test/java/com/iluwatar/forkjoin/ForkJoinSumCalculatorTest.java
@@ -0,0 +1,56 @@
+package com.iluwatar.forkjoin;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.stream.LongStream;
+import org.junit.jupiter.api.Test;
+
+class ForkJoinSumCalculatorTest {
+
+ @Test
+ void shouldReturnZeroForNullArray() {
+ ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
+
+ assertEquals(0L, calculator.calculateSum(null));
+ }
+
+ @Test
+ void shouldReturnZeroForEmptyArray() {
+ ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
+
+ assertEquals(0L, calculator.calculateSum(new long[0]));
+ }
+
+ @Test
+ void shouldCalculateSumOfSmallArray() {
+ ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
+ long[] numbers = {10, 20, 30, 40, 50};
+
+ long result = calculator.calculateSum(numbers);
+
+ assertEquals(150L, result);
+ }
+
+ @Test
+ void shouldCalculateSumOfLargeArray() {
+ ForkJoinSumCalculator calculator = new ForkJoinSumCalculator();
+ long[] numbers = LongStream.rangeClosed(1, 100_000).toArray();
+
+ long result = calculator.calculateSum(numbers);
+
+ long expected = 100_000L * 100_001L / 2;
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void shouldWorkWithCustomParallelism() {
+ // Use only 2 threads
+ ForkJoinSumCalculator calculator = new ForkJoinSumCalculator(2);
+ long[] numbers = LongStream.rangeClosed(1, 50_000).toArray();
+
+ long result = calculator.calculateSum(numbers);
+
+ long expected = 50_000L * 50_001L / 2;
+ assertEquals(expected, result);
+ }
+}
diff --git a/fork-join/src/test/java/com/iluwatar/forkjoin/SumTaskTest.java b/fork-join/src/test/java/com/iluwatar/forkjoin/SumTaskTest.java
new file mode 100644
index 000000000000..03df6a6bc268
--- /dev/null
+++ b/fork-join/src/test/java/com/iluwatar/forkjoin/SumTaskTest.java
@@ -0,0 +1,85 @@
+package com.iluwatar.forkjoin;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.concurrent.ForkJoinPool;
+import java.util.stream.LongStream;
+import org.junit.jupiter.api.Test;
+
+class SumTaskTest {
+
+ @Test
+ void shouldSumSmallArrayDirectly() {
+ // Array smaller than threshold — should compute without forking
+ long[] numbers = {1, 2, 3, 4, 5};
+ SumTask task = new SumTask(numbers, 0, numbers.length);
+
+ long result = ForkJoinPool.commonPool().invoke(task);
+
+ assertEquals(15L, result);
+ }
+
+ @Test
+ void shouldSumLargeArrayUsingForkJoin() {
+ // Array larger than threshold — will fork into subtasks
+ long[] numbers = LongStream.rangeClosed(1, 10_000).toArray();
+ SumTask task = new SumTask(numbers, 0, numbers.length);
+
+ long result = ForkJoinPool.commonPool().invoke(task);
+
+ // Sum of 1 to N = N*(N+1)/2
+ long expected = 10_000L * 10_001L / 2;
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void shouldSumPartialRange() {
+ // Sum only a portion of the array (indices 2 to 5)
+ long[] numbers = {10, 20, 30, 40, 50, 60};
+ SumTask task = new SumTask(numbers, 2, 5);
+
+ long result = ForkJoinPool.commonPool().invoke(task);
+
+ // 30 + 40 + 50 = 120
+ assertEquals(120L, result);
+ }
+
+ @Test
+ void shouldReturnZeroForEmptyRange() {
+ long[] numbers = {1, 2, 3};
+ SumTask task = new SumTask(numbers, 1, 1); // start == end, empty range
+
+ long result = ForkJoinPool.commonPool().invoke(task);
+
+ assertEquals(0L, result);
+ }
+
+ @Test
+ void shouldHandleSingleElement() {
+ long[] numbers = {42};
+ SumTask task = new SumTask(numbers, 0, 1);
+
+ long result = ForkJoinPool.commonPool().invoke(task);
+
+ assertEquals(42L, result);
+ }
+
+ @Test
+ void shouldProduceCorrectResultForMillionElements() {
+ long[] numbers = LongStream.rangeClosed(1, 1_000_000).toArray();
+ SumTask task = new SumTask(numbers, 0, numbers.length);
+
+ long result = ForkJoinPool.commonPool().invoke(task);
+
+ long expected = 1_000_000L * 1_000_001L / 2;
+ assertEquals(expected, result);
+ }
+
+ @Test
+ void shouldThrowExceptionWhenStartGreaterThanEnd() {
+ long[] numbers = {1, 2, 3, 4, 5};
+
+ assertThrows(IllegalArgumentException.class, () -> new SumTask(numbers, 4, 2));
+ }
+}
diff --git a/localization/pt/delegation/README.md b/localization/pt/delegation/README.md
new file mode 100644
index 000000000000..9f424895891d
--- /dev/null
+++ b/localization/pt/delegation/README.md
@@ -0,0 +1,157 @@
+---
+title: "Delegation Pattern in Java: Mastering Efficient Task Assignment"
+shortTitle: Delegation
+description: "Explore the Delegation Design Pattern in Java with real-world examples, class diagrams, and its benefits. Learn how to enhance your code flexibility and reuse."
+category: Behavioral
+language: pt
+tag:
+ - Decoupling
+ - Delegation
+ - Object composition
+---
+
+## Também conhecido como
+
+* Helper
+* Surrogate
+
+## Propósito
+
+Permitir que um objeto delegue a responsabilidade por uma tarefa a outro objeto auxiliar.
+
+## Explicação
+
+Exemplo do mundo real
+
+> Em um restaurante, o chef principal delega tarefas aos sous-chefs: um cuida das grelhas, outro das saladas e um terceiro é responsável pelas sobremesas. Cada sous-chef é especializado em sua área, permitindo que o chef principal se concentre na gestão geral da cozinha. Isso reflete o padrão Delegation, no qual um objeto principal delega tarefas específicas a objetos auxiliares, cada um especialista em seu domínio.
+
+Em outras palavras
+
+> Delegation é um padrão de design em que um objeto repassa uma tarefa a um objeto auxiliar.
+
+De acordo com a Wikipédia
+
+> Em programação orientada a objetos, delegação se refere a avaliar um membro (propriedade ou método) de um objeto (o receptor) no contexto de outro objeto original (o remetente). A delegação pode ser feita explicitamente, passando o objeto remetente para o objeto receptor, o que pode ser feito em qualquer linguagem orientada a objetos; ou implicitamente, pelas regras de busca de membros da linguagem, o que exige suporte da linguagem para esse recurso.
+
+Diagrama de sequência
+
+
+
+## Exemplo Programático
+
+Vamos considerar um exemplo de impressão.
+
+Temos uma interface `Printer` e três implementações: `CanonPrinter`, `EpsonPrinter` e `HpPrinter`.
+
+```java
+public interface Printer {
+ void print(final String message);
+}
+
+@Slf4j
+public class CanonPrinter implements Printer {
+ @Override
+ public void print(String message) {
+ LOGGER.info("Canon Printer : {}", message);
+ }
+}
+
+@Slf4j
+public class EpsonPrinter implements Printer {
+ @Override
+ public void print(String message) {
+ LOGGER.info("Epson Printer : {}", message);
+ }
+}
+
+@Slf4j
+public class HpPrinter implements Printer {
+ @Override
+ public void print(String message) {
+ LOGGER.info("HP Printer : {}", message);
+ }
+}
+```
+
+O `PrinterController` pode ser usado como um `Printer`, delegando qualquer trabalho tratado por essa interface a um objeto que a implemente.
+
+```java
+public class PrinterController implements Printer {
+
+ private final Printer printer;
+
+ public PrinterController(Printer printer) {
+ this.printer = printer;
+ }
+
+ @Override
+ public void print(String message) {
+ printer.print(message);
+ }
+}
+```
+
+No código cliente, os controladores de impressora podem imprimir mensagens de formas diferentes, dependendo do objeto para o qual delegam esse trabalho.
+
+```java
+public class App {
+
+ private static final String MESSAGE_TO_PRINT = "hello world";
+
+ public static void main(String[] args) {
+ var hpPrinterController = new PrinterController(new HpPrinter());
+ var canonPrinterController = new PrinterController(new CanonPrinter());
+ var epsonPrinterController = new PrinterController(new EpsonPrinter());
+
+ hpPrinterController.print(MESSAGE_TO_PRINT);
+ canonPrinterController.print(MESSAGE_TO_PRINT);
+ epsonPrinterController.print(MESSAGE_TO_PRINT);
+ }
+}
+```
+
+Saída do programa:
+
+```
+HP Printer:hello world
+Canon Printer:hello world
+Epson Printer:hello world
+```
+
+## Quando usar o padrão Delegation
+
+* Quando você deseja passar a responsabilidade de uma classe para outra sem usar herança.
+* Para obter reutilização baseada em composição em vez de herança.
+* Quando você precisa usar diversas classes auxiliares intercambiáveis em tempo de execução.
+
+## Aplicações do mundo real do padrão Delegation
+
+* O pacote java.awt.event do Java, no qual listeners são frequentemente usados para tratar eventos.
+* Classes wrapper do Java Collections Framework (java.util.Collections), que delegam a outros objetos de coleção.
+* No Spring Framework, a delegação é usada extensivamente no container IoC, no qual beans delegam tarefas a outros beans.
+
+## Benefícios e desafios do padrão Delegation
+
+Benefícios:
+
+* Reduz a criação de subclasses: os objetos podem delegar operações a objetos diferentes e alterá-los em tempo de execução, reduzindo a necessidade de criar subclasses.
+* Incentiva a reutilização: a delegação promove a reutilização do código do objeto auxiliar.
+* Aumenta a flexibilidade: ao delegar tarefas a objetos auxiliares, é possível alterar o comportamento das suas classes em tempo de execução.
+
+Desafios:
+
+* Sobrecarga em tempo de execução: a delegação pode introduzir camadas adicionais de indireção, o que pode resultar em pequenos custos de desempenho.
+* Complexidade: o design pode se tornar mais complicado, pois envolve classes e interfaces adicionais para gerenciar a delegação.
+
+## Padrões relacionados
+
+* [Composite](https://java-design-patterns.com/patterns/composite/): a delegação pode ser usada dentro de um padrão composite para delegar comportamentos específicos de componentes a componentes filhos.
+* [Strategy](https://java-design-patterns.com/patterns/strategy/): a delegação é frequentemente usada no padrão strategy, no qual um objeto de contexto delega tarefas a um objeto de estratégia.
+* https://java-design-patterns.com/patterns/proxy/: o padrão proxy é uma forma de delegação em que um objeto proxy controla o acesso a outro objeto, ao qual delega o trabalho.
+
+## Referências e Créditos
+
+* [Effective Java](https://amzn.to/4aGE7gX)
+* [Head First Design Patterns](https://amzn.to/3J9tuaB)
+* [Refactoring: Improving the Design of Existing Code](https://amzn.to/3VOcRsw)
+* [Delegate Pattern: Wikipedia ](https://en.wikipedia.org/wiki/Delegation_pattern)
diff --git a/localization/pt/money/README.md b/localization/pt/money/README.md
new file mode 100644
index 000000000000..278924fb1315
--- /dev/null
+++ b/localization/pt/money/README.md
@@ -0,0 +1,131 @@
+---
+title: "Money Pattern in Java: Encapsulating Monetary Values with Currency Consistency"
+shortTitle: Money
+description: "Learn how the Money design pattern in Java ensures currency safety, precision handling, and maintainable financial operations. Explore examples, applicability, and benefits of the pattern."
+category: Structural
+language: pt
+tag:
+ - Business
+ - Domain
+ - Encapsulation
+ - Immutable
+---
+
+## Também conhecido como
+
+* Monetary Value Object
+
+## Propósito
+
+Encapsular valores monetários e sua moeda associada em um objeto específico do domínio.
+
+## Explicação
+
+Exemplo do mundo real
+
+> Imagine um sistema de vale-presente on-line, no qual cada vale-presente mantém um saldo específico em uma determinada moeda. Em vez de usar apenas um valor de ponto flutuante para o saldo, o sistema usa um objeto Money para rastrear o valor e a moeda com precisão. Sempre que alguém usa o vale-presente, o saldo é atualizado com cálculos precisos que evitam erros de arredondamento de ponto flutuante, garantindo que a lógica de domínio permaneça consistente e correta.
+
+Em outras palavras
+
+> O padrão Money encapsula tanto um valor quanto sua moeda, garantindo que as operações financeiras sejam precisas, consistentes e fáceis de manter.
+
+De acordo com a Wikipédia
+
+> O padrão de design Money encapsula um valor monetário e sua moeda, permitindo operações aritméticas e conversões seguras, ao mesmo tempo em que preserva a precisão e a consistência nos cálculos financeiros.
+
+Mapa mental
+
+
+
+Fluxograma
+
+
+
+## Exemplo Programático
+
+Neste exemplo, criamos uma classe `Money` para demonstrar como valores monetários podem ser encapsulados junto com sua moeda. Essa abordagem ajuda a evitar imprecisões de ponto flutuante, garante que as operações aritméticas sejam tratadas de forma consistente e fornece uma maneira clara e centrada no domínio de trabalhar com dinheiro.
+
+```java
+@AllArgsConstructor
+@Getter
+public class Money {
+ private double amount;
+ private String currency;
+
+ public Money(double amnt, String curr) {
+ this.amount = amnt;
+ this.currency = curr;
+ }
+
+ private double roundToTwoDecimals(double value) {
+ return Math.round(value * 100.0) / 100.0;
+ }
+
+ public void addMoney(Money moneyToBeAdded) throws CannotAddTwoCurrienciesException {
+ if (!moneyToBeAdded.getCurrency().equals(this.currency)) {
+ throw new CannotAddTwoCurrienciesException("You are trying to add two different currencies");
+ }
+ this.amount = roundToTwoDecimals(this.amount + moneyToBeAdded.getAmount());
+ }
+
+ public void subtractMoney(Money moneyToBeSubtracted) throws CannotSubtractException {
+ if (!moneyToBeSubtracted.getCurrency().equals(this.currency)) {
+ throw new CannotSubtractException("You are trying to subtract two different currencies");
+ } else if (moneyToBeSubtracted.getAmount() > this.amount) {
+ throw new CannotSubtractException("The amount you are trying to subtract is larger than the amount you have");
+ }
+ this.amount = roundToTwoDecimals(this.amount - moneyToBeSubtracted.getAmount());
+ }
+
+ public void multiply(int factor) {
+ if (factor < 0) {
+ throw new IllegalArgumentException("Factor must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * factor);
+ }
+
+ public void exchangeCurrency(String currencyToChangeTo, double exchangeRate) {
+ if (exchangeRate < 0) {
+ throw new IllegalArgumentException("Exchange rate must be non-negative");
+ }
+ this.amount = roundToTwoDecimals(this.amount * exchangeRate);
+ this.currency = currencyToChangeTo;
+ }
+}
+```
+
+Ao encapsular toda a lógica relacionada a dinheiro em uma única classe, reduzimos o risco de misturar moedas diferentes, melhoramos a clareza do código-base e facilitamos futuras modificações, como adicionar novas moedas ou refinar as regras de arredondamento. Esse padrão fortalece o modelo de domínio ao tratar o dinheiro como um conceito distinto, e não apenas como mais um valor numérico.
+
+## Quando usar o padrão Money
+
+* Quando cálculos financeiros ou manipulações de dinheiro fazem parte da lógica de negócio
+* Quando é necessário um tratamento preciso de valores monetários para evitar imprecisões de ponto flutuante
+* Quando princípios de domain-driven design e tipagem forte são desejados
+
+## Aplicações do mundo real do padrão Money
+
+* A biblioteca JSR 354 (Java Money and Currency) em Java
+* Modelos de domínio personalizados em sistemas de e-commerce e contabilidade
+
+## Benefícios e desafios do padrão Money
+
+Benefícios
+
+* Fornece uma representação única e type-safe para valores monetários e moeda
+* Incentiva o encapsulamento de operações relacionadas, como adição, subtração e formatação
+* Evita erros de ponto flutuante ao usar inteiros ou bibliotecas decimais especializadas
+
+Desafios
+
+* Requer classes e infraestrutura adicionais para lidar com conversões e formatação de moeda
+* Pode introduzir sobrecarga de desempenho ao realizar um grande número de operações monetárias
+
+## Padrões relacionados
+
+* [Value Object](https://java-design-patterns.com/patterns/value-object/): Money é tipicamente um exemplo clássico de value object em domain-driven design.
+
+## Referências e Créditos
+
+* [Domain-Driven Design: Tackling Complexity in the Heart of Software](https://amzn.to/3wlDrze)
+* [Implementing Domain-Driven Design](https://amzn.to/4dmBjrB)
+* [Patterns of Enterprise Application Architecture](https://amzn.to/3WfKBPR)
diff --git a/localization/pt/polling-publisher/README.md b/localization/pt/polling-publisher/README.md
new file mode 100644
index 000000000000..d9cf98a550b3
--- /dev/null
+++ b/localization/pt/polling-publisher/README.md
@@ -0,0 +1,119 @@
+---
+title: "Polling Publisher-Subscriber Pattern in Java: Mastering Asynchronous Messaging Elegantly"
+shortTitle: Polling Pub/Sub
+description: "Learn how to implement a Polling Publisher-Subscriber system in Java using Spring Boot and Kafka. Explore the architecture, real-world analogies, and benefits of asynchronous communication with clean code examples."
+category: Architectural
+language: pt
+tag:
+ - Spring Boot
+ - Kafka
+ - Microservices
+ - Asynchronous Messaging
+ - Decoupling
+---
+
+## Também conhecido como
+
+* Event-Driven Architecture
+* Asynchronous Pub/Sub Pattern
+* Message Queue-Based Polling System
+
+## Propósito
+
+O padrão Polling Publisher-Subscriber desacopla os produtores de dados dos consumidores ao permitir uma comunicação assíncrona e orientada a mensagens. Um serviço consulta periodicamente (poll) uma fonte de dados e publica mensagens em um message broker (por exemplo, Kafka), que são então consumidas por um ou mais serviços assinantes.
+
+## Explicação
+
+### Exemplo do mundo real
+
+> Uma agência de notícias consulta constantemente as últimas atualizações. Assim que recebe novas informações, ela as publica em diferentes veículos (TV, jornais, aplicativos). Cada veículo consome e exibe as atualizações de forma independente.
+
+### Em outras palavras
+
+> Um serviço verifica regularmente se há atualizações (polling) e envia mensagens para o Kafka. Outro serviço escuta o Kafka e processa as mensagens de forma assíncrona.
+
+### De acordo com a Wikipédia
+
+> Este padrão se assemelha muito ao [modelo Publish–subscribe](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern), no qual as mensagens são enviadas pelos publicadores e recebidas pelos assinantes sem que eles se conheçam.
+
+### Fluxo da arquitetura
+
+```
++------------+ +--------+ +-------------+
+| Publisher | ---> | Kafka | ---> | Subscriber |
++------------+ +--------+ +-------------+
+```
+
+## Exemplo Programático (Spring Boot + Kafka)
+
+### Serviço Publisher
+
+- Usa o `@Scheduled` do Spring para consultar dados periodicamente.
+- Publica dados em um tópico do Kafka.
+- Opcionalmente expõe uma API REST para publicação manual de dados.
+
+```java
+@Scheduled(fixedRate = 5000)
+public void pollAndPublish() {
+ String data = pollingService.getLatestData();
+ kafkaTemplate.send("updates-topic", data);
+}
+```
+
+### Serviço Subscriber
+
+- Escuta um tópico do Kafka usando `@KafkaListener`.
+- Processa as mensagens de forma assíncrona.
+
+```java
+@KafkaListener(topics = "updates-topic")
+public void processUpdate(String message) {
+ log.info("Received update: {}", message);
+ updateProcessor.handle(message);
+}
+```
+
+## Quando usar o padrão Polling Publisher-Subscriber
+
+Use esse padrão quando:
+
+* Não é possível o produtor enviar dados em tempo real (push).
+* Deseja-se um baixo acoplamento entre produtores e consumidores.
+* É necessário processamento de eventos assíncrono e escalável.
+* Você está construindo uma arquitetura de microsserviços orientada a eventos.
+
+## Aplicações do mundo real
+
+* Painéis de relatórios em tempo real
+* Agregadores de health check para sistemas distribuídos
+* Processamento de telemetria de IoT
+* Sistemas de notificação e alerta
+
+## Benefícios e desafios do padrão Polling Pub/Sub
+
+### Benefícios
+
+* Baixo acoplamento entre serviços
+* Arquitetura assíncrona e escalável
+* Tolerante a falhas, com persistência de mensagens no Kafka
+* Fácil de estender com novos consumidores ou publicadores
+
+### Desafios
+
+* O polling introduz latência entre a geração e o consumo dos dados
+* Requer o gerenciamento e a configuração do Kafka (ou de outro broker)
+* Implantação e infraestrutura ligeiramente mais complexas
+
+## Padrões relacionados
+
+* [Observer Pattern](https://java-design-patterns.com/patterns/observer/)
+* [Mediator Pattern](https://java-design-patterns.com/patterns/mediator/)
+* [Message Queue Pattern](https://java-design-patterns.com/patterns/event-queue/)
+
+## Referências e Créditos
+
+* [Apache Kafka Documentation](https://kafka.apache.org/documentation/)
+* [Spring Kafka Documentation](https://docs.spring.io/spring-kafka)
+* [Spring Scheduled Tasks](https://www.baeldung.com/spring-scheduled-tasks)
+* [Spring Kafka Tutorial – Baeldung](https://www.baeldung.com/spring-kafka)
+* Inspired by: [iluwatar/java-design-patterns](https://github.com/iluwatar/java-design-patterns)
diff --git a/microservices-distributed-tracing/pom.xml b/microservices-distributed-tracing/pom.xml
index c7dddf0fa0b6..fa9e288879f1 100644
--- a/microservices-distributed-tracing/pom.xml
+++ b/microservices-distributed-tracing/pom.xml
@@ -52,7 +52,7 @@
io.zipkin.reporter2
zipkin-reporter-brave
- 3.5.0
+ 3.5.3
org.junit.jupiter
diff --git a/pom.xml b/pom.xml
index 46cec4ee61c4..4ed64e643219 100644
--- a/pom.xml
+++ b/pom.xml
@@ -139,6 +139,7 @@
fluent-interface
flux
flyweight
+ fork-join
front-controller
function-composition
game-loop
@@ -252,6 +253,7 @@
view-helper
virtual-proxy
visitor
+ write-ahead-log
backpressure
actor-model
rate-limiting-pattern
diff --git a/write-ahead-log/README.md b/write-ahead-log/README.md
new file mode 100644
index 000000000000..45caab13cadf
--- /dev/null
+++ b/write-ahead-log/README.md
@@ -0,0 +1,217 @@
+---
+title: "Write-Ahead Log (WAL) Pattern in Java: Ensuring Data Durability and Crash Recovery"
+shortTitle: Write-Ahead Log
+description: "Learn about the Write-Ahead Log (WAL) design pattern in Java. Discover how append-only logging guarantees data durability and fast crash recovery in database engines and distributed systems."
+category: Data Access
+language: en
+tag:
+ - Data access
+ - Storage
+ - Fault tolerance
+ - Transactions
+ - Performance
+---
+
+## Also known as
+
+* Append-Only Log
+* Redo Log
+* Journaling
+
+## Intent of Write-Ahead Log Pattern
+
+The Write-Ahead Log (WAL) design pattern ensures data durability and system recoverability in database engines, distributed consensus protocols, and transactional systems. It enforces a strict order of operations where any state mutation (e.g., insert, update, delete) must be written sequentially to an append-only log file on stable storage (disk) before it is applied to the main database state or in-memory storage structures.
+
+## Detailed Explanation of Write-Ahead Log Pattern with Real-World Examples
+
+Real-world example
+
+> Imagine an accountant managing a company's ledger. Before modifying the main financial summary balance sheets, the accountant immediately records every incoming transaction line-by-line into a sequential physical logbook. If power cuts out mid-day or the summary balance sheets are damaged, the accountant can re-open the physical logbook, replay every recorded entry from the beginning, and perfectly recalculate the final financial state.
+
+In plain words
+
+> Write-Ahead Log guarantees that no state mutation is lost during sudden system crashes by writing changes to a fast append-only disk log file before updating the in-memory store.
+
+Wikipedia says
+
+> In computer science, write-ahead logging (WAL) is a family of techniques for providing atomicity and durability (two of the ACID properties) in database systems. In a system using WAL, all modifications are written to a log before they are applied. Usually both redo and undo information are stored in the log.
+
+Class Diagram
+
+```mermaid
+classDiagram
+ class OperationType {
+ <>
+ SET
+ DELETE
+ CHECKPOINT
+ }
+
+ class LogEntry {
+ -long sequenceNumber
+ -OperationType type
+ -String key
+ -String value
+ +toLogString() String
+ +fromLogString(String line)$ LogEntry
+ }
+
+ class WriteAheadLog {
+ -File logFile
+ -AtomicLong sequenceNumberCounter
+ +append(OperationType type, String key, String value) LogEntry
+ +readAll() List~LogEntry~
+ +clear() void
+ }
+
+ class DatabaseStore {
+ -WriteAheadLog wal
+ -Map~String, String~ memTable
+ +put(String key, String value) void
+ +delete(String key) void
+ +get(String key) String
+ +checkpoint() void
+ +simulateCrash() void
+ +recover() void
+ }
+
+ DatabaseStore --> WriteAheadLog
+ WriteAheadLog --> LogEntry
+ LogEntry --> OperationType
+```
+
+## Programmatic Example of Write-Ahead Log Pattern in Java
+
+The `WriteAheadLog` class manages append-only sequential writes to disk:
+
+```java
+public class WriteAheadLog {
+ private final File logFile;
+ private final AtomicLong sequenceNumberCounter = new AtomicLong(0);
+
+ public synchronized LogEntry append(OperationType type, String key, String value) throws IOException {
+ long nextSeq = sequenceNumberCounter.incrementAndGet();
+ LogEntry entry = new LogEntry(nextSeq, type, key, value);
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFile, true))) {
+ writer.write(entry.toLogString());
+ writer.newLine();
+ writer.flush();
+ }
+ return entry;
+ }
+}
+```
+
+The `DatabaseStore` class coordinates writing to the log before modifying its in-memory `MemTable`:
+
+```java
+public class DatabaseStore {
+ private final WriteAheadLog wal;
+ private final Map memTable = new HashMap<>();
+
+ public synchronized void put(String key, String value) throws IOException {
+ wal.append(OperationType.SET, key, value);
+ memTable.put(key, value);
+ }
+
+ public synchronized void delete(String key) throws IOException {
+ wal.append(OperationType.DELETE, key, null);
+ memTable.remove(key);
+ }
+
+ public synchronized void recover() {
+ memTable.clear();
+ List entries = wal.readAll();
+ for (LogEntry entry : entries) {
+ if (entry.getType() == OperationType.SET) {
+ memTable.put(entry.getKey(), entry.getValue());
+ } else if (entry.getType() == OperationType.DELETE) {
+ memTable.remove(entry.getKey());
+ }
+ }
+ }
+}
+```
+
+The `App` class demonstrates initialization, writes, crash simulation, and WAL recovery:
+
+```java
+@Slf4j
+public class App {
+ public static void main(String[] args) {
+ try {
+ File logFile = File.createTempFile("wal_demo", ".log");
+ WriteAheadLog wal = new WriteAheadLog(logFile);
+ DatabaseStore store = new DatabaseStore(wal);
+
+ store.put("user:101", "Alice");
+ store.put("user:102", "Bob");
+ store.delete("user:103");
+
+ // Simulating system crash where in-memory state is lost
+ store.simulateCrash();
+
+ // System reboot & recovery from WAL log replay
+ store.recover();
+
+ LOGGER.info("MemTable post recovery: {}", store.getMemTableSnapshot());
+ } catch (IOException e) {
+ LOGGER.error("Error running WAL demo", e);
+ }
+ }
+}
+```
+
+Program output:
+
+```text
+15:45:00.100 [main] INFO com.iluwatar.writeaheadlog.App -- === 1. Initializing Storage Engine with WAL ===
+15:45:00.105 [main] INFO com.iluwatar.writeaheadlog.WriteAheadLog -- WAL Entry appended & flushed to disk: LogEntry(sequenceNumber=1, type=SET, key=user:101, value=Alice)
+15:45:00.106 [main] INFO com.iluwatar.writeaheadlog.DatabaseStore -- Applied SET operation to MemTable: user:101 = Alice
+15:45:00.107 [main] INFO com.iluwatar.writeaheadlog.App -- === 3. Simulating Unexpected System Crash ===
+15:45:00.108 [main] INFO com.iluwatar.writeaheadlog.DatabaseStore -- !!! SIMULATED SYSTEM CRASH: In-memory MemTable has been wiped !!!
+15:45:00.109 [main] INFO com.iluwatar.writeaheadlog.App -- === 4. System Restart & Recovery from WAL ===
+15:45:00.110 [main] INFO com.iluwatar.writeaheadlog.DatabaseStore -- Starting recovery process from WAL...
+15:45:00.112 [main] INFO com.iluwatar.writeaheadlog.DatabaseStore -- Recovery completed. Replayed 5 log entries into MemTable.
+15:45:00.113 [main] INFO com.iluwatar.writeaheadlog.App -- MemTable snapshot post recovery: {user:101=Alice, user:102=Bob Smith}
+```
+
+## When to Use the Write-Ahead Log Pattern in Java
+
+* Building storage engines or key-value data stores requiring ACID durability guarantees.
+* Implementing fault-tolerant distributed consensus protocols (e.g., Raft, Paxos).
+* System architectures where random disk I/O is expensive, allowing sequential append-only writes for maximum throughput.
+* Message brokers or event streams requiring replayability after failure.
+
+## Real-World Applications of Write-Ahead Log Pattern in Java
+
+* **PostgreSQL / MySQL (InnoDB):** Uses WAL / Redo Log for crash recovery and replication.
+* **SQLite:** Write-Ahead Logging mode for concurrency and atomic commits.
+* **Apache Cassandra / RocksDB:** Appends mutations to CommitLog / WAL before MemTable updates.
+* **Apache Kafka / Raft:** Log replication across distributed nodes for consensus and state machine replication.
+
+## Benefits and Trade-offs of Write-Ahead Log Pattern
+
+Benefits:
+
+* **High Performance:** Sequential disk writes are significantly faster than random disk updates (e.g., updating B-Trees directly).
+* **Durability & Fault Tolerance:** Guarantees no committed transaction is lost during sudden system crashes.
+* **Simplicity of Recovery:** Replaying ordered log records deterministically restores the exact last-known state.
+
+Trade-offs:
+
+* **Storage Overhead:** Log files grow over time, requiring periodic checkpointing and log truncation.
+* **Recovery Time:** Large log files without checkpoints can lead to slow startup/recovery times.
+
+## Related Java Design Patterns
+
+* [Event Sourcing](https://java-design-patterns.com/patterns/event-sourcing/): Captures state mutations as a sequence of events, similar to log replay.
+* [Command](https://java-design-patterns.com/patterns/command/): Encapsulates requests as objects, which can be serialized into WAL entries.
+* [Memento](https://java-design-patterns.com/patterns/memento/): Stores state snapshots (checkpoints) to truncate logs.
+
+## References and Credits
+
+* [Designing Data-Intensive Applications (Martin Kleppmann)](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/)
+* [PostgreSQL Documentation: Write-Ahead Logging (WAL)](https://www.postgresql.org/docs/current/wal-intro.html)
+* [Raft Consensus Algorithm Paper](https://raft.github.io/)
diff --git a/write-ahead-log/pom.xml b/write-ahead-log/pom.xml
new file mode 100644
index 000000000000..adb766953a47
--- /dev/null
+++ b/write-ahead-log/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+ 4.0.0
+
+ com.iluwatar
+ java-design-patterns
+ 1.26.0-SNAPSHOT
+
+ write-ahead-log
+ 1.26.0-SNAPSHOT
+ write-ahead-log
+ http://maven.apache.org
+
+ UTF-8
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ provided
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+
+
+
+ com.iluwatar.writeaheadlog.App
+
+
+
+
+
+
+
+
+
diff --git a/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/App.java b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/App.java
new file mode 100644
index 000000000000..e8e9fae2f763
--- /dev/null
+++ b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/App.java
@@ -0,0 +1,82 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import java.io.File;
+import java.io.IOException;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Main application class demonstrating the Write-Ahead Log (WAL) design pattern.
+ *
+ * The WAL pattern guarantees durability by ensuring every mutation (SET, DELETE) is written to a
+ * persistent append-only log file on disk BEFORE updating in-memory state. If the system crashes
+ * unexpectedly, replaying log entries from the WAL file restores state.
+ */
+@Slf4j
+public class App {
+
+ /**
+ * Application entry point.
+ *
+ * @param args command line arguments
+ */
+ public static void main(String[] args) {
+ try {
+ File logFile = File.createTempFile("wal_demo", ".log");
+ logFile.deleteOnExit();
+
+ LOGGER.info(
+ "=== 1. Initializing Storage Engine with WAL at {} ===", logFile.getAbsolutePath());
+ WriteAheadLog wal = new WriteAheadLog(logFile);
+ DatabaseStore store = new DatabaseStore(wal);
+
+ LOGGER.info("=== 2. Performing Data Operations (Write-Ahead Logging) ===");
+ store.put("user:101", "Alice");
+ store.put("user:102", "Bob");
+ store.put("user:103", "Charlie");
+ store.put("user:102", "Bob Smith");
+ store.delete("user:103");
+ store.checkpoint();
+
+ LOGGER.info("MemTable snapshot before crash: {}", store.getMemTableSnapshot());
+
+ LOGGER.info("=== 3. Simulating Unexpected System Crash ===");
+ store.simulateCrash();
+ LOGGER.info("MemTable snapshot after crash: {}", store.getMemTableSnapshot());
+
+ LOGGER.info("=== 4. System Restart & Recovery from WAL ===");
+ store.recover();
+ LOGGER.info("MemTable snapshot post recovery: {}", store.getMemTableSnapshot());
+
+ if (logFile.exists()) {
+ logFile.delete();
+ }
+ } catch (IOException e) {
+ LOGGER.error("An error occurred during WAL demonstration: {}", e.getMessage(), e);
+ }
+ }
+}
diff --git a/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/DatabaseStore.java b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/DatabaseStore.java
new file mode 100644
index 000000000000..dd6d94fce7aa
--- /dev/null
+++ b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/DatabaseStore.java
@@ -0,0 +1,131 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Storage engine demonstrating Write-Ahead Log (WAL) pattern. All mutations are logged and flushed
+ * to persistent WAL storage before modifying the in-memory MemTable.
+ */
+@Slf4j
+public class DatabaseStore {
+
+ @Getter private final WriteAheadLog wal;
+ private final Map memTable = new HashMap<>();
+
+ /**
+ * Constructs DatabaseStore with the specified WriteAheadLog.
+ *
+ * @param wal persistent write-ahead log manager
+ */
+ public DatabaseStore(WriteAheadLog wal) {
+ this.wal = wal;
+ }
+
+ /**
+ * Stores a key-value pair. First writes to WAL on disk, then updates the in-memory MemTable.
+ *
+ * @param key target entry key
+ * @param value target entry value
+ * @throws IOException if writing to WAL fails
+ */
+ public synchronized void put(String key, String value) throws IOException {
+ wal.append(OperationType.SET, key, value);
+ memTable.put(key, value);
+ LOGGER.info("Applied SET operation to MemTable: {} = {}", key, value);
+ }
+
+ /**
+ * Removes a key-value pair. First writes DELETE operation to WAL on disk, then updates the
+ * in-memory MemTable.
+ *
+ * @param key target entry key to delete
+ * @throws IOException if writing to WAL fails
+ */
+ public synchronized void delete(String key) throws IOException {
+ wal.append(OperationType.DELETE, key, null);
+ memTable.remove(key);
+ LOGGER.info("Applied DELETE operation to MemTable: {}", key);
+ }
+
+ /**
+ * Retrieves value associated with key from the in-memory MemTable.
+ *
+ * @param key key to lookup
+ * @return value or null if non-existent
+ */
+ public synchronized String get(String key) {
+ return memTable.get(key);
+ }
+
+ /**
+ * Returns an unmodifiable view of current in-memory MemTable state.
+ *
+ * @return unmodifiable map of stored data
+ */
+ public synchronized Map getMemTableSnapshot() {
+ return Collections.unmodifiableMap(new HashMap<>(memTable));
+ }
+
+ /**
+ * Writes a CHECKPOINT log record.
+ *
+ * @throws IOException if writing to WAL fails
+ */
+ public synchronized void checkpoint() throws IOException {
+ wal.append(OperationType.CHECKPOINT, null, null);
+ LOGGER.info("Checkpoint written to WAL.");
+ }
+
+ /** Simulates a system crash or power outage where in-memory state is wiped. */
+ public synchronized void simulateCrash() {
+ memTable.clear();
+ LOGGER.info("!!! SIMULATED SYSTEM CRASH: In-memory MemTable has been wiped !!!");
+ }
+
+ /** Replays log entries from persistent Write-Ahead Log to fully recover in-memory state. */
+ public synchronized void recover() {
+ LOGGER.info("Starting recovery process from WAL...");
+ memTable.clear();
+ List entries = wal.readAll();
+
+ for (LogEntry entry : entries) {
+ if (entry.getType() == OperationType.SET) {
+ memTable.put(entry.getKey(), entry.getValue());
+ } else if (entry.getType() == OperationType.DELETE) {
+ memTable.remove(entry.getKey());
+ }
+ }
+ LOGGER.info("Recovery completed. Replayed {} log entries into MemTable.", entries.size());
+ }
+}
diff --git a/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/LogEntry.java b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/LogEntry.java
new file mode 100644
index 000000000000..7ae212c8337c
--- /dev/null
+++ b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/LogEntry.java
@@ -0,0 +1,83 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+
+/** Represents a single record entry in the Write-Ahead Log. */
+@Getter
+@AllArgsConstructor
+@EqualsAndHashCode
+@ToString
+public class LogEntry {
+
+ private static final String DELIMITER = "|";
+
+ private final long sequenceNumber;
+ private final OperationType type;
+ private final String key;
+ private final String value;
+
+ /**
+ * Serializes the LogEntry to a delimited string format suitable for append-only logging.
+ *
+ * @return delimited log line representation
+ */
+ public String toLogString() {
+ return sequenceNumber
+ + DELIMITER
+ + type
+ + DELIMITER
+ + (key != null ? key : "")
+ + DELIMITER
+ + (value != null ? value : "");
+ }
+
+ /**
+ * Deserializes a delimited string line into a LogEntry instance.
+ *
+ * @param line serialized log line
+ * @return parsed LogEntry
+ * @throws IllegalArgumentException if the log line format is invalid
+ */
+ public static LogEntry fromLogString(String line) {
+ if (line == null || line.isBlank()) {
+ throw new IllegalArgumentException("Log line cannot be null or blank");
+ }
+ String[] parts = line.split("\\|", -1);
+ if (parts.length < 4) {
+ throw new IllegalArgumentException("Invalid log line format: " + line);
+ }
+ long sequenceNumber = Long.parseLong(parts[0]);
+ OperationType type = OperationType.valueOf(parts[1]);
+ String key = parts[2].isEmpty() ? null : parts[2];
+ String value = parts[3].isEmpty() ? null : parts[3];
+ return new LogEntry(sequenceNumber, type, key, value);
+ }
+}
diff --git a/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/OperationType.java b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/OperationType.java
new file mode 100644
index 000000000000..33e06111bdc4
--- /dev/null
+++ b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/OperationType.java
@@ -0,0 +1,33 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+/** Enumeration representing the type of operations logged in the Write-Ahead Log. */
+public enum OperationType {
+ SET,
+ DELETE,
+ CHECKPOINT
+}
diff --git a/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/WriteAheadLog.java b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/WriteAheadLog.java
new file mode 100644
index 000000000000..4ea2044ff2fc
--- /dev/null
+++ b/write-ahead-log/src/main/java/com/iluwatar/writeaheadlog/WriteAheadLog.java
@@ -0,0 +1,134 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Manages sequential append-only logging to persistent disk storage. Ensures state changes are
+ * flushed to stable storage before in-memory updates.
+ */
+@Slf4j
+public class WriteAheadLog {
+
+ @Getter private final File logFile;
+ private final AtomicLong sequenceNumberCounter = new AtomicLong(0);
+
+ /**
+ * Initializes WriteAheadLog with target log file. If log file exists, calculates the initial
+ * sequence number from existing entries.
+ *
+ * @param logFile file to use for append-only log entries
+ */
+ public WriteAheadLog(File logFile) {
+ this.logFile = logFile;
+ initSequenceNumber();
+ }
+
+ private void initSequenceNumber() {
+ if (logFile.exists()) {
+ List existingEntries = readAll();
+ if (!existingEntries.isEmpty()) {
+ long maxSeq = existingEntries.get(existingEntries.size() - 1).getSequenceNumber();
+ sequenceNumberCounter.set(maxSeq);
+ }
+ }
+ }
+
+ /**
+ * Appends an entry to the log file and flushes to ensure persistence.
+ *
+ * @param type operation type (SET, DELETE, CHECKPOINT)
+ * @param key operation target key
+ * @param value operation target value
+ * @return recorded LogEntry
+ * @throws IOException if writing to persistent storage fails
+ */
+ public synchronized LogEntry append(OperationType type, String key, String value)
+ throws IOException {
+ long nextSeq = sequenceNumberCounter.incrementAndGet();
+ LogEntry entry = new LogEntry(nextSeq, type, key, value);
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFile, true))) {
+ writer.write(entry.toLogString());
+ writer.newLine();
+ writer.flush();
+ }
+ LOGGER.info("WAL Entry appended & flushed to disk: {}", entry);
+ return entry;
+ }
+
+ /**
+ * Reads all log entries sequentially from the persistent log file.
+ *
+ * @return list of parsed LogEntries in sequential order
+ */
+ public synchronized List readAll() {
+ List entries = new ArrayList<>();
+ if (!logFile.exists()) {
+ return entries;
+ }
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.isBlank()) {
+ entries.add(LogEntry.fromLogString(line));
+ }
+ }
+ } catch (IOException e) {
+ LOGGER.error("Failed to read log entries from WAL file: {}", logFile.getAbsolutePath(), e);
+ }
+ return entries;
+ }
+
+ /**
+ * Clears the log file and resets the sequence number counter. Typically invoked after a
+ * successful checkpoint.
+ */
+ public synchronized void clear() {
+ if (logFile.exists()) {
+ try {
+ Files.delete(logFile.toPath());
+ sequenceNumberCounter.set(0);
+ LOGGER.info("WAL log cleared successfully.");
+ } catch (IOException e) {
+ LOGGER.error("Failed to clear WAL file: {}", logFile.getAbsolutePath(), e);
+ }
+ }
+ }
+}
diff --git a/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/AppTest.java b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/AppTest.java
new file mode 100644
index 000000000000..b3bdbe3360ba
--- /dev/null
+++ b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/AppTest.java
@@ -0,0 +1,38 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+
+import org.junit.jupiter.api.Test;
+
+class AppTest {
+
+ @Test
+ void testAppMainExecutesWithoutExceptions() {
+ assertDoesNotThrow(() -> App.main(new String[0]));
+ }
+}
diff --git a/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/DatabaseStoreTest.java b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/DatabaseStoreTest.java
new file mode 100644
index 000000000000..08288547348b
--- /dev/null
+++ b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/DatabaseStoreTest.java
@@ -0,0 +1,96 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class DatabaseStoreTest {
+
+ private File tempFile;
+ private WriteAheadLog wal;
+ private DatabaseStore store;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ tempFile = File.createTempFile("db_store_test", ".log");
+ wal = new WriteAheadLog(tempFile);
+ store = new DatabaseStore(wal);
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (tempFile != null && tempFile.exists()) {
+ tempFile.delete();
+ }
+ }
+
+ @Test
+ void testPutAndGet() throws IOException {
+ store.put("key1", "val1");
+ assertEquals("val1", store.get("key1"));
+ }
+
+ @Test
+ void testDelete() throws IOException {
+ store.put("key1", "val1");
+ assertEquals("val1", store.get("key1"));
+
+ store.delete("key1");
+ assertNull(store.get("key1"));
+ }
+
+ @Test
+ void testSimulateCrashAndRecovery() throws IOException {
+ store.put("key1", "val1");
+ store.put("key2", "val2");
+ store.put("key1", "val1_updated");
+ store.delete("key2");
+ store.checkpoint();
+
+ Map beforeCrashSnapshot = store.getMemTableSnapshot();
+ assertEquals("val1_updated", beforeCrashSnapshot.get("key1"));
+ assertNull(beforeCrashSnapshot.get("key2"));
+
+ store.simulateCrash();
+ assertNull(store.get("key1"));
+ assertNull(store.get("key2"));
+ assertTrue(store.getMemTableSnapshot().isEmpty());
+
+ store.recover();
+ Map recoveredSnapshot = store.getMemTableSnapshot();
+ assertEquals("val1_updated", recoveredSnapshot.get("key1"));
+ assertNull(recoveredSnapshot.get("key2"));
+ }
+}
diff --git a/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/LogEntryTest.java b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/LogEntryTest.java
new file mode 100644
index 000000000000..ea031e04c8a0
--- /dev/null
+++ b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/LogEntryTest.java
@@ -0,0 +1,80 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class LogEntryTest {
+
+ @Test
+ void testToLogStringAndFromLogString() {
+ LogEntry entry = new LogEntry(1, OperationType.SET, "key1", "val1");
+ String logString = entry.toLogString();
+ assertEquals("1|SET|key1|val1", logString);
+
+ LogEntry parsed = LogEntry.fromLogString(logString);
+ assertEquals(entry, parsed);
+ assertEquals(1, parsed.getSequenceNumber());
+ assertEquals(OperationType.SET, parsed.getType());
+ assertEquals("key1", parsed.getKey());
+ assertEquals("val1", parsed.getValue());
+ }
+
+ @Test
+ void testDeleteLogEntrySerialization() {
+ LogEntry entry = new LogEntry(2, OperationType.DELETE, "key2", null);
+ String logString = entry.toLogString();
+ assertEquals("2|DELETE|key2|", logString);
+
+ LogEntry parsed = LogEntry.fromLogString(logString);
+ assertEquals(entry, parsed);
+ assertEquals("key2", parsed.getKey());
+ assertNull(parsed.getValue());
+ }
+
+ @Test
+ void testInvalidLogStringThrowsException() {
+ assertThrows(IllegalArgumentException.class, () -> LogEntry.fromLogString(null));
+ assertThrows(IllegalArgumentException.class, () -> LogEntry.fromLogString(" "));
+ assertThrows(IllegalArgumentException.class, () -> LogEntry.fromLogString("1|SET|key1"));
+ }
+
+ @Test
+ void testEqualsAndHashCode() {
+ LogEntry entry1 = new LogEntry(1, OperationType.SET, "k", "v");
+ LogEntry entry2 = new LogEntry(1, OperationType.SET, "k", "v");
+ LogEntry entry3 = new LogEntry(2, OperationType.SET, "k", "v");
+
+ assertEquals(entry1, entry2);
+ assertEquals(entry1.hashCode(), entry2.hashCode());
+ assertNotEquals(entry1, entry3);
+ }
+}
diff --git a/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/WriteAheadLogTest.java b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/WriteAheadLogTest.java
new file mode 100644
index 000000000000..8f0410414e3c
--- /dev/null
+++ b/write-ahead-log/src/test/java/com/iluwatar/writeaheadlog/WriteAheadLogTest.java
@@ -0,0 +1,134 @@
+/*
+ * This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
+ *
+ * The MIT License
+ * Copyright © 2014-2022 Ilkka Seppälä
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.iluwatar.writeaheadlog;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.List;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class WriteAheadLogTest {
+
+ private File tempFile;
+ private WriteAheadLog wal;
+
+ @BeforeEach
+ void setUp() throws IOException {
+ tempFile = File.createTempFile("wal_test", ".log");
+ wal = new WriteAheadLog(tempFile);
+ }
+
+ @AfterEach
+ void tearDown() {
+ if (tempFile != null && tempFile.exists()) {
+ if (tempFile.isDirectory()) {
+ File[] files = tempFile.listFiles();
+ if (files != null) {
+ for (File f : files) {
+ f.delete();
+ }
+ }
+ }
+ tempFile.delete();
+ }
+ }
+
+ @Test
+ void testAppendAndReadAll() throws IOException {
+ LogEntry e1 = wal.append(OperationType.SET, "k1", "v1");
+ LogEntry e2 = wal.append(OperationType.SET, "k2", "v2");
+ LogEntry e3 = wal.append(OperationType.DELETE, "k1", null);
+
+ assertEquals(1, e1.getSequenceNumber());
+ assertEquals(2, e2.getSequenceNumber());
+ assertEquals(3, e3.getSequenceNumber());
+
+ List entries = wal.readAll();
+ assertEquals(3, entries.size());
+ assertEquals(e1, entries.get(0));
+ assertEquals(e2, entries.get(1));
+ assertEquals(e3, entries.get(2));
+ }
+
+ @Test
+ void testSequenceNumberResumptionOnReopen() throws IOException {
+ wal.append(OperationType.SET, "k1", "v1");
+ wal.append(OperationType.SET, "k2", "v2");
+
+ WriteAheadLog reopenedWal = new WriteAheadLog(tempFile);
+ LogEntry newEntry = reopenedWal.append(OperationType.SET, "k3", "v3");
+
+ assertEquals(3, newEntry.getSequenceNumber());
+ }
+
+ @Test
+ void testClear() throws IOException {
+ wal.append(OperationType.SET, "k1", "v1");
+ assertTrue(tempFile.exists());
+
+ wal.clear();
+ assertFalse(tempFile.exists());
+
+ List entries = wal.readAll();
+ assertNotNull(entries);
+ assertTrue(entries.isEmpty());
+ }
+
+ @Test
+ void testReadAllIOExceptionHandling() throws IOException {
+ File dir = Files.createTempDirectory("wal_dir_test").toFile();
+ WriteAheadLog dirWal = new WriteAheadLog(dir);
+
+ List entries = dirWal.readAll();
+ assertNotNull(entries);
+ assertTrue(entries.isEmpty());
+
+ dir.delete();
+ }
+
+ @Test
+ void testClearIOExceptionHandling() throws IOException {
+ File dir = Files.createTempDirectory("wal_nonempty_dir_test").toFile();
+ File child = new File(dir, "child.txt");
+ child.createNewFile();
+
+ WriteAheadLog dirWal = new WriteAheadLog(dir);
+ dirWal.clear();
+
+ assertTrue(dir.exists());
+
+ child.delete();
+ dir.delete();
+ }
+}