From 3dc11c18e123ed317a24da751339a876cac06b5d Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Wed, 7 Oct 2020 00:31:38 +0200 Subject: [PATCH] started to re-implement actions with promises --- pom.xml | 2 +- .../de/srsoftware/web4rail/Application.java | 18 ++++++++++ .../java/de/srsoftware/web4rail/Command.java | 24 +++++++++++++ .../de/srsoftware/web4rail/ControlUnit.java | 27 ++++++++++---- .../java/de/srsoftware/web4rail/Plan.java | 8 +++-- .../java/de/srsoftware/web4rail/Route.java | 36 +++++++++++++++---- .../de/srsoftware/web4rail/moving/Train.java | 29 ++++++++++----- .../de/srsoftware/web4rail/tiles/Tile.java | 7 ++-- .../de/srsoftware/web4rail/tiles/Turnout.java | 29 +++------------ .../srsoftware/web4rail/tiles/Turnout3E.java | 5 ++- .../srsoftware/web4rail/tiles/TurnoutL.java | 30 +++++++++++----- .../srsoftware/web4rail/tiles/TurnoutR.java | 19 ++++++---- 12 files changed, 163 insertions(+), 71 deletions(-) create mode 100644 src/main/java/de/srsoftware/web4rail/Command.java diff --git a/pom.xml b/pom.xml index bb93f28..90e46c1 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 de.srsoftware web4rail - 0.5.6 + 0.6.1 Web4Rail jar Java Model Railway Control diff --git a/src/main/java/de/srsoftware/web4rail/Application.java b/src/main/java/de/srsoftware/web4rail/Application.java index 8221df1..cb7df2e 100644 --- a/src/main/java/de/srsoftware/web4rail/Application.java +++ b/src/main/java/de/srsoftware/web4rail/Application.java @@ -15,6 +15,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.HashMap; +import java.util.concurrent.CompletableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,6 +74,23 @@ public class Application { if (response instanceof Page) { html = ((Page)response).html().toString().getBytes(UTF8); client.getResponseHeaders().add("content-type", "text/html"); + } else if (response instanceof CompletableFuture) { + CompletableFuture promise = (CompletableFuture) response; + promise.thenAccept(object -> { + try { + send(client,object); + } catch (IOException e) { + LOG.warn("Was not able to send {}!",object); + } + }).exceptionally(ex -> { + try { + send(client,ex.getMessage()); + } catch (IOException e) { + LOG.warn("Was not able to send {}!",ex); + } + throw new RuntimeException(ex); + }); + return; } else { html = (response == null ? "" : response.toString()).getBytes(UTF8); client.getResponseHeaders().add("content-type", "text/plain"); diff --git a/src/main/java/de/srsoftware/web4rail/Command.java b/src/main/java/de/srsoftware/web4rail/Command.java new file mode 100644 index 0000000..9cbc943 --- /dev/null +++ b/src/main/java/de/srsoftware/web4rail/Command.java @@ -0,0 +1,24 @@ +package de.srsoftware.web4rail; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Command extends CompletableFuture { + + private static final Logger LOG = LoggerFactory.getLogger(Command.class); + private String command; + + public Command(String command) { + this.command = command; + this.orTimeout(500, TimeUnit.MILLISECONDS); + LOG.debug("Created new Command({}).",command); + } + + @Override + public String toString() { + return command; + } +} diff --git a/src/main/java/de/srsoftware/web4rail/ControlUnit.java b/src/main/java/de/srsoftware/web4rail/ControlUnit.java index be1b787..a67e4d4 100644 --- a/src/main/java/de/srsoftware/web4rail/ControlUnit.java +++ b/src/main/java/de/srsoftware/web4rail/ControlUnit.java @@ -33,7 +33,7 @@ public class ControlUnit extends Thread implements Constants{ private static final String PORT = "port"; private static final String BUS = "bus"; - private class Reply{ + public class Reply{ private long secs; private int milis; private int code; @@ -47,11 +47,19 @@ public class ControlUnit extends Thread implements Constants{ message = scanner.nextLine().trim(); LOG.info("recv {}.{} {} {}.",secs,milis,code,message); } + + public String message() { + return message; + } @Override public String toString() { return "Reply("+secs+"."+milis+" / "+code+" / "+message+")"; } + + public boolean is(int code) { + return code == this.code; + } } @@ -60,7 +68,7 @@ public class ControlUnit extends Thread implements Constants{ private int port = DEFAULT_PORT; private int bus = 0; private boolean stopped = true; - private LinkedList queue = new LinkedList(); + private LinkedList queue = new LinkedList(); private Socket socket; private Scanner scanner; private boolean power = false; @@ -156,8 +164,10 @@ public class ControlUnit extends Thread implements Constants{ return win; } - public void queue(String command) { - queue.add(command); + public Command queue(String command) { + Command promise = new Command(command); + queue.add(promise); + return promise; } /** @@ -176,7 +186,10 @@ public class ControlUnit extends Thread implements Constants{ try { if (queue.isEmpty()) { Thread.sleep(10); - } else send(queue.poll()); + } else { + Command command = queue.pop(); + command.complete(send(command)); + } } catch (InterruptedException | IOException e) { e.printStackTrace(); } @@ -200,9 +213,9 @@ public class ControlUnit extends Thread implements Constants{ * @return * @throws IOException */ - private Reply send(String command) throws IOException { + private Reply send(Command command) throws IOException { if (command == null) return null; - writeln(command); + writeln(command.toString()); return new Reply(scanner); } diff --git a/src/main/java/de/srsoftware/web4rail/Plan.java b/src/main/java/de/srsoftware/web4rail/Plan.java index a1bc0f3..e40e40e 100644 --- a/src/main/java/de/srsoftware/web4rail/Plan.java +++ b/src/main/java/de/srsoftware/web4rail/Plan.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import java.util.Vector; +import java.util.concurrent.CompletableFuture; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -426,8 +427,9 @@ public class Plan implements Constants{ return false; } - public void place(Tile tile) throws IOException { + public Tile place(Tile tile) throws IOException { stream("place "+tile.tag(null)); + return tile; } private Object planAction(HashMap params) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException { @@ -580,7 +582,7 @@ public class Plan implements Constants{ stream(t("Warning: {}",t("Ghost train @ {}",contact))); } - public void queue(String command) { - controlUnit.queue(command); + public CompletableFuture queue(String command) { + return controlUnit.queue(command); } } diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java index b2c2218..80e951e 100644 --- a/src/main/java/de/srsoftware/web4rail/Route.java +++ b/src/main/java/de/srsoftware/web4rail/Route.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; +import java.util.concurrent.CompletableFuture; import org.json.JSONArray; import org.json.JSONException; @@ -21,6 +22,7 @@ import org.slf4j.LoggerFactory; import de.keawe.tools.translations.Translation; import de.srsoftware.tools.Tag; +import de.srsoftware.web4rail.ControlUnit.Reply; import de.srsoftware.web4rail.Plan.Direction; import de.srsoftware.web4rail.actions.Action; import de.srsoftware.web4rail.actions.ActivateRoute; @@ -290,15 +292,35 @@ public class Route implements Constants{ file.close(); } - public Route lock(Train train) throws IOException { - this.train = train; - for (Entry entry : turnouts.entrySet()) { - entry.getKey().state(entry.getValue()); + public CompletableFuture lock(Train train) throws IOException { + Vector locked = new Vector(); + try { + for (Tile tile : path) locked.add(tile.lock(this)); // try to lock all tiles along the path + } catch (Exception e) { // if something fails: unlock all tiles locked so far + for (Tile tile : locked) try { + tile.unlock(); + } catch (IOException ex) { + LOG.warn("Problem while unlocking {}",ex); + } + throw e; } - for (Tile tile : path) tile.lock(this); - return this; + CompletableFuture promise = null; + for (Entry entry : turnouts.entrySet()) {// try to switch all turnouts of this route + CompletableFuture reply = entry.getKey().state(entry.getValue()); // switching a turnout is an asynchronous process, so it returns a CompletableFuture here + promise = promise == null ? reply : promise.thenCombine(reply, (a,b) -> a); + } + promise.exceptionally(ex -> { + for (Tile tile : locked) try { + tile.unlock(); + } catch (IOException e) { + LOG.warn("Problem while unlocking {}",e); + } + throw new RuntimeException(ex); + }).thenRun(() -> this.train = train); + + return promise; } - + public List multiply(int size) { Vector routes = new Vector(); for (int i=0; i start() throws IOException { + if (block == null) return CompletableFuture.failedFuture(new RuntimeException(t("{} not in a block",this))); if (route != null) route.unlock().setSignals(Signal.STOP); HashSet routes = block.routes(); Vector availableRoutes = new Vector(); @@ -351,12 +352,18 @@ public class Train implements Constants { availableRoutes.add(rt); } Random rand = new Random(); - if (availableRoutes.isEmpty()) return t("No free routes from {}",block); - int sel = rand.nextInt(availableRoutes.size()); - route = availableRoutes.get(sel).lock(this).setSignals(null); - if (direction != route.startDirection) turn(); - setSpeed(100); - return t("started {}",this); + if (availableRoutes.isEmpty()) return CompletableFuture.failedFuture(new RuntimeException(t("No free routes from {}",block))); + route = availableRoutes.get(rand.nextInt(availableRoutes.size())); + return route.lock(this).thenApply(reply -> { + try { + route.setSignals(null); + if (direction != route.startDirection) turn(); + setSpeed(100); + return t("started {}",this); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } private Object stop() { @@ -375,7 +382,11 @@ public class Train implements Constants { } private void turn() throws IOException { - if (direction != null) direction = direction.inverse(); + LOG.debug("train.turn()"); + if (direction != null) { + direction = direction.inverse(); + for (Locomotive loco : locos) loco.turn(); + } if (block != null) block.train(this); } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java index 81eee5a..28f9a9e 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java @@ -155,9 +155,10 @@ public abstract class Tile implements Constants{ return this; } - public void lock(Route route) throws IOException { - this.route = route; - plan.place(this); + public Tile lock(Route lockingRoute) throws IOException { + if (route != null && route != lockingRoute) throw new IllegalStateException(this.toString()); + route = lockingRoute; + return plan.place(this); } public Plan plan() { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java index 72d3f2a..8c47050 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Turnout.java @@ -3,10 +3,12 @@ package de.srsoftware.web4rail.tiles; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.json.JSONObject; import de.srsoftware.tools.Tag; +import de.srsoftware.web4rail.ControlUnit.Reply; import de.srsoftware.web4rail.Device; import de.srsoftware.web4rail.Protocol; import de.srsoftware.web4rail.tags.Fieldset; @@ -35,29 +37,8 @@ public abstract class Turnout extends Tile implements Device{ @Override public Object click() throws IOException { LOG.debug("Turnout.click()"); - Object o = super.click(); - if (address != 0 && !initialized) { - String p = null; - switch (protocol) { - case DCC14: - case DCC27: - case DCC28: - case DCC128: - p = "N"; - break; - case MOTO: - p = "M"; - break; - case SELECTRIX: - p = "S"; - break; - default: - p = "P"; - } - plan.queue("INIT {} GA "+address+" "+p); - initialized = true; - } - return o; + init(); + return super.click(); } protected void init() { @@ -119,7 +100,7 @@ public abstract class Turnout extends Tile implements Device{ return state; } - public abstract void state(State newState) throws IOException; + public abstract CompletableFuture state(State newState) throws IOException; @Override public Tag tag(Map replacements) throws IOException { diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Turnout3E.java b/src/main/java/de/srsoftware/web4rail/tiles/Turnout3E.java index e64971d..e0a02fb 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/Turnout3E.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/Turnout3E.java @@ -3,8 +3,10 @@ package de.srsoftware.web4rail.tiles; import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; import de.srsoftware.web4rail.Connector; +import de.srsoftware.web4rail.ControlUnit.Reply; import de.srsoftware.web4rail.Plan.Direction; public class Turnout3E extends Turnout{ @@ -29,8 +31,9 @@ public class Turnout3E extends Turnout{ } @Override - public void state(State newState) throws IOException { + public CompletableFuture state(State newState) throws IOException { // TODO Auto-generated method stub LOG.warn("Turnout3E.state({}) not implemented, yet!",newState); + return null; } } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java index c522425..0e207f2 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutL.java @@ -2,8 +2,10 @@ package de.srsoftware.web4rail.tiles; import java.io.IOException; import java.util.HashMap; +import java.util.concurrent.CompletableFuture; import de.srsoftware.tools.Tag; +import de.srsoftware.web4rail.ControlUnit.Reply; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Label; @@ -18,7 +20,13 @@ public class TurnoutL extends Turnout { Object o = super.click(); if (route != null) { plan.stream(t("{} is locked by {}!",this,route)); - } else state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT); + } else { + CompletableFuture promise = state(state == State.STRAIGHT ? State.LEFT : State.STRAIGHT); + promise.exceptionally(ex -> { + LOG.warn("Failed to toggle turnout: ",ex); + throw new RuntimeException(ex); + }).thenAccept(reply -> LOG.debug("Success: {}",reply)); + } return o; } @@ -45,21 +53,25 @@ public class TurnoutL extends Turnout { } @Override - public void state(State newState) throws IOException { + public CompletableFuture state(State newState) throws IOException { init(); - LOG.debug("Setting {} to {}",this,newState); - int p = 0; + LOG.debug("Requesting to set {} to {}",this,newState); + CompletableFuture result; switch (newState) { case LEFT: - p = portB; + result = plan.queue("SET {} GA "+address+" "+portB+" 1 "+delay); break; case STRAIGHT: - p = portA; + result = plan.queue("SET {} GA "+address+" "+portA+" 1 "+delay); break; default: + throw new IllegalStateException(); } - if (p != 0) plan.queue("SET {} GA "+address+" "+p+" 1 "+delay); - state = newState; - plan.stream("place "+tag(null)); + return result.thenApply(reply -> { + LOG.debug("{} received {}",TurnoutL.this,reply); + if (!reply.is(200)) throw new RuntimeException(reply.message()); + state = newState; + return reply; + }); } } diff --git a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java index bd488b3..4b0d76f 100644 --- a/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java +++ b/src/main/java/de/srsoftware/web4rail/tiles/TurnoutR.java @@ -2,8 +2,10 @@ package de.srsoftware.web4rail.tiles; import java.io.IOException; import java.util.HashMap; +import java.util.concurrent.CompletableFuture; import de.srsoftware.tools.Tag; +import de.srsoftware.web4rail.ControlUnit.Reply; import de.srsoftware.web4rail.tags.Fieldset; import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Label; @@ -45,21 +47,24 @@ public class TurnoutR extends Turnout { } @Override - public void state(State newState) throws IOException { + public CompletableFuture state(State newState) throws IOException { init(); LOG.debug("Setting {} to {}",this,newState); - int p = 0; + CompletableFuture result; switch (newState) { case RIGHT: - p = portB; + result = plan.queue("SET {} GA "+address+" "+portB+" 1 "+delay); break; case STRAIGHT: - p = portA; + result = plan.queue("SET {} GA "+address+" "+portA+" 1 "+delay); break; default: + throw new IllegalStateException(); } - if (p != 0) plan.queue("SET {} GA "+address+" "+p+" 1 "+delay); - state = newState; - plan.stream("place "+tag(null)); + return result.thenApply(reply -> { + LOG.debug("{} received {}",reply); + if (reply.is(200)) state = newState; + return reply; + }); } }