summaryrefslogtreecommitdiff
path: root/chapter18/src/main/java
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2024-12-01 11:42:13 -0800
committerCliff Click <cliffc@acm.org>2025-01-15 15:07:30 -0800
commitfac0c965b11a103a1973153d40347557d9bf68d9 (patch)
treedc020bba02ac1011159904dbd1c7557b24f5fb0f /chapter18/src/main/java
parent158d5f4611944314cd66064fb7fc740d7ad001d3 (diff)
downloadSimple-fac0c965b11a103a1973153d40347557d9bf68d9.tar.gz
Simple-fac0c965b11a103a1973153d40347557d9bf68d9.zip
Start on a graph viz animation of peeps
Diffstat (limited to 'chapter18/src/main/java')
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java50
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/Parser.java2
-rw-r--r--chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java104
4 files changed, 156 insertions, 2 deletions
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java b/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
index de6d65a3..ec326a6f 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/GraphVisualizer.java
@@ -29,7 +29,7 @@ public class GraphVisualizer {
// nodes in the graph.
Collection<Node> all = findAll(stop, scope);
StringBuilder sb = new StringBuilder();
- sb.append("digraph chapter11 {\n");
+ sb.append("digraph chapter18 {\n");
sb.append("/*\n");
sb.append(stop._src);
sb.append("\n*/\n");
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java b/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java
new file mode 100644
index 00000000..da361afa
--- /dev/null
+++ b/chapter18/src/main/java/com/seaofnodes/simple/JSViewer.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple;
+
+import java.nio.file.Paths;
+
+public class JSViewer {
+ public static void main( String[] args ) throws Exception {
+ // Launch server
+ try( SimpleWebSocket server = new SimpleWebSocket(Paths.get("docs/index.html").toUri(),12345) ) {
+ int N=0;
+ while( true ) {
+ String src = server.get();
+ if( src==null || src.equals("null") )
+ break;
+ System.out.println(src);
+ // Client requests another frame
+ if( src.charAt(0)=='+' ) {
+ if( N<2 ) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph jsviewer_1 {\n");
+ sb.append("\tnode [shape=circle, style=filled];\n");
+ sb.append("\tSRC [label=\"").append("next frame").append("\", shape=oval];\n");
+ sb.append("}");
+ server.put(sb.toString());
+ System.out.println("Sent graph "+N);
+ N++;
+ } else {
+ // no more frames, send nothing
+ }
+ } else {
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("digraph jsviewer_0 {\n");
+ sb.append("\tnode [shape=circle, style=filled];\n");
+ sb.append("\tSRC [label=\"").append(src).append("\", shape=oval];\n");
+ sb.append("}");
+ server.put(sb.toString());
+ System.out.println("Sent graph "+N);
+ N++;
+ }
+
+ //// Client reset, send new program
+ //server.put("!");
+ }
+ System.out.println("Client requested shutdown");
+ server.close();
+ }
+
+ }
+
+}
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java
index 1cdf27fb..94575559 100644
--- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java
@@ -246,7 +246,7 @@ public class Parser {
*/
private Node parseFor() {
// { var x=init,y=init,...;
- // while( pred ) {
+ // while( test ) {
// body;
// next;
// }
diff --git a/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java b/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
new file mode 100644
index 00000000..86ae7c9b
--- /dev/null
+++ b/chapter18/src/main/java/com/seaofnodes/simple/SimpleWebSocket.java
@@ -0,0 +1,104 @@
+package com.seaofnodes.simple;
+
+import java.io.*;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+// Why-Oh-Why is this needed?
+
+// Pass in a URI and a port.
+// Opens the client; client has to open a websocket at the given port.
+// Connects, handshakes.
+// Thereafter you can call get/put to swap frames with client.
+
+class SimpleWebSocket extends ServerSocket {
+ final InputStream _in;
+ final OutputStream _out;
+
+ SimpleWebSocket(URI uri, int port) throws IOException, NoSuchAlgorithmException {
+ super(port);
+ // Launch client
+ java.awt.Desktop.getDesktop().browse(uri);
+ // Look for client; get in/out streams
+ Socket sock = accept();
+ _in = sock. getInputStream();
+ _out = sock.getOutputStream();
+
+
+ // Establish web socket with handshake; easier with a buffered reader
+ BufferedReader br = new BufferedReader(new InputStreamReader(_in));
+ String key = null;
+ while( br.ready() ) {
+ String line = br.readLine();
+ if( line.startsWith("Sec-WebSocket-Key: ") )
+ key = line.substring(19);
+ }
+ assert key!=null;
+ // Magic handshake
+ byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
+ + "Connection: Upgrade\r\n"
+ + "Upgrade: websocket\r\n"
+ + "Sec-WebSocket-Accept: "
+ + Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-1").digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes( StandardCharsets.UTF_8 )))
+ + "\r\n\r\n").getBytes( StandardCharsets.UTF_8 );
+ _out.write(response,0,response.length);
+ }
+
+ final byte[] _ins = new byte[1024];
+ final byte[] _str = new byte[1024];
+ final byte[] _key = new byte[4];
+ int _x, _len;
+ private int rawget() { assert _x<_len; return _ins[_x++]&0xFF; }
+
+ // Classic read from browser client. Encoded, so decoded here
+ public String get() throws IOException {
+ _len = _in.read(_ins); _x=0;
+ int op = rawget();
+ if( (op&0x80)!=0x80 ) // FIN bit not set?
+ throw Utils.TODO("Multi-part from client: "+op); // Multi-part message
+ op &= 0x7F; // Strip FIN bit
+ switch( op&15) {
+ case 1: {
+ // Message length
+ int len = (rawget()&0xFF)-128;
+ if( len > 125 )
+ throw Utils.TODO(); // Multi-byte length
+ // Encoding key
+ _key[0] = (byte)rawget();
+ _key[1] = (byte)rawget();
+ _key[2] = (byte)rawget();
+ _key[3] = (byte)rawget();
+ // Decode bytes
+ for( int i=0; i<len; i++ )
+ _str[i] = (byte)(rawget() ^ _key[i & 3]);
+
+ return new String(_str,0,len);
+ }
+ // Client closes
+ case 8: return null;
+ // Not (yet) implemented: continuation frames, ping/pong
+ default:
+ throw Utils.TODO("Unexpected op from client: "+op); // Multi-part message
+ }
+ }
+
+ // Classic put to browser client. No encoding.
+ public void put(String msg) throws IOException {
+ assert msg.length() < 125;
+ _out.write(129); // FIN
+ _out.write(msg.length());
+ for( int i=0; i<msg.length(); i++ )
+ _out.write(msg.charAt(i));
+ }
+
+ @Override public void close() throws IOException {
+ _out.write(0b10001000);
+ _out.flush();
+ super.close();
+ }
+}