Skip to content

Commit b445e84

Browse files
committed
feat: add DoubleBuffer implementation and initial tests
1 parent 33dee07 commit b445e84

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.thealgorithms.datastructures.buffers;
2+
3+
import java.util.concurrent.atomic.AtomicBoolean;
4+
import java.util.concurrent.atomic.AtomicInteger;
5+
6+
/**
7+
* The {@code DoubleBuffer} class implements a double buffering mechanism.
8+
* It maintains two buffers, allowing the program to switch between them.
9+
* This class is useful for separating read/write operations and optimizing memory usage.
10+
*
11+
* @param <Item> The type of elements stored in the double buffer.
12+
*/
13+
public class DoubleBuffer<Item> {
14+
private final Item[] primaryBuffer;
15+
private final Item[] secondaryBuffer;
16+
private final AtomicInteger primaryIndex;
17+
private final AtomicInteger secondaryIndex;
18+
private final int capacity;
19+
private final AtomicBoolean isPrimaryActive;
20+
21+
/**
22+
* Constructor to initialize the double buffer with a specified capacity.
23+
*
24+
* @param capacity The size of each buffer.
25+
* @throws IllegalArgumentException if the capacity is zero or negative.
26+
*/
27+
public DoubleBuffer(int capacity) {
28+
if (capacity <= 0) {
29+
throw new IllegalArgumentException("Buffer size must be positive");
30+
}
31+
// noinspection unchecked
32+
this.primaryBuffer = (Item[]) new Object[capacity];
33+
this.secondaryBuffer = (Item[]) new Object[capacity];
34+
this.primaryIndex = new AtomicInteger(0);
35+
this.secondaryIndex = new AtomicInteger(0);
36+
this.capacity = capacity;
37+
this.isPrimaryActive = new AtomicBoolean(true);
38+
}
39+
40+
/**
41+
* Checks if the active buffer is empty.
42+
*
43+
* @return {@code true} if the active buffer is empty, {@code false} otherwise.
44+
*/
45+
public boolean isEmpty() {
46+
return isPrimaryActive.get() ? primaryIndex.get() == 0 : secondaryIndex.get() == 0;
47+
}
48+
49+
/**
50+
* Switches between the primary and secondary buffers.
51+
*/
52+
public void switchBuffer() {
53+
isPrimaryActive.set(!isPrimaryActive.get());
54+
}
55+
56+
/**
57+
* Checks if the primary buffer is currently active.
58+
*
59+
* @return {@code true} if the primary buffer is active, {@code false} otherwise.
60+
*/
61+
public boolean isPrimaryActive() {
62+
return isPrimaryActive.get();
63+
}
64+
65+
/**
66+
* Adds an item to the active buffer.
67+
*
68+
* @param item The item to be added.
69+
* @throws IllegalArgumentException if the item is null.
70+
*/
71+
public void put(Item item) {
72+
if (item == null) {
73+
throw new IllegalArgumentException("Null items are not allowed");
74+
}
75+
76+
if (isPrimaryActive.get()) {
77+
if (primaryIndex.get() < capacity) {
78+
primaryBuffer[primaryIndex.getAndIncrement()] = item;
79+
}
80+
} else {
81+
if (secondaryIndex.get() < capacity) {
82+
secondaryBuffer[secondaryIndex.getAndIncrement()] = item;
83+
}
84+
}
85+
}
86+
87+
/**
88+
* Retrieves and removes the item at the front of the active buffer.
89+
*
90+
* @return The item from the active buffer, or {@code null} if the buffer is empty.
91+
*/
92+
public Item get() {
93+
if (isPrimaryActive.get()) {
94+
if (primaryIndex.get() == 0) {
95+
return null;
96+
}
97+
return primaryBuffer[primaryIndex.decrementAndGet()];
98+
} else {
99+
if (secondaryIndex.get() == 0) {
100+
return null;
101+
}
102+
return secondaryBuffer[secondaryIndex.decrementAndGet()];
103+
}
104+
}
105+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package com.thealgorithms.datastructures.buffers;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
11+
class DoubleBufferTest {
12+
13+
private DoubleBuffer<Integer> buffer;
14+
15+
@BeforeEach
16+
void setUp() {
17+
buffer = new DoubleBuffer<>(5);
18+
}
19+
20+
@Test
21+
void testInitialization() {
22+
assertTrue(buffer.isPrimaryActive());
23+
assertTrue(buffer.isEmpty());
24+
}
25+
26+
@Test
27+
void testPutAndGetFromPrimary() {
28+
buffer.put(1);
29+
buffer.put(2);
30+
buffer.put(3);
31+
32+
assertEquals(3, buffer.get());
33+
assertEquals(2, buffer.get());
34+
assertEquals(1, buffer.get());
35+
assertNull(buffer.get());
36+
}
37+
38+
@Test
39+
void testSwitchBuffers() {
40+
buffer.put(1);
41+
buffer.put(2);
42+
buffer.switchBuffer();
43+
44+
// Now the buffer should be empty as we switched to the secondary buffer
45+
assertTrue(buffer.isEmpty());
46+
47+
buffer.put(3);
48+
assertEquals(3, buffer.get());
49+
50+
// Switch back to primary
51+
buffer.switchBuffer();
52+
assertEquals(2, buffer.get());
53+
assertEquals(1, buffer.get());
54+
}
55+
56+
@Test
57+
void testEmptyBuffer() {
58+
assertNull(buffer.get());
59+
buffer.switchBuffer();
60+
assertNull(buffer.get());
61+
}
62+
63+
@Test
64+
void testIllegalArguments() {
65+
assertThrows(IllegalArgumentException.class, () -> new DoubleBuffer<>(0));
66+
assertThrows(IllegalArgumentException.class, () -> new DoubleBuffer<>(-1));
67+
68+
DoubleBuffer<String> strBuffer = new DoubleBuffer<>(1);
69+
assertThrows(IllegalArgumentException.class, () -> strBuffer.put(null));
70+
}
71+
}

0 commit comments

Comments
 (0)