001package jmri.jmrit.dispatcher; 002 003import java.io.File; 004import java.io.IOException; 005import java.util.ArrayList; 006import java.util.Arrays; 007import java.util.List; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010import jmri.util.FileUtil; 011import jmri.util.XmlFilenameFilter; 012import org.jdom2.Document; 013import org.jdom2.Element; 014import org.slf4j.Logger; 015import org.slf4j.LoggerFactory; 016 017import jmri.InstanceManager; 018import jmri.configurexml.AbstractXmlAdapter.EnumIO; 019import jmri.configurexml.AbstractXmlAdapter.EnumIoNamesNumbers; 020import jmri.jmrit.dispatcher.ActiveTrain.TrainDetection; 021 022/** 023 * Handles reading and writing of TrainInfo files to disk as an XML file to/from 024 * the dispatcher/traininfo/ directory in the user's preferences area 025 * <p> 026 * This class manipulates the files conforming to the dispatcher-traininfo DTD 027 * <p> 028 * The file is written when the user requests that train information be saved. A 029 * TrainInfo file is read when the user request it in the Activate New Train 030 * window 031 * 032 * <p> 033 * This file is part of JMRI. 034 * <p> 035 * JMRI is open source software; you can redistribute it and/or modify it under 036 * the terms of version 2 of the GNU General Public License as published by the 037 * Free Software Foundation. See the "COPYING" file for a copy of this license. 038 * <p> 039 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 040 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 041 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 042 * 043 * @author Dave Duchamp Copyright (C) 2009 044 */ 045public class TrainInfoFile extends jmri.jmrit.XmlFile { 046 047 public TrainInfoFile() { 048 super(); 049 } 050 // operational variables 051 private String fileLocation = FileUtil.getUserFilesPath() 052 + "dispatcher" + File.separator + "traininfo" + File.separator; 053 054 public void setFileLocation(String testLocation) { 055 fileLocation = testLocation; 056 } 057 private Document doc = null; 058 private Element root = null; 059 060 static final EnumIO<ActiveTrain.TrainDetection> trainsdectionFromEnumMap = new EnumIoNamesNumbers<>(ActiveTrain.TrainDetection.class); 061 static final EnumIO<ActiveTrain.TrainLengthUnits> trainlengthFromEnumMap = new EnumIoNamesNumbers<>(ActiveTrain.TrainLengthUnits.class); 062 063 /* 064 * Reads Dispatcher TrainInfo from a file in the user's preferences directory 065 * If the file containing Dispatcher TrainInfo does not exist this routine returns quietly. 066 * "name" is assumed to have the .xml or .XML extension already included 067 */ 068 public TrainInfo readTrainInfo(String name) throws org.jdom2.JDOMException, java.io.IOException { 069 log.debug("entered readTrainInfo for {}", name); 070 TrainInfo tInfo = null; 071 int version = 1; 072 // check if file exists 073 if (checkFile(fileLocation + name)) { 074 // file is present. 075 tInfo = new TrainInfo(); 076 root = rootFromName(fileLocation + name); 077 if (root != null) { 078 // there is a file 079 Element traininfo = root.getChild("traininfo"); 080 if (traininfo != null) { 081 // get version so we dont look for missing fields 082 if (traininfo.getAttribute("version") != null ) { 083 try { 084 version = traininfo.getAttribute("version").getIntValue(); 085 } catch (org.jdom2.DataConversionException ex) { 086 log.error("Traininfo file version number not an integer: assuming version 1"); 087 version = 1; 088 } 089 } else { 090 version = 1; 091 } 092 tInfo.setVersion(version); 093 // there are train info options defined, read them 094 if (traininfo.getAttribute("transitname") != null) { 095 // there is a transit name selected 096 tInfo.setTransitName(traininfo.getAttribute("transitname").getValue()); 097 } else { 098 log.error("Transit name missing when reading TrainInfoFile {}", name); 099 } 100 if (traininfo.getAttribute("dynamictransit") != null ) { 101 tInfo.setDynamicTransit(traininfo.getAttribute("dynamictransit").getValue().equals("yes")); 102 } 103 if (traininfo.getAttribute("dynamictransitcloseloop") != null ) { 104 tInfo.setDynamicTransitCloseLoopIfPossible(traininfo.getAttribute("dynamictransitcloseloop").getValue().equals("yes")); 105 } 106 if (version < 6) { 107 if (traininfo.getAttribute("trainname") != null) { 108 tInfo.setTrainName(traininfo.getAttribute("trainname").getValue()); 109 tInfo.setRosterId(traininfo.getAttribute("trainname").getValue()); 110 tInfo.setTrainUserName(traininfo.getAttribute("trainname").getValue()); 111 } else { 112 log.error("Train name missing when reading TrainInfoFile {}", name); 113 } 114 } else { 115 if (traininfo.getAttribute("trainname") != null) { 116 tInfo.setTrainName(traininfo.getAttribute("trainname").getValue()); 117 } 118 if (traininfo.getAttribute("rosterid") != null) { 119 tInfo.setRosterId(traininfo.getAttribute("rosterid").getValue()); 120 } 121 if (traininfo.getAttribute("trainusername") != null) { 122 tInfo.setTrainUserName(traininfo.getAttribute("trainusername").getValue()); 123 } 124 } 125 if (traininfo.getAttribute("dccaddress") != null) { 126 tInfo.setDccAddress(traininfo.getAttribute("dccaddress").getValue()); 127 } else { 128 log.error("DCC Address missing when reading TrainInfoFile {}", name); 129 } 130 if (traininfo.getAttribute("trainintransit") != null) { 131 tInfo.setTrainInTransit(true); 132 if (traininfo.getAttribute("trainintransit").getValue().equals("no")) { 133 tInfo.setTrainInTransit(false); 134 } 135 } else { 136 log.error("Train in Transit check box missing when reading TrainInfoFile {}", name); 137 } 138 if (traininfo.getAttribute("startblockname") != null) { 139 // there is a transit name selected 140 tInfo.setStartBlockName(traininfo.getAttribute("startblockname").getValue()); 141 } else { 142 log.error("Start block name missing when reading TrainInfoFile {}", name); 143 } 144 if (traininfo.getAttribute("endblockname") != null) { 145 // there is a transit name selected 146 tInfo.setDestinationBlockName(traininfo.getAttribute("endblockname").getValue()); 147 } else { 148 log.error("Destination block name missing when reading TrainInfoFile {}", name); 149 } 150 if (tInfo.getDynamicTransit()) { 151 if (traininfo.getAttribute("viablockname") != null) { 152 // there is a transit name selected 153 tInfo.setViaBlockName(traininfo.getAttribute("viablockname").getValue()); 154 } else { 155 log.error("Via block name missing for dynamic trainsit when reading TrainInfoFile {}", name); 156 } 157 } 158 if (traininfo.getAttribute("trainfromroster") != null) { 159 tInfo.setTrainFromRoster(true); 160 if (traininfo.getAttribute("trainfromroster").getValue().equals("no")) { 161 tInfo.setTrainFromRoster(false); 162 } 163 } 164 if (traininfo.getAttribute("trainfromtrains") != null) { 165 tInfo.setTrainFromTrains(true); 166 if (traininfo.getAttribute("trainfromtrains").getValue().equals("no")) { 167 tInfo.setTrainFromTrains(false); 168 } 169 } 170 if (traininfo.getAttribute("trainfromuser") != null) { 171 tInfo.setTrainFromUser(true); 172 if (traininfo.getAttribute("trainfromuser").getValue().equals("no")) { 173 tInfo.setTrainFromUser(false); 174 } 175 } 176 if (traininfo.getAttribute("trainfromsetlater") != null) { 177 tInfo.setTrainFromSetLater(true); 178 if (traininfo.getAttribute("trainfromsetlater").getValue().equals("no")) { 179 tInfo.setTrainFromSetLater(false); 180 } 181 } 182 if (traininfo.getAttribute("priority") != null) { 183 tInfo.setPriority(Integer.parseInt(traininfo.getAttribute("priority").getValue())); 184 } else { 185 log.error("Priority missing when reading TrainInfoFile {}", name); 186 } 187 if (traininfo.getAttribute("allocatealltheway") != null) { 188 if (traininfo.getAttribute("allocatealltheway").getValue().equals("yes")) { 189 tInfo.setAllocateAllTheWay(true); 190 } 191 } 192 if (traininfo.getAttribute("allocationmethod") != null) { 193 tInfo.setAllocationMethod(traininfo.getAttribute("allocationmethod").getIntValue()); 194 } 195 if (traininfo.getAttribute("nexttrain") != null) { 196 tInfo.setNextTrain(traininfo.getAttribute("nexttrain").getValue()); 197 } 198 if (traininfo.getAttribute("resetwhendone") != null) { 199 if (traininfo.getAttribute("resetwhendone").getValue().equals("yes")) { 200 tInfo.setResetWhenDone(true); 201 } 202 if (traininfo.getAttribute("delayedrestart") != null) { 203 // for older files that didnot have seperate restart details for to and fro 204 // we default that data to this data. 205 switch (traininfo.getAttribute("delayedrestart").getValue()) { 206 case "no": 207 tInfo.setDelayedRestart(ActiveTrain.NODELAY); 208 tInfo.setReverseDelayedRestart(ActiveTrain.NODELAY); 209 break; 210 case "sensor": 211 tInfo.setDelayedRestart(ActiveTrain.SENSORDELAY); 212 tInfo.setReverseDelayedRestart(ActiveTrain.SENSORDELAY); 213 if (traininfo.getAttribute("delayedrestartsensor") != null) { 214 tInfo.setRestartSensorName(traininfo.getAttribute("delayedrestartsensor").getValue()); 215 tInfo.setReverseRestartSensorName(traininfo.getAttribute("delayedrestartsensor").getValue()); 216 } 217 if (traininfo.getAttribute("resetrestartsensor") != null) { 218 tInfo.setResetRestartSensor(traininfo.getAttribute("resetrestartsensor").getValue().equals("yes")); 219 tInfo.setReverseResetRestartSensor(traininfo.getAttribute("resetrestartsensor").getValue().equals("yes")); 220 } 221 break; 222 case "timed": 223 tInfo.setDelayedRestart(ActiveTrain.TIMEDDELAY); 224 tInfo.setReverseDelayedRestart(ActiveTrain.TIMEDDELAY); 225 if (traininfo.getAttribute("delayedrestarttime") != null) { 226 tInfo.setRestartDelayMin((int) traininfo.getAttribute("delayedrestarttime").getLongValue()); 227 tInfo.setReverseRestartDelayMin((int) traininfo.getAttribute("delayedrestarttime").getLongValue()); 228 } break; 229 default: 230 break; 231 } 232 } 233 } 234 if (traininfo.getAttribute("reverseatend") != null) { 235 tInfo.setReverseAtEnd(true); 236 if (traininfo.getAttribute("reverseatend").getValue().equals("no")) { 237 tInfo.setReverseAtEnd(false); 238 } 239 if (version > 3) { 240 // fro delays are independent from to delays 241 if (traininfo.getAttribute("reversedelayedrestart") != null) { 242 switch (traininfo.getAttribute("reversedelayedrestart").getValue()) { 243 case "no": 244 tInfo.setReverseDelayedRestart(ActiveTrain.NODELAY); 245 break; 246 case "sensor": 247 tInfo.setReverseDelayedRestart(ActiveTrain.SENSORDELAY); 248 if (traininfo.getAttribute("reversedelayedrestartsensor") != null) { 249 tInfo.setReverseRestartSensorName( 250 traininfo.getAttribute("reversedelayedrestartsensor").getValue()); 251 } 252 if (traininfo.getAttribute("reverseresetrestartsensor") != null) { 253 tInfo.setReverseResetRestartSensor( 254 traininfo.getAttribute("reverseresetrestartsensor").getValue() 255 .equals("yes")); 256 } 257 break; 258 case "timed": 259 tInfo.setReverseDelayedRestart(ActiveTrain.TIMEDDELAY); 260 if (traininfo.getAttribute("reversedelayedrestarttime") != null) { 261 tInfo.setReverseRestartDelayMin((int) traininfo 262 .getAttribute("reversedelayedrestarttime").getLongValue()); 263 } 264 break; 265 default: 266 break; 267 } 268 } 269 } 270 } 271 if (traininfo.getAttribute("delayedstart") != null) { 272 switch (traininfo.getAttribute("delayedstart").getValue()) { 273 case "no": 274 tInfo.setDelayedStart(ActiveTrain.NODELAY); 275 break; 276 case "sensor": 277 tInfo.setDelayedStart(ActiveTrain.SENSORDELAY); 278 break; 279 default: 280 //This covers the old versions of the file with "yes" 281 tInfo.setDelayedStart(ActiveTrain.TIMEDDELAY); 282 break; 283 } 284 } 285 if (traininfo.getAttribute("departuretimehr") != null) { 286 tInfo.setDepartureTimeHr(Integer.parseInt(traininfo.getAttribute("departuretimehr").getValue())); 287 } 288 if (traininfo.getAttribute("departuretimemin") != null) { 289 tInfo.setDepartureTimeMin(Integer.parseInt(traininfo.getAttribute("departuretimemin").getValue())); 290 } 291 if (traininfo.getAttribute("delayedSensor") != null) { 292 tInfo.setDelaySensorName(traininfo.getAttribute("delayedSensor").getValue()); 293 } 294 if (traininfo.getAttribute("resetstartsensor") != null) { 295 tInfo.setResetStartSensor(traininfo.getAttribute("resetstartsensor").getValue().equals("yes")); 296 } 297 if (traininfo.getAttribute("traintype") != null) { 298 tInfo.setTrainType(traininfo.getAttribute("traintype").getValue()); 299 } 300 if (traininfo.getAttribute("autorun") != null) { 301 tInfo.setAutoRun(true); 302 if (traininfo.getAttribute("autorun").getValue().equals("no")) { 303 tInfo.setAutoRun(false); 304 } 305 } 306 if (traininfo.getAttribute("loadatstartup") != null) { 307 tInfo.setLoadAtStartup(true); 308 if (traininfo.getAttribute("loadatstartup").getValue().equals("no")) { 309 tInfo.setLoadAtStartup(false); 310 } 311 } 312 // here retrieve items related only to automatically run trains if present 313 if (traininfo.getAttribute("speedfactor") != null) { 314 tInfo.setSpeedFactor(Float.parseFloat(traininfo.getAttribute("speedfactor").getValue())); 315 } 316 if (traininfo.getAttribute("maxspeed") != null) { 317 tInfo.setMaxSpeed(Float.parseFloat(traininfo.getAttribute("maxspeed").getValue())); 318 } 319 // Optional scale km/h cap (absent in older files). 320 if (traininfo.getAttribute("maxspeedscalekmh") != null) { 321 try { 322 tInfo.setMaxSpeedScaleKmh(traininfo.getAttribute("maxspeedscalekmh").getFloatValue()); 323 } catch (org.jdom2.DataConversionException ex) { 324 // Malformed value: leave unset (0.0f) for backward compatibility. 325 } 326 } 327 if (traininfo.getAttribute("minreliableoperatingspeed") != null) { 328 tInfo.setMinReliableOperatingSpeed(Float.parseFloat(traininfo.getAttribute("minreliableoperatingspeed").getValue())); 329 } 330 if (traininfo.getAttribute("ramprate") != null) { 331 tInfo.setRampRate(traininfo.getAttribute("ramprate").getValue()); 332 } 333 tInfo.setTrainDetection(TrainDetection.TRAINDETECTION_WHOLETRAIN); 334 if (version > 4) { 335 if (traininfo.getAttribute("traindetection") != null) { 336 tInfo.setTrainDetection(trainsdectionFromEnumMap.inputFromAttribute(traininfo.getAttribute("traindetection"))); 337 } 338 } 339 else { 340 if (traininfo.getAttribute("resistancewheels").getValue().equals("no")) { 341 tInfo.setTrainDetection(TrainDetection.TRAINDETECTION_HEADONLY); 342 } 343 } 344 if (traininfo.getAttribute("runinreverse") != null) { 345 tInfo.setRunInReverse(true); 346 if (traininfo.getAttribute("runinreverse").getValue().equals("no")) { 347 tInfo.setRunInReverse(false); 348 } 349 } 350 if (traininfo.getAttribute("sounddecoder") != null) { 351 tInfo.setSoundDecoder(true); 352 if (traininfo.getAttribute("sounddecoder").getValue().equals("no")) { 353 tInfo.setSoundDecoder(false); 354 } 355 } 356 if (version > 5) { 357 if (traininfo.getAttribute("trainlengthunits") != null) { 358 tInfo.setTrainLengthUnits(trainlengthFromEnumMap.inputFromAttribute(traininfo.getAttribute("trainlengthunits"))); 359 } 360 } 361 if (traininfo.getAttribute("maxtrainlengthscalemeters") != null) { 362 tInfo.setMaxTrainLengthScaleMeters(Float.parseFloat(traininfo.getAttribute("maxtrainlengthscalemeters").getValue())); 363 } else { 364 if (traininfo.getAttribute("maxtrainlength") != null) { 365 if (InstanceManager.getDefault(DispatcherFrame.class).getUseScaleMeters()) { 366 tInfo.setMaxTrainLengthScaleMeters(Float.parseFloat(traininfo.getAttribute("maxtrainlength").getValue())); 367 } else { 368 tInfo.setMaxTrainLengthScaleFeet(Float.parseFloat(traininfo.getAttribute("maxtrainlength").getValue())); 369 } 370 } 371 } 372 if (traininfo.getAttribute("terminatewhendone") != null) { 373 tInfo.setTerminateWhenDone(false); 374 if (traininfo.getAttribute("terminatewhendone").getValue().equals("yes")) { 375 tInfo.setTerminateWhenDone(true); 376 } 377 } 378 if (traininfo.getAttribute("usespeedprofile") != null) { 379 tInfo.setUseSpeedProfile(false); 380 if (traininfo.getAttribute("usespeedprofile").getValue().equals("yes")) { 381 tInfo.setUseSpeedProfile(true); 382 } 383 } 384 if (traininfo.getAttribute("stopbyspeedprofile") != null) { 385 tInfo.setStopBySpeedProfile(false); 386 if (traininfo.getAttribute("stopbyspeedprofile").getValue().equals("yes")) { 387 tInfo.setStopBySpeedProfile(true); 388 } 389 } 390 391 // Read the per-train stop-by-speed-profile adjust factor (fraction of block length) 392 // Preferred attribute name since v5+: "stopbyspeedprofileadjust" 393 // Backward-compatibility: accept a couple of historical synonyms if present. 394 org.jdom2.Attribute adjAttr = traininfo.getAttribute("stopbyspeedprofileadjust"); 395 if (adjAttr == null) { 396 // Legacy fallbacks seen in older files/tests 397 adjAttr = traininfo.getAttribute("speedprofileadjust"); 398 } 399 if (adjAttr == null) { 400 adjAttr = traininfo.getAttribute("usespeedprofileadjust"); 401 } 402 if (adjAttr != null) { 403 try { 404 float v = adjAttr.getFloatValue(); 405 // Sanity: bounds to [0.0, 1.0] if needed; older content is a fraction (not percent) 406 if (v < 0.0f) v = 0.0f; 407 if (v > 1.0f) v = 1.0f; 408 tInfo.setStopBySpeedProfileAdjust(v); 409 } catch (org.jdom2.DataConversionException ex) { 410 // Malformed value -> leave adjust at default for backward compatibility 411 } 412 } 413 414 // Preferred: "overridestopsensor" (yes/no or true/false or 1/0) 415 // Fallback: legacy "usestopsensor" (yes/no or true/false or 1/0) 416 // Default: use sensors (existing behavior) 417 boolean useSensors = true; // default preserves existing behavior 418 419 if (traininfo.getAttribute("overridestopsensor") != null) { 420 String v = traininfo.getAttribute("overridestopsensor").getValue(); 421 boolean override = "yes".equalsIgnoreCase(v) || "true".equalsIgnoreCase(v) || "1".equals(v); 422 useSensors = !override; 423 } else if (traininfo.getAttribute("usestopsensor") != null) { 424 String v = traininfo.getAttribute("usestopsensor").getValue(); 425 boolean legacyUse = "yes".equalsIgnoreCase(v) || "true".equalsIgnoreCase(v) || "1".equals(v); 426 useSensors = legacyUse; 427 } 428 tInfo.setUseStopSensor(useSensors); 429 430 if (traininfo.getAttribute("stopbydistance_mm") != null) { 431 try { 432 float mm = traininfo.getAttribute("stopbydistance_mm").getFloatValue(); 433 if (mm > 0.0f) { 434 tInfo.setStopByDistanceMm(mm); 435 // HEAD by default; override if attribute present and equals "TAIL" 436 if (traininfo.getAttribute("stopbydistance_ref") != null) { 437 String ref = traininfo.getAttribute("stopbydistance_ref").getValue(); 438 if ("TAIL".equalsIgnoreCase(ref)) { 439 tInfo.setStopByDistanceRef(TrainInfo.StopReference.TAIL); 440 } else { 441 tInfo.setStopByDistanceRef(TrainInfo.StopReference.HEAD); 442 } 443 } 444 } 445 } catch (org.jdom2.DataConversionException ex) { 446 // Malformed value => leave unset for backward compatibility 447 } 448 } 449 450 if (traininfo.getAttribute("waittime") != null) { 451 tInfo.setWaitTime(traininfo.getAttribute("waittime").getFloatValue()); 452 } 453 if (traininfo.getAttribute("blockname") != null) { 454 tInfo.setBlockName(traininfo.getAttribute("blockname").getValue()); 455 } 456 if (traininfo.getAttribute("fnumberlight") != null) { 457 tInfo.setFNumberLight(Integer.parseInt(traininfo.getAttribute("fnumberlight").getValue())); 458 } 459 if (traininfo.getAttribute("fnumberbell") != null) { 460 tInfo.setFNumberBell(Integer.parseInt(traininfo.getAttribute("fnumberbell").getValue())); 461 } 462 if (traininfo.getAttribute("fnumberhorn") != null) { 463 tInfo.setFNumberHorn(Integer.parseInt(traininfo.getAttribute("fnumberhorn").getValue())); 464 } 465 466 // Physics: read additional train weight (metric tonnes) 467 if (traininfo.getAttribute("additionaltrainweight_tonnes") != null) { 468 try { 469 tInfo.setAdditionalTrainWeightMetricTonnes(traininfo.getAttribute("additionaltrainweight_tonnes").getFloatValue()); 470 } catch (org.jdom2.DataConversionException ex) { 471 // Malformed value -> leave default (0.0f) for backward compatibility 472 } 473 } 474 475 // Physics: read rolling resistance coefficient (dimensionless) 476 if (traininfo.getAttribute("rollingresistancecoeff") != null) { 477 try { 478 tInfo.setRollingResistanceCoeff(traininfo.getAttribute("rollingresistancecoeff").getFloatValue()); 479 } catch (org.jdom2.DataConversionException ex) { 480 // Malformed value -> leave default (0.002f) for backward compatibility 481 } 482 } 483 484 // Physics: read driver power percent (stored as fraction 0..1; default 1.0 if absent) 485 if (traininfo.getAttribute("driverpowerpercent") != null) { 486 try { 487 float dp = traininfo.getAttribute("driverpowerpercent").getFloatValue(); 488 if (dp < 0.0f) dp = 0.0f; 489 if (dp > 1.0f) dp = 1.0f; 490 tInfo.setDriverPowerPercent(dp); 491 } catch (org.jdom2.DataConversionException ex) { 492 // Malformed: ignore and leave TrainInfo default (typically 1.0f) 493 } 494 } 495 496 if (version == 1) { 497 String parseArray[]; 498 // If you only have a systemname then its everything before the dash 499 tInfo.setStartBlockId(tInfo.getStartBlockName().split("-")[0]); 500 // If you have a systemname and username you want everything before the open bracket 501 tInfo.setStartBlockId(tInfo.getStartBlockId().split("\\(")[0]); 502 // to guard against a dash in the names, we need the last part, not just [1] 503 parseArray = tInfo.getStartBlockName().split("-"); 504 tInfo.setStartBlockSeq(-1); // default value 505 if (parseArray.length > 0) { 506 try { 507 tInfo.setStartBlockSeq(Integer.parseInt(parseArray[parseArray.length -1])); 508 } 509 catch (NumberFormatException Ex) { 510 log.error("Invalid StartBlockSequence{}",parseArray[parseArray.length -1]); 511 } 512 } 513 // repeat for destination 514 tInfo.setDestinationBlockId(tInfo.getDestinationBlockName().split("-")[0]); 515 tInfo.setDestinationBlockId(tInfo.getDestinationBlockId().split("\\(")[0]); 516 parseArray = tInfo.getDestinationBlockName().split("-"); 517 tInfo.setDestinationBlockSeq(-1); 518 if (parseArray.length > 0) { 519 try { 520 tInfo.setDestinationBlockSeq(Integer.parseInt(parseArray[parseArray.length -1])); 521 } 522 catch (NumberFormatException Ex) { 523 log.error("Invalid StartBlockSequence{}",parseArray[parseArray.length -1]); 524 } 525 } 526 // Transit we need the whole thing or the bit before the first open bracket 527 tInfo.setTransitId(tInfo.getTransitName().split("\\(")[0]); 528 log.debug("v1: t = {}, bs = {}, be = {}", tInfo.getTransitName(), tInfo.getStartBlockName(), tInfo.getDestinationBlockName()); 529 } 530 if ( version > 1 ) { 531 if (traininfo.getAttribute("transitid") != null) { 532 // there is a transit name selected 533 tInfo.setTransitId(traininfo.getAttribute("transitid").getValue()); 534 } else { 535 log.error("Transit id missing when reading TrainInfoFile {}", name); 536 } 537 if (traininfo.getAttribute("startblockid") != null) { 538 // there is a transit name selected 539 tInfo.setStartBlockId(traininfo.getAttribute("startblockid").getValue()); 540 } else { 541 log.error("Start block Id missing when reading TrainInfoFile {}", name); 542 } 543 if (traininfo.getAttribute("endblockid") != null) { 544 // there is a transit name selected 545 tInfo.setDestinationBlockId(traininfo.getAttribute("endblockid").getValue()); 546 } else { 547 log.error("Destination block Id missing when reading TrainInfoFile {}", name); 548 } 549 if (traininfo.getAttribute("startblockseq") != null) { 550 // there is a transit name selected 551 try { 552 tInfo.setStartBlockSeq(traininfo.getAttribute("startblockseq").getIntValue()); 553 } 554 catch (org.jdom2.DataConversionException ex) { 555 log.error("Start block sequence invalid when reading TrainInfoFile"); 556 } 557 } else { 558 log.error("Start block sequence missing when reading TrainInfoFile {}", name); 559 } 560 if (traininfo.getAttribute("endblockseq") != null) { 561 // there is a transit name selected 562 try { 563 tInfo.setDestinationBlockSeq(traininfo.getAttribute("endblockseq").getIntValue()); 564 } 565 catch (org.jdom2.DataConversionException ex) { 566 log.error("Destination block sequence invalid when reading TrainInfoFile {}", name); 567 } 568 } else { 569 log.error("Destination block sequence missing when reading TrainInfoFile {}", name); 570 } 571 } 572 if ( version == 1 || version == 2) { 573 // Change transit and block names from sysName(userName) to displayName 574 tInfo.setTransitName(convertName(tInfo.getTransitName())); 575 tInfo.setStartBlockName(convertName(tInfo.getStartBlockName())); 576 tInfo.setDestinationBlockName(convertName(tInfo.getDestinationBlockName())); 577 } 578 } 579 } 580 } 581 return tInfo; 582 } 583 584 public String convertName(String name) { 585 // transit: sys(user), block: sys(user)-n 586 String newName = name; 587 588 Pattern p = Pattern.compile(".+\\((.+)\\)(-\\d+)*"); 589 Matcher m = p.matcher(name); 590 if (m.matches()) { 591 log.debug("regex: name = '{}', group 1 = '{}', group 2 = '{}'", name, m.group(1), m.group(2)); 592 if (m.group(1) != null) { 593 newName = m.group(1).trim(); 594 if (m.group(2) != null) { 595 newName = newName + m.group(2).trim(); 596 } 597 } 598 } 599 600 log.debug("convertName: old = '{}', new = '{}'", name, newName); 601 return newName; 602 } 603 604 /* 605 * Writes out Dispatcher options to a file in the user's preferences directory 606 */ 607 public void writeTrainInfo(TrainInfo tf, String name) throws java.io.IOException { 608 log.debug("entered writeTrainInfo"); 609 root = new Element("traininfofile"); 610 doc = newDocument(root, dtdLocation + "dispatcher-traininfo.dtd"); 611 // add XSLT processing instruction 612 // <?xml-stylesheet type="text/xsl" href="XSLT/block-values.xsl"?> 613 java.util.Map<String, String> m = new java.util.HashMap<>(); 614 m.put("type", "text/xsl"); 615 m.put("href", xsltLocation + "dispatcher-traininfo.xsl"); 616 org.jdom2.ProcessingInstruction p = new org.jdom2.ProcessingInstruction("xml-stylesheet", m); 617 doc.addContent(0, p); 618 619 // save Dispatcher TrainInfo in xml format 620 Element traininfo = new Element("traininfo"); 621 // write version number 622 traininfo.setAttribute("version", "8"); 623 traininfo.setAttribute("transitname", tf.getTransitName()); 624 traininfo.setAttribute("transitid", tf.getTransitId()); 625 traininfo.setAttribute("dynamictransit", (tf.getDynamicTransit() ? "yes" : "no")); 626 traininfo.setAttribute("trainname", tf.getTrainName()); 627 traininfo.setAttribute("trainusername", tf.getTrainUserName()); 628 traininfo.setAttribute("rosterid", tf.getRosterId()); 629 traininfo.setAttribute("dccaddress", tf.getDccAddress()); 630 traininfo.setAttribute("trainintransit", "" + (tf.getTrainInTransit() ? "yes" : "no")); 631 traininfo.setAttribute("startblockname", tf.getStartBlockName()); 632 traininfo.setAttribute("startblockid", tf.getStartBlockId()); 633 traininfo.setAttribute("startblockseq", Integer.toString(tf.getStartBlockSeq())); 634 traininfo.setAttribute("endblockname", tf.getDestinationBlockName()); 635 traininfo.setAttribute("endblockid", tf.getDestinationBlockId()); 636 traininfo.setAttribute("endblockseq", Integer.toString(tf.getDestinationBlockSeq())); 637 traininfo.setAttribute("viablockname", tf.getViaBlockName()); 638 traininfo.setAttribute("trainfromroster", "" + (tf.getTrainFromRoster() ? "yes" : "no")); 639 traininfo.setAttribute("trainfromtrains", "" + (tf.getTrainFromTrains() ? "yes" : "no")); 640 traininfo.setAttribute("trainfromuser", "" + (tf.getTrainFromUser() ? "yes" : "no")); 641 traininfo.setAttribute("trainfromsetlater", "" + (tf.getTrainFromSetLater() ? "yes" : "no")); 642 traininfo.setAttribute("priority", Integer.toString(tf.getPriority())); 643 traininfo.setAttribute("traindetection", trainsdectionFromEnumMap.outputFromEnum(tf.getTrainDetection())); 644 traininfo.setAttribute("resetwhendone", "" + (tf.getResetWhenDone() ? "yes" : "no")); 645 switch (tf.getDelayedRestart()) { 646 case ActiveTrain.SENSORDELAY: 647 traininfo.setAttribute("delayedrestart", "sensor"); 648 traininfo.setAttribute("delayedrestartsensor", tf.getRestartSensorName()); 649 traininfo.setAttribute("resetrestartsensor", "" + (tf.getResetRestartSensor() ? "yes" : "no")); 650 break; 651 case ActiveTrain.TIMEDDELAY: 652 traininfo.setAttribute("delayedrestart", "timed"); 653 traininfo.setAttribute("delayedrestarttime", Integer.toString(tf.getRestartDelayMin())); 654 break; 655 default: 656 traininfo.setAttribute("delayedrestart", "no"); 657 break; 658 } 659 660 traininfo.setAttribute("reverseatend", "" + (tf.getReverseAtEnd() ? "yes" : "no")); 661 switch (tf.getReverseDelayedRestart()) { 662 case ActiveTrain.SENSORDELAY: 663 traininfo.setAttribute("reversedelayedrestart", "sensor"); 664 traininfo.setAttribute("reversedelayedrestartsensor", tf.getReverseRestartSensorName()); 665 traininfo.setAttribute("reverseresetrestartsensor", "" + (tf.getReverseResetRestartSensor() ? "yes" : "no")); 666 break; 667 case ActiveTrain.TIMEDDELAY: 668 traininfo.setAttribute("reversedelayedrestart", "timed"); 669 traininfo.setAttribute("reversedelayedrestarttime", Integer.toString(tf.getReverseRestartDelayMin())); 670 break; 671 default: 672 traininfo.setAttribute("reversedelayedrestart", "no"); 673 break; 674 } 675 if (tf.getDelayedStart() == ActiveTrain.TIMEDDELAY) { 676 traininfo.setAttribute("delayedstart", "timed"); 677 } else if (tf.getDelayedStart() == ActiveTrain.SENSORDELAY) { 678 traininfo.setAttribute("delayedstart", "sensor"); 679 if (tf.getDelaySensorName() != null) { 680 traininfo.setAttribute("delayedSensor", tf.getDelaySensorName()); 681 traininfo.setAttribute("resetstartsensor", "" + (tf.getResetStartSensor() ? "yes" : "no")); 682 } 683 } 684 685 traininfo.setAttribute("terminatewhendone", (tf.getTerminateWhenDone() ? "yes" : "no")); 686 traininfo.setAttribute("departuretimehr", Integer.toString(tf.getDepartureTimeHr())); 687 traininfo.setAttribute("departuretimemin", Integer.toString(tf.getDepartureTimeMin())); 688 traininfo.setAttribute("traintype", tf.getTrainType()); 689 traininfo.setAttribute("autorun", "" + (tf.getAutoRun() ? "yes" : "no")); 690 traininfo.setAttribute("loadatstartup", "" + (tf.getLoadAtStartup() ? "yes" : "no")); 691 traininfo.setAttribute("allocatealltheway", "" + (tf.getAllocateAllTheWay() ? "yes" : "no")); 692 traininfo.setAttribute("allocationmethod", Integer.toString(tf.getAllocationMethod())); 693 traininfo.setAttribute("nexttrain", tf.getNextTrain()); 694 // here save items related to automatically running active trains 695 traininfo.setAttribute("speedfactor", Float.toString(tf.getSpeedFactor())); 696 traininfo.setAttribute("maxspeed", Float.toString(tf.getMaxSpeed())); 697 // Persist maximum speed in scale km/h when the user selected a speed cap. 698 // Omit when 0.0f (means "use throttle cap" for backward compatibility). 699 if (tf.getMaxSpeedScaleKmh() > 0.0f) { 700 traininfo.setAttribute("maxspeedscalekmh", Float.toString(tf.getMaxSpeedScaleKmh())); 701 } 702 traininfo.setAttribute("minreliableoperatingspeed", Float.toString(tf.getMinReliableOperatingSpeed())); 703 traininfo.setAttribute("ramprate", tf.getRampRate()); 704 traininfo.setAttribute("runinreverse", "" + (tf.getRunInReverse() ? "yes" : "no")); 705 traininfo.setAttribute("sounddecoder", "" + (tf.getSoundDecoder() ? "yes" : "no")); 706 traininfo.setAttribute("maxtrainlengthscalemeters", Float.toString(tf.getMaxTrainLengthScaleMeters())); 707 traininfo.setAttribute("trainlengthunits", trainlengthFromEnumMap.outputFromEnum(tf.getTrainLengthUnits())); 708 traininfo.setAttribute("usespeedprofile", "" + (tf.getUseSpeedProfile() ? "yes" : "no")); 709 traininfo.setAttribute("stopbyspeedprofile", "" + (tf.getStopBySpeedProfile() ? "yes" : "no")); 710 traininfo.setAttribute("stopbyspeedprofileadjust", Float.toString(tf.getStopBySpeedProfileAdjust())); 711 traininfo.setAttribute("usestopsensor", (tf.getUseStopSensor() ? "yes" : "no")); 712 traininfo.setAttribute("overridestopsensor", (tf.getUseStopSensor() ? "no" : "yes")); 713 traininfo.setAttribute("waittime", Float.toString(tf.getWaitTime())); 714 traininfo.setAttribute("blockname", tf.getBlockName()); 715 traininfo.setAttribute("fnumberlight", Integer.toString(tf.getFNumberLight())); 716 traininfo.setAttribute("fnumberbell", Integer.toString(tf.getFNumberBell())); 717 traininfo.setAttribute("fnumberhorn", Integer.toString(tf.getFNumberHorn())); 718 // Physics: persist additional train weight (metric tonnes, float) 719 traininfo.setAttribute("additionaltrainweight_tonnes", Float.toString(tf.getAdditionalTrainWeightMetricTonnes())); 720 // Physics: persist rolling resistance coefficient (dimensionless) 721 traininfo.setAttribute("rollingresistancecoeff", Float.toString(tf.getRollingResistanceCoeff())); 722 traininfo.setAttribute("driverpowerpercent", Float.toString(tf.getDriverPowerPercent())); 723 724 // Only write these when the user has set a positive distance in mm; otherwise omit. 725 if (tf.getStopByDistanceMm() > 0.0f) { 726 // Persist the explicit distance (mm) into the stop block 727 traininfo.setAttribute("stopbydistance_mm", Float.toString(tf.getStopByDistanceMm())); 728 // Persist whether the distance applies to the HEAD or TAIL of the train 729 traininfo.setAttribute("stopbydistance_ref", tf.getStopByDistanceRef().name()); // HEAD | TAIL 730 } 731 732 root.addContent(traininfo); 733 734 // write out the file 735 try { 736 if (!checkFile(fileLocation + name)) { 737 // file does not exist, create it 738 File file = new File(fileLocation + name); 739 if (!file.createNewFile()) // create file and check result 740 { 741 log.error("createNewFile failed"); 742 } 743 } 744 // write content to file 745 writeXML(findFile(fileLocation + name), doc); 746 } catch (java.io.IOException ioe) { 747 log.error("IO Exception writing", ioe); 748 throw (ioe); 749 } 750 } 751 752 /** 753 * Get the names of all current TrainInfo files. Returns names as an array 754 * of Strings. Returns an empty array if no files are present. Note: Fill 755 * names still end with .xml or .XML. (Modeled after a method in 756 * RecreateRosterAction.java by Bob Jacobsen) 757 * 758 * @return names as an array or an empty array if none present 759 */ 760 public String[] getTrainInfoFileNames() { 761 // ensure preferences will be found for read 762 FileUtil.createDirectory(fileLocation); 763 // create an array of file names from roster dir in preferences, count entries 764 List<String> names = new ArrayList<>(); 765 log.debug("directory of TrainInfoFiles is {}", fileLocation); 766 File fp = new File(fileLocation); 767 if (fp.exists()) { 768 String[] xmlList = fp.list(new XmlFilenameFilter()); 769 if (xmlList!=null) { 770 names.addAll(Arrays.asList(xmlList)); 771 } 772 } 773 // Sort the resulting array 774 names.sort((s1, s2) -> { 775 return s1.compareTo(s2); 776 }); 777 return names.toArray(new String[names.size()]); 778 } 779 780 /** 781 * Get the names of all current TrainInfo files. Returns list 782 * of files and some basic details for each file 783 *. 784 * @return names as an array or an empty array if none present 785 */ 786 public List<TrainInfoFileSummary> getTrainInfoFileSummaries() { 787 List<TrainInfoFileSummary> summaries = new ArrayList<>(); 788 for (String fileName : getTrainInfoFileNames()) { 789 try { 790 TrainInfo ti = readTrainInfo(fileName); 791 summaries.add(new TrainInfoFileSummary(fileName, ti.getTrainName(), ti.getTransitName(), 792 ti.getStartBlockName(), ti.getDestinationBlockName(), ti.getDccAddress())); 793 } catch (org.jdom2.JDOMException ex) { 794 summaries.add(new TrainInfoFileSummary(fileName)); 795 } catch (IOException ex) { 796 summaries.add(new TrainInfoFileSummary(fileName)); 797 } catch (RuntimeException ex) { 798 log.error("Traininfo File [{}] unexplained error, currupted?",fileName); 799 } 800 } 801 return summaries; 802 } 803 804 /** 805 * Delete a specified TrainInfo file. 806 * 807 * @param name the file to delete 808 */ 809 public void deleteTrainInfoFile(String name) { 810 // locate the file and delete it if it exists 811 File f = new File(fileLocation + name); 812 if (!f.delete()) { // delete file and check success 813 log.error("failed to delete TrainInfo file - {}", name); 814 } 815 } 816 817 private final static Logger log = LoggerFactory.getLogger(TrainInfoFile.class); 818}