001package jmri.managers.configurexml; 002 003import java.awt.GraphicsEnvironment; 004import java.util.List; 005import java.util.SortedSet; 006 007import jmri.InstanceManager; 008import jmri.Route; 009import jmri.RouteManager; 010import jmri.Sensor; 011import jmri.Turnout; 012import jmri.managers.DefaultRouteManager; 013import jmri.util.swing.JmriJOptionPane; 014 015import org.jdom2.Element; 016 017/** 018 * Provides the functionality for configuring RouteManagers. 019 * 020 * @author Dave Duchamp Copyright (c) 2004 021 * @author Daniel Boudreau Copyright (c) 2007 022 * @author Simon Reader Copyright (C) 2008 023 */ 024public class DefaultRouteManagerXml extends jmri.managers.configurexml.AbstractNamedBeanManagerConfigXML { 025 026 public DefaultRouteManagerXml() { 027 } 028 029 /** 030 * Default implementation for storing the contents of a RouteManager. 031 * 032 * @param o Object to store, of type RouteManager 033 * @return Element containing the complete info 034 */ 035 @Override 036 public Element store(Object o) { 037 Element routes = new Element("routes"); 038 setStoreElementClass(routes); 039 RouteManager rm = (RouteManager) o; 040 if (rm != null) { 041 SortedSet<Route> routeList = rm.getNamedBeanSet(); 042 // don't return an element if there are no routes to include 043 if (routeList.isEmpty()) { 044 return null; 045 } 046 for (Route r : routeList) { 047 // store the routes 048 String rName = r.getSystemName(); 049 log.debug("system name is {}", rName); 050 051 String cTurnout = r.getControlTurnout(); 052 int addedDelay = r.getRouteCommandDelay(); 053 boolean routeLocked = r.getLocked(); 054 String cLockTurnout = r.getLockControlTurnout(); 055 056 Element elem = new Element("route"); 057 elem.addContent(new Element("systemName").addContent(rName)); 058 059 // As a work-around for backward compatibility, store systemName and userName as attribute. 060 // TODO Remove this in e.g. JMRI 4.11.1 and then update all the loadref comparison files 061 String uName = r.getUserName(); 062 if (uName != null && !uName.equals("")) { 063 elem.setAttribute("userName", uName); 064 } 065 066 // store common parts 067 storeCommon(r, elem); 068 069 if (cTurnout != null && !cTurnout.equals("")) { 070 elem.setAttribute("controlTurnout", cTurnout); 071 int state = r.getControlTurnoutState(); 072 if (state == Route.ONTHROWN) { 073 elem.setAttribute("controlTurnoutState", "THROWN"); 074 } else if (state == Route.ONCHANGE) { 075 elem.setAttribute("controlTurnoutState", "CHANGE"); 076 } else if (state == Route.VETOCLOSED) { 077 elem.setAttribute("controlTurnoutState", "VETOCLOSED"); 078 } else if (state == Route.VETOTHROWN) { 079 elem.setAttribute("controlTurnoutState", "VETOTHROWN"); 080 } else { 081 elem.setAttribute("controlTurnoutState", "CLOSED"); 082 } 083 084 if (r.getControlTurnoutFeedback()) { 085 elem.setAttribute("controlTurnoutFeedback", "true"); 086 } // don't write if not set, accept default 087 } 088 if (cLockTurnout != null && !cLockTurnout.equals("")) { 089 elem.setAttribute("controlLockTurnout", cLockTurnout); 090 int state = r.getLockControlTurnoutState(); 091 if (state == Route.ONTHROWN) { 092 elem.setAttribute("controlLockTurnoutState", "THROWN"); 093 } else if (state == Route.ONCHANGE) { 094 elem.setAttribute("controlLockTurnoutState", "CHANGE"); 095 } else { 096 elem.setAttribute("controlLockTurnoutState", "CLOSED"); 097 } 098 } 099 if (addedDelay > 0) { 100 elem.setAttribute("addedDelay", Integer.toString(addedDelay)); 101 } 102 103 if (routeLocked) { 104 elem.setAttribute("routeLocked", "True"); 105 } 106 // add route output Turnouts, if any 107 int index = 0; 108 String rTurnout = null; 109 while ((rTurnout = r.getOutputTurnoutByIndex(index)) != null) { 110 Element rElem = new Element("routeOutputTurnout") 111 .setAttribute("systemName", rTurnout); 112 String sState = "CLOSED"; 113 if (r.getOutputTurnoutSetState(rTurnout) == Turnout.THROWN) { 114 sState = "THROWN"; 115 } else if (r.getOutputTurnoutSetState(rTurnout) == Route.TOGGLE) { 116 sState = "TOGGLE"; 117 } 118 rElem.setAttribute("state", sState); 119 elem.addContent(rElem); 120 index++; 121 } 122 // add route output Sensors, if any 123 index = 0; 124 String rSensor = null; 125 while ((rSensor = r.getOutputSensorByIndex(index)) != null) { 126 Element rElem = new Element("routeOutputSensor") 127 .setAttribute("systemName", rSensor); 128 String sState = "INACTIVE"; 129 if (r.getOutputSensorSetState(rSensor) == Sensor.ACTIVE) { 130 sState = "ACTIVE"; 131 } else if (r.getOutputSensorSetState(rSensor) == Route.TOGGLE) { 132 sState = "TOGGLE"; 133 } 134 rElem.setAttribute("state", sState); 135 elem.addContent(rElem); 136 index++; 137 } 138 // add route control Sensors, if any 139 index = 0; 140 while ((rSensor = r.getRouteSensorName(index)) != null) { 141 Element rsElem = new Element("routeSensor") 142 .setAttribute("systemName", rSensor); 143 int mode = r.getRouteSensorMode(index); 144 String modeName; 145 switch (mode) { 146 case Route.ONACTIVE: 147 modeName = "onActive"; 148 break; 149 case Route.ONINACTIVE: 150 modeName = "onInactive"; 151 break; 152 case Route.ONCHANGE: 153 modeName = "onChange"; 154 break; 155 case Route.VETOACTIVE: 156 modeName = "vetoActive"; 157 break; 158 case Route.VETOINACTIVE: 159 modeName = "vetoInactive"; 160 break; 161 default: 162 modeName = null; 163 } 164 if (modeName != null) { 165 rsElem.setAttribute("mode", modeName); 166 } 167 elem.addContent(rsElem); 168 index++; 169 } 170 // add sound and script file elements if needed 171 if (r.getOutputSoundName() != null && !r.getOutputSoundName().equals("")) { 172 Element rsElem = new Element("routeSoundFile") 173 .setAttribute("name", 174 jmri.util.FileUtil.getPortableFilename( 175 new java.io.File(r.getOutputSoundName())) 176 ); 177 elem.addContent(rsElem); 178 } 179 if (r.getOutputScriptName() != null && !r.getOutputScriptName().equals("")) { 180 Element rsElem = new Element("routeScriptFile") 181 .setAttribute("name", 182 jmri.util.FileUtil.getPortableFilename( 183 new java.io.File(r.getOutputScriptName())) 184 ); 185 elem.addContent(rsElem); 186 } 187 188 // add turnouts aligned sensor if there is one 189 if (!r.getTurnoutsAlignedSensor().equals("")) { 190 Element rsElem = new Element("turnoutsAlignedSensor") 191 .setAttribute("name", r.getTurnoutsAlignedSensor()); 192 elem.addContent(rsElem); 193 } 194 195 log.debug("store Route {}", rName); 196 routes.addContent(elem); 197 } 198 } 199 return routes; 200 } 201 202 /** 203 * Subclass provides implementation to create the correct top element, 204 * including the type information. Default implementation is to use the 205 * local class here. 206 * 207 * @param routes The top-level element being created 208 */ 209 public void setStoreElementClass(Element routes) { 210 routes.setAttribute("class", this.getClass().getName()); 211 } 212 213 /** 214 * Create a RouteManager object of the correct class, then register and fill 215 * it. 216 * 217 * @param sharedRoutes Top level Element to unpack. 218 * @return true if successful 219 */ 220 @Override 221 public boolean load(Element sharedRoutes, Element perNodeRoutes) { 222 // create the master object 223 replaceRouteManager(); 224 // load individual sharedRoutes 225 loadRoutes(sharedRoutes); 226 return true; 227 } 228 229 /** 230 * Utility method to load the individual Route objects. If there's no 231 * additional info needed for a specific route type, invoke this with the 232 * parent of the set of Route elements. 233 * 234 * @param routes Element containing the Route elements to load. 235 */ 236 public void loadRoutes(Element routes) { 237 List<Element> routeList = routes.getChildren("route"); 238 log.debug("Found {} routes", routeList.size()); 239 RouteManager tm = InstanceManager.getDefault(jmri.RouteManager.class); 240 int namesChanged = 0; 241 242 for (Element el : routeList) { 243 244 String sysName = getSystemName(el); 245 if (sysName == null) { 246 log.warn("unexpected null in systemName {}", el); 247 break; 248 } 249 // convert typeLetter from R to tm.typeLetter() 250 if (sysName.startsWith(tm.getSystemPrefix() + 'R')) { 251 String old = sysName; 252 sysName = tm.getSystemNamePrefix() + sysName.substring(tm.getSystemNamePrefix().length()); 253 log.warn("Converting route system name {} to {}", old, sysName); 254 namesChanged++; 255 } 256 // prepend systemNamePrefix if missing 257 if (!sysName.startsWith(tm.getSystemNamePrefix())) { 258 String old = sysName; 259 sysName = tm.getSystemNamePrefix() + sysName; 260 log.warn("Converting route system name {} to {}", old, sysName); 261 namesChanged++; 262 } 263 264 String userName = getUserName(el); 265 String cTurnout = null; 266 String cTurnoutState = null; 267 boolean cTurnoutFeedback = false; 268 String addedDelayTxt = null; 269 String routeLockedTxt = null; 270 String cLockTurnout = null; 271 String cLockTurnoutState = null; 272 int addedDelay = 0; 273 274 if (el.getAttribute("controlTurnout") != null) { 275 cTurnout = el.getAttribute("controlTurnout").getValue(); 276 } 277 if (el.getAttribute("controlTurnoutState") != null) { 278 cTurnoutState = el.getAttribute("controlTurnoutState").getValue(); 279 } 280 if (el.getAttribute("controlTurnoutFeedback") != null) { 281 cTurnoutFeedback = el.getAttribute("controlTurnoutFeedback").getValue().equals("true"); 282 } 283 if (el.getAttribute("controlLockTurnout") != null) { 284 cLockTurnout = el.getAttribute("controlLockTurnout").getValue(); 285 } 286 if (el.getAttribute("controlLockTurnoutState") != null) { 287 cLockTurnoutState = el.getAttribute("controlLockTurnoutState").getValue(); 288 } 289 if (el.getAttribute("addedDelay") != null) { 290 addedDelayTxt = el.getAttribute("addedDelay").getValue(); 291 if (addedDelayTxt != null) { 292 addedDelay = Integer.parseInt(addedDelayTxt); 293 } 294 } 295 if (el.getAttribute("routeLocked") != null) { 296 routeLockedTxt = el.getAttribute("routeLocked").getValue(); 297 } 298 299 log.debug("create route: ({})({})", sysName, (userName == null ? "<null>" : userName)); 300 301 Route r; 302 try { 303 r = tm.provideRoute(sysName, userName); 304 } catch (IllegalArgumentException ex) { 305 log.error("failed to create Route: {}", sysName); 306 return; 307 } 308 309 // load common parts 310 loadCommon(r, el); 311 312 // add control turnout if there is one 313 if (cTurnout != null) { 314 r.setControlTurnout(cTurnout); 315 if (cTurnoutState != null) { 316 switch (cTurnoutState) { 317 case "THROWN": 318 r.setControlTurnoutState(Route.ONTHROWN); 319 break; 320 case "CHANGE": 321 r.setControlTurnoutState(Route.ONCHANGE); 322 break; 323 case "VETOCLOSED": 324 r.setControlTurnoutState(Route.VETOCLOSED); 325 break; 326 case "VETOTHROWN": 327 r.setControlTurnoutState(Route.VETOTHROWN); 328 break; 329 default: 330 r.setControlTurnoutState(Route.ONCLOSED); 331 } 332 } else { 333 log.error("cTurnoutState was null!"); 334 } 335 r.setControlTurnoutFeedback(cTurnoutFeedback); 336 } 337 // set added delay 338 r.setRouteCommandDelay(addedDelay); 339 340 // determine if route locked 341 if (routeLockedTxt != null && routeLockedTxt.equals("True")) { 342 r.setLocked(true); 343 } 344 345 // add lock control turout if there is one 346 if (cLockTurnout != null) { 347 r.setLockControlTurnout(cLockTurnout); 348 if (cLockTurnoutState != null) { 349 if (cLockTurnoutState.equals("THROWN")) { 350 r.setLockControlTurnoutState(Route.ONTHROWN); 351 } else if (cLockTurnoutState.equals("CHANGE")) { 352 r.setLockControlTurnoutState(Route.ONCHANGE); 353 } else { 354 r.setLockControlTurnoutState(Route.ONCLOSED); 355 } 356 } else { 357 log.error("cLockTurnoutState was null!"); 358 } 359 } 360 361 // load output turnouts if there are any - old format first (1.7.6 and before) 362 List<Element> routeTurnoutList = el.getChildren("routeTurnout"); 363 if (routeTurnoutList.size() > 0) { 364 // This route has turnouts 365 for (Element element : routeTurnoutList) { 366 if (element.getAttribute("systemName") == null) { 367 log.warn("unexpected null in systemName {} {}", element, element.getAttributes()); 368 break; 369 } 370 String tSysName = element.getAttribute("systemName").getValue(); 371 String rState = element.getAttribute("state").getValue(); 372 int tSetState = Turnout.CLOSED; 373 if (rState.equals("THROWN")) { 374 tSetState = Turnout.THROWN; 375 } else if (rState.equals("TOGGLE")) { 376 tSetState = Route.TOGGLE; 377 } 378 // Add turnout to route 379 r.addOutputTurnout(tSysName, tSetState); 380 } 381 } 382 // load output turnouts if there are any - new format 383 routeTurnoutList = el.getChildren("routeOutputTurnout"); 384 if (routeTurnoutList.size() > 0) { 385 // This route has turnouts 386 for (int k = 0; k < routeTurnoutList.size(); k++) { // index k is required later to get Locked state 387 if (routeTurnoutList.get(k).getAttribute("systemName") == null) { 388 log.warn("unexpected null in systemName {} {}", routeTurnoutList.get(k), 389 routeTurnoutList.get(k).getAttributes()); 390 break; 391 } 392 String tSysName = routeTurnoutList.get(k) 393 .getAttribute("systemName").getValue(); 394 String rState = routeTurnoutList.get(k) 395 .getAttribute("state").getValue(); 396 int tSetState = Turnout.CLOSED; 397 if (rState.equals("THROWN")) { 398 tSetState = Turnout.THROWN; 399 } else if (rState.equals("TOGGLE")) { 400 tSetState = Route.TOGGLE; 401 } 402 // If the Turnout has already been added to the route and is the same as that loaded, 403 // we will not re add the turnout. 404 if (!r.isOutputTurnoutIncluded(tSysName)) { 405 406 // Add turnout to route 407 r.addOutputTurnout(tSysName, tSetState); 408 409 // determine if turnout should be locked 410 Turnout t = r.getOutputTurnout(k); 411 if (r.getLocked()) { 412 t.setLocked(Turnout.CABLOCKOUT + Turnout.PUSHBUTTONLOCKOUT, true); 413 } 414 } 415 } 416 } 417 // load output sensors if there are any - new format 418 List<Element> routeSensorList = el.getChildren("routeOutputSensor"); 419 for (Element sen : routeSensorList) { // this route has output sensors 420 if (sen.getAttribute("systemName") == null) { 421 log.warn("unexpected null in systemName {} {}", sen, sen.getAttributes()); 422 break; 423 } 424 String tSysName = sen.getAttribute("systemName").getValue(); 425 String rState = sen.getAttribute("state").getValue(); 426 int tSetState = Sensor.INACTIVE; 427 if (rState.equals("ACTIVE")) { 428 tSetState = Sensor.ACTIVE; 429 } else if (rState.equals("TOGGLE")) { 430 tSetState = Route.TOGGLE; 431 } 432 // If the Turnout has already been added to the route and is the same as that loaded, 433 // we will not re add the turnout. 434 if (r.isOutputSensorIncluded(tSysName)) { 435 break; 436 } 437 // Add turnout to route 438 r.addOutputSensor(tSysName, tSetState); 439 } 440 // load sound, script files if present 441 Element fileElement = el.getChild("routeSoundFile"); 442 if (fileElement != null) { 443 // convert to absolute path name 444 r.setOutputSoundName( 445 jmri.util.FileUtil.getExternalFilename(fileElement.getAttribute("name").getValue()) 446 ); 447 } 448 fileElement = el.getChild("routeScriptFile"); 449 if (fileElement != null) { 450 r.setOutputScriptName( 451 jmri.util.FileUtil.getExternalFilename(fileElement.getAttribute("name").getValue()) 452 ); 453 } 454 // load turnouts aligned sensor if there is one 455 fileElement = el.getChild("turnoutsAlignedSensor"); 456 if (fileElement != null) { 457 r.setTurnoutsAlignedSensor(fileElement.getAttribute("name").getValue()); 458 } 459 460 // load route control sensors, if there are any 461 routeSensorList = el.getChildren("routeSensor"); 462 for (Element sen : routeSensorList) { // this route has sensors 463 if (sen.getAttribute("systemName") == null) { 464 log.warn("unexpected null in systemName {} {}", sen, sen.getAttributes()); 465 break; 466 } 467 int mode = Route.ONACTIVE; // default mode 468 if (sen.getAttribute("mode") == null) { 469 break; 470 } 471 String sm = sen.getAttribute("mode").getValue(); 472 switch (sm) { 473 case "onActive": 474 mode = Route.ONACTIVE; 475 break; 476 case "onInactive": 477 mode = Route.ONINACTIVE; 478 break; 479 case "onChange": 480 mode = Route.ONCHANGE; 481 break; 482 case "vetoActive": 483 mode = Route.VETOACTIVE; 484 break; 485 case "vetoInactive": 486 mode = Route.VETOINACTIVE; 487 break; 488 default: 489 log.warn("unexpected sensor mode in route {} was {}", sysName, sm); 490 } 491 // Add Sensor to route 492 r.addSensorToRoute(sen.getAttribute("systemName").getValue(), mode); 493 } 494 // and start it working 495 r.activateRoute(); 496 } 497 if (namesChanged > 0) { 498 // TODO: replace the System property check with an in-application mechanism 499 // for notifying users of multiple changes that can be silenced as part of 500 // normal operations 501 if (!GraphicsEnvironment.isHeadless() && !Boolean.getBoolean("jmri.test.no-dialogs")) { 502 JmriJOptionPane.showMessageDialog(null, 503 Bundle.getMessage(namesChanged > 1 ? "RouteManager.SystemNamesChanged.Message" : "RouteManager.SystemNameChanged.Message", namesChanged), 504 Bundle.getMessage("Manager.SystemNamesChanged.Title", namesChanged, tm.getBeanTypeHandled(namesChanged > 1)), 505 JmriJOptionPane.WARNING_MESSAGE); 506 } 507 log.warn("System names for {} Routes changed; this may have operational impacts.", namesChanged); 508 } 509 } 510 511 /** 512 * Replace the current RouteManager, if there is one, with one newly created 513 * during a load operation. This is skipped if the present one is already of 514 * the right type. 515 */ 516 protected void replaceRouteManager() { 517 RouteManager current = InstanceManager.getNullableDefault(jmri.RouteManager.class); 518 if (current != null && current.getClass().getName() 519 .equals(DefaultRouteManager.class.getName())) { 520 return; 521 } 522 // if old manager exists, remove it from configuration process 523 if (current != null) { 524 InstanceManager.getDefault(jmri.ConfigureManager.class).deregister(current); 525 InstanceManager.deregister(current, RouteManager.class); 526 } 527 528 // register new one with InstanceManager 529 DefaultRouteManager pManager = InstanceManager.getDefault(DefaultRouteManager.class); 530 InstanceManager.store(pManager, RouteManager.class); 531 // register new one for configuration 532 InstanceManager.getDefault(jmri.ConfigureManager.class).registerConfig(pManager, jmri.Manager.ROUTES); 533 } 534 535 @Override 536 public int loadOrder() { 537 return InstanceManager.getDefault(jmri.RouteManager.class).getXMLOrder(); 538 } 539 540 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultRouteManagerXml.class); 541 542}