You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
984 lines
31 KiB
984 lines
31 KiB
package de.srsoftware.web4rail; |
|
|
|
import java.io.BufferedWriter; |
|
import java.io.FileInputStream; |
|
import java.io.FileWriter; |
|
import java.io.IOException; |
|
import java.util.HashMap; |
|
import java.util.HashSet; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Map.Entry; |
|
import java.util.Vector; |
|
|
|
import org.json.JSONArray; |
|
import org.json.JSONObject; |
|
import org.json.JSONTokener; |
|
import org.slf4j.Logger; |
|
import org.slf4j.LoggerFactory; |
|
|
|
import de.srsoftware.tools.Tag; |
|
import de.srsoftware.web4rail.Plan.Direction; |
|
import de.srsoftware.web4rail.actions.Action; |
|
import de.srsoftware.web4rail.actions.ActionList; |
|
import de.srsoftware.web4rail.actions.BrakeStart; |
|
import de.srsoftware.web4rail.actions.FinishRoute; |
|
import de.srsoftware.web4rail.actions.PreserveRoute; |
|
import de.srsoftware.web4rail.actions.SetSignal; |
|
import de.srsoftware.web4rail.actions.SetSpeed; |
|
import de.srsoftware.web4rail.actions.SetTurnout; |
|
import de.srsoftware.web4rail.conditions.Condition; |
|
import de.srsoftware.web4rail.conditions.ConditionList; |
|
import de.srsoftware.web4rail.moving.Train; |
|
import de.srsoftware.web4rail.tags.Checkbox; |
|
import de.srsoftware.web4rail.tags.Fieldset; |
|
import de.srsoftware.web4rail.tags.Input; |
|
import de.srsoftware.web4rail.tags.Table; |
|
import de.srsoftware.web4rail.tags.Window; |
|
import de.srsoftware.web4rail.threads.RoutePrepper; |
|
import de.srsoftware.web4rail.threads.Simulator; |
|
import de.srsoftware.web4rail.tiles.Block; |
|
import de.srsoftware.web4rail.tiles.BlockContact; |
|
import de.srsoftware.web4rail.tiles.Contact; |
|
import de.srsoftware.web4rail.tiles.Shadow; |
|
import de.srsoftware.web4rail.tiles.Signal; |
|
import de.srsoftware.web4rail.tiles.Tile; |
|
import de.srsoftware.web4rail.tiles.Turnout; |
|
/** |
|
* A route is a vector of tiles that leads from one block to another. |
|
* |
|
* @author Stephan Richter, SRSoftware 2020-2021 |
|
* |
|
*/ |
|
public class Route extends BaseClass { |
|
|
|
/* public enum State { |
|
FREE, LOCKED, PREPARED, STARTED; |
|
}*/ |
|
public static final Logger LOG = LoggerFactory.getLogger(Route.class); |
|
|
|
private static final String ACTIONS = "actions"; |
|
private static final String BRAKE_TIMES = "brake_times"; |
|
private static final String CONDITION_LIST = "condition_list"; |
|
private static final String END_DIRECTION = "direction_end"; |
|
private static final String ROUTES = "routes"; |
|
private static final String SETUP_ACTIONS = "setup_actions"; |
|
private static final String START_ACTIONS = "start_actions"; |
|
private static final String START_DIRECTION = "direction_start"; |
|
static final String NAME = "name"; |
|
static final String PATH = "path"; |
|
static final String SIGNALS = "signals"; |
|
static final String TURNOUTS = "turnouts"; |
|
//private State state = State.FREE; |
|
public static boolean freeBehindTrain = true; |
|
|
|
private static final String ROUTE_START = "route_start"; |
|
|
|
private static final String ROUTE_SETUP = "route_setup"; |
|
|
|
private static final String MIN_START_DELAY = "min_start_delay"; |
|
|
|
private static final String MAX_START_DELAY = "max_start_delay"; |
|
|
|
private static final String STARt_DELAY = "start_delay"; |
|
|
|
private static HashMap<Id, String> names = new HashMap<Id, String>(); // maps id to name. needed to keep names during plan.analyze() |
|
|
|
private HashMap<String,Integer> brakeTimes = new HashMap<String, Integer>(); |
|
private ConditionList conditions; |
|
private Vector<Contact> contacts; |
|
private Context context; |
|
private boolean disabled = false; |
|
private Block endBlock = null; |
|
public Direction endDirection; |
|
private Route nextPreparedRoute = null; |
|
private RoutePrepper nextRoutePrepper = null; |
|
private Vector<Tile> path; |
|
private Vector<Signal> signals; |
|
private HashMap<String,ActionList> triggeredActions = new HashMap<String, ActionList>(); |
|
private HashMap<Turnout,Turnout.State> turnouts; |
|
private Block startBlock = null; |
|
private Range startDelay = null; |
|
public Direction startDirection; |
|
private HashSet<Contact> triggeredContacts = new HashSet<>(); |
|
|
|
|
|
public Route() { |
|
conditions = new ConditionList(); |
|
conditions.parent(this); |
|
} |
|
|
|
/** |
|
* process commands from the client |
|
* @param params |
|
* @return |
|
* @throws IOException |
|
*/ |
|
public static Object action(HashMap<String, String> params) throws IOException { |
|
Route route = BaseClass.get(Id.from(params)); |
|
String action = params.get(ACTION); |
|
if (isNull(route) && !ACTION_AUTO.equals(action)) return t("Unknown route: {}",params.get(ID)); |
|
switch (params.get(ACTION)) { |
|
case ACTION_AUTO: |
|
if (isSet(route)) return route.simplyfyName().properties(); |
|
for (Route rt : BaseClass.listElements(Route.class)) rt.simplyfyName(); |
|
return plan.properties(new HashMap<String, String>()); |
|
case ACTION_DROP: |
|
route.remove(); |
|
plan.stream(t("Removed {}.",route)); |
|
return plan.properties(new HashMap<String,String>()); |
|
case ACTION_PROPS: |
|
return route.properties(); |
|
case ACTION_UPDATE: |
|
return route.update(params,plan); |
|
} |
|
return t("Unknown action: {}",params.get(ACTION)); |
|
} |
|
|
|
/** |
|
* adds a tile to the route |
|
* @param tile |
|
* @param direrction |
|
* @return |
|
*/ |
|
public Tile add(Tile tile, Direction direrction) { |
|
if (tile instanceof Shadow) tile = ((Shadow)tile).overlay(); |
|
if (tile instanceof Block) { |
|
endBlock = (Block) tile; |
|
endDirection = direrction; |
|
contacts.addAll(endBlock.contacts()); |
|
} |
|
path.add(tile); |
|
if (tile instanceof Contact) contacts.add((Contact) tile); |
|
if (tile instanceof Signal) { |
|
Signal signal = (Signal) tile; |
|
if (signal.isAffectedFrom(direrction)) addSignal(signal); |
|
} |
|
|
|
return tile; |
|
} |
|
|
|
/** |
|
* adds a action to the action list of the given trigger |
|
* @param trigger |
|
* @param action |
|
*/ |
|
public void add(String trigger, Action action) { |
|
ActionList actions = triggeredActions.get(trigger); |
|
if (isNull(actions)) { |
|
actions = new ActionList(this); |
|
triggeredActions.put(trigger, actions); |
|
} |
|
actions.add(action); |
|
} |
|
|
|
public void add(Condition condition) { |
|
conditions.add(condition); |
|
} |
|
|
|
public void addPropertiesFrom(Route existingRoute) { |
|
LOG.debug("addPropertiesFrom({})",existingRoute); |
|
disabled = existingRoute.disabled; |
|
|
|
for (Condition condition : existingRoute.conditions) { // bestehende Bedingungen der neuen zuweisen |
|
condition.parent(this); |
|
conditions.add(condition); |
|
} |
|
conditions.forEach(condition -> existingRoute.conditions.removeChild(condition)); |
|
for (Entry<String, ActionList> entry : triggeredActions.entrySet()) { |
|
String trigger = entry.getKey(); |
|
ActionList existingActionList = existingRoute.triggeredActions.get(trigger); |
|
if (isSet(existingActionList)) { |
|
LOG.debug("found action list for {} on existing route {}: {}",trigger,existingRoute,existingActionList); |
|
existingActionList.forEach(action -> LOG.debug("OLD Action: {}",action)); |
|
entry.getValue().merge(existingActionList); |
|
} |
|
} |
|
brakeTimes = new HashMap<String, Integer>(existingRoute.brakeTimes); |
|
} |
|
|
|
void addSignal(Signal signal) { |
|
signals.add(signal); |
|
} |
|
|
|
void addTurnout(Turnout t, Turnout.State s) { |
|
turnouts.put(t, s); |
|
} |
|
|
|
/** |
|
* checks, whether the route may be used in a given context |
|
* @param context |
|
* @return false, if any of the associated conditions is not fulfilled |
|
*/ |
|
public boolean allowed(Context context) { |
|
if (disabled) return false; |
|
return conditions.fulfilledBy(context); |
|
} |
|
|
|
private Fieldset basicProperties() { |
|
Fieldset fieldset = new Fieldset(t("Route properties")); |
|
|
|
// if (isSet(train)) train.link("span",t("Train")+": "+train).addTo(fieldset); |
|
Tag list = new Tag("ul"); |
|
startBlock.link("li",t("Origin: {} to {}",startBlock.name,startDirection)).addTo(list); |
|
endBlock.link("li",t("Destination: {} from {}",endBlock.name,endDirection.inverse())).addTo(list); |
|
list.addTo(fieldset); |
|
|
|
if (!signals.isEmpty()) { |
|
new Tag("h4").content(t("Signals")).addTo(fieldset); |
|
list = new Tag("ul"); |
|
for (Signal s : signals) Plan.addLink(s,s.toString(),list); |
|
list.addTo(fieldset); |
|
} |
|
|
|
this.button(t("Test"),Map.of(ACTION,ACTION_START)).addTo(fieldset); |
|
return fieldset; |
|
} |
|
|
|
public Integer brakeTime(String brakeId) { |
|
return brakeTimes.get(brakeId); |
|
} |
|
|
|
public void brakeTime(String brakeId, Integer newTimeStep) { |
|
LOG.debug("new brake time for route {}: {}",this,newTimeStep); |
|
brakeTimes.put(brakeId,newTimeStep); |
|
} |
|
|
|
private Fieldset brakeTimes() { |
|
Fieldset fieldset = new Fieldset(t("Brake time table")); |
|
Table table = new Table(); |
|
table.addHead(t("Train"),t("Brake time¹, forward"),t("Brake time¹, reverse")); |
|
for (Train t : BaseClass.listElements(Train.class)) { |
|
Integer fTime = brakeTimes.get(t.brakeId()); |
|
Integer rTime = brakeTimes.get(t.brakeId(true)); |
|
table.addRow(t,isSet(fTime)? fTime+NBSP+"ms" : "–",isSet(rTime)? fTime+NBSP+"ms" : "–"); |
|
} |
|
table.clazz("brake-times").addTo(fieldset); |
|
new Tag("p").content(t("1) Duration between {} {} steps during brake process.",Train.defaultSpeedStep,speedUnit)).addTo(fieldset); |
|
return fieldset; |
|
} |
|
|
|
public Route begin(Block block,Direction to) { |
|
// add those fields to clone, too! |
|
startBlock = block; |
|
contacts = new Vector<Contact>(startBlock.contacts()); |
|
signals = new Vector<Signal>(); |
|
path = new Vector<Tile>(); |
|
turnouts = new HashMap<>(); |
|
startDirection = to; |
|
path.add(block); |
|
return this; |
|
} |
|
|
|
protected Route clone() { |
|
Route clone = new Route(); |
|
clone.startBlock = startBlock; |
|
clone.startDirection = startDirection; |
|
clone.endBlock = endBlock; |
|
clone.endDirection = endDirection; |
|
clone.contacts = new Vector<Contact>(contacts); |
|
clone.signals = new Vector<Signal>(signals); |
|
clone.turnouts = new HashMap<>(turnouts); |
|
clone.path = new Vector<>(path); |
|
clone.brakeTimes = new HashMap<String, Integer>(brakeTimes); |
|
return clone; |
|
} |
|
|
|
public Route complete() { |
|
if (contacts.size()>1) { // mindestens 2 Kontakte: erster Kontakt aktiviert Block, vorletzter Kontakt leitet Bremsung ein |
|
Contact nextToLastContact = contacts.get(contacts.size()-2); |
|
String trigger = nextToLastContact.trigger(); |
|
add(trigger,new BrakeStart(this)); |
|
|
|
int count = 0; |
|
for (int i=0;i<contacts.size();i++) { // chose second contact, that is not a BlockContact |
|
Contact contact = contacts.get(i); |
|
if (contact instanceof BlockContact) continue; |
|
if (count++==1) { // second contact, that is not a BlockContact: |
|
for (Signal signal : signals) add(contact.trigger(),new SetSignal(this).set(signal).to(Signal.RED)); |
|
break; |
|
} |
|
} |
|
add(trigger,new PreserveRoute(this)); |
|
|
|
} |
|
if (!contacts.isEmpty()) add(contacts.lastElement().trigger(), new FinishRoute(this)); |
|
for (Entry<Turnout, Turnout.State> entry : turnouts.entrySet()) { |
|
Turnout turnout = entry.getKey(); |
|
Turnout.State state = entry.getValue(); |
|
add(ROUTE_SETUP,new SetTurnout(this).setTurnout(turnout).setState(state)); |
|
} |
|
for (Signal signal : signals) add(ROUTE_SETUP,new SetSignal(this).set(signal).to(Signal.GREEN)); |
|
startDelay = signals.isEmpty() ? new Range(0,0) : new Range(1000,7500); |
|
add(ROUTE_START,new SetSpeed(this).to(999)); |
|
return this; |
|
} |
|
|
|
/** |
|
* Kontakt der Route aktivieren |
|
* @param contact |
|
* @param trainHead |
|
* @return |
|
*/ |
|
public Context contact(Contact contact) { |
|
context.contact(contact); |
|
if (triggeredContacts.contains(contact)) return context; // don't trigger contact a second time |
|
triggeredContacts.add(contact); |
|
LOG.debug("{} on {} activated {}.",context.train(),this,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; |
|
actions.fire(context,"Route.Contact("+contact.addr()+")"); |
|
Context previousContext = context; |
|
if (context.invalidated()) context = null; // route has been freed in between. |
|
return previousContext; |
|
} |
|
|
|
public Vector<Contact> contacts() { |
|
return new Vector<>(contacts); |
|
} |
|
|
|
private Fieldset contactsAndActions() { |
|
Fieldset win = new Fieldset(t("Actions and contacts")); |
|
Tag list = new Tag("ol"); |
|
|
|
Tag setup = new Tag("li").content(t("Setup actions")+COL); |
|
ActionList setupActions = triggeredActions.get(ROUTE_SETUP); |
|
if (isNull(setupActions)) { |
|
setupActions = new ActionList(this); |
|
triggeredActions.put(ROUTE_SETUP, setupActions); |
|
} |
|
setupActions.list().addTo(setup).addTo(list); |
|
|
|
Tag start = new Tag("li").content(t("Start actions")+COL); |
|
ActionList startActions = triggeredActions.get(ROUTE_START); |
|
if (isNull(startActions)) { |
|
startActions = new ActionList(this); |
|
triggeredActions.put(ROUTE_START, startActions); |
|
} |
|
startActions.list().addTo(start).addTo(list); |
|
|
|
for (Contact c : contacts) { |
|
Tag item = c.link("span", c).addTo(new Tag("li")).content(NBSP); |
|
ActionList actions = triggeredActions.get(c.trigger()); |
|
if (isNull(actions)) { |
|
actions = new ActionList(this); |
|
triggeredActions.put(c.trigger(), actions); |
|
} |
|
actions.list().addTo(item).addTo(list); |
|
} |
|
list.addTo(win); |
|
return win; |
|
} |
|
|
|
public void dropBraketimes(String...brakeIds) { |
|
for (String brakeId : brakeIds) brakeTimes.remove(brakeId); |
|
} |
|
|
|
public Route dropNextPreparedRoute() { |
|
if (isSet(nextRoutePrepper)) nextRoutePrepper.stop(); |
|
nextRoutePrepper = null; |
|
|
|
try { |
|
return nextPreparedRoute; |
|
} finally { |
|
nextPreparedRoute = null; |
|
} |
|
} |
|
|
|
public Block endBlock() { |
|
return endBlock; |
|
} |
|
|
|
public void finish(Train train) { |
|
LOG.debug("{}.finish()",this); |
|
context.onInvalidate(null); // do in invalidate context of route's nextRoute |
|
context.invalidate(); |
|
train.endRoute(this); |
|
setSignals(Signal.RED); |
|
freeIgnoring(null); |
|
train = null; |
|
} |
|
|
|
private void freeIgnoring(Vector<Tile> skipTiles) { |
|
Train train = context.train(); |
|
Vector<Tile> reversedPath = reverse(path()); |
|
if (isSet(skipTiles)) reversedPath.removeAll(skipTiles); |
|
for (Tile tile : reversedPath) { |
|
if (isSet(train) && !train.onTrace(tile)) tile.free(train); |
|
} |
|
} |
|
|
|
private String generateName() { |
|
StringBuilder sb = new StringBuilder(); |
|
for (int i=0; i<path.size();i++) { |
|
Tile tile = path.get(i); |
|
if (i>0) sb.append("-"); |
|
if (tile instanceof Block) { |
|
sb.append(" ").append(((Block)tile).name).append(" "); |
|
if (i>0) break; // Kontakt nach dem Ziel-Block nicht mitnehmen |
|
} else { |
|
sb.append("("+tile.x+","+tile.y+")"); |
|
} |
|
} |
|
return sb.toString().trim(); |
|
} |
|
|
|
public Route getNextPreparedRoute() { |
|
return nextPreparedRoute; |
|
} |
|
|
|
public Id id() { |
|
if (isNull(id)) id = new Id(""+(generateName().hashCode())); |
|
return id; |
|
} |
|
|
|
public boolean isDisabled() { |
|
return disabled; |
|
} |
|
|
|
public boolean isFreeFor(Context train) { |
|
LOG.debug("{}.isFreeFor({})",this,train); |
|
if (isNull(train.train())) return false; |
|
for (Tile tile : path) { |
|
if (!tile.isFreeFor(train)) return false; |
|
} |
|
return true; |
|
} |
|
|
|
/** |
|
* creates a json representation of this route |
|
* @return |
|
*/ |
|
public JSONObject json() { |
|
JSONObject json = super.json(); |
|
Vector<String> tileIds = new Vector<String>(); |
|
for (Tile t : this.path) tileIds.add(t.id().toString()); |
|
json.put(PATH, tileIds); |
|
|
|
Vector<String> signalIds = new Vector<String>(); // list all signals affecting this route |
|
for (Tile t : this.signals) signalIds.add(t.id().toString()); |
|
json.put(SIGNALS, signalIds); |
|
|
|
JSONArray turnouts = new JSONArray(); |
|
for (Entry<Turnout, Turnout.State> entry : this.turnouts.entrySet()) { |
|
Turnout t = entry.getKey(); |
|
turnouts.put(new JSONObject(Map.of(Turnout.ID,t.id().toString(),Turnout.STATE,entry.getValue()))); |
|
} |
|
json.put(TURNOUTS, turnouts); |
|
json.put(START_DIRECTION, startDirection); |
|
json.put(END_DIRECTION, endDirection); |
|
|
|
json.put(BRAKE_TIMES, brakeTimes); |
|
|
|
if (!conditions.isEmpty()) { |
|
json.put(CONDITION_LIST, conditions.json()); |
|
} |
|
|
|
JSONObject jActions = new JSONObject(); |
|
for (Entry<String, ActionList> entry : triggeredActions.entrySet()) { |
|
String trigger = entry.getKey(); |
|
ActionList lst = entry.getValue(); |
|
jActions.put(trigger,lst.json()); |
|
} |
|
json.put(ACTIONS, jActions); |
|
|
|
String name = name(); |
|
if (isSet(name)) json.put(NAME, name); |
|
|
|
if (disabled) json.put(DISABLED, true); |
|
if (isSet(startDelay)) json.put(STARt_DELAY, startDelay.json()); |
|
|
|
return json; |
|
} |
|
|
|
private Route load(JSONObject json,Plan plan) { |
|
if (json.has(ID)) id = Id.from(json); |
|
if (json.has(NAME)) name(json.getString(NAME)); |
|
JSONArray pathIds = json.getJSONArray(PATH); |
|
startDirection = Direction.valueOf(json.getString(START_DIRECTION)); |
|
endDirection = Direction.valueOf(json.getString(END_DIRECTION)); |
|
for (Object tileId : pathIds) { |
|
Tile tile = plan.get(new Id((String) tileId),false); |
|
if (isNull(tile)) { |
|
continue; |
|
} |
|
if (isNull(startBlock)) { |
|
begin((Block) tile, startDirection); |
|
} else if (tile instanceof Block) { // make sure, endDirection is set on last block |
|
add(tile,endDirection); |
|
} else { |
|
add(tile, null); |
|
} |
|
} |
|
if (isNull(path) || path.isEmpty()) { |
|
LOG.warn("{} has no tiles. It will be ignored.",this); |
|
return null; |
|
} |
|
if (json.has(TURNOUTS)) { |
|
JSONArray turnouts = json.getJSONArray(TURNOUTS); |
|
for (int i=0; i<turnouts.length();i++) { |
|
JSONObject jTurnout = turnouts.getJSONObject(i); |
|
Turnout turnout = (Turnout) plan.get(new Id(jTurnout.getString(Turnout.ID)), false); |
|
addTurnout(turnout, Turnout.State.valueOf(jTurnout.getString(Turnout.STATE))); |
|
} |
|
} |
|
if (json.has(SIGNALS)) { |
|
for (Object signalId : json.getJSONArray(SIGNALS)) addSignal((Signal) plan.get(new Id((String) signalId), false)); |
|
} |
|
if (json.has(ACTIONS)) { |
|
loadActions(json.getJSONObject(ACTIONS)); |
|
} |
|
if (json.has("action_lists")) { // Legacy |
|
JSONArray jarr = json.getJSONArray("action_lists"); |
|
for (Object o : jarr) { |
|
if (o instanceof JSONObject) { |
|
JSONObject jo = (JSONObject) o; |
|
ActionList aList = new ActionList(this); |
|
String trigger = jo.getString("trigger"); |
|
JSONArray jActions = jo.getJSONArray(ACTIONS); |
|
for (Object ja : jActions) { |
|
JSONObject jao = (JSONObject) ja; |
|
String type = jao.getString(TYPE); |
|
Action action = Action.create(type, aList); |
|
if (isSet(action)) { |
|
action.load(jao); |
|
aList.add(action); |
|
} |
|
} |
|
triggeredActions.put(trigger, aList); |
|
} |
|
} |
|
} |
|
if (json.has("conditions")) { // Legacy |
|
JSONArray jConditions = json.getJSONArray("conditions"); |
|
for (Object o : jConditions) { |
|
if (o instanceof JSONObject) { |
|
JSONObject jo = (JSONObject) o; |
|
String type = jo.getString(TYPE); |
|
Condition condition = Condition.create(type); |
|
if (isSet(condition)) { |
|
condition.load(jo); |
|
conditions.add(condition); |
|
} |
|
} |
|
} |
|
} |
|
if (json.has(CONDITION_LIST)) conditions.load(json.getJSONObject(CONDITION_LIST)).parent(this); |
|
if (json.has(SETUP_ACTIONS)) { // Legacy |
|
Object so = json.get(SETUP_ACTIONS); |
|
if (so instanceof JSONObject) { |
|
JSONObject jo = (JSONObject) so; |
|
ActionList setupActions = new ActionList(this); |
|
setupActions.load(jo).parent(this); |
|
triggeredActions.put(ROUTE_SETUP, setupActions); |
|
} |
|
if (so instanceof JSONArray) { |
|
JSONArray ja = (JSONArray) so; |
|
ActionList setupActions = new ActionList(this); |
|
for (Object o : ja) { |
|
if (o instanceof JSONObject) { |
|
JSONObject jo = (JSONObject) o; |
|
String type = jo.getString(TYPE); |
|
Action action = Action.create(type, setupActions); |
|
if (isSet(action)) { |
|
action.load(jo); |
|
setupActions.add(action); |
|
} |
|
} |
|
} |
|
triggeredActions.put(ROUTE_SETUP, setupActions); |
|
} |
|
} |
|
if (json.has(START_ACTIONS)) { // Legacy |
|
Object so = json.get(START_ACTIONS); |
|
if (so instanceof JSONObject) { |
|
JSONObject jo = (JSONObject) so; |
|
ActionList startActions = new ActionList(this); |
|
startActions.load(jo).parent(this); |
|
triggeredActions.put(ROUTE_START, startActions); |
|
} |
|
if (so instanceof JSONArray) { |
|
JSONArray ja = (JSONArray) so; |
|
ActionList startActions = new ActionList(this); |
|
for (Object o : ja) { |
|
if (o instanceof JSONObject) { |
|
JSONObject jo = (JSONObject) o; |
|
String type = jo.getString(TYPE); |
|
Action action = Action.create(type, startActions); |
|
if (isSet(action)) { |
|
action.load(jo); |
|
startActions.add(action); |
|
} |
|
} |
|
} |
|
triggeredActions.put(ROUTE_START, startActions); |
|
} |
|
|
|
} |
|
if (json.has(DISABLED)) disabled = json.getBoolean(DISABLED); |
|
if (json.has(BRAKE_TIMES)) { |
|
JSONObject dummy = json.getJSONObject(BRAKE_TIMES); |
|
dummy.keySet().forEach(key -> brakeTimes.put(key, dummy.getInt(key))); |
|
} |
|
if (json.has(STARt_DELAY)) startDelay = new Range().load(json.getJSONObject(STARt_DELAY)); |
|
return plan.registerRoute(this); |
|
} |
|
|
|
private void loadActions(JSONObject jsonObject) { |
|
for (String trigger : jsonObject.keySet()) { |
|
JSONObject json = jsonObject.getJSONObject(trigger); |
|
String type = json.getString(TYPE); |
|
ActionList actionList = Action.create(type, this); |
|
if (isNull(actionList)) continue; |
|
actionList.load(json); |
|
triggeredActions.put(trigger, actionList); |
|
} |
|
} |
|
|
|
public static void loadAll(String filename, Plan plan) throws IOException { |
|
FileInputStream fis = new FileInputStream(filename); |
|
JSONTokener tokener = new JSONTokener(fis); |
|
JSONObject json = new JSONObject(tokener); |
|
JSONArray routes = json.getJSONArray(ROUTES); |
|
for (Object o : routes) { |
|
if (o instanceof JSONObject) { |
|
new Route().load((JSONObject)o, plan); |
|
} |
|
} |
|
fis.close(); |
|
} |
|
|
|
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()); |
|
return routes; |
|
} |
|
|
|
public String name() { |
|
String name = names.get(id()); |
|
if (isNull(name)) { |
|
name = generateName(); |
|
name(name); |
|
} |
|
return name; |
|
} |
|
|
|
public void name(String name) { |
|
if (name.isEmpty()) { |
|
names.remove(id()); |
|
} else names.put(id(),name); |
|
} |
|
|
|
public Vector<Tile> path() { |
|
return isSet(path) ? new Vector<>(path) : new Vector<>(); |
|
} |
|
|
|
public boolean prepareAndLock() { |
|
LOG.debug("{}.prepareAndLock()",this); |
|
Train train = context.train(); |
|
ActionList setupActions = triggeredActions.get(ROUTE_SETUP); |
|
if (isSet(setupActions) && !setupActions.fire(context.route(this),this+".prepare()")) { |
|
LOG.debug("Was not able to prepare route for {}.",train); |
|
return false; |
|
} |
|
|
|
for (Tile tile : path) { |
|
if (context.invalidated() || !tile.lockFor(context,false)) { |
|
LOG.debug("Was not able to allocate route for {}.",context); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
public boolean prepareNext(Context context) { |
|
if (isSet(nextRoutePrepper)) return false; |
|
nextRoutePrepper = new RoutePrepper(context); |
|
nextRoutePrepper.onRoutePrepared(() -> { |
|
nextPreparedRoute = nextRoutePrepper.route(); |
|
nextRoutePrepper = null; |
|
LOG.debug("prepared route after {}: {}",this,nextPreparedRoute); |
|
}); |
|
nextRoutePrepper.onFail(() -> { |
|
LOG.debug("preparing route next to {} failed, resetting.",this); |
|
Route rt = nextRoutePrepper.route(); // Nachfolgeroute kann ja schon reserviert sein, oder gar schon teilweise vorbereitet! |
|
if (isSet(rt)) rt.resetIgnoring(this); // angefangene Route freigeben ohne Teile der aktuellen Route freizugeben |
|
nextRoutePrepper = null; |
|
}); |
|
return nextRoutePrepper.prepareRoute(); |
|
} |
|
|
|
private Tag previewScript() { |
|
Tag script = new Tag("script").attr("type", "text/javascript"); |
|
for (Tile tile : path) { |
|
script.content("$('#"+tile.id()+"').addClass('preview');\n"); |
|
} |
|
return script; |
|
} |
|
|
|
@Override |
|
protected Window properties(List<Fieldset> preForm, FormInput formInputs, List<Fieldset> postForm,String...errors) { |
|
|
|
preForm.add(conditions.list(t("Route will only be available, if all conditions are fulfilled."))); |
|
preForm.add(contactsAndActions()); |
|
|
|
Tag nameSpan = new Tag("span"); |
|
new Input(NAME, name()).addTo(nameSpan); |
|
button(t("simplify name"), Map.of(ACTION,ACTION_AUTO,ROUTE,id().toString())).addTo(nameSpan); |
|
formInputs.add(t("Name"),nameSpan); |
|
Checkbox checkbox = new Checkbox(DISABLED, t("disabled"), disabled); |
|
if (disabled) checkbox.clazz("disabled"); |
|
formInputs.add(t("State"),checkbox); |
|
|
|
Tag span = new Tag("span"); |
|
new Input(MIN_START_DELAY,isSet(startDelay) ? startDelay.min : 0).numeric().addTo(span).content(" ... "); |
|
new Input(MAX_START_DELAY,isSet(startDelay) ? startDelay.max : 0).numeric().addTo(span).content(" ms"); |
|
formInputs.add(t("Start delay"),span); |
|
|
|
postForm.add(basicProperties()); |
|
if (!turnouts.isEmpty()) postForm.add(turnouts()); |
|
postForm.add(brakeTimes()); |
|
Window win = super.properties(preForm, formInputs, postForm,errors); |
|
previewScript().addTo(win); |
|
return win; |
|
} |
|
|
|
public boolean reactivate(Contact contact) { |
|
return triggeredContacts.remove(contact); |
|
} |
|
|
|
@Override |
|
public BaseClass remove() { |
|
LOG.debug("Removing route ({}) {}",id(),this); |
|
// if (isSet(train)) train.removeChild(this); |
|
for (Tile tile : path) { |
|
tile.removeChild(this); |
|
} |
|
conditions.remove(); |
|
for (String key : new Vector<String>(triggeredActions.keySet())){ |
|
ActionList actionList = triggeredActions.remove(key); |
|
if (isSet(actionList)) actionList.remove(); |
|
}; |
|
return super.remove(); |
|
} |
|
|
|
@Override |
|
public void removeChild(BaseClass child) { |
|
conditions.remove(child); |
|
contacts.remove(child); |
|
if (child == endBlock) endBlock = null; |
|
path.remove(child); |
|
signals.remove(child); |
|
// if (child == train) train = null; |
|
for (ActionList list : triggeredActions.values()) { |
|
list.removeChild(child); |
|
} |
|
turnouts.remove(child); |
|
if (child == startBlock) startBlock = null; |
|
triggeredContacts.remove(child); |
|
super.removeChild(child); |
|
} |
|
|
|
public boolean reserveFor(Context newContext) { |
|
LOG.debug("{}.reserverFor ({})",this,newContext); |
|
|
|
context = newContext; |
|
for (Tile tile : path) { |
|
if (newContext.invalidated() || !tile.reserveFor(newContext)) { |
|
LOG.debug("Was not able to allocate route for {}.",newContext); |
|
return false; |
|
} |
|
} |
|
return true; |
|
} |
|
|
|
public void reset() { |
|
resetIgnoring(null); |
|
} |
|
|
|
public void resetIgnoring(Route otherRoute) { |
|
LOG.debug("{}.resetIgnoring({})",this,otherRoute); |
|
resetNext(); |
|
setSignals(Signal.RED); |
|
Train train = context.train(); |
|
freeIgnoring(isSet(otherRoute) ? otherRoute.path : null); |
|
train.drop(this); |
|
context = null; |
|
} |
|
|
|
public void resetNext() { |
|
if (isSet(nextRoutePrepper)) nextRoutePrepper.stop(); |
|
if (isSet(nextPreparedRoute)) nextPreparedRoute.reset(); |
|
} |
|
|
|
public static void saveAll(String filename) throws IOException { |
|
BufferedWriter file = new BufferedWriter(new FileWriter(filename)); |
|
file.write("{\""+ROUTES+"\":[\n"); |
|
int count = 0; |
|
List<Route> routes = BaseClass.listElements(Route.class); |
|
for (Route route : routes) { |
|
file.write(route.json().toString()); |
|
if (++count < routes.size()) file.write(","); |
|
file.write("\n"); |
|
} |
|
file.write("]}"); |
|
file.close(); |
|
} |
|
|
|
public void setLast(Turnout.State state) { |
|
if (isNull(state) || state == Turnout.State.UNDEF) return; |
|
Tile lastTile = path.lastElement(); |
|
if (lastTile instanceof Turnout) addTurnout((Turnout) lastTile,state); |
|
} |
|
|
|
public void setNextPreparedRoute(Route route) { |
|
nextPreparedRoute = route; |
|
} |
|
|
|
|
|
public boolean setSignals(String state) { |
|
LOG.debug("{}.setSignals({})",this,state); |
|
for (Signal signal : signals) { |
|
if (!signal.state(isNull(state) ? Signal.GREEN : state)) return false; |
|
} |
|
return true; |
|
} |
|
|
|
public String shortName() { |
|
String[] parts = name().split("-"); |
|
return parts[0].trim()+"–"+parts[parts.length-1].trim(); |
|
} |
|
|
|
public Route simplyfyName() { |
|
String[] parts = name().split("-"); |
|
if (parts.length>1) name(parts[0].trim()+" - "+parts[parts.length-1].trim()); |
|
return this; |
|
} |
|
|
|
private void simulate() { |
|
new Simulator(this); |
|
} |
|
|
|
public boolean startNow() { |
|
LOG.debug("{}.startNow()",this); |
|
|
|
if (isNull(context) || context.invalidated()) { |
|
LOG.debug("Invalid context: {}",context); |
|
return false; |
|
} |
|
|
|
Train train = context.train(); |
|
if (isNull(train)) { |
|
LOG.debug("Context does not contain train: {}",context); |
|
return false; // can't set route's train to null |
|
} |
|
train.setRoute(this); // set new train |
|
|
|
triggeredContacts.clear(); |
|
|
|
ActionList startActions = triggeredActions.get(ROUTE_START); |
|
if (train.direction() != startDirection) train.turn(); |
|
|
|
if (isSet(startActions)) { |
|
context.route(this); |
|
String cause = this+".start("+train.name()+")"; |
|
if (!startActions.fire(context,cause)) { |
|
LOG.debug("Failed to execute {}",startActions); |
|
return false; // start actions failed |
|
} |
|
} |
|
|
|
context.waitTime(endBlock.getWaitTime(train, endDirection).random()); |
|
|
|
if (!contacts.isEmpty() && contacts.stream().filter(contact -> contact.addr() != 0).findAny().isEmpty()) simulate(); |
|
return true; |
|
} |
|
|
|
public boolean start() { |
|
if (isNull(context) || context.invalidated()) { |
|
LOG.debug("Invalid context: {}",context); |
|
return false; |
|
} |
|
|
|
Train train = context.train(); |
|
if (train.direction() != startDirection) train.turn(); |
|
if (isSet(startDelay)) sleep(startDelay.random()); |
|
return startNow(); |
|
} |
|
|
|
public Block startBlock() { |
|
return startBlock; |
|
} |
|
|
|
public Integer startSpeed() { |
|
LOG.debug("{}.startSpeed()",this); |
|
ActionList startActions = triggeredActions.get(ROUTE_START); |
|
Context context = new Context(this); |
|
return isSet(startActions) ? startActions.getSpeed(context) : null; |
|
} |
|
|
|
@Override |
|
public String toString() { |
|
return getClass().getSimpleName()+"("+name()+")"; |
|
} |
|
|
|
/* public Train train() { |
|
return train; |
|
}*/ |
|
|
|
private Fieldset turnouts() { |
|
Fieldset win = new Fieldset(t("Turnouts")); |
|
Tag list = new Tag("ul"); |
|
for (Entry<Turnout, Turnout.State> entry : turnouts.entrySet()) { |
|
Turnout turnout = entry.getKey(); |
|
Plan.addLink(turnout, turnout+": "+t(entry.getValue().toString()), list); |
|
} |
|
list.addTo(win); |
|
return win; |
|
} |
|
|
|
protected Object update(HashMap<String, String> params,Plan plan) { |
|
LOG.debug("update({})",params); |
|
String name = params.get(NAME); |
|
if (isSet(name)) name(name); |
|
|
|
disabled = "on".equals(params.get(DISABLED)); |
|
|
|
String delay = params.get(MIN_START_DELAY); |
|
if (isSet(delay)) try { |
|
int min = Integer.parseInt(delay); |
|
if (isNull(startDelay)) { |
|
startDelay = new Range(min, min); |
|
} else { |
|
startDelay.min = min; |
|
startDelay.validate(); |
|
} |
|
} catch (NumberFormatException e) {} |
|
|
|
delay = params.get(MAX_START_DELAY); |
|
if (isSet(delay)) try { |
|
int max = Integer.parseInt(delay); |
|
if (isNull(startDelay)) { |
|
startDelay = new Range(max, max); |
|
} else { |
|
startDelay.max = max; |
|
startDelay.validate(); |
|
} |
|
} catch (NumberFormatException e) {} |
|
|
|
|
|
Condition condition = Condition.create(params.get(REALM_CONDITION)); |
|
if (isSet(condition)) { |
|
condition.parent(this); |
|
conditions.add(condition); |
|
} |
|
|
|
super.update(params); |
|
return properties(); |
|
} |
|
|
|
public Integer waitTime() { |
|
return isNull(context) ? null : context.waitTime(); |
|
} |
|
}
|
|
|