├── .gitignore ├── jitpack.yml ├── pom.xml └── src └── main └── java └── io └── github └── evacchi ├── Actor.java ├── App.java ├── TypedActor.java ├── Weather.java ├── asyncchat ├── ChannelActor.java ├── ChatClient.java └── ChatServer.java ├── chat ├── BlockingChat.java ├── ChatClient.java └── ChatServer.java └── typed └── examples ├── HelloWorld.java ├── PingPong.java ├── VendingMachine.java └── VendingMachineAlt.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .classpath 3 | .project 4 | .settings/ 5 | target/ 6 | 7 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - oraclejdk17 3 | before_install: 4 | - curl -Ls https://sh.jbang.dev | bash -s - app setup 5 | install: 6 | - cd src/main/java/io/github/evacchi/ 7 | - ~/.jbang/bin/jbang export mavenrepo --force -O target -Dgroup=$GROUP -Dartifact=$ARTIFACT -Dversion=$VERSION Actor.java 8 | - mkdir -p ~/.m2/repository 9 | - cp -rv target/* ~/.m2/repository/ 10 | 11 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | com.github.evacchi 8 | min-java-actors 9 | 1.0-SNAPSHOT 10 | 11 | min-java-actors 12 | 13 | http://www.example.com 14 | 15 | 16 | UTF-8 17 | 17 18 | 17 19 | 20 | 21 | 22 | 23 | centralhttps://repo1.maven.org/maven2/ 24 | jitpackhttps://jitpack.io 25 | 26 | 27 | 28 | 29 | com.github.evacchi 30 | java-async-channels 31 | main-SNAPSHOT 32 | 33 | 34 | com.fasterxml.jackson.core 35 | jackson-databind 36 | 2.13.0 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | maven-clean-plugin 47 | 3.1.0 48 | 49 | 50 | 51 | maven-resources-plugin 52 | 3.0.2 53 | 54 | 55 | maven-compiler-plugin 56 | 3.8.0 57 | 58 | 59 | maven-surefire-plugin 60 | 2.22.1 61 | 62 | 63 | maven-jar-plugin 64 | 3.0.2 65 | 66 | 67 | maven-install-plugin 68 | 2.5.2 69 | 70 | 71 | maven-deploy-plugin 72 | 2.8.2 73 | 74 | 75 | 76 | maven-site-plugin 77 | 3.7.1 78 | 79 | 80 | maven-project-info-reports-plugin 81 | 3.0.0 82 | 83 | 84 | 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-compiler-plugin 89 | 90 | 17 91 | 17 92 | --enable-preview 93 | 94 | 95 | 96 | org.codehaus.mojo 97 | exec-maven-plugin 98 | 3.0.0 99 | 100 | 101 | server 102 | 103 | java 104 | 105 | 106 | io.github.evacchi.chat.ChatServer 107 | 108 | 109 | 110 | client 111 | 112 | java 113 | 114 | 115 | io.github.evacchi.chat.ChatClient 116 | 117 | 118 | 119 | asyncserver 120 | 121 | java 122 | 123 | 124 | io.github.evacchi.asyncchat.ChatServer 125 | 126 | 127 | 128 | asyncclient 129 | 130 | java 131 | 132 | 133 | io.github.evacchi.asyncchat.ChatClient 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/Actor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Inspired by Viktor Klang's minscalaactors.scala 3 | * https://gist.github.com/viktorklang/2362563 4 | * Copyright 2014 Viktor Klang 5 | * 6 | * Copyright 2021 Edoardo Vacchi 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | //JAVA 17 23 | //JAVAC_OPTIONS --enable-preview --release 17 24 | //JAVA_OPTIONS --enable-preview 25 | 26 | package io.github.evacchi; 27 | 28 | import java.util.concurrent.*; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | import java.util.function.Function; 31 | import static java.lang.System.out; 32 | 33 | public interface Actor { 34 | interface Behavior extends Function {} 35 | interface Effect extends Function {} 36 | interface Address { Address tell(Object msg); } 37 | 38 | static Effect Become(Behavior like) { return old -> like; } 39 | static Effect Stay = old -> old; 40 | static Effect Die = Become(msg -> { out.println("Dropping msg [" + msg + "] due to severe case of death."); return Stay; }); 41 | 42 | record System(ExecutorService executorService) { 43 | public Address actorOf(Function initial) { 44 | abstract class AtomicRunnableAddress implements Address, Runnable 45 | { final AtomicInteger on = new AtomicInteger(0); } 46 | var addr = new AtomicRunnableAddress() { 47 | final ConcurrentLinkedQueue mb = new ConcurrentLinkedQueue<>(); 48 | Behavior behavior = m -> (m instanceof Address self) ? Become(initial.apply(self)) : Stay; 49 | public Address tell(Object msg) { mb.offer(msg); async(); return this; } 50 | public void run() { 51 | try { if (on.get() == 1) { var m = mb.poll(); if (m!=null) { behavior = behavior.apply(m).apply(behavior); } }} 52 | finally { on.set(0); async(); }} 53 | void async() { 54 | if (!mb.isEmpty() && on.compareAndSet(0, 1)) { 55 | try { executorService.execute(this); } 56 | catch (Throwable t) { on.set(0); throw t; }}} 57 | }; 58 | return addr.tell(addr); // Make the actor self aware by seeding its address to the initial behavior 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/App.java: -------------------------------------------------------------------------------- 1 | package io.github.evacchi; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App 8 | { 9 | public static void main( String[] args ) 10 | { 11 | System.out.println( "Hello World!" ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/TypedActor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Inspired by Viktor Klang's minscalaactors.scala 3 | * https://gist.github.com/viktorklang/2362563 4 | * Copyright 2014 Viktor Klang 5 | * 6 | * Copyright 2021 Edoardo Vacchi 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | //JAVA 17 23 | //JAVAC_OPTIONS --enable-preview --release 17 24 | //JAVA_OPTIONS --enable-preview 25 | 26 | package io.github.evacchi; 27 | 28 | import java.util.concurrent.ConcurrentLinkedQueue; 29 | import java.util.concurrent.*; 30 | import java.util.concurrent.atomic.AtomicInteger; 31 | import java.util.function.Function; 32 | import static java.lang.System.out; 33 | 34 | public interface TypedActor { 35 | interface Effect extends Function, Behavior> {} 36 | interface Behavior extends Function> {} 37 | interface Address { Address tell(T msg); } 38 | static Effect Become(Behavior next) { return current -> next; } 39 | static Effect Stay() { return current -> current; } 40 | static Effect Die() { return Become(msg -> { out.println("Dropping msg [" + msg + "] due to severe case of death."); return Stay(); }); } 41 | record System(Executor executor) { 42 | public Address actorOf(Function, Behavior> initial) { 43 | abstract class AtomicRunnableAddress implements Address, Runnable 44 | { AtomicInteger on = new AtomicInteger(0); } 45 | return new AtomicRunnableAddress() { 46 | // Our awesome little mailbox, free of blocking and evil 47 | final ConcurrentLinkedQueue mbox = new ConcurrentLinkedQueue<>(); 48 | Behavior behavior = initial.apply(this); 49 | public Address tell(T msg) { mbox.offer(msg); async(); return this; } // Enqueue the message onto the mailbox and try to schedule for execution 50 | // Switch ourselves off, and then see if we should be rescheduled for execution 51 | public void run() { 52 | try { if (on.get() == 1) { T m = mbox.poll(); if (m != null) behavior = behavior.apply(m).apply(behavior); } 53 | } finally { on.set(0); async(); } 54 | } 55 | // If there's something to process, and we're not already scheduled 56 | void async() { 57 | if (!mbox.isEmpty() && on.compareAndSet(0, 1)) { 58 | // Schedule to run on the Executor and back out on failure 59 | try { executor.execute(this); } catch (Throwable t) { on.set(0); throw t; } 60 | } 61 | } 62 | }; 63 | } 64 | } 65 | 66 | static void main(String... args) { 67 | String choice = args.length >= 1? args[0] : "1"; 68 | switch (Integer.parseInt(choice)) { 69 | case 1, default -> new Demo1().run(); 70 | case 2 -> new Demo2().closure(); 71 | case 3 -> new Demo2().stateful(); 72 | case 4 -> new DemoVending().run(); 73 | } 74 | } 75 | 76 | class Demo1 { 77 | void run() { 78 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 79 | var actor = actorSystem.actorOf(self -> msg -> { 80 | out.println("self: " + self + " got msg " + msg); 81 | return TypedActor.Die(); 82 | }); 83 | actor.tell("foo"); 84 | actor.tell("foo"); 85 | } 86 | } 87 | 88 | class Demo2 { 89 | static record Ping(Address sender) {} 90 | sealed interface TPong { } 91 | static record Pong(Address sender) implements TPong {} 92 | static record DeadlyPong(Address sender) implements TPong {} 93 | void closure() { 94 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 95 | var ponger = actorSystem.actorOf((Address self) -> (Ping msg) -> pongerBehavior(self, msg, 0)); 96 | var pinger = actorSystem.actorOf((Address self) -> (TPong msg) -> pingerBehavior(self, msg)); 97 | ponger.tell(new Ping(pinger)); 98 | } 99 | 100 | void stateful() { 101 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 102 | var ponger = actorSystem.actorOf(StatefulPonger::new); 103 | var pinger = actorSystem.actorOf((Address self) -> (TPong msg) -> pingerBehavior(self, msg)); 104 | ponger.tell(new Ping(pinger)); 105 | } 106 | 107 | Effect pongerBehavior(Address self, Ping msg, int counter) { 108 | return switch (msg) { 109 | case Ping p && counter < 10 -> { 110 | out.println("ping! ➡️"); 111 | p.sender().tell(new Pong(self)); 112 | yield Become(m -> pongerBehavior(self, m, counter + 1)); 113 | } 114 | case Ping p -> { 115 | out.println("ping! ☠️"); 116 | p.sender().tell(new DeadlyPong(self)); 117 | yield Die(); 118 | } 119 | }; 120 | } 121 | Effect pingerBehavior(Address self, TPong msg) { 122 | return switch (msg) { 123 | case Pong p -> { 124 | out.println("pong! ⬅️"); 125 | p.sender().tell(new Ping(self)); 126 | yield Stay(); 127 | } 128 | case DeadlyPong p -> { 129 | out.println("pong! 😵"); 130 | p.sender().tell(new Ping(self)); 131 | yield Die(); 132 | } 133 | }; 134 | } 135 | 136 | static class StatefulPonger implements Behavior { 137 | Address self; int counter = 0; 138 | StatefulPonger(Address self) { this.self = self; } 139 | public Effect apply(Ping msg) { 140 | return switch (msg) { 141 | case Ping p && counter < 10 -> { 142 | out.println("ping! ➡️"); 143 | p.sender().tell(new Pong(self)); 144 | this.counter++; 145 | yield Stay(); 146 | } 147 | case Ping p -> { 148 | out.println("ping! ☠️"); 149 | p.sender().tell(new DeadlyPong(self)); 150 | yield Die(); 151 | } 152 | }; 153 | } 154 | } 155 | } 156 | 157 | class DemoVending { 158 | 159 | sealed interface Vend {} 160 | static record Coin(int amount) implements Vend{ 161 | public Coin { 162 | if (amount < 1 && amount > 100) 163 | throw new AssertionError("1 <= amount < 100"); 164 | } 165 | } 166 | static record Choice(String product) implements Vend{} 167 | 168 | void run() { 169 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 170 | var vendingMachine = actorSystem.actorOf((Address self) -> (Vend msg) -> new DemoVending().initial(msg)); 171 | vendingMachine.tell(new Coin(50)); 172 | vendingMachine.tell(new Coin(40)); 173 | vendingMachine.tell(new Coin(30)); 174 | vendingMachine.tell(new Choice("Chocolate")); 175 | } 176 | 177 | Effect initial(Vend message) { 178 | return switch(message) { 179 | case Coin c -> { 180 | out.println("Received first coin: " + c.amount); 181 | yield Become(m -> waitCoin(m, c.amount())); 182 | } 183 | default -> Stay(); // ignore message, stay in this state 184 | }; 185 | } 186 | Effect waitCoin(Object message, int counter) { 187 | return switch(message) { 188 | case Coin c && counter + c.amount() < 100 -> { 189 | var count = counter + c.amount(); 190 | out.println("Received coin: " + count + " of 100"); 191 | yield Become(m -> waitCoin(m, count)); 192 | } 193 | case Coin c -> { 194 | var count = counter + c.amount(); 195 | out.println("Received last coin: " + count + " of 100"); 196 | var change = counter + c.amount() - 100; 197 | yield Become(m -> vend(m, change)); 198 | } 199 | default -> Stay(); // ignore message, stay in this state 200 | }; 201 | } 202 | Effect vend(Object message, int change) { 203 | return switch(message) { 204 | case Choice c -> { 205 | vendProduct(c.product()); 206 | releaseChange(change); 207 | yield Become(this::initial); 208 | } 209 | default -> Stay(); // ignore message, stay in this state 210 | }; 211 | } 212 | 213 | void vendProduct(String product) { 214 | out.println("VENDING: " + product); 215 | } 216 | 217 | void releaseChange(int change) { 218 | out.println("CHANGE: " + change); 219 | } 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/Weather.java: -------------------------------------------------------------------------------- 1 | //JAVA 17 2 | //JAVAC_OPTIONS --enable-preview --source 17 3 | //JAVA_OPTIONS --enable-preview 4 | //SOURCES Actor.java 5 | package io.github.evacchi; 6 | 7 | import java.lang.System; 8 | import java.net.URI; 9 | import java.net.http.*; 10 | import java.util.concurrent.Executors; 11 | 12 | import static io.github.evacchi.Actor.*; 13 | 14 | public class Weather { 15 | public static void main(String... args) { 16 | var w = new Weather(); 17 | for (var a : args) { w.req(a); } 18 | } 19 | 20 | Actor.System system = new Actor.System(Executors.newCachedThreadPool()); 21 | Address client = system.actorOf(self -> this::httpClient); 22 | 23 | void req(String arg) { 24 | client.tell(arg); 25 | } 26 | private Effect httpClient(Object msg) { 27 | if (msg instanceof String city) { 28 | system.actorOf(self -> this.httpHandler(self, city)); 29 | return Stay; 30 | } else { 31 | System.err.println("Bad argument " + msg); 32 | return Stay; 33 | } 34 | } 35 | private Behavior httpHandler(Address self, String city) { 36 | var client = HttpClient.newHttpClient(); 37 | var request = HttpRequest.newBuilder() 38 | .uri(URI.create("https://wttr.in/" + city + "?format=3")) 39 | .build(); 40 | try { 41 | var resp = client.send(request, HttpResponse.BodyHandlers.ofString()); 42 | self.tell(resp); 43 | } catch (Exception e) { self.tell(e); } 44 | 45 | return msg -> switch (msg) { 46 | case HttpResponse resp -> { System.out.println(resp.body()); yield Die; } 47 | case Exception e -> { e.printStackTrace(); yield Die; } 48 | default -> Die; 49 | }; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/asyncchat/ChannelActor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrea Peruffo 3 | * Copyright 2021 Edoardo Vacchi 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | package io.github.evacchi.asyncchat; 20 | 21 | import io.github.evacchi.channels.Channels; 22 | 23 | import java.nio.charset.StandardCharsets; 24 | 25 | import static io.github.evacchi.Actor.*; 26 | 27 | interface ChannelActor { 28 | record LineRead(String payload) {} 29 | record WriteLine(String payload) {} 30 | record ReadBuffer(String content) {} 31 | 32 | static Behavior socketHandler(Address self, Address parent, Channels.Socket channel) { 33 | return socketHandler(self, parent, channel, ""); 34 | } 35 | private static Behavior socketHandler(Address self, Address clientManager, Channels.Socket channel, String partial) { 36 | channel.read() 37 | .thenAccept(s -> self.tell(new ReadBuffer(s))) 38 | .exceptionally(err -> { err.printStackTrace(); return null; }); 39 | 40 | return msg -> switch (msg) { 41 | case ReadBuffer incoming -> { 42 | var acc = (partial + incoming.content()); 43 | var eol = acc.indexOf('\n'); 44 | if (eol >= 0) { 45 | var line = acc.substring(0, eol); 46 | clientManager.tell(new LineRead(line)); 47 | var rest = incoming.content().substring(Math.min(eol + 2, incoming.content().length())); 48 | yield Become(socketHandler(self, clientManager, channel, rest)); 49 | } else { 50 | var rest = partial + incoming.content(); 51 | yield Become(socketHandler(self, clientManager, channel, rest)); 52 | } 53 | } 54 | case WriteLine line -> { 55 | channel.write((line.payload() + '\n').getBytes(StandardCharsets.UTF_8)); 56 | yield Stay; 57 | } 58 | default -> throw new RuntimeException("Unhandled message " + msg); 59 | }; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/asyncchat/ChatClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrea Peruffo 3 | * Copyright 2021 Edoardo Vacchi 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | //JAVA 17 20 | //JAVAC_OPTIONS --enable-preview --release 17 21 | //JAVA_OPTIONS --enable-preview 22 | //REPOS mavencentral,jitpack=https://jitpack.io/ 23 | //DEPS com.github.evacchi:min-java-actors:main-SNAPSHOT 24 | //DEPS com.github.evacchi:java-async-channels:main-SNAPSHOT 25 | //DEPS com.fasterxml.jackson.core:jackson-databind:2.13.0 26 | //SOURCES ChannelActor.java 27 | 28 | package io.github.evacchi.asyncchat; 29 | 30 | import com.fasterxml.jackson.core.JsonProcessingException; 31 | import com.fasterxml.jackson.databind.ObjectMapper; 32 | import io.github.evacchi.Actor; 33 | import io.github.evacchi.channels.Channels; 34 | 35 | import java.io.IOException; 36 | import java.io.UncheckedIOException; 37 | import java.util.Scanner; 38 | import java.util.concurrent.Executors; 39 | 40 | import static io.github.evacchi.Actor.*; 41 | import static java.lang.System.*; 42 | 43 | public interface ChatClient { 44 | record ClientConnection(Channels.Socket socket) { } 45 | record Message(String user, String text) {} 46 | 47 | Actor.System system = new Actor.System(Executors.newCachedThreadPool()); 48 | String HOST = "localhost"; int PORT = 4444; 49 | 50 | static void main(String[] args) throws IOException { 51 | var userName = args[0]; 52 | 53 | var channel = Channels.Socket.open(); 54 | var client = system.actorOf(self -> clientConnecting(self, channel)); 55 | 56 | out.printf("Login...............%s\n", userName); 57 | 58 | var scann = new Scanner(in); 59 | while (true) { 60 | var line = scann.nextLine(); 61 | if (line != null && !line.isBlank()) { 62 | client.tell(new Message(userName, line)); 63 | } 64 | } 65 | } 66 | 67 | static Actor.Behavior clientConnecting(Address self, Channels.Socket channel) { 68 | channel.connect(HOST, PORT) 69 | .thenAccept(skt -> self.tell(new ClientConnection(skt))) 70 | .exceptionally(err -> { err.printStackTrace(); return null; }); 71 | return msg -> switch (msg) { 72 | case ClientConnection conn -> { 73 | out.printf("Local connection....%s\n", conn.socket().localAddress()); 74 | out.printf("Remote connection...%s\n", conn.socket().remoteAddress()); 75 | var socket = 76 | system.actorOf(ca -> ChannelActor.socketHandler(ca, self, conn.socket())); 77 | yield Become(clientReady(self, socket)); 78 | } 79 | case Message m -> { 80 | err.println("Socket not connected"); 81 | yield Stay; 82 | } 83 | default -> throw new RuntimeException("Unhandled message " + msg); 84 | }; 85 | } 86 | 87 | static Actor.Behavior clientReady(Address self, Address socket) { 88 | var mapper = new ObjectMapper(); 89 | 90 | return msg -> { 91 | try { 92 | switch (msg) { 93 | case Message m -> { 94 | var jsonMsg = mapper.writeValueAsString(m); 95 | socket.tell(new ChannelActor.WriteLine(jsonMsg)); 96 | } 97 | case ChannelActor.LineRead lr -> { 98 | var message = mapper.readValue(lr.payload().trim(), Message.class); 99 | out.printf("%s > %s\n", message.user(), message.text()); 100 | } 101 | default -> throw new RuntimeException("Unhandled message " + msg); 102 | } 103 | return Stay; 104 | } catch(JsonProcessingException e) { throw new UncheckedIOException(e); } 105 | }; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/asyncchat/ChatServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Andrea Peruffo 3 | * Copyright 2021 Edoardo Vacchi 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | * 17 | */ 18 | 19 | //JAVA 17 20 | //JAVAC_OPTIONS --enable-preview --release 17 21 | //JAVA_OPTIONS --enable-preview 22 | //REPOS jitpack=https://jitpack.io/ 23 | //DEPS com.github.evacchi:java-async-channels:main-SNAPSHOT 24 | //DEPS com.github.evacchi:min-java-actors:main-SNAPSHOT 25 | //SOURCES ChannelActor.java 26 | 27 | package io.github.evacchi.asyncchat; 28 | 29 | import io.github.evacchi.Actor; 30 | import io.github.evacchi.channels.Channels; 31 | 32 | import java.io.IOException; 33 | import java.util.ArrayList; 34 | import java.util.concurrent.Executors; 35 | 36 | import static io.github.evacchi.Actor.*; 37 | import static java.lang.System.*; 38 | 39 | public interface ChatServer { 40 | record ClientConnection(Channels.Socket socket) { } 41 | record ClientConnected(Address addr) { } 42 | 43 | Actor.System system = new Actor.System(Executors.newCachedThreadPool()); 44 | String HOST = "localhost"; int PORT = 4444; 45 | 46 | static void main(String... args) throws IOException, InterruptedException { 47 | var serverSocket = Channels.ServerSocket.open(HOST, PORT); 48 | 49 | var clientManager = 50 | system.actorOf(self -> clientManager(self)); 51 | var serverSocketHandler = 52 | system.actorOf(self -> serverSocketHandler(self, clientManager, serverSocket)); 53 | 54 | Thread.currentThread().join(); 55 | } 56 | 57 | static Behavior serverSocketHandler(Address self, Address childrenManager, Channels.ServerSocket serverSocket) { 58 | serverSocket.accept() 59 | .thenAccept(skt -> self.tell(new ClientConnection(skt))) 60 | .exceptionally(exc -> { exc.printStackTrace(); return null; }); 61 | 62 | return msg -> switch (msg) { 63 | case ClientConnection conn -> { 64 | out.printf("Client connected at %s\n", conn.socket().remoteAddress()); 65 | var client = 66 | system.actorOf(ca -> ChannelActor.socketHandler(ca, childrenManager, conn.socket())); 67 | childrenManager.tell(new ClientConnected(client)); 68 | 69 | yield Become(serverSocketHandler(self, childrenManager, serverSocket)); 70 | } 71 | default -> throw new RuntimeException("Unhandled message " + msg); 72 | }; 73 | } 74 | 75 | static Behavior clientManager(Address self) { 76 | var clients = new ArrayList
(); 77 | return msg -> { 78 | switch (msg) { 79 | case ClientConnected cc -> clients.add(cc.addr()); 80 | case ChannelActor.LineRead lr -> 81 | clients.forEach(client -> client.tell(new ChannelActor.WriteLine(lr.payload()))); 82 | default -> throw new RuntimeException("Unhandled message " + msg); 83 | } 84 | return Stay; 85 | }; 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/chat/BlockingChat.java: -------------------------------------------------------------------------------- 1 | package io.github.evacchi.chat; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.PrintWriter; 7 | import java.net.ServerSocket; 8 | import java.net.Socket; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public interface BlockingChat { 13 | class Server { 14 | ServerSocket serverSocket; 15 | List clientHandlers; 16 | Server(int port) throws IOException { 17 | serverSocket = new ServerSocket(port); 18 | clientHandlers = new ArrayList<>(); 19 | } 20 | void start() throws IOException { 21 | while (true) { 22 | // blocks until a new connection is established 23 | var clientSocket = serverSocket.accept(); 24 | // then for each clientSocket... 25 | var handler = new ClientHandler(clientSocket, this); 26 | clientHandlers.add(handler); 27 | /* start handler.read() in a separate thread */ 28 | } 29 | } 30 | // called by clientHandlers that want to propagate 31 | // messages to all the connected clients 32 | void broadcast(String msg) { 33 | for (var handler: clientHandlers) { 34 | handler.write(msg); 35 | } 36 | } 37 | } 38 | class ClientHandler { 39 | Server parent; BufferedReader in; PrintWriter out; 40 | 41 | ClientHandler(Socket clientSocket, Server server) throws IOException { 42 | // get the in/out streams from the socket 43 | in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); 44 | out = new PrintWriter(clientSocket.getOutputStream(), true); 45 | // keep a handle to the server to propagate messages back 46 | parent = server; 47 | } 48 | // on its own thread 49 | void read() throws IOException { 50 | while (in.ready()) { 51 | var line = validate(in.readLine()); 52 | // for each line, broadcast to all other connected clients 53 | parent.broadcast(line); 54 | } 55 | } 56 | 57 | String validate(String readLine) { 58 | // validate here 59 | return readLine; 60 | } 61 | 62 | // called by server.broadcast() 63 | void write(String msg) { 64 | out.println(msg); 65 | } 66 | } 67 | 68 | class Client { 69 | Socket socket; BufferedReader userInput, socketInput; PrintWriter socketOutput; 70 | Client(String host, int port) throws IOException { 71 | this.socket = new Socket(host, port); 72 | this.userInput = new BufferedReader(new InputStreamReader(System.in)); 73 | this.socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream())); 74 | this.socketOutput = new PrintWriter(socket.getOutputStream(), true); 75 | } 76 | // on its own thread 77 | void readUserInput() throws IOException { 78 | while (userInput.ready()) { 79 | var line = parse(userInput.readLine()); 80 | write(line); 81 | } 82 | } 83 | 84 | 85 | // on its own thread 86 | void readServerInput() throws IOException { 87 | while (userInput.ready()) { 88 | var line = serialize(userInput.readLine()); 89 | System.out.println(line); // echo to the user 90 | write(line); 91 | } 92 | } 93 | void write(String line) { socketOutput.println(line); } 94 | 95 | private String serialize(String line) { /* serialize */ return line; } 96 | private String parse(String line) { /* parse line */ return line; } 97 | 98 | } 99 | 100 | 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/chat/ChatClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Edoardo Vacchi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | //JAVA 17 19 | //JAVAC_OPTIONS --enable-preview --release 17 20 | //JAVA_OPTIONS --enable-preview 21 | //REPOS mavencentral,jitpack=https://jitpack.io/ 22 | //DEPS com.github.evacchi:min-java-actors:main-SNAPSHOT 23 | //DEPS com.fasterxml.jackson.core:jackson-databind:2.13.0 24 | 25 | package io.github.evacchi.chat; 26 | 27 | import com.fasterxml.jackson.databind.ObjectMapper; 28 | import io.github.evacchi.Actor; 29 | 30 | import java.io.*; 31 | import java.net.Socket; 32 | import java.util.concurrent.*; 33 | 34 | import static io.github.evacchi.Actor.*; 35 | import static java.lang.System.*; 36 | import static java.util.concurrent.TimeUnit.*; 37 | 38 | public interface ChatClient { 39 | 40 | interface IOLineReader { void read(String line) throws IOException; } 41 | interface IOBehavior { Actor.Effect apply(Object msg) throws IOException; } 42 | static Actor.Behavior IO(IOBehavior behavior) { 43 | return msg -> { 44 | try { return behavior.apply(msg); } 45 | catch (IOException e) { throw new UncheckedIOException(e); } 46 | }; 47 | } 48 | 49 | String host = "localhost"; 50 | int portNumber = 4444; 51 | ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4); 52 | Actor.System sys = new Actor.System(Executors.newCachedThreadPool()); 53 | 54 | static Object Poll = new Object(); 55 | record Message(String user, String text) {} 56 | 57 | static void main(String[] args) throws IOException { 58 | var userName = args[0]; 59 | 60 | var mapper = new ObjectMapper(); 61 | 62 | var socket = new Socket(host, portNumber); 63 | var userInput = new BufferedReader(new InputStreamReader(in)); 64 | var socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream())); 65 | var socketOutput = new PrintWriter(socket.getOutputStream(), true); 66 | 67 | out.printf("Login........%s\n", userName); 68 | out.printf("Local Port...%d\n", socket.getLocalPort()); 69 | out.printf("Server.......%s\n", socket.getRemoteSocketAddress()); 70 | 71 | var serverOut = sys.actorOf(self -> IO(msg -> { 72 | if (msg instanceof Message m) 73 | socketOutput.println(mapper.writeValueAsString(m)); 74 | return Stay; 75 | })); 76 | var userIn = sys.actorOf(self -> readLine(self, 77 | userInput, 78 | line -> serverOut.tell(new Message(userName, line)))); 79 | var serverSocketReader = sys.actorOf(self -> readLine(self, 80 | socketInput, 81 | line -> { 82 | Message message = mapper.readValue(line, Message.class); 83 | out.printf("%s > %s\n", message.user(), message.text()); 84 | })); 85 | } 86 | 87 | static Actor.Behavior readLine(Actor.Address self, BufferedReader in, IOLineReader lineReader) { 88 | // schedule a message to self 89 | scheduler.schedule(() -> self.tell(Poll), 100, MILLISECONDS); 90 | 91 | return IO(msg -> { 92 | // ignore non-Poll messages 93 | if (msg != Poll) return Stay; 94 | if (in.ready()) { 95 | var input = in.readLine(); 96 | lineReader.read(input); 97 | } 98 | 99 | // "stay" in the same state, ensuring that the initializer is re-evaluated 100 | return Become(readLine(self, in, lineReader)); 101 | }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/chat/ChatServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Edoardo Vacchi 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | //JAVA 17 19 | //JAVAC_OPTIONS --enable-preview --release 17 20 | //JAVA_OPTIONS --enable-preview 21 | //REPOS jitpack=https://jitpack.io/ 22 | //DEPS com.github.evacchi:min-java-actors:main-SNAPSHOT 23 | 24 | package io.github.evacchi.chat; 25 | 26 | import io.github.evacchi.Actor; 27 | 28 | import java.io.*; 29 | import java.net.*; 30 | import java.util.ArrayList; 31 | import java.util.concurrent.*; 32 | 33 | import static io.github.evacchi.Actor.*; 34 | import static java.lang.System.*; 35 | import static java.util.concurrent.TimeUnit.*; 36 | 37 | public interface ChatServer { 38 | interface IOBehavior { Actor.Effect apply(Object msg) throws IOException; } 39 | static Actor.Behavior IO(IOBehavior behavior) { 40 | return msg -> { 41 | try { return behavior.apply(msg); } 42 | catch (IOException e) { throw new UncheckedIOException(e); } 43 | }; 44 | } 45 | static Object Poll = new Object(); 46 | record ServerMessage(String payload) {} 47 | record CreateClient(Socket socket) {} 48 | 49 | int portNumber = 4444; 50 | 51 | Actor.System sys = new Actor.System(Executors.newCachedThreadPool()); 52 | Actor.System io = new Actor.System(Executors.newFixedThreadPool(2)); 53 | ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); 54 | 55 | static void main(String... args) throws IOException { 56 | var serverSocket = new ServerSocket(portNumber); 57 | out.printf("Server started at %s.\n", serverSocket.getLocalSocketAddress()); 58 | 59 | var clientManager = 60 | sys.actorOf(self -> clientManager(self)); 61 | var serverSocketHandler = 62 | io.actorOf(self -> serverSocketHandler(self, clientManager, serverSocket)); 63 | } 64 | 65 | static Behavior clientManager(Address self) { 66 | var clients = new ArrayList
(); 67 | return IO(msg -> { 68 | switch (msg) { 69 | // create a connection handler and a message handler for that client 70 | case CreateClient n -> { 71 | var socket = n.socket(); 72 | out.println("accepts : " + socket.getRemoteSocketAddress()); 73 | 74 | var in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 75 | var clientInput = 76 | io.actorOf(me -> clientInput(me, self, in)); 77 | 78 | var writer = new PrintWriter(socket.getOutputStream(), true); 79 | var clientOutput = 80 | sys.actorOf(client -> clientOutput(writer)); 81 | 82 | clients.add(clientOutput); 83 | return Stay; 84 | } 85 | // broadcast the message to all connected clients 86 | case ServerMessage m -> { 87 | clients.forEach(c -> c.tell(m)); 88 | return Stay; 89 | } 90 | // ignore all other messages 91 | default -> { 92 | return Stay; 93 | } 94 | } 95 | }); 96 | } 97 | 98 | static Behavior serverSocketHandler(Address self, Address clientManager, ServerSocket serverSocket) { 99 | scheduler.schedule(() -> self.tell(Poll), 1000, MILLISECONDS); 100 | return IO(msg -> { 101 | if (msg != Poll) return Stay; 102 | var socket = serverSocket.accept(); 103 | clientManager.tell(new CreateClient(socket)); 104 | return Become(serverSocketHandler(self, clientManager, serverSocket)); 105 | }); 106 | } 107 | 108 | static Behavior clientInput(Address self, Address clientManager, BufferedReader in) { 109 | // schedule a message to self 110 | scheduler.schedule(() -> self.tell(Poll), 100, MILLISECONDS); 111 | 112 | return IO(msg -> { 113 | // ignore non-Poll messages 114 | if (msg != Poll) return Stay; 115 | if (in.ready()) { 116 | var m = in.readLine(); 117 | // log message to stdout 118 | out.println(m); 119 | // broadcast to all other clients 120 | clientManager.tell(new ServerMessage(m)); 121 | } 122 | 123 | // "stay" in the same state, ensuring that the initializer is re-evaluated 124 | return Become(clientInput(self, clientManager, in)); 125 | }); 126 | } 127 | 128 | static Behavior clientOutput(PrintWriter writer) { 129 | return msg -> { 130 | if (msg instanceof ServerMessage m) writer.println(m.payload()); 131 | return Stay; 132 | }; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/typed/examples/HelloWorld.java: -------------------------------------------------------------------------------- 1 | //JAVA 17 2 | //JAVAC_OPTIONS --enable-preview --release 17 3 | //JAVA_OPTIONS --enable-preview 4 | //SOURCES ../../TypedActor.java 5 | 6 | package io.github.evacchi.typed.examples; 7 | 8 | import io.github.evacchi.TypedActor; 9 | 10 | import java.util.concurrent.Executors; 11 | 12 | import static io.github.evacchi.TypedActor.*; 13 | import static java.lang.System.out; 14 | 15 | 16 | interface HelloWorld { 17 | static void main(String... args) throws InterruptedException { 18 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 19 | Address actor = actorSystem.actorOf(self -> msg -> { 20 | out.println("self: " + self +"; got msg: '" + msg + "'; length: " + msg.length()); 21 | return TypedActor.Die(); 22 | }); 23 | actor.tell("foo"); 24 | actor.tell("bar"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/typed/examples/PingPong.java: -------------------------------------------------------------------------------- 1 | //JAVA 17 2 | //JAVAC_OPTIONS --enable-preview --release 17 3 | //JAVA_OPTIONS --enable-preview 4 | //SOURCES ../../TypedActor.java 5 | package io.github.evacchi.typed.examples; 6 | 7 | import io.github.evacchi.TypedActor; 8 | 9 | import java.util.concurrent.Executors; 10 | 11 | import static io.github.evacchi.TypedActor.*; 12 | import static java.lang.System.out; 13 | 14 | public interface PingPong { 15 | 16 | sealed interface Pong {} 17 | record SimplePong(Address sender) implements Pong {} 18 | record DeadlyPong(Address sender) implements Pong {} 19 | 20 | record Ping(Address sender) {} 21 | 22 | static void main(String... args) { 23 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 24 | Address ponger = actorSystem.actorOf(self -> msg -> pongerBehavior(self, msg, 0)); 25 | Address pinger = actorSystem.actorOf(self -> msg -> pingerBehavior(self, msg)); 26 | ponger.tell(new Ping(pinger)); 27 | } 28 | static Effect pongerBehavior(Address self, Ping msg, int counter) { 29 | if (counter < 10) { 30 | out.println("ping! 👉"); 31 | msg.sender().tell(new SimplePong(self)); 32 | return Become(m -> pongerBehavior(self, m, counter + 1)); 33 | } else { 34 | out.println("ping! 💀"); 35 | msg.sender().tell(new DeadlyPong(self)); 36 | return Die(); 37 | } 38 | } 39 | static Effect pingerBehavior(Address self, Pong msg) { 40 | return switch (msg) { 41 | case SimplePong p -> { 42 | out.println("pong! 👈"); 43 | p.sender().tell(new Ping(self)); 44 | yield Stay(); 45 | } 46 | case DeadlyPong p -> { 47 | out.println("pong! 😵"); 48 | p.sender().tell(new Ping(self)); 49 | yield Die(); 50 | } 51 | }; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/typed/examples/VendingMachine.java: -------------------------------------------------------------------------------- 1 | //JAVA 17 2 | //JAVAC_OPTIONS --enable-preview --release 17 3 | //JAVA_OPTIONS --enable-preview 4 | //SOURCES ../../TypedActor.java 5 | package io.github.evacchi.typed.examples; 6 | 7 | import io.github.evacchi.TypedActor; 8 | 9 | import java.util.concurrent.Executors; 10 | 11 | import static io.github.evacchi.TypedActor.*; 12 | import static java.lang.System.out; 13 | 14 | interface VendMessage {} 15 | 16 | record Coin(int amount) implements VendMessage { 17 | public Coin { 18 | if (amount < 1 && amount > 100) 19 | throw new AssertionError("1 <= amount < 100"); 20 | } 21 | } 22 | 23 | record Vended(String product) implements VendMessage {} 24 | 25 | public class VendingMachine { 26 | static record Choice(String product) implements VendMessage {} 27 | 28 | TypedActor.System sys = new TypedActor.System(Executors.newCachedThreadPool()); 29 | 30 | Address vendingMachine = sys.actorOf(self -> initial(self)); 31 | Address itemPicker = sys.actorOf(self -> msg -> itemPicker(msg)); 32 | 33 | public static void main(String... args) { 34 | new VendingMachine().vendingMachine 35 | .tell(new Coin(50)) 36 | .tell(new Coin(40)) 37 | .tell(new Coin(30)) 38 | .tell(new Choice("Chocolate")); 39 | } 40 | 41 | Behavior initial(Address self) { 42 | return message -> { 43 | if (message instanceof Coin c) { 44 | out.printf("Received first coin: %d\n", c.amount()); 45 | return Become(waitCoin(self, c.amount())); 46 | } else return Stay(); // ignore message, stay in this state 47 | }; 48 | } 49 | 50 | Behavior waitCoin(Address self, int accumulator) { 51 | out.printf("Budget updated: %d\n", accumulator); 52 | return m -> switch (m) { 53 | case Coin c && accumulator + c.amount() < 100 -> 54 | Become(waitCoin(self, accumulator + c.amount())); 55 | case Coin c -> 56 | Become(vend(self, accumulator + c.amount())); 57 | default -> Stay(); 58 | }; 59 | } 60 | Behavior vend(Address self, int total) { 61 | out.printf("Pick an Item! (Budget: %d)\n", total); 62 | return message -> switch(message) { 63 | case Choice c -> { 64 | itemPicker.tell(c); 65 | releaseChange(total - 100); 66 | yield Stay(); 67 | } 68 | case Vended v -> Become(initial(self)); 69 | default -> Stay(); // ignore message, stay in this state 70 | }; 71 | } 72 | 73 | Effect itemPicker(Choice message) { 74 | vendProduct(message.product()); 75 | vendingMachine.tell(new Vended(message.product())); 76 | return Stay(); 77 | } 78 | 79 | void vendProduct(String product) { 80 | out.printf("VENDING: %s\n", product); 81 | } 82 | 83 | void releaseChange(int change) { 84 | out.printf("CHANGE: %s\n", %d); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/io/github/evacchi/typed/examples/VendingMachineAlt.java: -------------------------------------------------------------------------------- 1 | //JAVA 17 2 | //JAVAC_OPTIONS --enable-preview --release 17 3 | //JAVA_OPTIONS --enable-preview 4 | //SOURCES ../../TypedActor.java 5 | package io.github.evacchi.typed.examples; 6 | 7 | import io.github.evacchi.TypedActor; 8 | 9 | import java.util.concurrent.Executors; 10 | 11 | import static io.github.evacchi.TypedActor.*; 12 | import static java.lang.System.out; 13 | 14 | public interface VendingMachineAlt { 15 | sealed interface Vend {} 16 | static record Coin(int amount) implements Vend { 17 | public Coin { 18 | if (amount < 1 && amount > 100) 19 | throw new AssertionError("1 <= amount < 100"); 20 | } 21 | } 22 | static record Choice(String product) implements Vend {} 23 | 24 | static void main(String... args) { 25 | var actorSystem = new TypedActor.System(Executors.newCachedThreadPool()); 26 | Address vendingMachine = actorSystem.actorOf(VendingMachineAlt::initial); 27 | vendingMachine 28 | .tell(new Coin(50)) 29 | .tell(new Coin(40)) 30 | .tell(new Coin(30)) 31 | .tell(new Choice("Chocolate")); 32 | } 33 | static Behavior initial(Address self) { 34 | return message -> { 35 | if (message instanceof Coin c) { 36 | out.println("Received first coin: " + c.amount); 37 | return Become(waitCoin(self, c.amount())); 38 | } else return Stay(); // ignore message, stay in this state 39 | }; 40 | } 41 | static Behavior waitCoin(Address self, int counter) { 42 | return message -> switch(message) { 43 | case Coin c && counter + c.amount() < 100 -> { 44 | var count = counter + c.amount(); 45 | out.println("Received coin: " + count + " of 100"); 46 | yield Become(waitCoin(self, count)); 47 | } 48 | case Coin c -> { 49 | var count = counter + c.amount(); 50 | out.println("Received last coin: " + count + " of 100"); 51 | var change = counter + c.amount() - 100; 52 | yield Become(vend(self, change)); 53 | } 54 | default -> Stay(); // ignore message, stay in this state 55 | }; 56 | } 57 | static Behavior vend(Address self, int change) { 58 | return message -> switch(message) { 59 | case Choice c -> { 60 | vendProduct(c.product()); 61 | releaseChange(change); 62 | yield Become(initial(self)); 63 | } 64 | default -> Stay(); // ignore message, stay in this state 65 | }; 66 | } 67 | 68 | 69 | static void vendProduct(String product) { 70 | out.println("VENDING: " + product); 71 | } 72 | 73 | static void releaseChange(int change) { 74 | out.println("CHANGE: " + change); 75 | } 76 | 77 | } 78 | --------------------------------------------------------------------------------