diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java new file mode 100644 index 0000000000..6b315265b4 --- /dev/null +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -0,0 +1,75 @@ +package com.baeldung.circularbuffer; + +public class CircularBuffer { + + private static final int DEFAULT_CAPACITY = 8; + + private final int capacity; + private final E[] data; + private volatile int writeSequence, readSequence; + + @SuppressWarnings("unchecked") + public CircularBuffer(int capacity) { + + this.capacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity; + this.data = (E[]) new Object[capacity]; + + this.readSequence = 0; + this.writeSequence = -1; + } + + public boolean offer(E element) { + + if (isNotFull()) { + + int nextWriteSeq = writeSequence + 1; + data[nextWriteSeq % capacity] = element; + + writeSequence++; + return true; + } + + return false; + } + + public E poll() { + + if (isNotEmpty()) { + + E nextValue = data[readSequence % capacity]; + readSequence++; + return nextValue; + } + + return null; + } + + public int capacity() { + return capacity; + } + + public int size() { + + return (writeSequence - readSequence) + 1; + } + + public boolean isEmpty() { + + return writeSequence < readSequence; + } + + public boolean isFull() { + + return size() >= capacity; + } + + private boolean isNotEmpty() { + + return !isEmpty(); + } + + private boolean isNotFull() { + + return !isFull(); + } +} diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java new file mode 100644 index 0000000000..4b8e8646f3 --- /dev/null +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java @@ -0,0 +1,72 @@ +package com.baeldung.circularbuffer; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; + +public class CircularBufferUnitTest { + + private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; + private final int defaultCapacity = shapes.length; + + @Test + public void givenCircularBuffer_whenAnElementIsEnqueued_thenSizeIsOne() { + CircularBuffer buffer = new CircularBuffer<>(defaultCapacity); + + assertTrue(buffer.offer("Square")); + assertEquals(1, buffer.size()); + } + + @Test + public void givenCircularBuffer_whenAnElementIsDequeued_thenElementMatchesEnqueuedElement() { + CircularBuffer buffer = new CircularBuffer<>(defaultCapacity); + + buffer.offer("Triangle"); + + String shape = buffer.poll(); + assertEquals("Triangle", shape); + } + + @Test + public void givenCircularBuffer_whenAnElementIsEnqueuedAndDeququed_thenBufferIsEmpty() { + + CircularBuffer buffer = new CircularBuffer<>(defaultCapacity); + + buffer.offer("Rectangle"); + + assertFalse(buffer.isEmpty()); + assertEquals(1, buffer.size()); + + buffer.poll(); + + assertTrue(buffer.isEmpty()); + } + + @Test + public void givenCircularBuffer_whenFilledToCapacity_thenNoMoreElementsCanBeEnqueued() { + + int capacity = shapes.length; + CircularBuffer buffer = new CircularBuffer<>(capacity); + + assertTrue(buffer.isEmpty()); + + for (String shape : shapes) { + buffer.offer(shape); + } + + assertTrue(buffer.isFull()); + assertFalse(buffer.offer("Octagon")); + } + + @Test + public void givenCircularBuffer_whenBufferIsEmpty_thenReturnsNull() { + + CircularBuffer buffer = new CircularBuffer<>(1); + + assertTrue(buffer.isEmpty()); + assertNull(buffer.poll()); + } +} diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java new file mode 100644 index 0000000000..83ce27043b --- /dev/null +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java @@ -0,0 +1,80 @@ +package com.baeldung.circularbuffer; + +import static org.junit.Assert.assertArrayEquals; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import org.junit.jupiter.api.Test; + +public class ProducerConsumerLiveTest { + + private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; + + @Test + public void givenACircularBuffer_whenInterleavingProducerConsumer_thenElementsMatch() throws Exception { + CircularBuffer buffer = new CircularBuffer(shapes.length); + + ExecutorService executorService = Executors.newFixedThreadPool(2); + + executorService.submit(new Producer(buffer, shapes)); + Future consumed = executorService.submit(new Consumer(buffer, shapes.length)); + + String[] shapesConsumed = consumed.get(5L, TimeUnit.SECONDS); + assertArrayEquals(shapes, shapesConsumed); + } + + static class Producer implements Runnable { + + private CircularBuffer buffer; + private T[] items; + + public Producer(CircularBuffer buffer, T[] items) { + this.buffer = buffer; + this.items = items; + } + + @Override + public void run() { + + for (int i = 0; i < items.length;) { + if (buffer.offer(items[i])) { + System.out.println("Produced: " + items[i]); + i++; + LockSupport.parkNanos(5); + } + } + } + } + + @SuppressWarnings("unchecked") + static class Consumer implements Callable { + + private CircularBuffer buffer; + private int expectedCount; + + public Consumer(CircularBuffer buffer, int expectedCount) { + this.buffer = buffer; + this.expectedCount = expectedCount; + } + + @Override + public T[] call() throws Exception { + T[] items = (T[]) new Object[expectedCount]; + for (int i = 0; i < items.length;) { + T item = buffer.poll(); + if (item != null) { + items[i++] = item; + + LockSupport.parkNanos(5); + System.out.println("Consumed: " + item); + } + } + return items; + } + } +}