From 853ae2a0c13842a7427210029294a47fa2f51f13 Mon Sep 17 00:00:00 2001
From: Stephan Richter <github@keawe.de>
Date: Thu, 11 Feb 2021 16:30:34 +0100
Subject: [PATCH] code optimization

---
 doc/routing/routing.plantuml                  |   8 +-
 pom.xml                                       |   2 +-
 resources/logback.xml                         |  64 ++++---
 .../de/srsoftware/web4rail/BaseClass.java     |   3 +-
 .../de/srsoftware/web4rail/Constants.java     |   2 +-
 .../java/de/srsoftware/web4rail/Route.java    | 162 +++++++++---------
 .../web4rail/actions/ActionList.java          |   2 +-
 .../web4rail/actions/AddDestination.java      |   9 +-
 .../web4rail/moving/Locomotive.java           |   2 +-
 .../de/srsoftware/web4rail/moving/Train.java  |  78 +++++----
 .../de/srsoftware/web4rail/tiles/Contact.java |  19 +-
 .../de/srsoftware/web4rail/tiles/Tile.java    |   2 +-
 12 files changed, 183 insertions(+), 170 deletions(-)

diff --git a/doc/routing/routing.plantuml b/doc/routing/routing.plantuml
index bb762a0..c3d989b 100644
--- a/doc/routing/routing.plantuml
+++ b/doc/routing/routing.plantuml
@@ -14,14 +14,8 @@ FREE --> LOCKED : lockIngoring(ignoredRoute)
 LOCKED --> PREPARED : prepare()
 PREPARED --> STARTED : start(newTrain)
 STARTED --> FREE : reset()
+STARTED -left-> FREE : finish()
 PREPARED --> FREE : reset()
 LOCKED --> FREE : reset()
 
 @enduml
-
-
-@startuml
-start
-:HelloWorld;
-end
-@enduml
diff --git a/pom.xml b/pom.xml
index a01bf95..9d48adc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
 	<modelVersion>4.0.0</modelVersion>
 	<groupId>de.srsoftware</groupId>
 	<artifactId>web4rail</artifactId>
-	<version>1.3.38</version>
+	<version>1.3.39</version>
 	<name>Web4Rail</name>
 	<packaging>jar</packaging>
 	<description>Java Model Railway Control</description>
diff --git a/resources/logback.xml b/resources/logback.xml
index 1c9fa1e..3bd4215 100644
--- a/resources/logback.xml
+++ b/resources/logback.xml
@@ -1,27 +1,39 @@
-<configuration scan="true" scanPeriod="60 seconds" debug="true">
-  <!-- scan="true" enables automatic updates if config file changes, see http://logback.qos.ch/manual/configuration.html -->
-  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
-    <encoder>
-      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n</pattern>
-    </encoder>    
-	 <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-		<level>DEBUG</level>
-	</filter>	
-  </appender>
-  
-  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
-    <append>false</append>
-    <file>Web4Rail.log</file>
-	<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
-		<level>DEBUG</level>
-	</filter>	
-    <encoder>
-      <pattern>%d{HH:mm:ss.SSS} [%15thread] %-5level %logger - %msg%n</pattern>
-    </encoder>
-  </appender>
-  
-  <root>
-    <appender-ref ref="FILE" />
-    <appender-ref ref="STDOUT" />
-  </root>
+<configuration scan="true" scanPeriod="60 seconds"
+	debug="true">
+	<!-- scan="true" enables automatic updates if config file changes, see http://logback.qos.ch/manual/configuration.html -->
+	<appender name="STDOUT"
+		class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n
+			</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>DEBUG</level>
+		</filter>
+	</appender>
+
+	<appender name="FILE" class="ch.qos.logback.core.FileAppender">
+		<append>false</append>
+		<file>Web4Rail.log</file>
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>DEBUG</level>
+		</filter>
+		<encoder>
+			<pattern>%d{HH:mm:ss.SSS} [%15thread] %-5level %logger - %msg%n
+			</pattern>
+		</encoder>
+	</appender>
+
+	<root level="DEBUG">
+		<appender-ref ref="FILE" />
+		<appender-ref ref="STDOUT" />
+	</root>
+	
+	<logger name="de.srsoftware.web4rail" level="INFO" />
+	<logger name="de.srsoftware.web4rail.Application" level="DEBUG" />
+	<logger name="de.srsoftware.web4rail.Route" level="DEBUG" />
+	<logger name="de.srsoftware.web4rail.actions.ActionList" level="DEBUG" />
+	<logger name="de.srsoftware.web4rail.moving.Train" level="DEBUG" />
+	<logger name="de.srsoftware.web4rail.tiles.Contact" level="DEBUG" />
+
 </configuration>
diff --git a/src/main/java/de/srsoftware/web4rail/BaseClass.java b/src/main/java/de/srsoftware/web4rail/BaseClass.java
index 7aff06d..77e61e9 100644
--- a/src/main/java/de/srsoftware/web4rail/BaseClass.java
+++ b/src/main/java/de/srsoftware/web4rail/BaseClass.java
@@ -125,8 +125,9 @@ public abstract class BaseClass implements Constants{
 			return contact;
 		}
 		
-		public void contact(Contact newContact) {
+		public Context contact(Contact newContact) {
 			contact = newContact;
+			return this;
 		}
 
 		
diff --git a/src/main/java/de/srsoftware/web4rail/Constants.java b/src/main/java/de/srsoftware/web4rail/Constants.java
index c78a880..58ba06d 100644
--- a/src/main/java/de/srsoftware/web4rail/Constants.java
+++ b/src/main/java/de/srsoftware/web4rail/Constants.java
@@ -52,7 +52,7 @@ public interface Constants {
 	
 	public static final String ASSIGN = "assign";
 	public static final String  BLOCK               = "block";
-	public static final String  COL                 = ":&nbsp;";
+	public static final String  COL                 = ": ";
 	public static final String  CONTACT             = "contact";
 	public static final String  CONTEXT             = "context";
 	public static final String  DEFAULT_SPEED_UNIT  = "km/h";
diff --git a/src/main/java/de/srsoftware/web4rail/Route.java b/src/main/java/de/srsoftware/web4rail/Route.java
index 80102ec..f168db2 100644
--- a/src/main/java/de/srsoftware/web4rail/Route.java
+++ b/src/main/java/de/srsoftware/web4rail/Route.java
@@ -77,11 +77,6 @@ public class Route extends BaseClass {
 
 	private static final String ROUTE_SETUP = "route_setup";
 
-	public static final String DESTINATION_PREFIX = "@";
-	public static final char TURN_FLAG = '±';
-	public static final char FLAG_SEPARATOR = '+';
-	public static final char SHUNTING_FLAG = '¥';
-	
 	private int startSpeed;
 	private static HashMap<Id, String> names = new HashMap<Id, String>(); // maps id to name. needed to keep names during plan.analyze()
 		
@@ -137,6 +132,7 @@ public class Route extends BaseClass {
 			train.setSpeed(0);
 			LOG.debug("Estimated distance: {}",estimatedDistance);
 
+			if (startSpeed <= endSpeed) return;
 			Integer newTimeStep = timeStep;
 			while (calcDistance(newTimeStep) < estimatedDistance) { // zu schnell gebremst
 				newTimeStep += (1+newTimeStep/8);			
@@ -423,12 +419,11 @@ public class Route extends BaseClass {
 		if (triggeredContacts.contains(contact)) return; // don't trigger contact a second time
 		triggeredContacts.add(contact);
 		LOG.debug("{} on {} activated {}.",train,this,contact);
-		traceTrainFrom(contact);
 		ActionList actions = triggeredActions.get(contact.trigger());
 		LOG.debug("Contact has id {} / trigger {} and is assigned with {}",contact.id(),contact.trigger(),isNull(actions)?t("nothing"):actions);
 		if (isNull(actions)) return;
-		context.contact(contact);
 		actions.fire(context);
+		traceTrainFrom(contact);		
 	}
 
 	public Vector<Contact> contacts() {
@@ -482,6 +477,7 @@ public class Route extends BaseClass {
 	
 	public void finish() {
 		LOG.debug("{}.finish()",this);
+		
 		if (isSet(train)) {
 			if (train.nextRoutePrepared()) {
 				LOG.debug("{} has prepared next route: {}",train,train.nextRoute());
@@ -495,72 +491,27 @@ public class Route extends BaseClass {
 		}
 		brakeProcessor = null;
 		
-		context.clear(); // prevent delayed actions from firing after route has finished
-		setSignals(Signal.RED);
-		for (Tile tile : path) try { // remove route from tiles on path
-			tile.unset(this);
-		} catch (IllegalArgumentException e) {}
+		free();
 		
-		Tile lastTile = path.lastElement();
-		if (lastTile instanceof Contact) {
-			lastTile.setTrain(null);
-			if (isSet(train)) train.removeChild(lastTile);
-		}
-		if (isSet(train)) { 
-			train.set(endBlock);
-			train.heading(endDirection);
-			if (endBlock == train.destination()) {
-				String destTag = null;
-				for (String tag : train.tags()) {
-					if (tag.startsWith(DESTINATION_PREFIX)) {
-						destTag = tag;
-						break;
-					}
-				}
-				train.destination(null);
-				if (isSet(destTag)) {
-					String[] parts = destTag.split("@");
-					String destId = parts[1];
-					boolean turn = false;
-					
-					for (int i=destId.length()-1; i>0; i--) {
-						switch (destId.charAt(i)) {
-							case FLAG_SEPARATOR:
-								destId = destId.substring(0,i);
-								i=0;
-								break;
-							case TURN_FLAG:
-								turn = true; 
-								break;
-						}
-					}
-					if (destId.equals(endBlock.id().toString())) {
-						if (turn) train.turn();
-						train.removeTag(destTag);
-						destTag = destTag.substring(parts[1].length()+1);
-						if (destTag.isEmpty()) { // no further destinations
-							destTag = null;
-						} else train.addTag(destTag);
-					}					
-				}
-				
-				if (isNull(destTag)) {
-					train.quitAutopilot();
-					plan.stream(t("{} reached it`s destination!",train));
-				}
-			} else {
-				train.setWaitTime(endBlock.getWaitTime(train,train.direction()));
-			}
+		if (isSet(train)) {
+			moveTrainToEndBlock();
 			if (train.route() == this) train.route(null);
-			if (startBlock.train() == train && !train.onTrace(startBlock)) startBlock.setTrain(null); // withdraw train from start block only if trace does not go back there
-		}
-		train = null;
+			train = null;
+		}		
+	
+		state = State.FREE;
 	}
 	
-	public boolean free() {
-		LOG.debug("{}.free()",this);
+	/**
+	 * sets all signals of this route to RED,
+	 * frees all tiles occupied by this route
+	 */
+	private void free() {
+		LOG.debug("{}.reset()",this);
+		context.clear(); // prevent delayed actions from firing after route has finished
+
 		setSignals(Signal.RED);
-		for (Tile tile : path) try {
+		for (Tile tile : path) try { // remove route from tiles on path
 			tile.unset(this);
 		} catch (IllegalArgumentException e) {}
 		
@@ -569,16 +520,8 @@ public class Route extends BaseClass {
 			lastTile.setTrain(null);
 			if (isSet(train)) train.removeChild(lastTile);
 		}
-		if (isSet(train)) {
-			train.set(startBlock);
-			train.heading(startDirection);
-			if (train.route() == this) train.route(null);
-			train = null;
-		}
-		state = State.FREE;
-		return true;
 	}
-	
+
 	private String generateName() {
 		StringBuilder sb = new StringBuilder();
 		for (int i=0; i<path.size();i++) {
@@ -816,11 +759,12 @@ public class Route extends BaseClass {
 	}
 		
 	public boolean lock() {
-		LOG.debug("{}.lock({})",this);
+		LOG.debug("{}.lock()",this);
 		return lockIgnoring(null);
 	}
 	
 	public boolean lockIgnoring(Route ignoredRoute) {
+		LOG.debug("{}.lockIgnoring({})",this,ignoredRoute);
 		HashSet<Tile> ignoredPath = new HashSet<Tile>();
 		if (isSet(ignoredRoute)) ignoredPath.addAll(ignoredRoute.path);
 		boolean success = true;
@@ -838,6 +782,51 @@ public class Route extends BaseClass {
 		return success;
 	}
 	
+	private void moveTrainToEndBlock() {
+		if (isNull(train)) return;
+	
+		train.set(endBlock);
+		train.heading(endDirection);
+	
+		if (endBlock == train.destination()) {
+			train.destination(null); // unset old destination
+			String destTag = train.destinationTag();
+			if (isSet(destTag)) {
+				String[] parts = destTag.split(Train.DESTINATION_PREFIX);
+				String destId = parts[1];
+				boolean turn = false;
+				
+				for (int i=destId.length()-1; i>0; i--) {
+					switch (destId.charAt(i)) {
+						case Train.FLAG_SEPARATOR:
+							destId = destId.substring(0,i);
+							i=0;
+							break;
+						case Train.TURN_FLAG:
+							turn = true; 
+							break;
+					}
+				}
+				if (destId.equals(endBlock.id().toString())) { 
+					if (turn) train.turn();
+					
+					// update destination tag: remove and add altered tag:
+					train.removeTag(destTag);
+					destTag = destTag.substring(parts[1].length()+1);
+					if (destTag.isEmpty()) { // no further destinations
+						destTag = null;
+					} else train.addTag(destTag);
+				}					
+			}
+			
+			if (isNull(destTag)) {
+				train.quitAutopilot();
+				plan.stream(t("{} reached it`s destination!",train));
+			}
+		} else train.setWaitTime(endBlock.getWaitTime(train,train.direction()));
+		if (startBlock.train() == train && !train.onTrace(startBlock)) startBlock.setTrain(null); // withdraw train from start block only if trace does not go back there
+	}
+	
 	public List<Route> multiply(int size) {
 		Vector<Route> routes = new Vector<Route>();
 		for (int i=0; i<size; i++) routes.add(i==0 ? this : this.clone());
@@ -866,7 +855,7 @@ public class Route extends BaseClass {
 	}
 	
 	public boolean prepare() {
-		LOG.debug("{}.firesSetupActions({})",this);
+		LOG.debug("{}.prepare()",this);
 		ActionList setupActions = triggeredActions.get(ROUTE_SETUP);
 		if (isSet(setupActions) && !setupActions.fire(context)) return false;
 		state = State.PREPARED;
@@ -939,6 +928,20 @@ public class Route extends BaseClass {
 		super.removeChild(child);
 	}
 	
+	public boolean reset() {
+		LOG.debug("{}.reset()",this);
+		free();
+		
+		if (isSet(train)) {
+			train.set(startBlock);
+			train.heading(startDirection);
+			if (train.route() == this) train.route(null);
+			train = null;
+		}
+		state = State.FREE;
+		return true;
+	}
+	
 	public static void saveAll(String filename) throws IOException {
 		BufferedWriter file = new BufferedWriter(new FileWriter(filename));
 		file.write("{\""+ROUTES+"\":[\n");
@@ -954,6 +957,7 @@ public class Route extends BaseClass {
 	}
 	
 	public Context set(Context newContext) {
+		LOG.debug("{}.set({})",this,newContext);
 		context = newContext;
 		context.route(this);
 		return context;
diff --git a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java
index afc36c7..51c523e 100644
--- a/src/main/java/de/srsoftware/web4rail/actions/ActionList.java
+++ b/src/main/java/de/srsoftware/web4rail/actions/ActionList.java
@@ -263,6 +263,6 @@ public class ActionList extends Action implements Iterable<Action>{
 	
 	@Override
 	public String toString() {
-		return actions.toString();
+		return actions.isEmpty() ? "[no actions]" : actions.toString();
 	}
 }
diff --git a/src/main/java/de/srsoftware/web4rail/actions/AddDestination.java b/src/main/java/de/srsoftware/web4rail/actions/AddDestination.java
index 6cfa0be..f941f33 100644
--- a/src/main/java/de/srsoftware/web4rail/actions/AddDestination.java
+++ b/src/main/java/de/srsoftware/web4rail/actions/AddDestination.java
@@ -10,7 +10,6 @@ import org.json.JSONObject;
 import de.srsoftware.tools.Tag;
 import de.srsoftware.web4rail.Application;
 import de.srsoftware.web4rail.BaseClass;
-import de.srsoftware.web4rail.Route;
 import de.srsoftware.web4rail.moving.Train;
 import de.srsoftware.web4rail.tags.Checkbox;
 import de.srsoftware.web4rail.tags.Fieldset;
@@ -43,11 +42,11 @@ public class AddDestination extends Action {
 			return true;
 		} 
 		String flags = "+";
-		if (turnAtDestination) flags += Route.TURN_FLAG;
-		if (shunting) flags += Route.SHUNTING_FLAG;
-		String dest = Route.DESTINATION_PREFIX+destination.id() + (flags.length()>1 ? flags : "");
+		if (turnAtDestination) flags += Train.TURN_FLAG;
+		if (shunting) flags += Train.SHUNTING_FLAG;
+		String dest = Train.DESTINATION_PREFIX+destination.id() + (flags.length()>1 ? flags : "");
 		for (String tag: train.tags()) {
-			if (tag.startsWith(Route.DESTINATION_PREFIX)) {
+			if (tag.startsWith(Train.DESTINATION_PREFIX)) {
 				train.removeTag(tag);
 				dest = tag+dest;				
 				break;
diff --git a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java
index f0661ba..71e9cc3 100644
--- a/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java
+++ b/src/main/java/de/srsoftware/web4rail/moving/Locomotive.java
@@ -143,7 +143,7 @@ public class Locomotive extends Car implements Constants,Device{
 		par.addTo(fieldset);
 
 		Tag direction = new Tag("p");
-		if ((isSet(train) && train.speed > 0) || (isSet(loco) && loco.speed > 0)) {
+		if ((isSet(train) && (train.speed > 0 || isSet(train.route()))) || (isSet(loco) && loco.speed > 0)) {
 			params.put(ACTION, ACTION_STOP);
 			new Button(t("Stop"),params).clazz(ACTION_STOP).addTo(direction);			
 		}
diff --git a/src/main/java/de/srsoftware/web4rail/moving/Train.java b/src/main/java/de/srsoftware/web4rail/moving/Train.java
index 4d988aa..f65787e 100644
--- a/src/main/java/de/srsoftware/web4rail/moving/Train.java
+++ b/src/main/java/de/srsoftware/web4rail/moving/Train.java
@@ -75,7 +75,11 @@ public class Train extends BaseClass implements Comparable<Train> {
 	public static final String DESTINATION = "destination";
 
 	private static final String ACTION_REVERSE = "reverse";
-	
+	public static final String DESTINATION_PREFIX = "@";
+	public static final char TURN_FLAG = '±';
+	public static final char FLAG_SEPARATOR = '+';
+	public static final char SHUNTING_FLAG = '¥';
+
 	private HashSet<String> tags = new HashSet<String>();
 	private boolean f1,f2,f3,f4;
 
@@ -105,7 +109,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 						if (stop) return;
 						if (isNull(route)) { // may have been set by start action in between
 							Object o = Train.this.start();
-							LOG.debug("{}.start called, route now is {}",this,route);
+							LOG.debug("{}.start called, route now is {}",Train.this,route);
 							if (isSet(route)) {
 								if (o instanceof String) plan.stream((String)o);
 								//if (isSet(destination)) Thread.sleep(1000); // limit load on PathFinder
@@ -162,17 +166,18 @@ public class Train extends BaseClass implements Comparable<Train> {
 			case ACTION_QUIT:
 				return train.quitAutopilot();
 			case ACTION_REVERSE:
-				return train.reverse();
+				return train.reverse().properties();
 			case ACTION_SLOWER10:
 				return train.slower(10);
 			case ACTION_START:
-				return train.start();
+				train.start();
+				return train.properties();
 			case ACTION_STOP:
 				return train.stopNow();
 			case ACTION_TIMES:
 				return train.removeBrakeTimes();
 			case ACTION_TURN:
-				return train.turn();
+				return train.turn().properties();
 			case ACTION_UPDATE:
 				return train.update(params);		 
 		}
@@ -247,7 +252,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 		TreeSet<String> carIds = new TreeSet<String>();
 		cars.stream().map(car -> car.id()+":"+(car.orientation == reversed ? "r":"f")).forEach(carIds::add);		
 		String brakeId = md5sum(carIds);
-		LOG.debug("generated new brake id for {}: {}",this,brakeId);
+		LOG.debug("generated new {} brake id for {}: {}",reversed?"backward":"forward",this,brakeId);
 		return brakeId;
 	}
 	
@@ -395,31 +400,22 @@ public class Train extends BaseClass implements Comparable<Train> {
 	
 	public Block destination() {
 		if (isNull(destination)) {
-			String destId = null;
-			for (String tag : tags) {
-				if (tag.startsWith(Route.DESTINATION_PREFIX)) {
-					destId = tag;
-					break;
-				}
-			}
-			if (isSet(destId)) {
-				String[] parts = destId.split(Route.DESTINATION_PREFIX);
-				destId = parts[1];
-				
-				for (int i=destId.length()-1; i>0; i--) {
-					switch (destId.charAt(i)) {
-						case Route.FLAG_SEPARATOR:
-							destId = destId.substring(0,i);
+			String destTag = destinationTag();
+			if (isSet(destTag)) {
+				for (int i=destTag.length()-1; i>0; i--) {
+					switch (destTag.charAt(i)) {
+						case FLAG_SEPARATOR:
+							destTag = destTag.substring(0,i);
 							i=0;
 							break;
-						case Route.SHUNTING_FLAG:
+						case SHUNTING_FLAG:
 							shunting = true; 
 							break;
 					}
 				}
 
-				BaseClass object = BaseClass.get(new Id(destId));
-				if (object instanceof Block) destination = (Block) object;
+				Block block = BaseClass.get(new Id(destTag));
+				if (isSet(block)) destination = block;
 			}
 		}
 		return destination;
@@ -429,6 +425,17 @@ public class Train extends BaseClass implements Comparable<Train> {
 		destination = dest;
 		return this;
 	}
+	
+	public String destinationTag() {
+		for (String tag : tags()) { // check, if endBlock is in train's destinations
+			if (tag.startsWith(DESTINATION_PREFIX)) {
+				String[] parts = tag.split(DESTINATION_PREFIX);
+				return parts[1];
+			}
+		}
+		return null;
+	}
+
 
 	public String directedName() {
 		String result = name();
@@ -730,7 +737,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 
 	public Object quitAutopilot() {
 		if (isSet(nextRoute)) {
-			nextRoute.free();
+			nextRoute.reset();
 			nextRoute = null;
 		}
 		if (isSet(autopilot)) {
@@ -786,7 +793,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 		error = error || !nextRoute.prepare();
 
 		if (error) {
-			nextRoute.free(); // may unlock tiles belonging to the current route. 
+			nextRoute.reset(); // may unlock tiles belonging to the current route. 
 			route.lock(); // corrects unlocked tiles of nextRoute
 		} else {
 			this.nextRoute = nextRoute;
@@ -799,7 +806,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 	 * before: CabCar→ MiddleCar→ Loco→
 	 * after: ←Loco ←MiddleCar ←CabCar 
 	 */
-	public Tag reverse() {
+	public Train reverse() {
 		LOG.debug("train.reverse();");
 
 		if (isSet(direction)) {
@@ -807,7 +814,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 			reverseTrace();
 		}
 		if (isSet(currentBlock)) plan.place(currentBlock);
-		return properties();
+		return this;
 	}
 	
 	private void reverseTrace() {
@@ -981,7 +988,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 		LOG.debug("{}.start()",this);
 		if (isNull(currentBlock)) return t("{} not in a block",this);
 		if (maxSpeed() == 0) return t("Train has maximum speed of 0 {}, cannot go!",speedUnit);
-		if (isSet(route)) route.free(); // reset route previously chosen
+		if (isSet(route)) route.reset(); // reset route previously chosen
 
 		String error = null;
 		if (isSet(nextRoute)) {
@@ -1003,14 +1010,13 @@ public class Train extends BaseClass implements Comparable<Train> {
 		if (isNull(error) && !route.start(this)) error = t("Was not able to assign {} to {}!",this,route);
 		if (isSet(error)) {
 			LOG.debug("{}.start:error = {}",this,error);
-			route.free();
+			route.reset();
 			route = null;
 			return error;
 		}
 		startSimulation();
-		Window win = properties();
-		new Tag("p").content(t("Started {}",this)).addTo(win);
-		return win;
+		plan.stream(t("Started {}",this));
+		return this;
 	}
 	
 	public static void startAll() {
@@ -1019,7 +1025,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 	}
 	
 	private void startSimulation() {
-		LOG.debug("{}.startSimulation({})",this);
+		LOG.debug("{}.startSimulation()",this);
 		for (Contact contact : route.contacts()) {
 			if (contact.addr() != 0) {
 				LOG.debug("{}.startSimulation aborted!",this);
@@ -1056,7 +1062,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 		quitAutopilot();
 		if (isSet(route)) {
 			route.brakeCancel();
-			route.free();
+			route.reset();
 			route = null;
 		}
 		setSpeed(0);
@@ -1096,7 +1102,7 @@ public class Train extends BaseClass implements Comparable<Train> {
 	 * after: ←CabCar ←MiddleCar ←Loco 
 	 * @return 
 	 */
-	public Tag turn() {
+	public Train turn() {
 		LOG.debug("{}.turn()",this);
 		for (Car car : cars) car.turn();
 		Collections.reverse(cars);
diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java
index 5c992cc..dbc24a1 100644
--- a/src/main/java/de/srsoftware/web4rail/tiles/Contact.java
+++ b/src/main/java/de/srsoftware/web4rail/tiles/Contact.java
@@ -10,6 +10,8 @@ import java.util.TreeMap;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import de.srsoftware.tools.Tag;
 import de.srsoftware.web4rail.Application;
@@ -23,7 +25,7 @@ import de.srsoftware.web4rail.tags.Select;
 import de.srsoftware.web4rail.tags.Window;
 
 public class Contact extends Tile{
-	
+	private static Logger LOG = LoggerFactory.getLogger(Contact.class);
 	private static final String ADDRESS = "address";
 	private static final HashMap<Integer, Contact> contactsByAddr = new HashMap<Integer, Contact>();
 	private boolean state = false;
@@ -81,17 +83,12 @@ public class Contact extends Tile{
 			LOG.debug("{} activated.",this);
 			state = true;
 			if (isSet(timer)) timer.abort();
-			Context context = null;
 			Route route = route();
-			if (isSet(route)) {
-				context = route.context();
-				actions.fire(context);
-				route.contact(this);
-			}
-			if (isNull(context)) {
-				context = new Context(this);
-				actions.fire(context);
-			}
+			Context context = isSet(route) ? route.context().contact(this) : new Context(this);
+			
+			actions.fire(context);
+			if (isSet(route)) route.contact(this);
+			
 			for (Listener listener : listeners) {
 				listener.fired();
 			}
diff --git a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java
index 7530ebd..493fb2b 100644
--- a/src/main/java/de/srsoftware/web4rail/tiles/Tile.java
+++ b/src/main/java/de/srsoftware/web4rail/tiles/Tile.java
@@ -378,7 +378,7 @@ public abstract class Tile extends BaseClass implements Comparable<Tile>{
 	}	
 
 	public Tile setRoute(Route lockingRoute) {
-		Route.LOG.debug("{}.setRoute({})",this,lockingRoute);
+		LOG.debug("{}.setRoute({})",this,lockingRoute);
 		if (isNull(lockingRoute)) throw new NullPointerException();
 		if (isSet(route)) {
 			if (route == lockingRoute) return this; // nothing changed