From b445e846f25737dd9f13f018f73d486ba4212b1f Mon Sep 17 00:00:00 2001 From: Lamine Gaye Date: Thu, 17 Oct 2024 01:25:10 +0000 Subject: [PATCH] feat: add DoubleBuffer implementation and initial tests --- .../datastructures/buffers/DoubleBuffer.java | 105 ++++++++++++++++++ .../buffers/DoubleBufferTest.java | 71 ++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/main/java/com/thealgorithms/datastructures/buffers/DoubleBuffer.java create mode 100644 src/test/java/com/thealgorithms/datastructures/buffers/DoubleBufferTest.java diff --git a/src/main/java/com/thealgorithms/datastructures/buffers/DoubleBuffer.java b/src/main/java/com/thealgorithms/datastructures/buffers/DoubleBuffer.java new file mode 100644 index 000000000000..4be72b51c9d2 --- /dev/null +++ b/src/main/java/com/thealgorithms/datastructures/buffers/DoubleBuffer.java @@ -0,0 +1,105 @@ +package com.thealgorithms.datastructures.buffers; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * The {@code DoubleBuffer} class implements a double buffering mechanism. + * It maintains two buffers, allowing the program to switch between them. + * This class is useful for separating read/write operations and optimizing memory usage. + * + * @param The type of elements stored in the double buffer. + */ +public class DoubleBuffer { + private final Item[] primaryBuffer; + private final Item[] secondaryBuffer; + private final AtomicInteger primaryIndex; + private final AtomicInteger secondaryIndex; + private final int capacity; + private final AtomicBoolean isPrimaryActive; + + /** + * Constructor to initialize the double buffer with a specified capacity. + * + * @param capacity The size of each buffer. + * @throws IllegalArgumentException if the capacity is zero or negative. + */ + public DoubleBuffer(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Buffer size must be positive"); + } + // noinspection unchecked + this.primaryBuffer = (Item[]) new Object[capacity]; + this.secondaryBuffer = (Item[]) new Object[capacity]; + this.primaryIndex = new AtomicInteger(0); + this.secondaryIndex = new AtomicInteger(0); + this.capacity = capacity; + this.isPrimaryActive = new AtomicBoolean(true); + } + + /** + * Checks if the active buffer is empty. + * + * @return {@code true} if the active buffer is empty, {@code false} otherwise. + */ + public boolean isEmpty() { + return isPrimaryActive.get() ? primaryIndex.get() == 0 : secondaryIndex.get() == 0; + } + + /** + * Switches between the primary and secondary buffers. + */ + public void switchBuffer() { + isPrimaryActive.set(!isPrimaryActive.get()); + } + + /** + * Checks if the primary buffer is currently active. + * + * @return {@code true} if the primary buffer is active, {@code false} otherwise. + */ + public boolean isPrimaryActive() { + return isPrimaryActive.get(); + } + + /** + * Adds an item to the active buffer. + * + * @param item The item to be added. + * @throws IllegalArgumentException if the item is null. + */ + public void put(Item item) { + if (item == null) { + throw new IllegalArgumentException("Null items are not allowed"); + } + + if (isPrimaryActive.get()) { + if (primaryIndex.get() < capacity) { + primaryBuffer[primaryIndex.getAndIncrement()] = item; + } + } else { + if (secondaryIndex.get() < capacity) { + secondaryBuffer[secondaryIndex.getAndIncrement()] = item; + } + } + } + + /** + * Retrieves and removes the item at the front of the active buffer. + * + * @return The item from the active buffer, or {@code null} if the buffer is empty. + */ + public Item get() { + if (isPrimaryActive.get()) { + if (primaryIndex.get() == 0) { + return null; + } + return primaryBuffer[primaryIndex.decrementAndGet()]; + } else { + if (secondaryIndex.get() == 0) { + return null; + } + return secondaryBuffer[secondaryIndex.decrementAndGet()]; + } + } +} diff --git a/src/test/java/com/thealgorithms/datastructures/buffers/DoubleBufferTest.java b/src/test/java/com/thealgorithms/datastructures/buffers/DoubleBufferTest.java new file mode 100644 index 000000000000..4e14e6ffdcd5 --- /dev/null +++ b/src/test/java/com/thealgorithms/datastructures/buffers/DoubleBufferTest.java @@ -0,0 +1,71 @@ +package com.thealgorithms.datastructures.buffers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class DoubleBufferTest { + + private DoubleBuffer buffer; + + @BeforeEach + void setUp() { + buffer = new DoubleBuffer<>(5); + } + + @Test + void testInitialization() { + assertTrue(buffer.isPrimaryActive()); + assertTrue(buffer.isEmpty()); + } + + @Test + void testPutAndGetFromPrimary() { + buffer.put(1); + buffer.put(2); + buffer.put(3); + + assertEquals(3, buffer.get()); + assertEquals(2, buffer.get()); + assertEquals(1, buffer.get()); + assertNull(buffer.get()); + } + + @Test + void testSwitchBuffers() { + buffer.put(1); + buffer.put(2); + buffer.switchBuffer(); + + // Now the buffer should be empty as we switched to the secondary buffer + assertTrue(buffer.isEmpty()); + + buffer.put(3); + assertEquals(3, buffer.get()); + + // Switch back to primary + buffer.switchBuffer(); + assertEquals(2, buffer.get()); + assertEquals(1, buffer.get()); + } + + @Test + void testEmptyBuffer() { + assertNull(buffer.get()); + buffer.switchBuffer(); + assertNull(buffer.get()); + } + + @Test + void testIllegalArguments() { + assertThrows(IllegalArgumentException.class, () -> new DoubleBuffer<>(0)); + assertThrows(IllegalArgumentException.class, () -> new DoubleBuffer<>(-1)); + + DoubleBuffer strBuffer = new DoubleBuffer<>(1); + assertThrows(IllegalArgumentException.class, () -> strBuffer.put(null)); + } +}