Add SIN.04028 tutorial material, tutor prompt and conversation log
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
void main() throws Exception {
|
||||
FileMessageBoard.watch("consoleOut/inbox", message -> {
|
||||
if (message.type().equals("TEXT"))
|
||||
IO.println(message.value());
|
||||
});
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
record Route(String from, String to) {}
|
||||
|
||||
Route waitForRoute(String controlMailbox) throws InterruptedException {
|
||||
var messages = new LinkedBlockingQueue<Message>();
|
||||
|
||||
var controlListener = Thread.ofVirtual().start(() -> {
|
||||
try {
|
||||
FileMessageBoard.watch(controlMailbox, messages::add);
|
||||
} catch (InterruptedException _) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var message = messages.take();
|
||||
|
||||
if (!message.type().equals("ROUTE")) {
|
||||
IO.println("Ignored " + message.type() + "; expected ROUTE");
|
||||
continue;
|
||||
}
|
||||
|
||||
var mailboxes = message.value().lines().toList();
|
||||
|
||||
if (mailboxes.size() != 2
|
||||
|| mailboxes.get(0).isBlank()
|
||||
|| mailboxes.get(1).isBlank()) {
|
||||
IO.println("ROUTE needs exactly two lines: <from> and <to>");
|
||||
continue;
|
||||
}
|
||||
|
||||
return new Route(mailboxes.get(0), mailboxes.get(1));
|
||||
}
|
||||
} finally {
|
||||
controlListener.interrupt();
|
||||
controlListener.join();
|
||||
}
|
||||
}
|
||||
|
||||
void main(String[] arguments) throws Exception {
|
||||
if (arguments.length != 1
|
||||
|| !arguments[0].matches("[A-Za-z0-9_-]+")) {
|
||||
IO.println("Usage: java ConfigurableMediator.java <mediator-name>");
|
||||
return;
|
||||
}
|
||||
|
||||
var name = arguments[0];
|
||||
var controlMailbox = "mediators/" + name + "/inbox";
|
||||
|
||||
IO.println(name + " waits for ROUTE on " + controlMailbox);
|
||||
|
||||
var route = waitForRoute(controlMailbox);
|
||||
|
||||
IO.println(name + " mediates: " + route.from() + " -> " + route.to());
|
||||
|
||||
FileMessageBoard.watch(route.from(), message -> {
|
||||
try {
|
||||
FileMessageBoard.publish(route.to(), message);
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardWatchEventKinds;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
final class FileMessageBoard {
|
||||
static final Path ROOT = Path.of(
|
||||
System.getProperty("messageBoard.root", "messageBoard")
|
||||
).toAbsolutePath().normalize();
|
||||
|
||||
private FileMessageBoard() {}
|
||||
|
||||
static String serialize(Message message) {
|
||||
var text = message.type() + "\n" + message.value();
|
||||
|
||||
if (message.timestamp() != null)
|
||||
text += "\nTIMESTAMP: " + message.timestamp();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static Message read(Path file) throws IOException {
|
||||
var lines = Files.readString(file).split("\\R", -1);
|
||||
|
||||
if (lines.length < 2)
|
||||
throw new IOException("A message needs a type and a value: " + file);
|
||||
|
||||
var endOfValue = lines.length;
|
||||
Instant timestamp = null;
|
||||
var lastLine = lines[lines.length - 1];
|
||||
|
||||
if (lastLine.startsWith("TIMESTAMP: ")) {
|
||||
timestamp = Instant.parse(lastLine.substring("TIMESTAMP: ".length()));
|
||||
endOfValue--;
|
||||
}
|
||||
|
||||
var value = String.join(
|
||||
"\n",
|
||||
Arrays.copyOfRange(lines, 1, endOfValue)
|
||||
);
|
||||
|
||||
return new Message(lines[0], value, timestamp);
|
||||
}
|
||||
|
||||
static void publish(String mailbox, Message message) throws IOException {
|
||||
publishText(mailbox, serialize(message));
|
||||
}
|
||||
|
||||
static void publishText(String mailbox, String content) throws IOException {
|
||||
var directory = directory(mailbox);
|
||||
Files.createDirectories(directory);
|
||||
|
||||
var id = UUID.randomUUID().toString();
|
||||
var temporary = directory.resolve(id + ".tmp");
|
||||
var published = directory.resolve(id + ".msg");
|
||||
|
||||
Files.writeString(temporary, content);
|
||||
Files.move(temporary, published, StandardCopyOption.ATOMIC_MOVE);
|
||||
}
|
||||
|
||||
static void watch(String mailbox, Consumer<Message> receiver)
|
||||
throws IOException, InterruptedException {
|
||||
var directory = directory(mailbox);
|
||||
Files.createDirectories(directory);
|
||||
|
||||
try (var watcher = FileSystems.getDefault().newWatchService()) {
|
||||
directory.register(watcher, StandardWatchEventKinds.ENTRY_CREATE);
|
||||
|
||||
while (true) {
|
||||
var key = watcher.take();
|
||||
|
||||
for (var event : key.pollEvents()) {
|
||||
if (event.kind() == StandardWatchEventKinds.OVERFLOW)
|
||||
continue;
|
||||
|
||||
var filename = (Path) event.context();
|
||||
|
||||
if (!filename.toString().endsWith(".msg"))
|
||||
continue;
|
||||
|
||||
var file = directory.resolve(filename);
|
||||
|
||||
if (Files.isRegularFile(file))
|
||||
receiver.accept(read(file));
|
||||
}
|
||||
|
||||
if (!key.reset())
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Path directory(String name) {
|
||||
var directory = ROOT.resolve(name).normalize();
|
||||
|
||||
if (!directory.startsWith(ROOT))
|
||||
throw new IllegalArgumentException("Mailbox escapes the message board");
|
||||
|
||||
return directory;
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
void main(String[] arguments) throws Exception {
|
||||
if (arguments.length != 2) {
|
||||
IO.println("Usage: java Mediator.java <from-mailbox> <to-mailbox>");
|
||||
return;
|
||||
}
|
||||
|
||||
var from = arguments[0];
|
||||
var to = arguments[1];
|
||||
|
||||
IO.println("Mediating: " + from + " -> " + to);
|
||||
|
||||
FileMessageBoard.watch(from, message -> {
|
||||
try {
|
||||
FileMessageBoard.publish(to, message);
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
void configure(String mediator, String from, String to) throws Exception {
|
||||
FileMessageBoard.publish(
|
||||
"mediators/" + mediator + "/inbox",
|
||||
new Message("ROUTE", from + "\n" + to)
|
||||
);
|
||||
}
|
||||
|
||||
void main() throws Exception {
|
||||
configure(
|
||||
"mediator1",
|
||||
"consoleIn/outbox",
|
||||
"rot13/inbox"
|
||||
);
|
||||
|
||||
configure(
|
||||
"mediator2",
|
||||
"rot13/outbox",
|
||||
"consoleOut/inbox"
|
||||
);
|
||||
|
||||
IO.println("Configured mediator1 and mediator2");
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import java.time.Instant;
|
||||
|
||||
record Message(String type, String value, Instant timestamp) {
|
||||
Message(String type, String value) {
|
||||
this(type, value, Instant.now());
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
String rot13(String text) {
|
||||
var result = new StringBuilder();
|
||||
|
||||
for (var character : text.toCharArray()) {
|
||||
if (character >= 'a' && character <= 'z')
|
||||
character = (char) ('a' + (character - 'a' + 13) % 26);
|
||||
else if (character >= 'A' && character <= 'Z')
|
||||
character = (char) ('A' + (character - 'A' + 13) % 26);
|
||||
|
||||
result.append(character);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
void main() throws Exception {
|
||||
FileMessageBoard.watch("rot13/inbox", message -> {
|
||||
if (!message.type().equals("TEXT"))
|
||||
return;
|
||||
|
||||
try {
|
||||
FileMessageBoard.publish(
|
||||
"rot13/outbox",
|
||||
new Message("TEXT", rot13(message.value()))
|
||||
);
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
void main() throws Exception {
|
||||
while (true) {
|
||||
var text = IO.readln();
|
||||
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
FileMessageBoard.publish(
|
||||
"consoleIn/outbox",
|
||||
new Message("TEXT", text)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user