Initial commit

This commit is contained in:
github-classroom[bot]
2022-03-31 09:49:56 +00:00
commit 5a1de0b8e7
83 changed files with 17003 additions and 0 deletions
@@ -0,0 +1,61 @@
/*
* Gradle build configuration for specific lab module / exercise
*/
// enabled plugins
plugins {
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
}
// Project/Module information
description = 'Lab04 CircularBuffer Solution'
group = 'ch.zhaw.prog2'
version = '2022.1'
// Dependency configuration
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.+'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.8.+'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.+'
testImplementation 'org.mockito:mockito-core:4.3.1'
}
// Test task configuration
test {
// Use JUnit platform for unit tests
useJUnitPlatform()
testLogging {
events "PASSED", "SKIPPED", "FAILED"
}
}
// Configuration for Application plugin
application {
// Define the main class for the application.
mainClass = 'ch.zhaw.prog2.circularbuffer.CircBufferSimulation'
}
// Required to allow System.in.read() when run with gradle.
run {
standardInput = System.in
}
// Java plugin configuration
java {
// By default the Java version of the gradle process is used as source/target version.
// This can be overridden, to ensure a specific version. Enable only if required.
// sourceCompatibility = JavaVersion.VERSION_17 // ensure Java source code compatibility
// targetCompatibility = JavaVersion.VERSION_17 // version of the created byte-code
// Java compiler specific options
compileJava {
// source files should be UTF-8 encoded
options.encoding = 'UTF-8'
// for more options see https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.CompileOptions.html
}
}
@@ -0,0 +1,11 @@
package ch.zhaw.prog2.circularbuffer;
public interface Buffer<T> {
boolean put(T element) throws InterruptedException;
T get() throws InterruptedException;
boolean empty();
boolean full();
int count();
void printBufferSlots();
void printBufferContent();
}
@@ -0,0 +1,119 @@
package ch.zhaw.prog2.circularbuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CircBufferSimulation {
public static void main(String[] args) {
final int capacity = 15; // Number of buffer items
final int prodCount = 1; // Number of producer threads
final int consCount = 1; // Number of consumer threads
final int maxProdTime = 500; // max. production time for one item
final int maxConsTime = 500; // max. consumption time for one item
try {
Buffer<String> buffer = new GuardedCircularBuffer<>(String.class, capacity);
// start consumers
Consumer[] consumers = new Consumer[consCount];
for (int i = 0; i < consCount; i++) {
consumers[i] = new Consumer("Consumer_" + i, buffer, maxConsTime);
consumers[i].start();
}
// start producers
Producer[] producers = new Producer[prodCount];
for (int i = 0; i < prodCount; i++) {
producers[i] = new Producer("Producer_" + i, buffer, maxProdTime);
producers[i].start();
}
// print live buffer status
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(2);
// print occupied slots of buffer every second
scheduled.scheduleAtFixedRate(buffer::printBufferSlots, 1, 1, TimeUnit.SECONDS);
// print content of buffer every 5 seconds
scheduled.scheduleAtFixedRate(buffer::printBufferContent, 5,5, TimeUnit.SECONDS);
System.out.println("Press Enter to terminate");
System.in.read();
System.out.println("Shutting down ...");
// shutdown producers
for (Producer producer : producers) {
producer.terminate();
}
// shutdown consumers
for (Consumer consumer : consumers) {
consumer.terminate();
}
// shutdown statistics
scheduled.shutdown();
System.out.println("Simulation ended.");
} catch (Exception logOrIgnore) {
System.out.println(logOrIgnore.getMessage());
}
}
private static class Producer extends Thread {
private volatile boolean running = true;
private final Buffer<String> buffer;
private final int maxProdTime;
public Producer(String name, Buffer<String> buffer, int prodTime) {
super(name);
this.buffer = buffer;
maxProdTime = prodTime;
}
public void terminate() {
running = false;
}
@Override
public void run() {
running = true;
int number = 1;
try {
while (running) {
buffer.put("#" + number++);
sleep((int) (100 + Math.random() * maxProdTime));
}
} catch (InterruptedException ex) {
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
}
}
}
private static class Consumer extends Thread {
private volatile boolean running = true;
private final Buffer<String> buffer;
private final int maxConsTime;
public Consumer(String name, Buffer<String> buffer, int consTime) {
super(name);
this.buffer = buffer;
maxConsTime = consTime;
}
public void terminate() {
running = false;
}
@Override
public void run() {
running = true;
try {
while (running) {
buffer.get();
Thread.sleep(100 + (int) (Math.random() * maxConsTime));
}
} catch (InterruptedException ex) {
System.err.println("Interrupted in " + getName() + ": " + ex.getMessage());
}
}
}
}
@@ -0,0 +1,102 @@
package ch.zhaw.prog2.circularbuffer;
import java.lang.reflect.Array;
public class CircularBuffer<T> implements Buffer<T> {
private static final char EMPTY = '-';
private static final char FILLED = '*';
private final StringBuffer printout;
private final T[] items;
private int count = 0;
private int insertPosition = 0;
private int outputPosition = 0;
@SuppressWarnings("unchecked")
public CircularBuffer(Class<T> clazz, int bufferSize) {
printout = new StringBuffer();
if (bufferSize <= 1)
bufferSize = 1;
this.items = (T[]) Array.newInstance(clazz, bufferSize);
}
public boolean put(T item) {
if (this.full())
return false;
items[insertPosition] = item;
insertPosition = (insertPosition + 1) % items.length;
count++;
return true;
}
public T get() {
if (empty())
return null;
T item = items[outputPosition];
outputPosition = (outputPosition + 1) % items.length;
count--;
return item;
}
public boolean empty() {
return count == 0;
}
public boolean full() {
return count >= items.length;
}
public int count() {
return count;
}
public void printBufferSlots() {
printout.delete(0, printout.length());
int i = 0;
// from where to where is the buffer filled?
printout.append("filled where: ||");
if (full()) {
for (i = 0; i < items.length; i++)
printout.append(FILLED);
} else {
char c1;
char c2;
if (insertPosition < outputPosition) { // if iPos < oPos, the buffer
// starts with
// filled slots at pos. 0; otherwise it
// starts with empty slots
c1 = FILLED; // * -> filled slot
c2 = EMPTY; // - -> empty slot
} else {
c1 = EMPTY;
c2 = FILLED;
}
for (i = 0; i < outputPosition && i < insertPosition; i++)
printout.append(c1);
for (; i < outputPosition || i < insertPosition; i++)
printout.append(c2);
for (; i < items.length; i++)
printout.append(c1);
}
printout.append("|| how full: || ");
// how full is the buffer generally?
for (i = 0; i < count; i++)
printout.append(FILLED);
for (; i < items.length; i++)
printout.append(EMPTY);
printout.append("||");
System.out.println(printout);
}
public void printBufferContent() {
System.out.println("Anzahl Elemente im Puffer: " + count);
for (int i = 0; i < count; i++) {
int index = (outputPosition + i) % items.length;
System.out.println("index: " + index + " wert: " + items[index]);
}
}
}
@@ -0,0 +1,49 @@
package ch.zhaw.prog2.circularbuffer;
public class GuardedCircularBuffer<T> implements Buffer<T> {
private CircularBuffer<T> buffer;
public GuardedCircularBuffer(Class<T> clazz, int bufferSize) {
buffer = new CircularBuffer<>(clazz, bufferSize);
}
public synchronized boolean put(T item) throws InterruptedException {
while (buffer.full()) {
wait();
}
boolean retVal = buffer.put(item);
notifyAll();
return retVal;
}
public synchronized T get() throws InterruptedException {
while (buffer.empty()) {
wait();
}
T item = buffer.get();
notifyAll();
return item;
}
public synchronized void printBufferSlots() {
buffer.printBufferSlots();
}
public synchronized void printBufferContent() {
buffer.printBufferContent();
}
public synchronized boolean empty() {
return buffer.empty();
}
public synchronized boolean full() {
return buffer.full();
}
public synchronized int count() {
return buffer.count();
}
}
@@ -0,0 +1,223 @@
package ch.zhaw.prog2.circularbuffer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class GuardedCircularBufferTest {
private static final int BUFFER_SIZE = 5;
private static final String DEFAULT_ITEM = "Item";
private GuardedCircularBuffer<String> buffer;
@BeforeEach
void setUp() {
buffer = new GuardedCircularBuffer<>(String.class, BUFFER_SIZE);
}
@AfterEach
void tearDown() {
buffer = null;
}
@Test
void testEmpty() {
assertTrue(buffer.empty(), "Must return true if buffer is empty");
assertDoesNotThrow(() -> { buffer.put("Some content"); }, "Must not throw an exception");
assertFalse(buffer.empty(), "Must return false if buffer is not empty");
}
@Test
void testFull() {
assertFalse(buffer.full(), "Must return false if buffer is empty");
for(int num=0; num < BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
}
assertTrue(buffer.full(), "Must return true if buffer is full");
assertDoesNotThrow(() -> { buffer.get(); }, "Must not throw an exception");
assertFalse(buffer.full(), "Must return false if buffer is not full");
}
@Test
void testCount() {
assertEquals(0, buffer.count(), "Initial should be 0");
for(int num=1; num <= BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> { buffer.put(item); }, "Must not throw an exception");
assertEquals(num, buffer.count());
}
}
@Test
void testSinglePutGet() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
assertDoesNotThrow(() -> { buffer.put(DEFAULT_ITEM); }, "Must not throw an exception");
assertEquals(1, buffer.count());
AtomicReference<String> returnItem = new AtomicReference<>();
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
assertEquals(DEFAULT_ITEM, returnItem.get());
}
@Test
void testMultiplePutGet() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
// write items
for (int num = 0; num < BUFFER_SIZE; num++) {
String item = DEFAULT_ITEM + " " + num;
assertDoesNotThrow(() -> {
buffer.put(item);
}, "Must not throw an exception");
}
// read items in same order
for (int num = 0; num < BUFFER_SIZE; num++) {
AtomicReference<String> returnItem = new AtomicReference<>();
assertDoesNotThrow(() -> { returnItem.set(buffer.get()); }, "Must not throw an exception");
assertEquals(DEFAULT_ITEM + " " + num, returnItem.get());
}
}
@Test
void testBlockProduce() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE+1, DEFAULT_ITEM));
TimeUnit.MILLISECONDS.sleep(100);
assertTrue(buffer.full(), "Buffer should be full");
executorService.shutdown();
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
@Test
void testBlockConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(new Consumer<String>(buffer, 2));
TimeUnit.MILLISECONDS.sleep(100);
assertTrue(buffer.empty(), "Buffer should be empty");
executorService.shutdown();
assertFalse(executorService.awaitTermination(3, TimeUnit.SECONDS), "Executor should be blocking");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
@Test
void testProduceThenConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
try {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Producer<String>(buffer, BUFFER_SIZE, DEFAULT_ITEM));
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(BUFFER_SIZE, buffer.count(), "Buffer must contain " + BUFFER_SIZE + " items");
Consumer<String> consumer = new Consumer<>(buffer, BUFFER_SIZE);
executorService.execute(consumer);
TimeUnit.MILLISECONDS.sleep(100);
assertEquals(0, buffer.count(), "Buffer must contain 0 items");
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(BUFFER_SIZE).toArray();
assertArrayEquals(expected, consumer.getItems().toArray());
executorService.shutdown();
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
}
}
@Test
void testProduceAndConsume() {
assertTrue(buffer.empty(), "Make sure buffer is empty");
ExecutorService executorService = Executors.newFixedThreadPool(2);
try {
executorService.execute(new Producer<String>(buffer, 10*BUFFER_SIZE, "Item"));
TimeUnit.SECONDS.sleep(1);
Consumer<String> consumer = new Consumer<>(buffer, 10*BUFFER_SIZE);
executorService.execute(consumer);
TimeUnit.SECONDS.sleep(1);
List<String> receivedItems = consumer.getItems();
assertEquals(10*BUFFER_SIZE, receivedItems.size());
Object[] expected = Stream.generate(() -> DEFAULT_ITEM).limit(10*BUFFER_SIZE).toArray();
assertArrayEquals(expected, consumer.getItems().toArray());
executorService.shutdown();
assertTrue(executorService.awaitTermination(3, TimeUnit.SECONDS), "Timeout shutting down Executor");
} catch (InterruptedException e) {
fail("Interrupted executor", e);
} finally {
executorService.shutdownNow();
}
}
private class Producer<T> implements Runnable {
private GuardedCircularBuffer<T> buffer;
private int numItems;
private T item;
public Producer(GuardedCircularBuffer<T> buffer, int numItems, T item) {
this.buffer = buffer;
this.numItems = numItems;
this.item = item;
}
@Override
public void run() {
for (int num = 0; num < numItems; num++) {
try {
buffer.put(item);
} catch (InterruptedException e) {
System.err.println("Interrupted Producer at " + num + ": " + e.getMessage());
}
}
}
}
private class Consumer<T> implements Runnable {
private GuardedCircularBuffer<T> buffer;
private int numItems;
private List<T> items;
public Consumer(GuardedCircularBuffer<T> buffer, int numItems) {
this.buffer = buffer;
this.numItems = numItems;
this.items = new ArrayList<>();
}
public List<T> getItems() {
return items;
}
@Override
public void run() {
for (int num = 0; num < numItems; num++) {
try {
this.items.add(buffer.get());
} catch (InterruptedException e) {
System.err.println("Interrupted Consumer at " + num + ": " + e.getMessage());
}
}
}
}
}