initial commit
This commit is contained in:
commit
3aca31de74
40 changed files with 1701 additions and 0 deletions
12
wash/.classpath
Normal file
12
wash/.classpath
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="module" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="lib" path="/cs/labs.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
28
wash/.project
Normal file
28
wash/.project
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>3. Washing machine</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
<filteredResources>
|
||||
<filter>
|
||||
<id>1678721711437</id>
|
||||
<name></name>
|
||||
<type>30</type>
|
||||
<matcher>
|
||||
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||
</matcher>
|
||||
</filter>
|
||||
</filteredResources>
|
||||
</projectDescription>
|
2
wash/.settings/org.eclipse.core.resources.prefs
Normal file
2
wash/.settings/org.eclipse.core.resources.prefs
Normal file
|
@ -0,0 +1,2 @@
|
|||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
25
wash/src/actor/ActorThread.java
Normal file
25
wash/src/actor/ActorThread.java
Normal file
|
@ -0,0 +1,25 @@
|
|||
package actor;
|
||||
|
||||
public abstract class ActorThread<M> extends Thread {
|
||||
|
||||
// TODO: one suitable attribute here
|
||||
|
||||
/** Called by another thread, to send a message to this thread. */
|
||||
public void send(M message) {
|
||||
// TODO: implement this method (one or a few lines)
|
||||
}
|
||||
|
||||
/** Returns the first message in the queue, or blocks if none available. */
|
||||
protected M receive() throws InterruptedException {
|
||||
// TODO: implement this method (one or a few lines)
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns the first message in the queue, or blocks up to 'timeout'
|
||||
milliseconds if none available. Returns null if no message is obtained
|
||||
within 'timeout' milliseconds. */
|
||||
protected M receiveWithTimeout(long timeout) throws InterruptedException {
|
||||
// TODO: implement this method (one or a few lines)
|
||||
return null;
|
||||
}
|
||||
}
|
334
wash/src/actor/test/ActorThreadTest.java
Normal file
334
wash/src/actor/test/ActorThreadTest.java
Normal file
|
@ -0,0 +1,334 @@
|
|||
package actor.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.DelayQueue;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
|
||||
import actor.ActorThread;
|
||||
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
class ActorThreadTest {
|
||||
|
||||
@Test
|
||||
@Order(1)
|
||||
void testBidirectional() throws InterruptedException {
|
||||
checkMainPrints(ExampleBidirectional::main,
|
||||
"ClientThread sending request\n" +
|
||||
"request received by FibonacciThread\n" +
|
||||
"received result fib(14) = 377\n" +
|
||||
"FibonacciThread terminated\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(2)
|
||||
void testProducerConsumer() throws InterruptedException {
|
||||
checkMainPrints(ExampleProducerConsumer::main,
|
||||
"consumer eagerly awaiting messages...\n" +
|
||||
"received [ole]\n" +
|
||||
"received [dole]\n" +
|
||||
"received [doff]\n" +
|
||||
"all done\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(3)
|
||||
void testMessagingWithTimeout() throws InterruptedException {
|
||||
checkMainPrints(ExampleMessagingWithTimeout::main,
|
||||
"consumer eagerly awaiting messages...\n" +
|
||||
"received [ole]\n" +
|
||||
"received [dole]\n" +
|
||||
"received [null]\n" +
|
||||
"received [doff]\n" +
|
||||
"all done\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(4)
|
||||
void testMessagingWithTimeoutButMessagesWaiting() throws InterruptedException {
|
||||
checkMainPrints(ExampleReceiveWithTimeoutKeepsMessagesInOrder::main,
|
||||
"consumer eagerly awaiting messages...\n" +
|
||||
"received [yxi]\n" +
|
||||
"received [kaxi]\n" +
|
||||
"received [kolme]\n" +
|
||||
"received [null]\n" +
|
||||
"all done\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that ActorThread has one single attribute
|
||||
* of type BlockingQueue (or a type implementing BlockingQueue),
|
||||
* and that the chosen queue type does not have a capacity limit
|
||||
*/
|
||||
@Test
|
||||
@Order(5)
|
||||
void testActorThreadUsesBlockingQueue() throws IllegalArgumentException, IllegalAccessException {
|
||||
Field[] attributes = ActorThread.class.getDeclaredFields();
|
||||
assertEquals(1, attributes.length, "expected one single attribute (BlockingQueue)");
|
||||
|
||||
if (attributes.length == 1) {
|
||||
Field queueAttribute = attributes[0];
|
||||
Class<?> attributeType = queueAttribute.getType();
|
||||
assertTrue(BlockingQueue.class.isAssignableFrom(attributeType), "expected BlockingQueue attribute");
|
||||
|
||||
// inspect the actual attribute value
|
||||
ActorThread<?> t = new ActorThread<>() { /* concrete subclass to abstract superclass */ };
|
||||
queueAttribute.setAccessible(true);
|
||||
Object value = queueAttribute.get(t);
|
||||
assertFalse(value instanceof ArrayBlockingQueue, "ArrayBlockingQueue has a limited capacity: can lead to deadlock when used for message queues. Consider LinkedBlockingQueue");
|
||||
assertFalse(value instanceof DelayQueue, "DelayQueue introduces additional delays: inappropriate for message queues. Consider LinkedBlockingQueue");
|
||||
assertFalse(value instanceof PriorityBlockingQueue, "PriorityBlockingQueue reorders elements: inappropriate for message queues. Consider LinkedBlockingQueue");
|
||||
assertFalse(value instanceof SynchronousQueue, "SynchronousQueue has a limited capacity: can lead to deadlock when used for message queues. Consider LinkedBlockingQueue");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that ActorThread methods are not synchronized
|
||||
* (they shouldn't be, as BlockingQueues are thread-safe)
|
||||
*/
|
||||
@Test
|
||||
@Order(6)
|
||||
void testActorThreadNotUsingSynchronized() {
|
||||
Method[] methods = ActorThread.class.getDeclaredMethods();
|
||||
assertEquals(3, methods.length, "expected three methods: send, receive, receiveWithTimeout");
|
||||
|
||||
for (Method m : methods) {
|
||||
assertFalse(Modifier.isSynchronized(m.getModifiers()), "method " + m.getName() + " is synchronized, but shouldn't be");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that ActorThread method send() is not declared
|
||||
* 'throws InterruptedException'
|
||||
*/
|
||||
@Test
|
||||
@Order(7)
|
||||
void testSendNotDeclaredThrows() {
|
||||
for (Method m : ActorThread.class.getDeclaredMethods()) {
|
||||
if ("send".equals(m.getName())) {
|
||||
Type[] exceptionsThrown = m.getExceptionTypes();
|
||||
if (exceptionsThrown.length != 0) {
|
||||
String[] exceptions = Stream.of(exceptionsThrown)
|
||||
.map(t -> {
|
||||
String n = t.getTypeName();
|
||||
int dot = n.lastIndexOf('.');
|
||||
return (dot >= 0) ? n.substring(dot + 1) : n;
|
||||
}).toArray(String[]::new);
|
||||
String typeString = String.join(", ", exceptions);
|
||||
|
||||
System.err.println("** ERROR:");
|
||||
System.err.println("**");
|
||||
System.err.println("** Method 'send()' was declared 'throws " + typeString + "'.");
|
||||
System.err.println("** This method should not throw any exceptions.\n");
|
||||
System.err.println("** HINT:");
|
||||
System.err.println("**");
|
||||
System.err.println("** Don't use the BlockingQueue method put(). Use add() or offer() instead.");
|
||||
System.err.println("** Then remove 'throws " + typeString + "' from method 'send'.");
|
||||
}
|
||||
assertEquals(0, exceptionsThrown.length, "method 'send' should not throw any exceptions");
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("no method 'send' found in ActorThread");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that ActorThread methods receive() and receiveWithTimeout()
|
||||
* are declared 'throws InterruptedException'
|
||||
*/
|
||||
@Test
|
||||
@Order(8)
|
||||
void testReceivesDeclaredThrows() {
|
||||
int k = 0;
|
||||
for (Method m : ActorThread.class.getDeclaredMethods()) {
|
||||
String name = m.getName();
|
||||
if ("receive".equals(name) || "receiveWithTimeout".equals(name)) {
|
||||
Type[] exceptionsThrown = m.getExceptionTypes();
|
||||
Set<String> typeNames = Stream.of(exceptionsThrown).map(Type::getTypeName).collect(Collectors.toSet());
|
||||
boolean throwsInterruptedException = typeNames.contains(InterruptedException.class.getTypeName());
|
||||
assertTrue(throwsInterruptedException, "method '" + name + "' should be declared 'throws InterruptedException'");
|
||||
k++;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(2, k, "missing method 'receive' and/or 'receiveWithTimeout'");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(9)
|
||||
void testReceiveBlocks() throws InterruptedException {
|
||||
AtomicBoolean interruptionHandledCorrectly = new AtomicBoolean(false);
|
||||
ActorThread<?> blocker = new ActorThread<>() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
receive();
|
||||
} catch (InterruptedException e) {
|
||||
// interruption expected: check
|
||||
// InterruptedException handled correctly
|
||||
interruptionHandledCorrectly.set(true);
|
||||
} catch (Throwable unexpected) {
|
||||
fail("unexpected exception", unexpected);
|
||||
}
|
||||
}
|
||||
};
|
||||
blocker.start();
|
||||
expectThreadState(blocker, Thread.State.WAITING);
|
||||
blocker.interrupt();
|
||||
expectThreadState(blocker, Thread.State.TERMINATED);
|
||||
|
||||
assertTrue(interruptionHandledCorrectly.get(), "receive must not catch InterruptedException");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(10)
|
||||
void testReceiveWithTimeoutBlocks() throws InterruptedException {
|
||||
AtomicBoolean interruptionHandledCorrectly = new AtomicBoolean(false);
|
||||
ActorThread<?> blocker = new ActorThread<>() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
receiveWithTimeout(60 * 60 * 1000); // one hour
|
||||
} catch (InterruptedException e) {
|
||||
// interruption expected: check
|
||||
// InterruptedException handled correctly
|
||||
interruptionHandledCorrectly.set(true);
|
||||
} catch (Throwable unexpected) {
|
||||
fail("unexpected exception", unexpected);
|
||||
}
|
||||
}
|
||||
};
|
||||
blocker.start();
|
||||
expectThreadState(blocker, Thread.State.TIMED_WAITING);
|
||||
blocker.interrupt();
|
||||
expectThreadState(blocker, Thread.State.TERMINATED);
|
||||
|
||||
assertTrue(interruptionHandledCorrectly.get(), "receiveWithTimeout must not catch InterruptedException");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(11)
|
||||
void testReceiveWithTimeoutDelay() throws InterruptedException {
|
||||
List<Long> measurements = new ArrayList<>();
|
||||
ActorThread<?> delayed = new ActorThread<>() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
long t0 = System.currentTimeMillis();
|
||||
receiveWithTimeout(300);
|
||||
long t1 = System.currentTimeMillis();
|
||||
receiveWithTimeout(100);
|
||||
long t2 = System.currentTimeMillis();
|
||||
receiveWithTimeout(200);
|
||||
long t3 = System.currentTimeMillis();
|
||||
measurements.add(t1 - t0);
|
||||
measurements.add(t2 - t1);
|
||||
measurements.add(t3 - t2);
|
||||
} catch (Throwable unexpected) {
|
||||
fail("unexpected exception", unexpected);
|
||||
}
|
||||
}
|
||||
};
|
||||
delayed.start();
|
||||
delayed.join();
|
||||
|
||||
assertEquals(3, measurements.size());
|
||||
|
||||
// allow 50ms additional delay: huge overkill
|
||||
assertTrue(measurements.get(0) >= 300 && measurements.get(0) < 350);
|
||||
assertTrue(measurements.get(1) >= 100 && measurements.get(1) < 150);
|
||||
assertTrue(measurements.get(2) >= 200 && measurements.get(2) < 250);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(12)
|
||||
void testFinal() {
|
||||
Field[] attributes = ActorThread.class.getDeclaredFields();
|
||||
|
||||
// previous tests check we have exactly one appropriate attribute
|
||||
if (attributes.length >= 1) {
|
||||
Field queueAttribute = attributes[0];
|
||||
int mod = queueAttribute.getModifiers();
|
||||
assertTrue(Modifier.isFinal(mod), "reference attribute should be final");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(13)
|
||||
void testPrivate() {
|
||||
Field[] attributes = ActorThread.class.getDeclaredFields();
|
||||
|
||||
// previous tests check we have exactly one appropriate attribute
|
||||
if (attributes.length >= 1) {
|
||||
Field queueAttribute = attributes[0];
|
||||
int mod = queueAttribute.getModifiers();
|
||||
assertTrue(Modifier.isPrivate(mod), "queue attribute should be private");
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/** Helper interface for making lambdas, for a main function that throws InterruptedException */
|
||||
private interface InterruptibleMain {
|
||||
void invoke(String... args) throws InterruptedException;
|
||||
}
|
||||
|
||||
/** Helper method: run a main method in another class, and check the printed output. */
|
||||
private void checkMainPrints(InterruptibleMain main, String expectedOutput) throws InterruptedException {
|
||||
PrintStream sysout = System.out;
|
||||
OutputStream bos = new ByteArrayOutputStream();
|
||||
try {
|
||||
System.setOut(new PrintStream(bos, true));
|
||||
main.invoke();
|
||||
} finally {
|
||||
System.setOut(sysout);
|
||||
|
||||
// make sure line feeds are always represented as "\n",
|
||||
// regardless of what the system uses
|
||||
String actualOutput = bos.toString().replace(System.getProperty("line.separator"), "\n");
|
||||
assertEquals(expectedOutput, actualOutput);
|
||||
}
|
||||
}
|
||||
|
||||
/** Helper method: check thread t is in the given state. */
|
||||
private static void expectThreadState(Thread t, Thread.State expectedState) throws InterruptedException {
|
||||
// there is no way to wait for a thread state change:
|
||||
// we have to resort to polling
|
||||
|
||||
long t0 = System.currentTimeMillis(), now = t0;
|
||||
Thread.State actualState = t.getState();
|
||||
while (actualState != expectedState
|
||||
&& actualState != Thread.State.TERMINATED
|
||||
&& now < t0 + 1000) // give up after one second
|
||||
{
|
||||
Thread.sleep(20);
|
||||
actualState = t.getState();
|
||||
now = System.currentTimeMillis();
|
||||
}
|
||||
assertEquals(expectedState, actualState);
|
||||
}
|
||||
}
|
67
wash/src/actor/test/ExampleBidirectional.java
Normal file
67
wash/src/actor/test/ExampleBidirectional.java
Normal file
|
@ -0,0 +1,67 @@
|
|||
package actor.test;
|
||||
|
||||
import actor.ActorThread;
|
||||
|
||||
public class ExampleBidirectional {
|
||||
|
||||
private ActorThread<Integer>
|
||||
ct = new ClientThread(),
|
||||
ft = new FibonacciThread();
|
||||
|
||||
class ClientThread extends ActorThread<Integer> {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("ClientThread sending request");
|
||||
ft.send(14);
|
||||
int reply = receive();
|
||||
System.out.println("received result fib(14) = " + reply);
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// not expected to happen
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FibonacciThread extends ActorThread<Integer> {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
|
||||
int n = receive();
|
||||
System.out.println("request received by FibonacciThread");
|
||||
|
||||
int f2 = 0;
|
||||
int f1 = 1;
|
||||
for (int k = 2; k <= n; k++) {
|
||||
int s = f2 + f1;
|
||||
f2 = f1;
|
||||
f1 = s;
|
||||
Thread.sleep(100);
|
||||
}
|
||||
ct.send(f1);
|
||||
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("FibonacciThread terminated");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ExampleBidirectional app = new ExampleBidirectional();
|
||||
|
||||
app.ct.start();
|
||||
app.ft.start();
|
||||
|
||||
app.ct.join();
|
||||
app.ft.interrupt();
|
||||
app.ft.join();
|
||||
}
|
||||
|
||||
}
|
57
wash/src/actor/test/ExampleMessagingWithTimeout.java
Normal file
57
wash/src/actor/test/ExampleMessagingWithTimeout.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package actor.test;
|
||||
|
||||
import actor.ActorThread;
|
||||
|
||||
public class ExampleMessagingWithTimeout {
|
||||
|
||||
private Producer p = new Producer();
|
||||
private Consumer c = new Consumer();
|
||||
|
||||
class Producer extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
c.send("ole");
|
||||
Thread.sleep(300);
|
||||
c.send("dole");
|
||||
Thread.sleep(1000);
|
||||
c.send("doff");
|
||||
} catch (InterruptedException e) {
|
||||
// not expected to happen
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Consumer extends ActorThread<String> {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("consumer eagerly awaiting messages...");
|
||||
for (int k = 0; k < 3; k++) {
|
||||
String s = receiveWithTimeout(500);
|
||||
System.out.println("received [" + s + "]");
|
||||
}
|
||||
String s = receiveWithTimeout(2000);
|
||||
System.out.println("received [" + s + "]");
|
||||
} catch (InterruptedException e) {
|
||||
// not expected to happen
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ExampleMessagingWithTimeout app = new ExampleMessagingWithTimeout();
|
||||
|
||||
app.p.start();
|
||||
app.c.start();
|
||||
|
||||
app.p.join();
|
||||
app.c.join();
|
||||
System.out.println("all done");
|
||||
}
|
||||
}
|
55
wash/src/actor/test/ExampleProducerConsumer.java
Normal file
55
wash/src/actor/test/ExampleProducerConsumer.java
Normal file
|
@ -0,0 +1,55 @@
|
|||
package actor.test;
|
||||
|
||||
import actor.ActorThread;
|
||||
|
||||
public class ExampleProducerConsumer {
|
||||
|
||||
private Producer p = new Producer();
|
||||
private Consumer c = new Consumer();
|
||||
|
||||
class Producer extends Thread {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
c.send("ole");
|
||||
Thread.sleep(300);
|
||||
c.send("dole");
|
||||
Thread.sleep(300);
|
||||
c.send("doff");
|
||||
} catch (InterruptedException e) {
|
||||
// not expected to happen
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Consumer extends ActorThread<String> {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("consumer eagerly awaiting messages...");
|
||||
for (int k = 0; k < 3; k++) {
|
||||
String s = receive();
|
||||
System.out.println("received [" + s + "]");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// not expected to happen
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ExampleProducerConsumer app = new ExampleProducerConsumer();
|
||||
|
||||
app.p.start();
|
||||
app.c.start();
|
||||
|
||||
app.p.join();
|
||||
app.c.join();
|
||||
System.out.println("all done");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package actor.test;
|
||||
|
||||
import actor.ActorThread;
|
||||
|
||||
/**
|
||||
* This test was introduced to detect possible errors in the implementation
|
||||
* of ActorThread.receiveWithTimeout().
|
||||
*
|
||||
* So if this test is the only one that fails, review your
|
||||
* receiveWithTimeout() implementation. It must not remove more than one
|
||||
* message from the queue.
|
||||
*/
|
||||
public class ExampleReceiveWithTimeoutKeepsMessagesInOrder {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
|
||||
ActorThread<String> c = new ActorThread<>() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("consumer eagerly awaiting messages...");
|
||||
for (int k = 0; k < 4; k++) {
|
||||
String s = receiveWithTimeout(100);
|
||||
System.out.println("received [" + s + "]");
|
||||
}
|
||||
} catch (InterruptedException unexpected) {
|
||||
throw new Error(unexpected);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
c.send("yxi");
|
||||
c.send("kaxi");
|
||||
c.send("kolme");
|
||||
|
||||
c.start();
|
||||
c.join();
|
||||
System.out.println("all done");
|
||||
}
|
||||
}
|
7
wash/src/wash/control/Settings.java
Normal file
7
wash/src/wash/control/Settings.java
Normal file
|
@ -0,0 +1,7 @@
|
|||
package wash.control;
|
||||
|
||||
interface Settings {
|
||||
// simulation speed-up factor: 50 means the simulation is 50 times faster than
|
||||
// real time. Modify this as you wish.
|
||||
int SPEEDUP = 50;
|
||||
}
|
42
wash/src/wash/control/SpinController.java
Normal file
42
wash/src/wash/control/SpinController.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
package wash.control;
|
||||
|
||||
import actor.ActorThread;
|
||||
import wash.io.WashingIO;
|
||||
import wash.io.WashingIO.Spin;
|
||||
|
||||
public class SpinController extends ActorThread<WashingMessage> {
|
||||
|
||||
// TODO: add attributes
|
||||
|
||||
public SpinController(WashingIO io) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
// this is to demonstrate how to control the barrel spin:
|
||||
// io.setSpinMode(Spin.IDLE);
|
||||
|
||||
try {
|
||||
|
||||
// ... TODO ...
|
||||
|
||||
while (true) {
|
||||
// wait for up to a (simulated) minute for a WashingMessage
|
||||
WashingMessage m = receiveWithTimeout(60000 / Settings.SPEEDUP);
|
||||
|
||||
// if m is null, it means a minute passed and no message was received
|
||||
if (m != null) {
|
||||
System.out.println("got " + m);
|
||||
}
|
||||
|
||||
// ... TODO ...
|
||||
}
|
||||
} catch (InterruptedException unexpected) {
|
||||
// we don't expect this thread to be interrupted,
|
||||
// so throw an error if it happens anyway
|
||||
throw new Error(unexpected);
|
||||
}
|
||||
}
|
||||
}
|
18
wash/src/wash/control/TemperatureController.java
Normal file
18
wash/src/wash/control/TemperatureController.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package wash.control;
|
||||
|
||||
import actor.ActorThread;
|
||||
import wash.io.WashingIO;
|
||||
|
||||
public class TemperatureController extends ActorThread<WashingMessage> {
|
||||
|
||||
// TODO: add attributes
|
||||
|
||||
public TemperatureController(WashingIO io) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO
|
||||
}
|
||||
}
|
31
wash/src/wash/control/Wash.java
Normal file
31
wash/src/wash/control/Wash.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package wash.control;
|
||||
|
||||
import actor.ActorThread;
|
||||
import wash.io.WashingIO;
|
||||
import wash.simulation.WashingSimulator;
|
||||
|
||||
public class Wash {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
WashingSimulator sim = new WashingSimulator(Settings.SPEEDUP);
|
||||
|
||||
WashingIO io = sim.startSimulation();
|
||||
|
||||
ActorThread<WashingMessage> temp = new TemperatureController(io);
|
||||
ActorThread<WashingMessage> water = new WaterController(io);
|
||||
ActorThread<WashingMessage> spin = new SpinController(io);
|
||||
|
||||
temp.start();
|
||||
water.start();
|
||||
spin.start();
|
||||
|
||||
while (true) {
|
||||
int n = io.awaitButton();
|
||||
System.out.println("user selected program " + n);
|
||||
|
||||
// TODO:
|
||||
// if the user presses buttons 1-3, start a washing program
|
||||
// if the user presses button 0, and a program has been started, stop it
|
||||
}
|
||||
}
|
||||
};
|
30
wash/src/wash/control/WashingMessage.java
Normal file
30
wash/src/wash/control/WashingMessage.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package wash.control;
|
||||
|
||||
import actor.ActorThread;
|
||||
|
||||
/**
|
||||
* Class used for messaging
|
||||
* - from washing programs to spin controller (SPIN_xxx)
|
||||
* - from washing programs to temperature controller (TEMP_xxx)
|
||||
* - from washing programs to water controller (WATER_xxx)
|
||||
* - from controllers to washing programs (ACKNOWLEDGMENT)
|
||||
*
|
||||
* @param sender the thread that sent the message
|
||||
* @param order an order, such as SPIN_FAST or WATER_DRAIN
|
||||
*/
|
||||
public record WashingMessage(ActorThread<WashingMessage> sender, Order order) {
|
||||
|
||||
// possible values for the 'order' attribute
|
||||
public enum Order {
|
||||
SPIN_OFF,
|
||||
SPIN_SLOW,
|
||||
SPIN_FAST,
|
||||
TEMP_IDLE,
|
||||
TEMP_SET_40,
|
||||
TEMP_SET_60,
|
||||
WATER_IDLE,
|
||||
WATER_FILL,
|
||||
WATER_DRAIN,
|
||||
ACKNOWLEDGMENT
|
||||
}
|
||||
}
|
79
wash/src/wash/control/WashingProgram3.java
Normal file
79
wash/src/wash/control/WashingProgram3.java
Normal file
|
@ -0,0 +1,79 @@
|
|||
package wash.control;
|
||||
|
||||
import actor.ActorThread;
|
||||
import wash.io.WashingIO;
|
||||
|
||||
import static wash.control.WashingMessage.Order.*;
|
||||
|
||||
/**
|
||||
* Program 3 for washing machine. This also serves as an example of how washing
|
||||
* programs can be structured.
|
||||
*
|
||||
* This short program stops all regulation of temperature and water levels,
|
||||
* stops the barrel from spinning, and drains the machine of water.
|
||||
*
|
||||
* It can be used after an emergency stop (program 0) or a power failure.
|
||||
*/
|
||||
public class WashingProgram3 extends ActorThread<WashingMessage> {
|
||||
|
||||
private WashingIO io;
|
||||
private ActorThread<WashingMessage> temp;
|
||||
private ActorThread<WashingMessage> water;
|
||||
private ActorThread<WashingMessage> spin;
|
||||
|
||||
public WashingProgram3(WashingIO io,
|
||||
ActorThread<WashingMessage> temp,
|
||||
ActorThread<WashingMessage> water,
|
||||
ActorThread<WashingMessage> spin)
|
||||
{
|
||||
this.io = io;
|
||||
this.temp = temp;
|
||||
this.water = water;
|
||||
this.spin = spin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
System.out.println("washing program 3 started");
|
||||
|
||||
// Switch off heating
|
||||
temp.send(new WashingMessage(this, TEMP_IDLE));
|
||||
|
||||
// Wait for temperature controller to acknowledge
|
||||
WashingMessage ack1 = receive();
|
||||
System.out.println("got " + ack1);
|
||||
|
||||
// Drain barrel, which may take some time. To ensure the barrel
|
||||
// is drained before we continue, an acknowledgment is required.
|
||||
water.send(new WashingMessage(this, WATER_DRAIN));
|
||||
WashingMessage ack2 = receive(); // wait for acknowledgment
|
||||
System.out.println("got " + ack2);
|
||||
|
||||
// Now that the barrel is drained, we can turn off water regulation.
|
||||
water.send(new WashingMessage(this, WATER_IDLE));
|
||||
WashingMessage ack3 = receive(); // wait for acknowledgment
|
||||
System.out.println("got " + ack3);
|
||||
|
||||
// Switch off spin. We expect an acknowledgment, to ensure
|
||||
// the hatch isn't opened while the barrel is spinning.
|
||||
spin.send(new WashingMessage(this, SPIN_OFF));
|
||||
WashingMessage ack4 = receive(); // wait for acknowledgment
|
||||
System.out.println("got " + ack4);
|
||||
|
||||
// Unlock hatch
|
||||
io.lock(false);
|
||||
|
||||
System.out.println("washing program 3 finished");
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
// If we end up here, it means the program was interrupt()'ed:
|
||||
// set all controllers to idle
|
||||
|
||||
temp.send(new WashingMessage(this, TEMP_IDLE));
|
||||
water.send(new WashingMessage(this, WATER_IDLE));
|
||||
spin.send(new WashingMessage(this, SPIN_OFF));
|
||||
System.out.println("washing program terminated");
|
||||
}
|
||||
}
|
||||
}
|
18
wash/src/wash/control/WaterController.java
Normal file
18
wash/src/wash/control/WaterController.java
Normal file
|
@ -0,0 +1,18 @@
|
|||
package wash.control;
|
||||
|
||||
import actor.ActorThread;
|
||||
import wash.io.WashingIO;
|
||||
|
||||
public class WaterController extends ActorThread<WashingMessage> {
|
||||
|
||||
// TODO: add attributes
|
||||
|
||||
public WaterController(WashingIO io) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// TODO
|
||||
}
|
||||
}
|
32
wash/src/wash/io/WashingIO.java
Normal file
32
wash/src/wash/io/WashingIO.java
Normal file
|
@ -0,0 +1,32 @@
|
|||
package wash.io;
|
||||
|
||||
/** Input/Output (IO) for our washing machine */
|
||||
public interface WashingIO {
|
||||
|
||||
/** @return water level, in range 0..20 liters */
|
||||
double getWaterLevel();
|
||||
|
||||
/** @return temperature, in degrees Celsius */
|
||||
double getTemperature();
|
||||
|
||||
/** Blocks until a program button (0, 1, 2, 3) is pressed */
|
||||
int awaitButton() throws InterruptedException;
|
||||
|
||||
/** Turn heating element on (true) or off (false) */
|
||||
void heat(boolean on);
|
||||
|
||||
/** Set inlet valve to open (true) or closed (false) */
|
||||
void fill(boolean on);
|
||||
|
||||
/** Turn drain pump on (true) or off (false) */
|
||||
void drain(boolean on);
|
||||
|
||||
/** Set hatch to locked (true) or unlocked (false) */
|
||||
void lock(boolean locked);
|
||||
|
||||
/** @param mode one of Spin.IDLE, Spin.LEFT, Spin.RIGHT, Spin.FAST */
|
||||
void setSpinMode(Spin mode);
|
||||
|
||||
/** Values for setSpinMode */
|
||||
enum Spin { IDLE, LEFT, RIGHT, FAST };
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue