Browse Source

added javadoc comments to some classes

lookup-tables
Stephan Richter 5 years ago
parent
commit
4441b012fa
  1. 2
      pom.xml
  2. 83
      src/main/java/de/srsoftware/web4rail/Application.java
  3. 63
      src/main/java/de/srsoftware/web4rail/Command.java
  4. 8
      src/main/java/de/srsoftware/web4rail/Constants.java
  5. 76
      src/main/java/de/srsoftware/web4rail/ControlUnit.java
  6. 40
      src/main/java/de/srsoftware/web4rail/Page.java
  7. 271
      src/main/java/de/srsoftware/web4rail/Plan.java

2
pom.xml

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.srsoftware</groupId> <groupId>de.srsoftware</groupId>
<artifactId>web4rail</artifactId> <artifactId>web4rail</artifactId>
<version>0.7.12</version> <version>0.7.13</version>
<name>Web4Rail</name> <name>Web4Rail</name>
<packaging>jar</packaging> <packaging>jar</packaging>
<description>Java Model Railway Control</description> <description>Java Model Railway Control</description>

83
src/main/java/de/srsoftware/web4rail/Application.java

@ -32,10 +32,29 @@ import de.srsoftware.web4rail.moving.Car;
import de.srsoftware.web4rail.moving.Locomotive; import de.srsoftware.web4rail.moving.Locomotive;
import de.srsoftware.web4rail.moving.Train; import de.srsoftware.web4rail.moving.Train;
/**
* Entry point class for the Web4Rail application
*
* @author Stephan Richter, SRSoftware
*
*/
public class Application implements Constants{ public class Application implements Constants{
private static Plan plan; private static Plan plan; // the track layout in use
private static final Logger LOG = LoggerFactory.getLogger(Application.class); private static final Logger LOG = LoggerFactory.getLogger(Application.class);
/**
* entry point for the application:<br/>
* creates a http server, loads a plan and directs a browser to the respective page
* @param args
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Configuration config = new Configuration(Configuration.dir("Web4Rail")+"/app.config"); Configuration config = new Configuration(Configuration.dir("Web4Rail")+"/app.config");
LOG.debug("config: {}",config); LOG.debug("config: {}",config);
@ -55,6 +74,10 @@ public class Application implements Constants{
Desktop.getDesktop().browse(URI.create("http://"+InetAddress.getLocalHost().getHostName()+":"+config.getInt(PORT)+"/plan")); Desktop.getDesktop().browse(URI.create("http://"+InetAddress.getLocalHost().getHostName()+":"+config.getInt(PORT)+"/plan"));
} }
/**
* helper class creating unique ids for use throuout the application
* @return
*/
public static int createId() { public static int createId() {
try { try {
Thread.sleep(1); Thread.sleep(1);
@ -64,6 +87,19 @@ public class Application implements Constants{
return new Date().hashCode(); return new Date().hashCode();
} }
/**
* handles request from clients by delegating them to respective classes
* @param params
* @return
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
*/
private static Object handle(HashMap<String, String> params) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { private static Object handle(HashMap<String, String> params) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
String realm = params.get(REALM); String realm = params.get(REALM);
@ -94,6 +130,11 @@ public class Application implements Constants{
return t("Unknown realm: {}",params.get(REALM)); return t("Unknown realm: {}",params.get(REALM));
} }
/**
* creates a map from url-encoded data
* @param data
* @return
*/
private static HashMap<String, String> inflate(String data) { private static HashMap<String, String> inflate(String data) {
//LOG.debug("inflate({})",data); //LOG.debug("inflate({})",data);
HashMap<String, String> params = new HashMap<String, String>(); HashMap<String, String> params = new HashMap<String, String>();
@ -108,10 +149,21 @@ public class Application implements Constants{
return params; return params;
} }
/**
* creates a map from url-encoded data
* @param data
* @return
*/
private static HashMap<String, String> inflate(byte[] data) { private static HashMap<String, String> inflate(byte[] data) {
return inflate(new String(data,UTF8)); return inflate(new String(data,UTF8));
} }
/**
* sends a response generated from the application to a given client
* @param client
* @param response
* @throws IOException
*/
private static void send(HttpExchange client, Object response) throws IOException { private static void send(HttpExchange client, Object response) throws IOException {
byte[] html; byte[] html;
if (response instanceof Page) { if (response instanceof Page) {
@ -145,6 +197,13 @@ public class Application implements Constants{
os.close(); os.close();
} }
/**
* sends an error to a given client
* @param client
* @param code
* @param msg
* @throws IOException
*/
private static void sendError(HttpExchange client, int code, String msg) throws IOException { private static void sendError(HttpExchange client, int code, String msg) throws IOException {
client.sendResponseHeaders(code, msg.length()); client.sendResponseHeaders(code, msg.length());
LOG.error(msg); LOG.error(msg);
@ -153,6 +212,11 @@ public class Application implements Constants{
out.close(); out.close();
} }
/**
* sends a requested file to the given client
* @param client
* @throws IOException
*/
private static void sendFile(HttpExchange client) throws IOException { private static void sendFile(HttpExchange client) throws IOException {
URI uri = client.getRequestURI(); URI uri = client.getRequestURI();
File file = new File(System.getProperty("user.dir")+"/resources"+uri); File file = new File(System.getProperty("user.dir")+"/resources"+uri);
@ -170,6 +234,11 @@ public class Application implements Constants{
sendError(client,404,t("Could not find \"{}\"",uri)); sendError(client,404,t("Could not find \"{}\"",uri));
} }
/**
* sends a response to a given client
* @param client
* @throws IOException
*/
private static void sendPlan(HttpExchange client) throws IOException { private static void sendPlan(HttpExchange client) throws IOException {
try { try {
HashMap<String, String> params = inflate(client.getRequestBody().readAllBytes()); HashMap<String, String> params = inflate(client.getRequestBody().readAllBytes());
@ -189,6 +258,12 @@ public class Application implements Constants{
send(client,new Page().append(e.getMessage())); send(client,new Page().append(e.getMessage()));
} }
} }
/**
* establishes an event stream connection between the application and a given client
* @param client
* @throws IOException
*/
private static void stream(HttpExchange client) throws IOException { private static void stream(HttpExchange client) throws IOException {
client.getResponseHeaders().set("content-type", "text/event-stream"); client.getResponseHeaders().set("content-type", "text/event-stream");
client.sendResponseHeaders(200, 0); client.sendResponseHeaders(200, 0);
@ -196,6 +271,12 @@ public class Application implements Constants{
plan.addClient(sseWriter); plan.addClient(sseWriter);
} }
/**
* shorthand for Translations.get(text,fills)
* @param text
* @param fills
* @return
*/
private static String t(String text, Object...fills) { private static String t(String text, Object...fills) {
return Translation.get(Application.class, text, fills); return Translation.get(Application.class, text, fills);
} }

63
src/main/java/de/srsoftware/web4rail/Command.java

@ -7,18 +7,31 @@ import java.util.concurrent.TimeoutException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/**
* handles SRCP commands and their replies received from the SRCP daemon
* @author Stephan Richter, SRSoftware
*
*/
public class Command { public class Command {
private static final Logger LOG = LoggerFactory.getLogger(Command.class); private static final Logger LOG = LoggerFactory.getLogger(Command.class);
private String command; private String command;
private Reply reply = null; private Reply reply = null;
/**
* encapsulates a reply received from the SRCP daemon
*
*/
public static class Reply{ public static class Reply{
private long secs; private long secs;
private int milis; private int milis;
private int code; private int code;
private String message; private String message;
/**
* parses a reply from the SRCP daemon
* @param scanner
*/
public Reply(Scanner scanner) { public Reply(Scanner scanner) {
String word = scanner.next(); String word = scanner.next();
secs = Long.parseLong(word.substring(0, word.length()-4)); secs = Long.parseLong(word.substring(0, word.length()-4));
@ -28,6 +41,11 @@ public class Command {
LOG.info("recv {}.{} {} {}.",secs,milis,code,message); LOG.info("recv {}.{} {} {}.",secs,milis,code,message);
} }
/**
* creates a reply with given data
* @param code
* @param message
*/
public Reply(int code, String message) { public Reply(int code, String message) {
secs = new Date().getTime(); secs = new Date().getTime();
milis = (int) (secs % 1000); milis = (int) (secs % 1000);
@ -36,14 +54,25 @@ public class Command {
this.message = message; this.message = message;
} }
/**
* checks if a response has a specific code
* @param code
* @return true if the given code equals the response's code
*/
public boolean is(int code) { public boolean is(int code) {
return code == this.code; return code == this.code;
} }
/**
* @return true, if the response code is between 200 and 300
*/
public boolean succeeded() { public boolean succeeded() {
return (code > 199 && code < 300); return (code > 199 && code < 300);
} }
/**
* @return the message passed along with the response from the SRCP deameon
*/
public String message() { public String message() {
return message; return message;
} }
@ -54,15 +83,27 @@ public class Command {
} }
} }
/**
* encapsulates a command to be send to the SRCP daemon
* @param command
*/
public Command(String command) { public Command(String command) {
this.command = command; this.command = command;
LOG.debug("Created new Command({}).",command); LOG.debug("Created new Command({}).",command);
} }
/**
* called, if the response indicates an error
* @param reply
*/
protected void onFailure(Reply reply) { protected void onFailure(Reply reply) {
LOG.warn("onFailure({})",command); LOG.warn("onFailure({})",command);
} }
/**
* called, when a response from the SRCP daemon is received
* @param reply
*/
public void onResponse(Reply reply) { public void onResponse(Reply reply) {
this.reply = reply; this.reply = reply;
if (reply.succeeded()) { if (reply.succeeded()) {
@ -70,18 +111,36 @@ public class Command {
} else onFailure(reply); } else onFailure(reply);
} }
/**
* called, when the response from the SRCP daemon indicates success of the operation
*/
public void onSuccess(){ public void onSuccess(){
LOG.debug("onSuccess({})",command); LOG.debug("onSuccess({})",command);
} }
/**
* parses the reply from the SRCP daemon
* @param scanner
*/
public void readReplyFrom(Scanner scanner) { public void readReplyFrom(Scanner scanner) {
onResponse(new Reply(scanner)); onResponse(new Reply(scanner));
} }
/**
* waits for the reply from the SRCP daemon for one second
* @return
* @throws TimeoutException
*/
public Reply reply() throws TimeoutException { public Reply reply() throws TimeoutException {
return reply(100); return reply(100);
} }
/**
* waits for the reply from the SRCP daemon for a given timeout
* @param timeout time (in 10ms units) to wait, before a timeout is thrown
* @return
* @throws TimeoutException
*/
public Reply reply(int timeout) throws TimeoutException { public Reply reply(int timeout) throws TimeoutException {
int counter = 0; int counter = 0;
while (reply == null) try { while (reply == null) try {
@ -93,6 +152,10 @@ public class Command {
return reply; return reply;
} }
/**
* generate a timeout exception
* @throws TimeoutException
*/
private void timeout() throws TimeoutException { private void timeout() throws TimeoutException {
String msg = command; String msg = command;
command = null; command = null;

8
src/main/java/de/srsoftware/web4rail/Constants.java

@ -3,6 +3,14 @@ package de.srsoftware.web4rail;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import de.keawe.tools.translations.Translation;
/**
* this interface collects constants inherited to other classes of this application
*
* @author Stephan Richter, SRSoftware
*
*/
public interface Constants { public interface Constants {
public static final String ACTION = "action"; public static final String ACTION = "action";
public static final String ACTION_ADD = "add"; public static final String ACTION_ADD = "add";

76
src/main/java/de/srsoftware/web4rail/ControlUnit.java

@ -23,6 +23,12 @@ import de.srsoftware.web4rail.tags.Form;
import de.srsoftware.web4rail.tags.Input; import de.srsoftware.web4rail.tags.Input;
import de.srsoftware.web4rail.tags.Label; import de.srsoftware.web4rail.tags.Label;
/**
* abstraction of a SRCP daemon (control unit)
*
* @author Stephan Richter, SRSoftware
*
*/
public class ControlUnit extends Thread implements Constants{ public class ControlUnit extends Thread implements Constants{
private static final Logger LOG = LoggerFactory.getLogger(ControlUnit.class); private static final Logger LOG = LoggerFactory.getLogger(ControlUnit.class);
private static final String DEFAULT_HOST = "localhost"; private static final String DEFAULT_HOST = "localhost";
@ -53,6 +59,11 @@ public class ControlUnit extends Thread implements Constants{
return this; return this;
} }
/**
* performs a handshake as specified in the SRCP protocol
* @throws TimeoutException
* @throws IOException
*/
private void handshake() throws TimeoutException, IOException { private void handshake() throws TimeoutException, IOException {
String proto = null; String proto = null;
if (scanner.hasNext()) { if (scanner.hasNext()) {
@ -79,6 +90,9 @@ public class ControlUnit extends Thread implements Constants{
if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply()); if (!command.reply().succeeded()) throw new IOException("Handshake failed: "+command.reply());
} }
/**
* @return json string containing the connection information
*/
private JSONObject json() { private JSONObject json() {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put(HOST, host); json.put(HOST, host);
@ -87,6 +101,11 @@ public class ControlUnit extends Thread implements Constants{
return json; return json;
} }
/**
* load connection information from file
* @param filename
* @throws IOException
*/
public void load(String filename) throws IOException { public void load(String filename) throws IOException {
BufferedReader file = new BufferedReader(new FileReader(filename)); BufferedReader file = new BufferedReader(new FileReader(filename));
JSONObject json = new JSONObject(file.readLine()); JSONObject json = new JSONObject(file.readLine());
@ -96,6 +115,11 @@ public class ControlUnit extends Thread implements Constants{
if (json.has(HOST)) host = json.getString(HOST); if (json.has(HOST)) host = json.getString(HOST);
} }
/**
* test method
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) throws InterruptedException {
ControlUnit cu = new ControlUnit(null).setEndpoint("Modellbahn", DEFAULT_PORT).setBus(1).restart(); ControlUnit cu = new ControlUnit(null).setEndpoint("Modellbahn", DEFAULT_PORT).setBus(1).restart();
Thread.sleep(1000); Thread.sleep(1000);
@ -116,6 +140,11 @@ public class ControlUnit extends Thread implements Constants{
cu.end(); cu.end();
} }
/**
* process actions related to the SRCP daemon
* @param params
* @return
*/
public Object process(HashMap<String, String> params) { public Object process(HashMap<String, String> params) {
switch (params.get(ACTION)) { switch (params.get(ACTION)) {
case ACTION_CONNECT: case ACTION_CONNECT:
@ -134,11 +163,19 @@ public class ControlUnit extends Thread implements Constants{
return t("Unknown action: {}",params.get(ACTION)); return t("Unknown action: {}",params.get(ACTION));
} }
/**
* turn of power immediately
* @return
*/
public Object emergency() { public Object emergency() {
power = true; power = true;
return togglePower(); return togglePower();
} }
/**
* generate a properties view for the client
* @return
*/
public Object properties() { public Object properties() {
Window win = new Window("cu-props", t("Properties of the control unit")); Window win = new Window("cu-props", t("Properties of the control unit"));
Form form = new Form(); Form form = new Form();
@ -155,6 +192,11 @@ public class ControlUnit extends Thread implements Constants{
return win; return win;
} }
/**
* add a command to the queue of commands to be sent to the server
* @param command
* @return
*/
public Command queue(Command command) { public Command queue(Command command) {
queue.add(command); queue.add(command);
return command; return command;
@ -170,6 +212,9 @@ public class ControlUnit extends Thread implements Constants{
return this; return this;
} }
/**
* thread, that repeatedly checks the queue for new commands and sends them to the SRCP daemon
*/
@Override @Override
public void run() { public void run() {
while (!stopped) { while (!stopped) {
@ -191,6 +236,11 @@ public class ControlUnit extends Thread implements Constants{
} }
} }
/**
* save settings to file
* @param filename
* @throws IOException
*/
public void save(String filename) throws IOException { public void save(String filename) throws IOException {
BufferedWriter file = new BufferedWriter(new FileWriter(filename)); BufferedWriter file = new BufferedWriter(new FileWriter(filename));
file.write(json()+"\n"); file.write(json()+"\n");
@ -211,11 +261,22 @@ public class ControlUnit extends Thread implements Constants{
command.readReplyFrom(scanner); command.readReplyFrom(scanner);
} }
/**
* defines the bus on the SRCP deamon, to which commands shall be assigned
* @param bus
* @return
*/
private ControlUnit setBus(int bus) { private ControlUnit setBus(int bus) {
this.bus = bus; this.bus = bus;
return this; return this;
} }
/**
* set up the connection endpoint
* @param newHost
* @param newPort
* @return
*/
public ControlUnit setEndpoint(String newHost, int newPort){ public ControlUnit setEndpoint(String newHost, int newPort){
host = newHost; host = newHost;
port = newPort; port = newPort;
@ -235,10 +296,20 @@ public class ControlUnit extends Thread implements Constants{
super.start(); super.start();
} }
/**
* shorthand for Translation.get(text,fills)
* @param text
* @param fills
* @return
*/
private static String t(String text,Object...fills) { private static String t(String text,Object...fills) {
return Translation.get(Application.class, text, fills); return Translation.get(Application.class, text, fills);
} }
/**
* togge power on/off at the SRCP daemon
* @return
*/
private Command togglePower() { private Command togglePower() {
power = !power; power = !power;
String PW = power?"ON":"OFF"; String PW = power?"ON":"OFF";
@ -259,6 +330,11 @@ public class ControlUnit extends Thread implements Constants{
} }
/**
* update connection parameters
* @param params
* @return
*/
public String update(HashMap<String, String> params) { public String update(HashMap<String, String> params) {
if (params.containsKey(HOST)) host = params.get(HOST); if (params.containsKey(HOST)) host = params.get(HOST);
if (params.containsKey(PORT)) port = Integer.parseInt(params.get(PORT)); if (params.containsKey(PORT)) port = Integer.parseInt(params.get(PORT));

40
src/main/java/de/srsoftware/web4rail/Page.java

@ -4,8 +4,12 @@ import java.util.Vector;
import de.srsoftware.tools.Tag; import de.srsoftware.tools.Tag;
/**
*
* helper class to create html pages
* @author Stephan Richter, SRSoftware
*
*/
public class Page { public class Page {
private StringBuffer buf; private StringBuffer buf;
private Vector<String> cssFiles = new Vector<String>(); private Vector<String> cssFiles = new Vector<String>();
@ -15,9 +19,17 @@ public class Page {
buf = new StringBuffer(); buf = new StringBuffer();
} }
@Override public Page append(Object code) {
public String toString() { buf.append(code);
return head().append(body(buf)).toString(); return this;
}
private StringBuffer body(StringBuffer content) {
return new StringBuffer()
.append("\t<body>\n")
.append(content)
.append("\t</body>\n")
.append("</html>\n");
} }
private StringBuffer head() { private StringBuffer head() {
@ -34,20 +46,12 @@ public class Page {
return sb.append("\t</head>\n"); return sb.append("\t</head>\n");
} }
private StringBuffer body(StringBuffer content) {
return new StringBuffer()
.append("\t<body>\n")
.append(content)
.append("\t</body>\n")
.append("</html>\n");
}
public StringBuffer html() { public StringBuffer html() {
return head().append(body(buf)); return head().append(body(buf));
} }
public Page append(Object code) { public Page js(String jsPath) {
buf.append(code); jsFiles.add(jsPath);
return this; return this;
} }
@ -56,8 +60,8 @@ public class Page {
return this; return this;
} }
public Page js(String jsPath) { @Override
jsFiles.add(jsPath); public String toString() {
return this; return head().append(body(buf)).toString();
} }
} }

271
src/main/java/de/srsoftware/web4rail/Plan.java

@ -58,7 +58,21 @@ import de.srsoftware.web4rail.tiles.TurnoutRN;
import de.srsoftware.web4rail.tiles.TurnoutRS; import de.srsoftware.web4rail.tiles.TurnoutRS;
import de.srsoftware.web4rail.tiles.TurnoutRW; import de.srsoftware.web4rail.tiles.TurnoutRW;
/**
* This class is a central part of the Application, as it loads, holds and saves all kinds of information:
* <ul>
* <li>Tack layout</li>
* <li>Trains and Cars</li>
* <li>Routes</li>
* <li>...</li>
* </ul>
* @author Stephan Richter, SRSoftware
*
*/
public class Plan implements Constants{ public class Plan implements Constants{
/**
* The four directions Trains can be within blocks
*/
public enum Direction{ public enum Direction{
NORTH, SOUTH, EAST, WEST; NORTH, SOUTH, EAST, WEST;
@ -73,6 +87,9 @@ public class Plan implements Constants{
} }
} }
/**
* This thread sends a heartbea to the client
*/
private class Heartbeat extends Thread { private class Heartbeat extends Thread {
@Override @Override
public void run() { public void run() {
@ -96,15 +113,31 @@ public class Plan implements Constants{
private static final HashMap<OutputStreamWriter,Integer> clients = new HashMap<OutputStreamWriter, Integer>(); private static final HashMap<OutputStreamWriter,Integer> clients = new HashMap<OutputStreamWriter, Integer>();
private static final String ACTION_QR = "qrcode"; private static final String ACTION_QR = "qrcode";
public HashMap<String,Tile> tiles = new HashMap<String,Tile>(); public HashMap<String,Tile> tiles = new HashMap<String,Tile>(); // The list of tiles of this plan, i.e. the Track layout
private HashSet<Block> blocks = new HashSet<Block>(); private HashSet<Block> blocks = new HashSet<Block>(); // the list of tiles, that are blocks
private HashMap<Integer, Route> routes = new HashMap<Integer, Route>(); private HashMap<Integer, Route> routes = new HashMap<Integer, Route>(); // the list of routes of the track layout
private ControlUnit controlUnit = new ControlUnit(this); private ControlUnit controlUnit = new ControlUnit(this); // the control unit, to which the plan is connected
/**
* creates a new plan, starts to send heart beats
*/
public Plan() { public Plan() {
new Heartbeat().start(); new Heartbeat().start();
} }
/**
* manages plan-related commands
* @param params the parameters passed from the client
* @return Object returned to the client
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
*/
public Object action(HashMap<String, String> params) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { public Object action(HashMap<String, String> params) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
switch (params.get(ACTION)) { switch (params.get(ACTION)) {
case ACTION_ADD: case ACTION_ADD:
@ -123,6 +156,11 @@ public class Plan implements Constants{
return t("Unknown action: {}",params.get(ACTION)); return t("Unknown action: {}",params.get(ACTION));
} }
/**
* generates the action menu that is appended to the plan
* @return
* @throws IOException
*/
private Tag actionMenu() throws IOException { private Tag actionMenu() throws IOException {
Tag actionMenu = new Tag("div").clazz("actions").content(t("Actions")); Tag actionMenu = new Tag("div").clazz("actions").content(t("Actions"));
Tag actions = new Tag("div").clazz("list").content(""); Tag actions = new Tag("div").clazz("list").content("");
@ -133,17 +171,45 @@ public class Plan implements Constants{
return actions.addTo(actionMenu); return actions.addTo(actionMenu);
} }
/**
* attaches a new client to the event stream of the plan
* @param client
*/
public void addClient(OutputStreamWriter client) { public void addClient(OutputStreamWriter client) {
LOG.debug("Client connected."); LOG.debug("Client connected.");
clients.put(client, 0); clients.put(client, 0);
} }
/**
* helper function: creates a list element with a link that will call the <it>clickTile</it> function of the client side javascript.
* @param tile the tile a click on which shall be simulated
* @param content the text to be displayed to the user
* @param list the tag to which the link tag shall be added
* @return returns the list element itself
* TODO: replace occurences by calls to <it>return request({...});</li>, then remove clickTile from the client javascript
*/
public static Tag addLink(Tile tile,String content,Tag list) { public static Tag addLink(Tile tile,String content,Tag list) {
Tag li = new Tag("li"); Tag li = new Tag("li");
new Tag("span").clazz("link").attr("onclick", "return clickTile("+tile.x+","+tile.y+");").content(content).addTo(li).addTo(list); new Tag("span").clazz("link").attr("onclick", "return clickTile("+tile.x+","+tile.y+");").content(content).addTo(li).addTo(list);
return li; return li;
} }
/**
* add a tile of the specified class to the track layout
* @param clazz
* @param xs
* @param ys
* @param configJson
* @return
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws IOException
*/
private String addTile(String clazz, String xs, String ys, String configJson) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException { private String addTile(String clazz, String xs, String ys, String configJson) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IOException {
int x = Integer.parseInt(xs); int x = Integer.parseInt(xs);
int y = Integer.parseInt(ys); int y = Integer.parseInt(ys);
@ -161,6 +227,10 @@ public class Plan implements Constants{
return t("Added {}",tile.getClass().getSimpleName()); return t("Added {}",tile.getClass().getSimpleName());
} }
/**
* search all possible routes in the plan
* @return a string giving information how many routes have been found
*/
private String analyze() { private String analyze() {
Vector<Route> routes = new Vector<Route>(); Vector<Route> routes = new Vector<Route>();
for (Block block : blocks) { for (Block block : blocks) {
@ -175,19 +245,38 @@ public class Plan implements Constants{
return t("Found {} routes.",routes.size()); return t("Found {} routes.",routes.size());
} }
/**
* @return the list of blocks known to the plan
*/
public Collection<Block> blocks() { public Collection<Block> blocks() {
return blocks; return blocks;
} }
/**
* calls tile.click()
* @param tile
* @return
* @throws IOException
*/
private Object click(Tile tile) throws IOException { private Object click(Tile tile) throws IOException {
if (tile == null) return null; if (tile == null) return null;
return tile.click(); return tile.click();
} }
/**
* @return the control unit currently connected to the plan
*/
public ControlUnit controlUnit() { public ControlUnit controlUnit() {
return controlUnit; return controlUnit;
} }
/**
* completes a given route during a call to {@link #analyze()}.
* It therefore traces where the current part of the route comes from and where it may go.
* @param route an incomplete route, that shall be completed
* @param connector
* @return the set of routes, that result from the tracing operation
*/
private Collection<Route> follow(Route route, Connector connector) { private Collection<Route> follow(Route route, Connector connector) {
Tile tile = get(Tile.id(connector.x,connector.y),false); Tile tile = get(Tile.id(connector.x,connector.y),false);
Vector<Route> results = new Vector<>(); Vector<Route> results = new Vector<>();
@ -209,7 +298,6 @@ public class Plan implements Constants{
} }
Map<Connector, State> connectors = tile.connections(connector.from); Map<Connector, State> connectors = tile.connections(connector.from);
List<Route>routes = route.multiply(connectors.size()); List<Route>routes = route.multiply(connectors.size());
LOG.debug("{}",tile);
if (connectors.size()>1) LOG.debug("SPLITTING @ {}",tile); if (connectors.size()>1) LOG.debug("SPLITTING @ {}",tile);
for (Entry<Connector, State> entry: connectors.entrySet()) { for (Entry<Connector, State> entry: connectors.entrySet()) {
@ -223,6 +311,12 @@ public class Plan implements Constants{
return results; return results;
} }
/**
* returns the tile referenced by the tile id
* @param tileId a combination of the coordinates of the requested tile
* @param resolveShadows if this is set to true, this function will return the overlaying tiles, if the id belongs to a shadow tile.
* @return the tile belonging to the id, or the overlaying tile if the respective tile is a shadow tile.
*/
public Tile get(String tileId,boolean resolveShadows) { public Tile get(String tileId,boolean resolveShadows) {
Tile tile = tiles.get(tileId); Tile tile = tiles.get(tileId);
if (resolveShadows && tile instanceof Shadow) tile = ((Shadow)tile).overlay(); if (resolveShadows && tile instanceof Shadow) tile = ((Shadow)tile).overlay();
@ -230,6 +324,11 @@ public class Plan implements Constants{
} }
/**
* generates the hardware menu attached to the plan
* @return
* @throws IOException
*/
private Tag hardwareMenu() throws IOException { private Tag hardwareMenu() throws IOException {
Tag tileMenu = new Tag("div").clazz("hardware").content(t("Hardware")); Tag tileMenu = new Tag("div").clazz("hardware").content(t("Hardware"));
Tag list = new Tag("div").clazz("list").content(""); Tag list = new Tag("div").clazz("list").content("");
@ -237,14 +336,26 @@ public class Plan implements Constants{
return list.addTo(tileMenu); return list.addTo(tileMenu);
} }
/**
* prepares the hardware div of the plan
* @return
*/
private Tag heartbeat() { private Tag heartbeat() {
return new Div("heartbeat").content(""); return new Div("heartbeat").content("");
} }
/**
* send a heatbeat to the client
*/
public void heatbeat() { public void heatbeat() {
stream("heartbeat @ "+new Date().getTime()); stream("heartbeat @ "+new Date().getTime());
} }
/**
* generates a html document of this plan
* @return
* @throws IOException
*/
public Page html() throws IOException { public Page html() throws IOException {
Page page = new Page().append("<div id=\"plan\">"); Page page = new Page().append("<div id=\"plan\">");
for (Tile tile: tiles.values()) { for (Tile tile: tiles.values()) {
@ -261,6 +372,19 @@ public class Plan implements Constants{
.js("js/plan.js"); .js("js/plan.js");
} }
/**
* loads a track layout from a file, along with its assigned cars, trains, routes and control unit settings
* @param filename
* @return
* @throws IOException
* @throws ClassNotFoundException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
* @throws NoSuchMethodException
* @throws SecurityException
*/
public static Plan load(String filename) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { public static Plan load(String filename) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
Plan plan = new Plan(); Plan plan = new Plan();
try { try {
@ -292,6 +416,11 @@ public class Plan implements Constants{
return plan; return plan;
} }
/**
* creates the main menu attached to the plan
* @return
* @throws IOException
*/
private Tag menu() throws IOException { private Tag menu() throws IOException {
Tag menu = new Tag("div").clazz("menu"); Tag menu = new Tag("div").clazz("menu");
new Tag("div").clazz("emergency").content(t("Emergency")).attr("onclick","return request({realm:'"+REALM_CU+"',action:'"+ACTION_EMERGENCY+"'});").addTo(menu); new Tag("div").clazz("emergency").content(t("Emergency")).attr("onclick","return request({realm:'"+REALM_CU+"',action:'"+ACTION_EMERGENCY+"'});").addTo(menu);
@ -303,10 +432,18 @@ public class Plan implements Constants{
return menu; return menu;
} }
/**
* prepares the messages div of the plan
* @return
*/
private Tag messages() { private Tag messages() {
return new Div("messages").content(""); return new Div("messages").content("");
} }
/**
* creates the move-tile menu of the plan
* @return
*/
private Tag moveMenu() { private Tag moveMenu() {
Tag tileMenu = new Tag("div").clazz("move").title(t("Move tiles")).content(t("↹")); Tag tileMenu = new Tag("div").clazz("move").title(t("Move tiles")).content(t("↹"));
Tag tiles = new Tag("div").clazz("list").content(""); Tag tiles = new Tag("div").clazz("list").content("");
@ -317,6 +454,14 @@ public class Plan implements Constants{
return tiles.addTo(tileMenu); return tiles.addTo(tileMenu);
} }
/**
* processes move-tile instructions sent from the client
* @param direction
* @param tileId
* @return
* @throws NumberFormatException
* @throws IOException
*/
private String moveTile(String direction, String tileId) throws NumberFormatException, IOException { private String moveTile(String direction, String tileId) throws NumberFormatException, IOException {
switch (direction) { switch (direction) {
case "south": case "south":
@ -331,6 +476,13 @@ public class Plan implements Constants{
throw new InvalidParameterException(t("\"{}\" is not a known direction!")); throw new InvalidParameterException(t("\"{}\" is not a known direction!"));
} }
/**
* processes move-tile instructions sent from the client (subroutine)
* @param tile
* @param direction
* @return
* @throws IOException
*/
private String moveTile(Tile tile, Direction direction) throws IOException { private String moveTile(Tile tile, Direction direction) throws IOException {
boolean moved = false; boolean moved = false;
if (tile != null) { if (tile != null) {
@ -353,6 +505,14 @@ public class Plan implements Constants{
return t(moved ? "Tile(s) moved.":"No tile(s) moved."); return t(moved ? "Tile(s) moved.":"No tile(s) moved.");
} }
/**
* processes move-tile instructions sent from the client (subroutine)
* @param tile
* @param xstep
* @param ystep
* @return
* @throws IOException
*/
private boolean moveTile(Tile tile,int xstep,int ystep) throws IOException { private boolean moveTile(Tile tile,int xstep,int ystep) throws IOException {
LOG.error("moveTile({} +{}/+{})",tile,xstep,ystep); LOG.error("moveTile({} +{}/+{})",tile,xstep,ystep);
Stack<Tile> stack = new Stack<Tile>(); Stack<Tile> stack = new Stack<Tile>();
@ -372,29 +532,53 @@ public class Plan implements Constants{
return false; return false;
} }
/**
* adds a new tile to the plan on the client side
* @param tile
* @return
* @throws IOException
*/
public Tile place(Tile tile) throws IOException { public Tile place(Tile tile) throws IOException {
stream("place "+tile.tag(null)); stream("place "+tile.tag(null));
return tile; return tile;
} }
/**
* adds a command to the control unit's command queue
* @param command
* @return
*/
public Command queue(Command command) { public Command queue(Command command) {
return controlUnit.queue(command); return controlUnit.queue(command);
} }
/**
* adds a new route to the plan
* @param route
* @return
*/
Route registerRoute(Route route) { Route registerRoute(Route route) {
for (Tile tile: route.path()) tile.add(route); for (Tile tile: route.path()) tile.add(route);
routes.put(route.id(), route); routes.put(route.id(), route);
return route; return route;
} }
/**
* removes a tile from the track layout
* @param tile
*/
private void remove(Tile tile) { private void remove(Tile tile) {
remove_intern(tile.x,tile.y); removeTile(tile.x,tile.y);
if (tile instanceof Block) blocks.remove(tile); if (tile instanceof Block) blocks.remove(tile);
for (int i=1; i<tile.len(); i++) remove_intern(tile.x+i, tile.y); // remove shadow tiles for (int i=1; i<tile.len(); i++) removeTile(tile.x+i, tile.y); // remove shadow tiles
for (int i=1; i<tile.height(); i++) remove_intern(tile.x, tile.y+i); // remove shadow tiles for (int i=1; i<tile.height(); i++) removeTile(tile.x, tile.y+i); // remove shadow tiles
if (tile != null) stream("remove "+tile.id()); if (tile != null) stream("remove "+tile.id());
} }
/**
* removes a route from the track layout
* @param route
*/
public void remove(Route route) { public void remove(Route route) {
for (Tile tile : route.path()) tile.remove(route); for (Tile tile : route.path()) tile.remove(route);
for (Train train : Train.list()) { for (Train train : Train.list()) {
@ -404,10 +588,20 @@ public class Plan implements Constants{
stream(t("Removed {}.",route)); stream(t("Removed {}.",route));
} }
private void remove_intern(int x, int y) { /**
* removes a tile from the track layout (subroutine)
* @param x
* @param y
*/
private void removeTile(int x, int y) {
LOG.debug("removed {} from tile list",tiles.remove(Tile.id(x, y))); LOG.debug("removed {} from tile list",tiles.remove(Tile.id(x, y)));
} }
/**
* returns a specific route from the list of routes assigned to this plan
* @param routeId the id of the route requestd
* @return
*/
public Route route(int routeId) { public Route route(int routeId) {
return routes.get(routeId); return routes.get(routeId);
} }
@ -425,6 +619,12 @@ public class Plan implements Constants{
return t("Unknown action: {}",params.get(ACTION)); return t("Unknown action: {}",params.get(ACTION));
} }
/**
* saves the plan to a set of files, along with its cars, tiles, trains, routes and control unit settings
* @param name
* @return
* @throws IOException
*/
private String saveTo(String name) throws IOException { private String saveTo(String name) throws IOException {
if (name == null || name.isEmpty()) throw new NullPointerException("Name must not be empty!"); if (name == null || name.isEmpty()) throw new NullPointerException("Name must not be empty!");
Car.saveAll(name+".cars"); Car.saveAll(name+".cars");
@ -435,20 +635,38 @@ public class Plan implements Constants{
return t("Plan saved as \"{}\".",name); return t("Plan saved as \"{}\".",name);
} }
/**
* adds a tile to the plan at a specific position
* @param x
* @param y
* @param tile
* @throws IOException
*/
public void set(int x,int y,Tile tile) throws IOException { public void set(int x,int y,Tile tile) throws IOException {
if (tile == null) return; if (tile == null) return;
if (tile instanceof Block) blocks.add((Block) tile); if (tile instanceof Block) blocks.add((Block) tile);
for (int i=1; i<tile.len(); i++) set(x+i,y,new Shadow(tile)); for (int i=1; i<tile.len(); i++) set(x+i,y,new Shadow(tile));
for (int i=1; i<tile.height(); i++) set(x,y+i,new Shadow(tile)); for (int i=1; i<tile.height(); i++) set(x,y+i,new Shadow(tile));
set_intern(x,y,tile); setIntern(x,y,tile);
place(tile); place(tile);
} }
private void set_intern(int x, int y, Tile tile) { /**
* adds a tile to the plan at a specific position (subroutine)
* @param x
* @param y
* @param tile
*/
private void setIntern(int x, int y, Tile tile) {
tile.position(x, y).plan(this); tile.position(x, y).plan(this);
tiles.put(tile.id(),tile); tiles.put(tile.id(),tile);
} }
/**
* shows the properties of an entity specified in the params.context value
* @param params
* @return
*/
public Window showContext(HashMap<String, String> params) { public Window showContext(HashMap<String, String> params) {
String[] parts = params.get(CONTEXT).split(":"); String[] parts = params.get(CONTEXT).split(":");
String realm = parts[0]; String realm = parts[0];
@ -460,6 +678,10 @@ public class Plan implements Constants{
return null; return null;
} }
/**
* sends some data to the clients
* @param data
*/
public synchronized void stream(String data) { public synchronized void stream(String data) {
data = data.replaceAll("\n", "").replaceAll("\r", ""); data = data.replaceAll("\n", "").replaceAll("\r", "");
//if (!data.startsWith("heartbeat")) LOG.debug("streaming: {}",data); //if (!data.startsWith("heartbeat")) LOG.debug("streaming: {}",data);
@ -488,10 +710,21 @@ public class Plan implements Constants{
} }
} }
/**
* shorthand for Translations.get(message,fills)
* @param message
* @param fills
* @return
*/
private String t(String message, Object...fills) { private String t(String message, Object...fills) {
return Translation.get(Application.class, message, fills); return Translation.get(Application.class, message, fills);
} }
/**
* generates the menu for selecting tiles to be added to the layout
* @return
* @throws IOException
*/
private Tag tileMenu() throws IOException { private Tag tileMenu() throws IOException {
Tag tileMenu = new Tag("div").clazz("addtile").title(t("Add tile")).content("╦"); Tag tileMenu = new Tag("div").clazz("addtile").title(t("Add tile")).content("╦");
@ -529,6 +762,11 @@ public class Plan implements Constants{
return tiles.addTo(tileMenu); return tiles.addTo(tileMenu);
} }
/**
* generates the train menu
* @return
* @throws IOException
*/
private Tag trainMenu() throws IOException { private Tag trainMenu() throws IOException {
Tag tileMenu = new Tag("div").clazz("trains").content(t("Trains")); Tag tileMenu = new Tag("div").clazz("trains").content(t("Trains"));
Tag tiles = new Tag("div").clazz("list").content(""); Tag tiles = new Tag("div").clazz("list").content("");
@ -537,10 +775,21 @@ public class Plan implements Constants{
return tiles.addTo(tileMenu); return tiles.addTo(tileMenu);
} }
/**
* updates a tile
* @param tile
* @param params
* @return
* @throws IOException
*/
private Tile update(Tile tile, HashMap<String, String> params) throws IOException { private Tile update(Tile tile, HashMap<String, String> params) throws IOException {
return tile == null ? null : tile.update(params); return tile == null ? null : tile.update(params);
} }
/**
* sends a Ghost train warning to the client
* @param contact
*/
public void warn(Contact contact) { public void warn(Contact contact) {
stream(t("Warning: {}",t("Ghost train @ {}",contact))); stream(t("Warning: {}",t("Ghost train @ {}",contact)));
} }

Loading…
Cancel
Save