001package jmri.jmrit.operations.trains; 002 003import java.awt.Color; 004import java.beans.PropertyChangeListener; 005import java.io.*; 006import java.text.MessageFormat; 007import java.util.*; 008 009import org.jdom2.Element; 010 011import jmri.InstanceManager; 012import jmri.beans.Identifiable; 013import jmri.beans.PropertyChangeSupport; 014import jmri.jmrit.display.Editor; 015import jmri.jmrit.display.EditorManager; 016import jmri.jmrit.operations.locations.*; 017import jmri.jmrit.operations.rollingstock.RollingStock; 018import jmri.jmrit.operations.rollingstock.RollingStockManager; 019import jmri.jmrit.operations.rollingstock.cars.*; 020import jmri.jmrit.operations.rollingstock.engines.*; 021import jmri.jmrit.operations.routes.*; 022import jmri.jmrit.operations.setup.Control; 023import jmri.jmrit.operations.setup.Setup; 024import jmri.jmrit.operations.trains.excel.TrainCustomManifest; 025import jmri.jmrit.roster.RosterEntry; 026import jmri.script.JmriScriptEngineManager; 027import jmri.util.FileUtil; 028import jmri.util.swing.JmriJOptionPane; 029 030/** 031 * Represents a train on the layout 032 * 033 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 034 * 2014, 2015 035 * @author Rodney Black Copyright (C) 2011 036 */ 037public class Train extends PropertyChangeSupport implements Identifiable, PropertyChangeListener { 038 039 /* 040 * WARNING DO NOT LOAD CAR OR ENGINE MANAGERS WHEN Train.java IS CREATED IT 041 * CAUSES A RECURSIVE LOOP AT LOAD TIME, SEE EXAMPLES BELOW CarManager 042 * carManager = InstanceManager.getDefault(CarManager.class); EngineManager 043 * engineManager = InstanceManager.getDefault(EngineManager.class); 044 */ 045 046 // The release date for JMRI operations 10/29/2008 047 048 public static final String NONE = ""; 049 050 protected String _id = NONE; 051 protected String _name = NONE; 052 protected String _description = NONE; 053 protected RouteLocation _current = null;// where the train is located in its route 054 protected String _buildFailedMessage = NONE; // the build failed message for this train 055 protected boolean _built = false; // when true, a train manifest has been built 056 protected boolean _modified = false; // when true, user has modified train after being built 057 protected boolean _build = true; // when true, build this train 058 protected boolean _buildFailed = false; // when true, build for this train failed 059 protected boolean _printed = false; // when true, manifest has been printed 060 protected boolean _sendToTerminal = false; // when true, cars picked up by train only go to terminal 061 protected boolean _allowLocalMoves = true; // when true, cars with custom loads can be moved locally 062 protected boolean _allowThroughCars = true; // when true, cars from the origin can be sent to the terminal 063 protected boolean _buildNormal = false; // when true build this train in normal mode 064 protected boolean _allowCarsReturnStaging = false; // when true allow cars to return to staging 065 protected boolean _serviceAllCarsWithFinalDestinations = false; // when true, service cars with final destinations 066 protected boolean _buildConsist = false; // when true, build a consist for this train using single locomotives 067 protected boolean _sendCarsWithCustomLoadsToStaging = false; // when true, send cars to staging if spurs full 068 protected Route _route = null; 069 protected Track _departureTrack; // the departure track from staging 070 protected Track _terminationTrack; // the termination track into staging 071 protected String _carRoadOption = ALL_ROADS;// train car road name restrictions 072 protected String _locoRoadOption = ALL_ROADS;// train engine road name restrictions 073 protected int _requires = NO_CABOOSE_OR_FRED; // train requirements, caboose, FRED 074 protected String _numberEngines = "0"; // number of engines this train requires 075 protected String _engineRoad = NONE; // required road name for engines assigned to this train 076 protected String _engineModel = NONE; // required model of engines assigned to this train 077 protected String _cabooseRoad = NONE; // required road name for cabooses assigned to this train 078 protected String _departureTime = "00:00"; // NOI18N departure time for this train 079 protected String _leadEngineId = NONE; // lead engine for train icon info 080 protected String _builtStartYear = NONE; // built start year 081 protected String _builtEndYear = NONE; // built end year 082 protected String _loadOption = ALL_LOADS;// train load restrictions 083 protected String _ownerOption = ALL_OWNERS;// train owner name restrictions 084 protected List<String> _buildScripts = new ArrayList<>(); // list of script pathnames to run before train is built 085 protected List<String> _afterBuildScripts = new ArrayList<>(); // list of script pathnames to run after train is 086 // built 087 protected List<String> _moveScripts = new ArrayList<>(); // list of script pathnames to run when train is moved 088 protected List<String> _terminationScripts = new ArrayList<>(); // list of script pathnames to run when train is 089 // terminated 090 protected String _railroadName = NONE; // optional railroad name for this train 091 protected String _logoPathName = NONE; // optional manifest logo for this train 092 protected boolean _showTimes = true; // when true, show arrival and departure times for this train 093 protected Engine _leadEngine = null; // lead engine for icon 094 protected String _switchListStatus = UNKNOWN; // print switch list status 095 protected String _comment = NONE; 096 protected String _serviceStatus = NONE; // status only if train is being built 097 protected int _statusCode = CODE_UNKNOWN; 098 protected int _oldStatusCode = CODE_UNKNOWN; 099 protected String _statusTerminatedDate = NONE; 100 protected int _statusCarsRequested = 0; 101 protected String _tableRowColorName = NONE; // color of row in Trains table 102 protected String _tableRowColorResetName = NONE; // color of row in Trains table when reset 103 104 // Engine change and helper engines 105 protected int _leg2Options = NO_CABOOSE_OR_FRED; // options 106 protected RouteLocation _leg2Start = null; // route location when 2nd leg begins 107 protected RouteLocation _end2Leg = null; // route location where 2nd leg ends 108 protected String _leg2Engines = "0"; // number of engines 2nd leg 109 protected String _leg2Road = NONE; // engine road name 2nd leg 110 protected String _leg2Model = NONE; // engine model 2nd leg 111 protected String _leg2CabooseRoad = NONE; // road name for caboose 2nd leg 112 113 protected int _leg3Options = NO_CABOOSE_OR_FRED; // options 114 protected RouteLocation _leg3Start = null; // route location when 3rd leg begins 115 protected RouteLocation _leg3End = null; // route location where 3rd leg ends 116 protected String _leg3Engines = "0"; // number of engines 3rd leg 117 protected String _leg3Road = NONE; // engine road name 3rd leg 118 protected String _leg3Model = NONE; // engine model 3rd leg 119 protected String _leg3CabooseRoad = NONE; // road name for caboose 3rd leg 120 121 // engine change and helper options 122 public static final int CHANGE_ENGINES = 1; // change engines 123 public static final int HELPER_ENGINES = 2; // add helper engines 124 public static final int ADD_CABOOSE = 4; // add caboose 125 public static final int REMOVE_CABOOSE = 8; // remove caboose 126 127 // property change names 128 public static final String DISPOSE_CHANGED_PROPERTY = "TrainDispose"; // NOI18N 129 public static final String STOPS_CHANGED_PROPERTY = "TrainStops"; // NOI18N 130 public static final String TYPES_CHANGED_PROPERTY = "TrainTypes"; // NOI18N 131 public static final String BUILT_CHANGED_PROPERTY = "TrainBuilt"; // NOI18N 132 public static final String BUILT_YEAR_CHANGED_PROPERTY = "TrainBuiltYear"; // NOI18N 133 public static final String BUILD_CHANGED_PROPERTY = "TrainBuild"; // NOI18N 134 public static final String ROADS_CHANGED_PROPERTY = "TrainRoads"; // NOI18N 135 public static final String LOADS_CHANGED_PROPERTY = "TrainLoads"; // NOI18N 136 public static final String OWNERS_CHANGED_PROPERTY = "TrainOwners"; // NOI18N 137 public static final String NAME_CHANGED_PROPERTY = "TrainName"; // NOI18N 138 public static final String DESCRIPTION_CHANGED_PROPERTY = "TrainDescription"; // NOI18N 139 public static final String STATUS_CHANGED_PROPERTY = "TrainStatus"; // NOI18N 140 public static final String DEPARTURETIME_CHANGED_PROPERTY = "TrainDepartureTime"; // NOI18N 141 public static final String TRAIN_LOCATION_CHANGED_PROPERTY = "TrainLocation"; // NOI18N 142 public static final String TRAIN_ROUTE_CHANGED_PROPERTY = "TrainRoute"; // NOI18N 143 public static final String TRAIN_REQUIREMENTS_CHANGED_PROPERTY = "TrainRequirements"; // NOI18N 144 public static final String TRAIN_MOVE_COMPLETE_CHANGED_PROPERTY = "TrainMoveComplete"; // NOI18N 145 public static final String TRAIN_ROW_COLOR_CHANGED_PROPERTY = "TrianRowColor"; // NOI18N 146 public static final String TRAIN_ROW_COLOR_RESET_CHANGED_PROPERTY = "TrianRowColorReset"; // NOI18N 147 public static final String TRAIN_MODIFIED_CHANGED_PROPERTY = "TrainModified"; // NOI18N 148 149 // Train status 150 public static final String TRAIN_RESET = Bundle.getMessage("TrainReset"); 151 public static final String RUN_SCRIPTS = Bundle.getMessage("RunScripts"); 152 public static final String BUILDING = Bundle.getMessage("Building"); 153 public static final String BUILD_FAILED = Bundle.getMessage("BuildFailed"); 154 public static final String BUILT = Bundle.getMessage("Built"); 155 public static final String PARTIAL_BUILT = Bundle.getMessage("Partial"); 156 public static final String TRAIN_EN_ROUTE = Bundle.getMessage("TrainEnRoute"); 157 public static final String TERMINATED = Bundle.getMessage("Terminated"); 158 public static final String MANIFEST_MODIFIED = Bundle.getMessage("Modified"); 159 160 // Train status codes 161 public static final int CODE_TRAIN_RESET = 0; 162 public static final int CODE_RUN_SCRIPTS = 0x100; 163 public static final int CODE_BUILDING = 0x01; 164 public static final int CODE_BUILD_FAILED = 0x02; 165 public static final int CODE_BUILT = 0x10; 166 public static final int CODE_PARTIAL_BUILT = CODE_BUILT + 0x04; 167 public static final int CODE_TRAIN_EN_ROUTE = CODE_BUILT + 0x08; 168 public static final int CODE_TERMINATED = 0x80; 169 public static final int CODE_MANIFEST_MODIFIED = 0x200; 170 public static final int CODE_UNKNOWN = 0xFFFF; 171 172 // train requirements 173 public static final int NO_CABOOSE_OR_FRED = 0; // default 174 public static final int CABOOSE = 1; 175 public static final int FRED = 2; 176 177 // road options 178 public static final String ALL_ROADS = Bundle.getMessage("All"); 179 public static final String INCLUDE_ROADS = Bundle.getMessage("Include"); 180 public static final String EXCLUDE_ROADS = Bundle.getMessage("Exclude"); 181 182 // owner options 183 public static final String ALL_OWNERS = Bundle.getMessage("All"); 184 public static final String INCLUDE_OWNERS = Bundle.getMessage("Include"); 185 public static final String EXCLUDE_OWNERS = Bundle.getMessage("Exclude"); 186 187 // load options 188 public static final String ALL_LOADS = Bundle.getMessage("All"); 189 public static final String INCLUDE_LOADS = Bundle.getMessage("Include"); 190 public static final String EXCLUDE_LOADS = Bundle.getMessage("Exclude"); 191 192 // Switch list status 193 public static final String UNKNOWN = ""; 194 public static final String PRINTED = Bundle.getMessage("Printed"); 195 196 public static final String AUTO = Bundle.getMessage("Auto"); 197 public static final String AUTO_HPT = Bundle.getMessage("AutoHPT"); 198 199 public Train(String id, String name) { 200 log.debug("New train ({}) id: {}", name, id); 201 _name = name; 202 _id = id; 203 // a new train accepts all types 204 setTypeNames(InstanceManager.getDefault(CarTypes.class).getNames()); 205 setTypeNames(InstanceManager.getDefault(EngineTypes.class).getNames()); 206 addPropertyChangeListerners(); 207 } 208 209 @Override 210 public String getId() { 211 return _id; 212 } 213 214 /** 215 * Sets the name of this train, normally a short name that can fit within the 216 * train icon. 217 * 218 * @param name the train's name. 219 */ 220 public void setName(String name) { 221 String old = _name; 222 _name = name; 223 if (!old.equals(name)) { 224 setDirtyAndFirePropertyChange(NAME_CHANGED_PROPERTY, old, name); 225 } 226 } 227 228 // for combo boxes 229 /** 230 * Get's a train's name 231 * 232 * @return train's name 233 */ 234 @Override 235 public String toString() { 236 return _name; 237 } 238 239 /** 240 * Get's a train's name 241 * 242 * @return train's name 243 */ 244 public String getName() { 245 return _name; 246 } 247 248 /** 249 * @return The name of the color when highlighting the train's row 250 */ 251 public String getTableRowColorName() { 252 return _tableRowColorName; 253 } 254 255 public void setTableRowColorName(String colorName) { 256 String old = _tableRowColorName; 257 _tableRowColorName = colorName; 258 if (!old.equals(colorName)) { 259 setDirtyAndFirePropertyChange(TRAIN_ROW_COLOR_CHANGED_PROPERTY, old, colorName); 260 } 261 } 262 263 /** 264 * @return The name of the train row color when the train is reset 265 */ 266 public String getTableRowColorNameReset() { 267 return _tableRowColorResetName; 268 } 269 270 public void setTableRowColorNameReset(String colorName) { 271 String old = _tableRowColorResetName; 272 _tableRowColorResetName = colorName; 273 if (!old.equals(colorName)) { 274 setDirtyAndFirePropertyChange(TRAIN_ROW_COLOR_RESET_CHANGED_PROPERTY, old, colorName); 275 } 276 } 277 278 /** 279 * @return The color when highlighting the train's row 280 */ 281 public Color getTableRowColor() { 282 String colorName = getTableRowColorName(); 283 if (colorName.equals(NONE)) { 284 return null; 285 } else { 286 return Setup.getColor(colorName); 287 } 288 } 289 290 /** 291 * Get's train's departure time 292 * 293 * @return train's departure time in the String format hh:mm 294 */ 295 public String getDepartureTime() { 296 // check to see if the route has a departure time 297 RouteLocation rl = getTrainDepartsRouteLocation(); 298 if (rl != null) { 299 rl.removePropertyChangeListener(this); 300 rl.addPropertyChangeListener(this); 301 if (!rl.getDepartureTime().equals(RouteLocation.NONE)) { 302 return rl.getDepartureTime(); 303 } 304 } 305 return _departureTime; 306 } 307 308 /** 309 * Get's train's departure time in 12hr or 24hr format 310 * 311 * @return train's departure time in the String format hh:mm or hh:mm(AM/PM) 312 */ 313 public String getFormatedDepartureTime() { 314 // check to see if the route has a departure time 315 RouteLocation rl = getTrainDepartsRouteLocation(); 316 if (rl != null && !rl.getDepartureTime().equals(RouteLocation.NONE)) { 317 // need to forward any changes to departure time 318 rl.removePropertyChangeListener(this); 319 rl.addPropertyChangeListener(this); 320 return rl.getFormatedDepartureTime(); 321 } 322 return (parseTime(getDepartTimeMinutes())); 323 } 324 325 /** 326 * Get train's departure time in minutes from midnight for sorting 327 * 328 * @return int hh*60+mm 329 */ 330 public int getDepartTimeMinutes() { 331 int hour = Integer.parseInt(getDepartureTimeHour()); 332 int minute = Integer.parseInt(getDepartureTimeMinute()); 333 return (hour * 60) + minute; 334 } 335 336 public void setDepartureTime(String hour, String minute) { 337 String old = _departureTime; 338 int h = Integer.parseInt(hour); 339 if (h < 10) { 340 hour = "0" + h; 341 } 342 int m = Integer.parseInt(minute); 343 if (m < 10) { 344 minute = "0" + m; 345 } 346 String time = hour + ":" + minute; 347 _departureTime = time; 348 if (!old.equals(time)) { 349 setDirtyAndFirePropertyChange(DEPARTURETIME_CHANGED_PROPERTY, old, _departureTime); 350 setModified(true); 351 } 352 } 353 354 public String getDepartureTimeHour() { 355 String[] time = getDepartureTime().split(":"); 356 return time[0]; 357 } 358 359 public String getDepartureTimeMinute() { 360 String[] time = getDepartureTime().split(":"); 361 return time[1]; 362 } 363 364 public static final String ALREADY_SERVICED = "-1"; // NOI18N 365 366 /** 367 * Gets the expected time when this train will arrive at the location rl. 368 * Expected arrival time is based on the number of car pick up and set outs for 369 * this train. TODO Doesn't provide expected arrival time if train is in route, 370 * instead provides relative time. If train is at or has passed the location 371 * return -1. 372 * 373 * @param routeLocation The RouteLocation. 374 * @return expected arrival time in minutes (append AM or PM if 12 hour format) 375 */ 376 public String getExpectedArrivalTime(RouteLocation routeLocation) { 377 int minutes = getExpectedTravelTimeInMinutes(routeLocation); 378 if (minutes == -1) { 379 return ALREADY_SERVICED; 380 } 381 log.debug("Expected arrival time for train ({}) at ({}), {} minutes", getName(), routeLocation.getName(), 382 minutes); 383 // TODO use fast clock to get current time vs departure time 384 // for now use relative 385 return parseTime(minutes); 386 } 387 388 public String getExpectedDepartureTime(RouteLocation routeLocation) { 389 int minutes = getExpectedTravelTimeInMinutes(routeLocation); 390 if (minutes == -1) { 391 return ALREADY_SERVICED; 392 } 393 // figure out the work at this location, note that there can be 394 // consecutive locations with the same name 395 if (getRoute() != null) { 396 boolean foundRouteLocation = false; 397 for (RouteLocation rl : getRoute().getLocationsBySequenceList()) { 398 if (rl == routeLocation) { 399 foundRouteLocation = true; 400 } 401 if (foundRouteLocation) { 402 if (rl.getSplitName() 403 .equals(routeLocation.getSplitName())) { 404 minutes = minutes + getWorkTimeAtLocation(rl); 405 } else { 406 break; // done 407 } 408 } 409 } 410 } 411 log.debug("Expected departure time {} for train ({}) at ({})", minutes, getName(), routeLocation.getName()); 412 return parseTime(minutes); 413 } 414 415 public int getWorkTimeAtLocation(RouteLocation routeLocation) { 416 int minutes = 0; 417 // departure? 418 if (routeLocation == getTrainDepartsRouteLocation()) { 419 return minutes; 420 } 421 // add any work at this location 422 for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) { 423 if (rs.getRouteLocation() == routeLocation && !rs.getTrackName().equals(RollingStock.NONE)) { 424 minutes += Setup.getSwitchTime(); 425 } 426 if (rs.getRouteDestination() == routeLocation) { 427 minutes += Setup.getSwitchTime(); 428 } 429 } 430 return minutes; 431 } 432 433 public int getExpectedTravelTimeInMinutes(RouteLocation routeLocation) { 434 int minutes = 0; 435 if (!isTrainEnRoute()) { 436 minutes += Integer.parseInt(getDepartureTimeMinute()); 437 minutes += 60 * Integer.parseInt(getDepartureTimeHour()); 438 } else { 439 minutes = -1; // -1 means train has already served the location 440 } 441 // boolean trainAt = false; 442 boolean trainLocFound = false; 443 if (getRoute() != null) { 444 List<RouteLocation> routeList = getRoute().getLocationsBySequenceList(); 445 for (int i = 0; i < routeList.size(); i++) { 446 RouteLocation rl = routeList.get(i); 447 if (rl == routeLocation) { 448 break; // done 449 } 450 // start recording time after finding where the train is 451 if (!trainLocFound && isTrainEnRoute()) { 452 if (rl == getCurrentRouteLocation()) { 453 trainLocFound = true; 454 // add travel time 455 minutes = Setup.getTravelTime(); 456 } 457 continue; 458 } 459 // is there a departure time from this location? 460 if (!rl.getDepartureTime().equals(RouteLocation.NONE)) { 461 String dt = rl.getDepartureTime(); 462 log.debug("Location {} departure time {}", rl.getName(), dt); 463 String[] time = dt.split(":"); 464 minutes = 60 * Integer.parseInt(time[0]) + Integer.parseInt(time[1]); 465 // log.debug("New minutes: "+minutes); 466 } 467 // add wait time 468 minutes += rl.getWait(); 469 // add travel time if new location 470 RouteLocation next = routeList.get(i + 1); 471 if (next != null && 472 !rl.getSplitName().equals(next.getSplitName())) { 473 minutes += Setup.getTravelTime(); 474 } 475 // don't count work if there's a departure time 476 if (i == 0 || !rl.getDepartureTime().equals(RouteLocation.NONE)) { 477 continue; 478 } 479 // now add the work at the location 480 minutes = minutes + getWorkTimeAtLocation(rl); 481 } 482 } 483 return minutes; 484 } 485 486 /** 487 * Returns time in hour:minute format 488 * 489 * @param minutes number of minutes from midnight 490 * @return hour:minute (optionally AM:PM format) 491 */ 492 private String parseTime(int minutes) { 493 int hours = 0; 494 int days = 0; 495 496 if (minutes >= 60) { 497 int h = minutes / 60; 498 minutes = minutes - h * 60; 499 hours += h; 500 } 501 502 String d = ""; 503 if (hours >= 24) { 504 int nd = hours / 24; 505 hours = hours - nd * 24; 506 days += nd; 507 d = Integer.toString(days) + ":"; 508 } 509 510 // AM_PM field 511 String am_pm = ""; 512 if (Setup.is12hrFormatEnabled()) { 513 am_pm = " " + Bundle.getMessage("AM"); 514 if (hours >= 12) { 515 hours = hours - 12; 516 am_pm = " " + Bundle.getMessage("PM"); 517 } 518 if (hours == 0) { 519 hours = 12; 520 } 521 } 522 523 String h = Integer.toString(hours); 524 if (hours < 10) { 525 h = "0" + h; 526 } 527 if (minutes < 10) { 528 return d + h + ":0" + minutes + am_pm; // NOI18N 529 } 530 return d + h + ":" + minutes + am_pm; 531 } 532 533 /** 534 * Set train requirements. If NO_CABOOSE_OR_FRED, then train doesn't require a 535 * caboose or car with FRED. 536 * 537 * @param requires NO_CABOOSE_OR_FRED, CABOOSE, FRED 538 */ 539 public void setRequirements(int requires) { 540 int old = _requires; 541 _requires = requires; 542 if (old != requires) { 543 setDirtyAndFirePropertyChange(TRAIN_REQUIREMENTS_CHANGED_PROPERTY, Integer.toString(old), 544 Integer.toString(requires)); 545 } 546 } 547 548 /** 549 * Get a train's requirements with regards to the last car in the train. 550 * 551 * @return NONE CABOOSE FRED 552 */ 553 public int getRequirements() { 554 return _requires; 555 } 556 557 public boolean isCabooseNeeded() { 558 return (getRequirements() & CABOOSE) == CABOOSE; 559 } 560 561 public boolean isFredNeeded() { 562 return (getRequirements() & FRED) == FRED; 563 } 564 565 public void setRoute(Route route) { 566 Route old = _route; 567 String oldRoute = NONE; 568 String newRoute = NONE; 569 if (old != null) { 570 old.removePropertyChangeListener(this); 571 oldRoute = old.toString(); 572 } 573 if (route != null) { 574 route.addPropertyChangeListener(this); 575 newRoute = route.toString(); 576 } 577 _route = route; 578 _skipLocationsList.clear(); 579 if (old == null || !old.equals(route)) { 580 setDirtyAndFirePropertyChange(TRAIN_ROUTE_CHANGED_PROPERTY, oldRoute, newRoute); 581 } 582 } 583 584 /** 585 * Gets the train's route 586 * 587 * @return train's route 588 */ 589 public Route getRoute() { 590 return _route; 591 } 592 593 /** 594 * Get's the train's route name. 595 * 596 * @return Train's route name. 597 */ 598 public String getTrainRouteName() { 599 if (getRoute() == null) { 600 return NONE; 601 } 602 return getRoute().getName(); 603 } 604 605 /** 606 * Get the train's departure location's name 607 * 608 * @return train's departure location's name 609 */ 610 public String getTrainDepartsName() { 611 if (getTrainDepartsRouteLocation() != null) { 612 return getTrainDepartsRouteLocation().getName(); 613 } 614 return NONE; 615 } 616 617 public RouteLocation getTrainDepartsRouteLocation() { 618 if (getRoute() == null) { 619 return null; 620 } 621 return getRoute().getDepartsRouteLocation(); 622 } 623 624 public String getTrainDepartsDirection() { 625 String direction = NONE; 626 if (getTrainDepartsRouteLocation() != null) { 627 direction = getTrainDepartsRouteLocation().getTrainDirectionString(); 628 } 629 return direction; 630 } 631 632 /** 633 * Get train's final location's name 634 * 635 * @return train's final location's name 636 */ 637 public String getTrainTerminatesName() { 638 if (getTrainTerminatesRouteLocation() != null) { 639 return getTrainTerminatesRouteLocation().getName(); 640 } 641 return NONE; 642 } 643 644 public RouteLocation getTrainTerminatesRouteLocation() { 645 if (getRoute() == null) { 646 return null; 647 } 648 return getRoute().getTerminatesRouteLocation(); 649 } 650 651 /** 652 * Returns the order the train should be blocked. 653 * 654 * @return routeLocations for this train. 655 */ 656 public List<RouteLocation> getTrainBlockingOrder() { 657 if (getRoute() == null) { 658 return null; 659 } 660 return getRoute().getBlockingOrder(); 661 } 662 663 /** 664 * Set train's current route location 665 * 666 * @param location The current RouteLocation. 667 */ 668 protected void setCurrentLocation(RouteLocation location) { 669 RouteLocation old = _current; 670 _current = location; 671 if ((old != null && !old.equals(location)) || (old == null && location != null)) { 672 setDirtyAndFirePropertyChange("current", old, location); // NOI18N 673 } 674 } 675 676 /** 677 * Get train's current location name 678 * 679 * @return Train's current route location name 680 */ 681 public String getCurrentLocationName() { 682 if (getCurrentRouteLocation() == null) { 683 return NONE; 684 } 685 return getCurrentRouteLocation().getName(); 686 } 687 688 /** 689 * Get train's current route location 690 * 691 * @return Train's current route location 692 */ 693 public RouteLocation getCurrentRouteLocation() { 694 if (getRoute() == null) { 695 return null; 696 } 697 if (_current == null) { 698 return null; 699 } 700 // this will verify that the current location still exists 701 return getRoute().getLocationById(_current.getId()); 702 } 703 704 /** 705 * Get the train's next location name 706 * 707 * @return Train's next route location name 708 */ 709 public String getNextLocationName() { 710 return getNextLocationName(1); 711 } 712 713 /** 714 * Get a location name in a train's route from the current train's location. A 715 * number of "1" means get the next location name in a train's route. 716 * 717 * @param number The stop number, must be greater than 0 718 * @return Name of the location that is the number of stops away from the 719 * train's current location. 720 */ 721 public String getNextLocationName(int number) { 722 RouteLocation rl = getCurrentRouteLocation(); 723 while (number-- > 0) { 724 rl = getNextRouteLocation(rl); 725 if (rl == null) { 726 return NONE; 727 } 728 } 729 return rl.getName(); 730 } 731 732 public RouteLocation getNextRouteLocation(RouteLocation currentRouteLocation) { 733 if (getRoute() == null) { 734 return null; 735 } 736 List<RouteLocation> routeList = getRoute().getLocationsBySequenceList(); 737 for (int i = 0; i < routeList.size(); i++) { 738 RouteLocation rl = routeList.get(i); 739 if (rl == currentRouteLocation) { 740 i++; 741 if (i < routeList.size()) { 742 return routeList.get(i); 743 } 744 break; 745 } 746 } 747 return null; // At end of route 748 } 749 750 public void setDepartureTrack(Track track) { 751 Track old = _departureTrack; 752 _departureTrack = track; 753 if (old != track) { 754 setDirtyAndFirePropertyChange("DepartureTrackChanged", old, track); // NOI18N 755 } 756 } 757 758 public Track getDepartureTrack() { 759 return _departureTrack; 760 } 761 762 public boolean isDepartingStaging() { 763 return getDepartureTrack() != null; 764 } 765 766 public void setTerminationTrack(Track track) { 767 Track old = _terminationTrack; 768 _terminationTrack = track; 769 if (old != track) { 770 setDirtyAndFirePropertyChange("TerminationTrackChanged", old, track); // NOI18N 771 } 772 } 773 774 public Track getTerminationTrack() { 775 return _terminationTrack; 776 } 777 778 /** 779 * Set the train's machine readable status. Calls update train table row color. 780 * 781 * @param code machine readable 782 */ 783 public void setStatusCode(int code) { 784 String oldStatus = getStatus(); 785 int oldCode = getStatusCode(); 786 _statusCode = code; 787 // always fire property change for train en route 788 if (oldCode != getStatusCode() || code == CODE_TRAIN_EN_ROUTE) { 789 setDirtyAndFirePropertyChange(STATUS_CHANGED_PROPERTY, oldStatus, getStatus()); 790 } 791 updateTrainTableRowColor(); 792 } 793 794 public void updateTrainTableRowColor() { 795 if (!InstanceManager.getDefault(TrainManager.class).isRowColorManual()) { 796 switch (getStatusCode()) { 797 case CODE_TRAIN_RESET: 798 String color = getTableRowColorNameReset(); 799 if (color.equals(NONE)) { 800 color = InstanceManager.getDefault(TrainManager.class).getRowColorNameForReset(); 801 } 802 setTableRowColorName(color); 803 break; 804 case CODE_BUILT: 805 case CODE_PARTIAL_BUILT: 806 setTableRowColorName(InstanceManager.getDefault(TrainManager.class).getRowColorNameForBuilt()); 807 break; 808 case CODE_BUILD_FAILED: 809 setTableRowColorName( 810 InstanceManager.getDefault(TrainManager.class).getRowColorNameForBuildFailed()); 811 break; 812 case CODE_TRAIN_EN_ROUTE: 813 setTableRowColorName( 814 InstanceManager.getDefault(TrainManager.class).getRowColorNameForTrainEnRoute()); 815 break; 816 case CODE_TERMINATED: 817 setTableRowColorName(InstanceManager.getDefault(TrainManager.class).getRowColorNameForTerminated()); 818 break; 819 default: // all other cases do nothing 820 break; 821 } 822 } 823 } 824 825 /** 826 * Get train's status in the default locale. 827 * 828 * @return Human-readable status 829 */ 830 public String getStatus() { 831 return this.getStatus(Locale.getDefault()); 832 } 833 834 /** 835 * Get train's status in the specified locale. 836 * 837 * @param locale The Locale. 838 * @return Human-readable status 839 */ 840 public String getStatus(Locale locale) { 841 return this.getStatus(locale, this.getStatusCode()); 842 } 843 844 /** 845 * Get the human-readable status for the requested status code. 846 * 847 * @param locale The Locale. 848 * @param code requested status 849 * @return Human-readable status 850 */ 851 public String getStatus(Locale locale, int code) { 852 switch (code) { 853 case CODE_RUN_SCRIPTS: 854 return RUN_SCRIPTS; 855 case CODE_BUILDING: 856 return BUILDING; 857 case CODE_BUILD_FAILED: 858 return BUILD_FAILED; 859 case CODE_BUILT: 860 return Bundle.getMessage(locale, "StatusBuilt", this.getNumberCarsWorked()); // NOI18N 861 case CODE_PARTIAL_BUILT: 862 return Bundle.getMessage(locale, "StatusPartialBuilt", this.getNumberCarsWorked(), 863 this.getNumberCarsRequested()); // NOI18N 864 case CODE_TERMINATED: 865 return Bundle.getMessage(locale, "StatusTerminated", this.getTerminationDate()); // NOI18N 866 case CODE_TRAIN_EN_ROUTE: 867 return Bundle.getMessage(locale, "StatusEnRoute", this.getNumberCarsInTrain(), this.getTrainLength(), 868 Setup.getLengthUnit().toLowerCase(), this.getTrainWeight()); // NOI18N 869 case CODE_TRAIN_RESET: 870 return TRAIN_RESET; 871 case CODE_MANIFEST_MODIFIED: 872 return MANIFEST_MODIFIED; 873 case CODE_UNKNOWN: 874 default: 875 return UNKNOWN; 876 } 877 } 878 879 public String getMRStatus() { 880 switch (getStatusCode()) { 881 case CODE_PARTIAL_BUILT: 882 return getStatusCode() + "||" + this.getNumberCarsRequested(); // NOI18N 883 case CODE_TERMINATED: 884 return getStatusCode() + "||" + this.getTerminationDate(); // NOI18N 885 default: 886 return Integer.toString(getStatusCode()); 887 } 888 } 889 890 public int getStatusCode() { 891 return _statusCode; 892 } 893 894 protected void setOldStatusCode(int code) { 895 _oldStatusCode = code; 896 } 897 898 protected int getOldStatusCode() { 899 return _oldStatusCode; 900 } 901 902 /** 903 * Used to determine if train has departed the first location in the train's 904 * route 905 * 906 * @return true if train has departed 907 */ 908 public boolean isTrainEnRoute() { 909 return !getCurrentLocationName().equals(NONE) && getTrainDepartsRouteLocation() != getCurrentRouteLocation(); 910 } 911 912 /** 913 * Used to determine if train is a local switcher serving one location. Note the 914 * train can have more than location in its route, but all location names must 915 * be "same". See TrainCommon.splitString(String name) for the definition of the 916 * "same" name. 917 * 918 * @return true if local switcher 919 */ 920 public boolean isLocalSwitcher() { 921 String departureName = TrainCommon.splitString(getTrainDepartsName()); 922 Route route = getRoute(); 923 if (route != null) { 924 for (RouteLocation rl : route.getLocationsBySequenceList()) { 925 if (!departureName.equals(rl.getSplitName())) { 926 return false; // not a local switcher 927 } 928 } 929 } 930 return true; 931 } 932 933 public boolean isTurn() { 934 return !isLocalSwitcher() && 935 TrainCommon.splitString(getTrainDepartsName()) 936 .equals(TrainCommon.splitString(getTrainTerminatesName())); 937 } 938 939 /** 940 * Used to determine if train is carrying only passenger cars. 941 * 942 * @return true if only passenger cars have been assigned to this train. 943 */ 944 public boolean isOnlyPassengerCars() { 945 for (Car car : InstanceManager.getDefault(CarManager.class).getList(this)) { 946 if (!car.isPassenger()) { 947 return false; 948 } 949 } 950 return true; 951 } 952 953 List<String> _skipLocationsList = new ArrayList<>(); 954 955 protected String[] getTrainSkipsLocations() { 956 String[] locationIds = new String[_skipLocationsList.size()]; 957 for (int i = 0; i < _skipLocationsList.size(); i++) { 958 locationIds[i] = _skipLocationsList.get(i); 959 } 960 return locationIds; 961 } 962 963 protected void setTrainSkipsLocations(String[] locationIds) { 964 if (locationIds.length > 0) { 965 Arrays.sort(locationIds); 966 for (String id : locationIds) { 967 _skipLocationsList.add(id); 968 } 969 } 970 } 971 972 /** 973 * Train will skip the RouteLocation 974 * 975 * @param routelocationId RouteLocation Id 976 */ 977 public void addTrainSkipsLocation(String routelocationId) { 978 // insert at start of _skipLocationsList, sort later 979 if (!_skipLocationsList.contains(routelocationId)) { 980 _skipLocationsList.add(0, routelocationId); 981 log.debug("train does not stop at {}", routelocationId); 982 setDirtyAndFirePropertyChange(STOPS_CHANGED_PROPERTY, _skipLocationsList.size() - 1, 983 _skipLocationsList.size()); 984 } 985 } 986 987 public void deleteTrainSkipsLocation(String locationId) { 988 _skipLocationsList.remove(locationId); 989 log.debug("train will stop at {}", locationId); 990 setDirtyAndFirePropertyChange(STOPS_CHANGED_PROPERTY, _skipLocationsList.size() + 1, _skipLocationsList.size()); 991 } 992 993 /** 994 * Determines if this train skips a location (doesn't service the location). 995 * 996 * @param locationId The route location id. 997 * @return true if the train will not service the location. 998 */ 999 public boolean isLocationSkipped(String locationId) { 1000 return _skipLocationsList.contains(locationId); 1001 } 1002 1003 List<String> _typeList = new ArrayList<>(); 1004 1005 /** 1006 * Get's the type names of rolling stock this train will service 1007 * 1008 * @return The type names for cars and or engines 1009 */ 1010 protected String[] getTypeNames() { 1011 return _typeList.toArray(new String[0]); 1012 } 1013 1014 public String[] getCarTypeNames() { 1015 List<String> list = new ArrayList<>(); 1016 for (String type : _typeList) { 1017 if (InstanceManager.getDefault(CarTypes.class).containsName(type)) { 1018 list.add(type); 1019 } 1020 } 1021 return list.toArray(new String[0]); 1022 } 1023 1024 public String[] getLocoTypeNames() { 1025 List<String> list = new ArrayList<>(); 1026 for (String type : _typeList) { 1027 if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) { 1028 list.add(type); 1029 } 1030 } 1031 return list.toArray(new String[0]); 1032 } 1033 1034 /** 1035 * Set the type of cars or engines this train will service, see types in Cars 1036 * and Engines. 1037 * 1038 * @param types The type names for cars and or engines 1039 */ 1040 protected void setTypeNames(String[] types) { 1041 if (types.length > 0) { 1042 Arrays.sort(types); 1043 for (String type : types) { 1044 _typeList.add(type); 1045 } 1046 } 1047 } 1048 1049 /** 1050 * Add a car or engine type name that this train will service. 1051 * 1052 * @param type The new type name to service. 1053 */ 1054 public void addTypeName(String type) { 1055 // insert at start of list, sort later 1056 if (type == null || _typeList.contains(type)) { 1057 return; 1058 } 1059 _typeList.add(0, type); 1060 log.debug("Train ({}) add car type ({})", getName(), type); 1061 setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() - 1, _typeList.size()); 1062 } 1063 1064 public void deleteTypeName(String type) { 1065 if (_typeList.remove(type)) { 1066 log.debug("Train ({}) delete car type ({})", getName(), type); 1067 setDirtyAndFirePropertyChange(TYPES_CHANGED_PROPERTY, _typeList.size() + 1, _typeList.size()); 1068 } 1069 } 1070 1071 /** 1072 * Returns true if this train will service the type of car or engine. 1073 * 1074 * @param type The car or engine type name. 1075 * @return true if this train will service the particular type. 1076 */ 1077 public boolean isTypeNameAccepted(String type) { 1078 return _typeList.contains(type); 1079 } 1080 1081 protected void replaceType(String oldType, String newType) { 1082 if (isTypeNameAccepted(oldType)) { 1083 deleteTypeName(oldType); 1084 addTypeName(newType); 1085 // adjust loads with type in them 1086 for (String load : getLoadNames()) { 1087 String[] splitLoad = load.split(CarLoad.SPLIT_CHAR); 1088 if (splitLoad.length > 1) { 1089 if (splitLoad[0].equals(oldType)) { 1090 deleteLoadName(load); 1091 if (newType != null) { 1092 load = newType + CarLoad.SPLIT_CHAR + splitLoad[1]; 1093 addLoadName(load); 1094 } 1095 } 1096 } 1097 } 1098 } 1099 } 1100 1101 /** 1102 * Get how this train deals with car road names. 1103 * 1104 * @return ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS 1105 */ 1106 public String getCarRoadOption() { 1107 return _carRoadOption; 1108 } 1109 1110 /** 1111 * Set how this train deals with car road names. 1112 * 1113 * @param option ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS 1114 */ 1115 public void setCarRoadOption(String option) { 1116 String old = _carRoadOption; 1117 _carRoadOption = option; 1118 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option); 1119 } 1120 1121 List<String> _carRoadList = new ArrayList<>(); 1122 1123 protected void setCarRoadNames(String[] roads) { 1124 setRoadNames(roads, _carRoadList); 1125 } 1126 1127 /** 1128 * Provides a list of car road names that the train will either service or exclude. 1129 * See setCarRoadOption 1130 * 1131 * @return Array of sorted road names as Strings 1132 */ 1133 public String[] getCarRoadNames() { 1134 String[] roads = _carRoadList.toArray(new String[0]); 1135 if (_carRoadList.size() > 0) { 1136 Arrays.sort(roads); 1137 } 1138 return roads; 1139 } 1140 1141 /** 1142 * Add a car road name that the train will either service or exclude. See 1143 * setCarRoadOption 1144 * 1145 * @param road The string road name. 1146 * @return true if road name was added, false if road name wasn't in the list. 1147 */ 1148 public boolean addCarRoadName(String road) { 1149 if (_carRoadList.contains(road)) { 1150 return false; 1151 } 1152 _carRoadList.add(road); 1153 log.debug("train ({}) add car road {}", getName(), road); 1154 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _carRoadList.size() - 1, _carRoadList.size()); 1155 return true; 1156 } 1157 1158 /** 1159 * Delete a car road name that the train will either service or exclude. See 1160 * setRoadOption 1161 * 1162 * @param road The string road name to delete. 1163 * @return true if road name was removed, false if road name wasn't in the list. 1164 */ 1165 public boolean deleteCarRoadName(String road) { 1166 if (_carRoadList.remove(road)) { 1167 log.debug("train ({}) delete car road {}", getName(), road); 1168 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _carRoadList.size() + 1, _carRoadList.size()); 1169 return true; 1170 } 1171 return false; 1172 } 1173 1174 /** 1175 * Determine if train will service a specific road name for a car. 1176 * 1177 * @param road the road name to check. 1178 * @return true if train will service this road name. 1179 */ 1180 public boolean isCarRoadNameAccepted(String road) { 1181 if (_carRoadOption.equals(ALL_ROADS)) { 1182 return true; 1183 } 1184 if (_carRoadOption.equals(INCLUDE_ROADS)) { 1185 return _carRoadList.contains(road); 1186 } 1187 // exclude! 1188 return !_carRoadList.contains(road); 1189 } 1190 1191 /** 1192 * Get how this train deals with locomotive road names. 1193 * 1194 * @return ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS 1195 */ 1196 public String getLocoRoadOption() { 1197 return _locoRoadOption; 1198 } 1199 1200 /** 1201 * Set how this train deals with locomotive road names. 1202 * 1203 * @param option ALL_ROADS INCLUDE_ROADS EXCLUDE_ROADS 1204 */ 1205 public void setLocoRoadOption(String option) { 1206 String old = _locoRoadOption; 1207 _locoRoadOption = option; 1208 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, old, option); 1209 } 1210 1211 List<String> _locoRoadList = new ArrayList<>(); 1212 1213 protected void setLocoRoadNames(String[] roads) { 1214 setRoadNames(roads, _locoRoadList); 1215 } 1216 1217 private void setRoadNames(String[] roads, List<String> list) { 1218 if (roads.length > 0) { 1219 Arrays.sort(roads); 1220 for (String road : roads) { 1221 if (!road.isEmpty()) { 1222 list.add(road); 1223 } 1224 } 1225 } 1226 } 1227 1228 /** 1229 * Provides a list of engine road names that the train will either service or exclude. 1230 * See setLocoRoadOption 1231 * 1232 * @return Array of sorted road names as Strings 1233 */ 1234 public String[] getLocoRoadNames() { 1235 String[] roads = _locoRoadList.toArray(new String[0]); 1236 if (_locoRoadList.size() > 0) { 1237 Arrays.sort(roads); 1238 } 1239 return roads; 1240 } 1241 1242 /** 1243 * Add a engine road name that the train will either service or exclude. See 1244 * setLocoRoadOption 1245 * 1246 * @param road The string road name. 1247 * @return true if road name was added, false if road name wasn't in the list. 1248 */ 1249 public boolean addLocoRoadName(String road) { 1250 if (_locoRoadList.contains(road)) { 1251 return false; 1252 } 1253 _locoRoadList.add(road); 1254 log.debug("train ({}) add engine road {}", getName(), road); 1255 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _locoRoadList.size() - 1, _locoRoadList.size()); 1256 return true; 1257 } 1258 1259 /** 1260 * Delete a engine road name that the train will either service or exclude. See 1261 * setLocoRoadOption 1262 * 1263 * @param road The string road name to delete. 1264 * @return true if road name was removed, false if road name wasn't in the list. 1265 */ 1266 public boolean deleteLocoRoadName(String road) { 1267 if (_locoRoadList.remove(road)) { 1268 log.debug("train ({}) delete engine road {}", getName(), road); 1269 setDirtyAndFirePropertyChange(ROADS_CHANGED_PROPERTY, _locoRoadList.size() + 1, _locoRoadList.size()); 1270 return true; 1271 } 1272 return false; 1273 } 1274 1275 /** 1276 * Determine if train will service a specific road name for an engine. 1277 * 1278 * @param road the road name to check. 1279 * @return true if train will service this road name. 1280 */ 1281 public boolean isLocoRoadNameAccepted(String road) { 1282 if (_locoRoadOption.equals(ALL_ROADS)) { 1283 return true; 1284 } 1285 if (_locoRoadOption.equals(INCLUDE_ROADS)) { 1286 return _locoRoadList.contains(road); 1287 } 1288 // exclude! 1289 return !_locoRoadList.contains(road); 1290 } 1291 1292 protected void replaceRoad(String oldRoad, String newRoad) { 1293 if (newRoad != null) { 1294 if (deleteCarRoadName(oldRoad)) { 1295 addCarRoadName(newRoad); 1296 } 1297 if (deleteLocoRoadName(oldRoad)) { 1298 addLocoRoadName(newRoad); 1299 } 1300 if (getEngineRoad().equals(oldRoad)) { 1301 setEngineRoad(newRoad); 1302 } 1303 if (getCabooseRoad().equals(oldRoad)) { 1304 setCabooseRoad(newRoad); 1305 } 1306 if (getSecondLegEngineRoad().equals(oldRoad)) { 1307 setSecondLegEngineRoad(newRoad); 1308 } 1309 if (getSecondLegCabooseRoad().equals(oldRoad)) { 1310 setSecondLegCabooseRoad(newRoad); 1311 } 1312 if (getThirdLegEngineRoad().equals(oldRoad)) { 1313 setThirdLegEngineRoad(newRoad); 1314 } 1315 if (getThirdLegCabooseRoad().equals(oldRoad)) { 1316 setThirdLegCabooseRoad(newRoad); 1317 } 1318 } 1319 } 1320 1321 /** 1322 * Gets the car load option for this train. 1323 * 1324 * @return ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS 1325 */ 1326 public String getLoadOption() { 1327 return _loadOption; 1328 } 1329 1330 /** 1331 * Set how this train deals with car loads 1332 * 1333 * @param option ALL_LOADS INCLUDE_LOADS EXCLUDE_LOADS 1334 */ 1335 public void setLoadOption(String option) { 1336 String old = _loadOption; 1337 _loadOption = option; 1338 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, old, option); 1339 } 1340 1341 List<String> _loadList = new ArrayList<>(); 1342 1343 protected void setLoadNames(String[] loads) { 1344 if (loads.length > 0) { 1345 Arrays.sort(loads); 1346 for (String load : loads) { 1347 if (!load.isEmpty()) { 1348 _loadList.add(load); 1349 } 1350 } 1351 } 1352 } 1353 1354 /** 1355 * Provides a list of loads that the train will either service or exclude. See 1356 * setLoadOption 1357 * 1358 * @return Array of load names as Strings 1359 */ 1360 public String[] getLoadNames() { 1361 String[] loads = _loadList.toArray(new String[0]); 1362 if (_loadList.size() > 0) { 1363 Arrays.sort(loads); 1364 } 1365 return loads; 1366 } 1367 1368 /** 1369 * Add a load that the train will either service or exclude. See setLoadOption 1370 * 1371 * @param load The string load name. 1372 * @return true if load name was added, false if load name wasn't in the list. 1373 */ 1374 public boolean addLoadName(String load) { 1375 if (_loadList.contains(load)) { 1376 return false; 1377 } 1378 _loadList.add(load); 1379 log.debug("train ({}) add car load {}", getName(), load); 1380 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() - 1, _loadList.size()); 1381 return true; 1382 } 1383 1384 /** 1385 * Delete a load name that the train will either service or exclude. See 1386 * setLoadOption 1387 * 1388 * @param load The string load name. 1389 * @return true if load name was removed, false if load name wasn't in the list. 1390 */ 1391 public boolean deleteLoadName(String load) { 1392 if (_loadList.remove(load)) { 1393 log.debug("train ({}) delete car load {}", getName(), load); 1394 setDirtyAndFirePropertyChange(LOADS_CHANGED_PROPERTY, _loadList.size() + 1, _loadList.size()); 1395 return true; 1396 } 1397 return false; 1398 } 1399 1400 /** 1401 * Determine if train will service a specific load name. 1402 * 1403 * @param load the load name to check. 1404 * @return true if train will service this load. 1405 */ 1406 public boolean isLoadNameAccepted(String load) { 1407 if (_loadOption.equals(ALL_LOADS)) { 1408 return true; 1409 } 1410 if (_loadOption.equals(INCLUDE_LOADS)) { 1411 return _loadList.contains(load); 1412 } 1413 // exclude! 1414 return !_loadList.contains(load); 1415 } 1416 1417 /** 1418 * Determine if train will service a specific load and car type. 1419 * 1420 * @param load the load name to check. 1421 * @param type the type of car used to carry the load. 1422 * @return true if train will service this load. 1423 */ 1424 public boolean isLoadNameAccepted(String load, String type) { 1425 if (_loadOption.equals(ALL_LOADS)) { 1426 return true; 1427 } 1428 if (_loadOption.equals(INCLUDE_LOADS)) { 1429 return _loadList.contains(load) || _loadList.contains(type + CarLoad.SPLIT_CHAR + load); 1430 } 1431 // exclude! 1432 return !_loadList.contains(load) && !_loadList.contains(type + CarLoad.SPLIT_CHAR + load); 1433 } 1434 1435 public String getOwnerOption() { 1436 return _ownerOption; 1437 } 1438 1439 /** 1440 * Set how this train deals with car owner names 1441 * 1442 * @param option ALL_OWNERS INCLUDE_OWNERS EXCLUDE_OWNERS 1443 */ 1444 public void setOwnerOption(String option) { 1445 String old = _ownerOption; 1446 _ownerOption = option; 1447 setDirtyAndFirePropertyChange(OWNERS_CHANGED_PROPERTY, old, option); 1448 } 1449 1450 List<String> _ownerList = new ArrayList<>(); 1451 1452 protected void setOwnerNames(String[] owners) { 1453 if (owners.length > 0) { 1454 Arrays.sort(owners); 1455 for (String owner : owners) { 1456 if (!owner.isEmpty()) { 1457 _ownerList.add(owner); 1458 } 1459 } 1460 } 1461 } 1462 1463 /** 1464 * Provides a list of owner names that the train will either service or exclude. 1465 * See setOwnerOption 1466 * 1467 * @return Array of owner names as Strings 1468 */ 1469 public String[] getOwnerNames() { 1470 String[] owners = _ownerList.toArray(new String[0]); 1471 if (_ownerList.size() > 0) { 1472 Arrays.sort(owners); 1473 } 1474 return owners; 1475 } 1476 1477 /** 1478 * Add a owner name that the train will either service or exclude. See 1479 * setOwnerOption 1480 * 1481 * @param owner The string representing the owner's name. 1482 * @return true if owner name was added, false if owner name wasn't in the list. 1483 */ 1484 public boolean addOwnerName(String owner) { 1485 if (_ownerList.contains(owner)) { 1486 return false; 1487 } 1488 _ownerList.add(owner); 1489 log.debug("train ({}) add car owner {}", getName(), owner); 1490 setDirtyAndFirePropertyChange(OWNERS_CHANGED_PROPERTY, _ownerList.size() - 1, _ownerList.size()); 1491 return true; 1492 } 1493 1494 /** 1495 * Delete a owner name that the train will either service or exclude. See 1496 * setOwnerOption 1497 * 1498 * @param owner The string representing the owner's name. 1499 * @return true if owner name was removed, false if owner name wasn't in the 1500 * list. 1501 */ 1502 public boolean deleteOwnerName(String owner) { 1503 if (_ownerList.remove(owner)) { 1504 log.debug("train ({}) delete car owner {}", getName(), owner); 1505 setDirtyAndFirePropertyChange(OWNERS_CHANGED_PROPERTY, _ownerList.size() + 1, _ownerList.size()); 1506 return true; 1507 } 1508 return false; 1509 } 1510 1511 /** 1512 * Determine if train will service a specific owner name. 1513 * 1514 * @param owner the owner name to check. 1515 * @return true if train will service this owner name. 1516 */ 1517 public boolean isOwnerNameAccepted(String owner) { 1518 if (_ownerOption.equals(ALL_OWNERS)) { 1519 return true; 1520 } 1521 if (_ownerOption.equals(INCLUDE_OWNERS)) { 1522 return _ownerList.contains(owner); 1523 } 1524 // exclude! 1525 return !_ownerList.contains(owner); 1526 } 1527 1528 protected void replaceOwner(String oldName, String newName) { 1529 if (deleteOwnerName(oldName)) { 1530 addOwnerName(newName); 1531 } 1532 } 1533 1534 /** 1535 * Only rolling stock built in or after this year will be used. 1536 * 1537 * @param year A string representing a year. 1538 */ 1539 public void setBuiltStartYear(String year) { 1540 String old = _builtStartYear; 1541 _builtStartYear = year; 1542 if (!old.equals(year)) { 1543 setDirtyAndFirePropertyChange(BUILT_YEAR_CHANGED_PROPERTY, old, year); 1544 } 1545 } 1546 1547 public String getBuiltStartYear() { 1548 return _builtStartYear; 1549 } 1550 1551 /** 1552 * Only rolling stock built in or before this year will be used. 1553 * 1554 * @param year A string representing a year. 1555 */ 1556 public void setBuiltEndYear(String year) { 1557 String old = _builtEndYear; 1558 _builtEndYear = year; 1559 if (!old.equals(year)) { 1560 setDirtyAndFirePropertyChange(BUILT_YEAR_CHANGED_PROPERTY, old, year); 1561 } 1562 } 1563 1564 public String getBuiltEndYear() { 1565 return _builtEndYear; 1566 } 1567 1568 /** 1569 * Determine if train will service rolling stock by built date. 1570 * 1571 * @param date A string representing the built date for a car or engine. 1572 * @return true is built date is in the acceptable range. 1573 */ 1574 public boolean isBuiltDateAccepted(String date) { 1575 if (getBuiltStartYear().equals(NONE) && getBuiltEndYear().equals(NONE)) { 1576 return true; // range dates not defined 1577 } 1578 int startYear = 0; // default start year; 1579 int endYear = 99999; // default end year; 1580 int builtYear = -1900; 1581 if (!getBuiltStartYear().equals(NONE)) { 1582 try { 1583 startYear = Integer.parseInt(getBuiltStartYear()); 1584 } catch (NumberFormatException e) { 1585 log.debug("Train ({}) built start date not initialized, start: {}", getName(), getBuiltStartYear()); 1586 } 1587 } 1588 if (!getBuiltEndYear().equals(NONE)) { 1589 try { 1590 endYear = Integer.parseInt(getBuiltEndYear()); 1591 } catch (NumberFormatException e) { 1592 log.debug("Train ({}) built end date not initialized, end: {}", getName(), getBuiltEndYear()); 1593 } 1594 } 1595 try { 1596 builtYear = Integer.parseInt(RollingStockManager.convertBuildDate(date)); 1597 } catch (NumberFormatException e) { 1598 log.debug("Unable to parse car built date {}", date); 1599 } 1600 if (startYear < builtYear && builtYear < endYear) { 1601 return true; 1602 } 1603 return false; 1604 } 1605 1606 private final boolean debugFlag = false; 1607 1608 /** 1609 * Determines if this train will service this car. Note this code doesn't check 1610 * the location or tracks that needs to be done separately. See Router.java. 1611 * 1612 * @param car The car to be tested. 1613 * @return true if this train can service the car. 1614 */ 1615 public boolean isServiceable(Car car) { 1616 return isServiceable(null, car); 1617 } 1618 1619 /** 1620 * Note that this code was written after TrainBuilder. It does pretty much the 1621 * same as TrainBuilder but with much fewer build report messages. 1622 * 1623 * @param buildReport PrintWriter 1624 * @param car the car to be tested 1625 * @return true if this train can service the car. 1626 */ 1627 public boolean isServiceable(PrintWriter buildReport, Car car) { 1628 setServiceStatus(NONE); 1629 // check to see if train can carry car 1630 if (!isTypeNameAccepted(car.getTypeName())) { 1631 addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarType", 1632 getName(), car.toString(), car.getTypeName())); 1633 return false; 1634 } 1635 if (!isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1636 addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarLoad", 1637 getName(), car.toString(), car.getTypeName(), car.getLoadName())); 1638 return false; 1639 } 1640 if (!isBuiltDateAccepted(car.getBuilt()) || 1641 !isOwnerNameAccepted(car.getOwnerName()) || 1642 !isCarRoadNameAccepted(car.getRoadName())) { 1643 addLine(buildReport, Bundle.getMessage("trainCanNotServiceCar", 1644 getName(), car.toString())); 1645 return false; 1646 } 1647 1648 Route route = getRoute(); 1649 if (route == null) { 1650 return false; 1651 } 1652 1653 if (car.getLocation() == null || car.getTrack() == null) { 1654 return false; 1655 } 1656 1657 // determine if the car's location and destination is serviced by this 1658 // train 1659 if (route.getLastLocationByName(car.getLocationName()) == null) { 1660 addLine(buildReport, Bundle.getMessage("trainNotThisLocation", 1661 getName(), car.getLocationName())); 1662 return false; 1663 } 1664 if (car.getDestination() != null && route.getLastLocationByName(car.getDestinationName()) == null) { 1665 addLine(buildReport, Bundle.getMessage("trainNotThisLocation", 1666 getName(), car.getDestinationName())); 1667 return false; 1668 } 1669 // now find the car in the train's route 1670 List<RouteLocation> rLocations = route.getLocationsBySequenceList(); 1671 for (RouteLocation rLoc : rLocations) { 1672 if (rLoc.getName().equals(car.getLocationName()) && 1673 rLoc.isPickUpAllowed() && 1674 rLoc.getMaxCarMoves() > 0 && 1675 !isLocationSkipped(rLoc.getId()) && 1676 ((car.getLocation().getTrainDirections() & rLoc.getTrainDirection()) != 0 || isLocalSwitcher())) { 1677 1678 if (((car.getTrack().getTrainDirections() & rLoc.getTrainDirection()) == 0 && !isLocalSwitcher()) || 1679 !car.getTrack().isPickupTrainAccepted(this)) { 1680 addLine(buildReport, 1681 Bundle.getMessage("trainCanNotServiceCarFrom", 1682 getName(), car.toString(), car.getLocationName(), car.getTrackName(), 1683 rLoc.getId())); 1684 continue; 1685 } 1686 if (debugFlag) { 1687 log.debug("Car ({}) can be picked up by train ({}) location ({}, {}) destination ({}, {})", 1688 car.toString(), getName(), car.getLocationName(), car.getTrackName(), 1689 car.getDestinationName(), car.getDestinationTrackName()); 1690 } 1691 addLine(buildReport, Bundle.getMessage("trainCanPickUpCar", 1692 getName(), car.toString(), car.getLocationName(), car.getTrackName(), rLoc.getId())); 1693 if (car.getDestination() == null) { 1694 if (debugFlag) { 1695 log.debug("Car ({}) does not have a destination", car.toString()); 1696 } 1697 return true; 1698 } 1699 // now check car's destination 1700 return isServiceableDestination(buildReport, car, rLoc, rLocations); 1701 } else if (rLoc.getName().equals(car.getLocationName())) { 1702 addLine(buildReport, Bundle.getMessage("trainCanNotServiceCarFrom", 1703 getName(), car.toString(), car.getLocationName(), car.getTrackName(), rLoc.getId())); 1704 } 1705 } 1706 if (debugFlag) { 1707 log.debug("Train ({}) can't service car ({}) from ({}, {})", getName(), car.toString(), 1708 car.getLocationName(), car.getTrackName()); 1709 } 1710 return false; 1711 } 1712 1713 /** 1714 * Second step in determining if train can service car, check to see if car's 1715 * destination is serviced by this train's route. 1716 * 1717 * @param buildReport add messages if needed to build report 1718 * @param car The test car 1719 * @param rLoc Where in the train's route the car was found 1720 * @param rLocations The ordered routeLocations in this train's route 1721 * @return true if car's destination can be serviced 1722 */ 1723 private boolean isServiceableDestination(PrintWriter buildReport, Car car, RouteLocation rLoc, 1724 List<RouteLocation> rLocations) { 1725 // need the car's length when building train 1726 int length = car.getTotalLength(); 1727 // car can be a kernel so get total length 1728 if (car.getKernel() != null) { 1729 length = car.getKernel().getTotalLength(); 1730 } 1731 // now see if the train's route services the car's destination 1732 for (int k = rLocations.indexOf(rLoc); k < rLocations.size(); k++) { 1733 RouteLocation rldest = rLocations.get(k); 1734 if (rldest.getName().equals(car.getDestinationName()) && 1735 rldest.isDropAllowed() && 1736 rldest.getMaxCarMoves() > 0 && 1737 !isLocationSkipped(rldest.getId()) && 1738 ((car.getDestination().getTrainDirections() & rldest.getTrainDirection()) != 0 || 1739 isLocalSwitcher()) && 1740 (!Setup.isCheckCarDestinationEnabled() || 1741 car.getTrack().isDestinationAccepted(car.getDestination()))) { 1742 // found a destination, now check destination track 1743 if (car.getDestinationTrack() != null) { 1744 if (!isServicableTrack(buildReport, car, rldest, car.getDestinationTrack())) { 1745 continue; 1746 } 1747 } else if (rldest.getLocation().isStaging() && 1748 getStatusCode() == CODE_BUILDING && 1749 getTerminationTrack() != null && 1750 getTerminationTrack().getLocation() == rldest.getLocation()) { 1751 if (debugFlag) { 1752 log.debug("Car ({}) destination is staging, check train ({}) termination track ({})", 1753 car.toString(), getName(), getTerminationTrack().getName()); 1754 } 1755 String status = car.checkDestination(getTerminationTrack().getLocation(), getTerminationTrack()); 1756 if (!status.equals(Track.OKAY)) { 1757 addLine(buildReport, 1758 Bundle.getMessage("trainCanNotDeliverToStaging", 1759 getName(), car.toString(), 1760 getTerminationTrack().getLocation().getName(), 1761 getTerminationTrack().getName(), status)); 1762 setServiceStatus(status); 1763 continue; 1764 } 1765 } else { 1766 if (debugFlag) { 1767 log.debug("Find track for car ({}) at destination ({})", car.toString(), 1768 car.getDestinationName()); 1769 } 1770 // determine if there's a destination track that is willing to accept this car 1771 String status = ""; 1772 List<Track> tracks = rldest.getLocation().getTracksList(); 1773 for (Track track : tracks) { 1774 if (!isServicableTrack(buildReport, car, rldest, track)) { 1775 continue; 1776 } 1777 // will the track accept this car? 1778 status = track.isRollingStockAccepted(car); 1779 if (status.equals(Track.OKAY) || status.startsWith(Track.LENGTH)) { 1780 if (debugFlag) { 1781 log.debug("Found track ({}) for car ({})", track.getName(), car.toString()); 1782 } 1783 break; // found track 1784 } 1785 } 1786 if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) { 1787 if (debugFlag) { 1788 log.debug("Destination ({}) can not service car ({}) using train ({}) no track available", 1789 car.getDestinationName(), car.toString(), getName()); // NOI18N 1790 } 1791 addLine(buildReport, Bundle.getMessage("trainCanNotDeliverNoTracks", 1792 getName(), car.toString(), car.getDestinationName(), rldest.getId())); 1793 continue; 1794 } 1795 } 1796 // restriction to only carry cars to terminal? 1797 // ignore send to terminal if a local move 1798 if (isSendCarsToTerminalEnabled() && 1799 !car.isLocalMove() && 1800 !car.getSplitLocationName() 1801 .equals(TrainCommon.splitString(getTrainDepartsName())) && 1802 !car.getSplitDestinationName() 1803 .equals(TrainCommon.splitString(getTrainTerminatesName()))) { 1804 if (debugFlag) { 1805 log.debug("option send cars to terminal is enabled"); 1806 } 1807 addLine(buildReport, 1808 Bundle.getMessage("trainCanNotCarryCarOption", 1809 getName(), car.toString(), car.getLocationName(), 1810 car.getTrackName(), car.getDestinationName(), 1811 car.getDestinationTrackName())); 1812 continue; 1813 } 1814 // don't allow local move when car is in staging 1815 if (!isTurn() && car.getTrack().isStaging() && 1816 rldest.getLocation() == car.getLocation()) { 1817 log.debug( 1818 "Car ({}) at ({}, {}) not allowed to perform local move in staging ({})", 1819 car.toString(), car.getLocationName(), car.getTrackName(), rldest.getName()); 1820 continue; 1821 } 1822 // allow car to return to staging? 1823 if (isAllowReturnToStagingEnabled() && 1824 car.getTrack().isStaging() && 1825 rldest.getLocation() == car.getLocation()) { 1826 addLine(buildReport, 1827 Bundle.getMessage("trainCanReturnCarToStaging", 1828 getName(), car.toString(), car.getDestinationName(), 1829 car.getDestinationTrackName())); 1830 return true; 1831 } 1832 // is this a local move? 1833 if (!isLocalSwitcher() && 1834 !isAllowLocalMovesEnabled() && 1835 !car.isCaboose() && 1836 !car.hasFred() && 1837 !car.isPassenger() && 1838 car.isLocalMove()) { 1839 if (debugFlag) { 1840 log.debug("Local move not allowed"); 1841 } 1842 addLine(buildReport, Bundle.getMessage("trainCanNotPerformLocalMove", 1843 getName(), car.toString(), car.getLocationName())); 1844 continue; 1845 } 1846 // Can cars travel from origin to terminal? 1847 if (!isAllowThroughCarsEnabled() && 1848 TrainCommon.splitString(getTrainDepartsName()) 1849 .equals(rLoc.getSplitName()) && 1850 TrainCommon.splitString(getTrainTerminatesName()) 1851 .equals(rldest.getSplitName()) && 1852 !TrainCommon.splitString(getTrainDepartsName()) 1853 .equals(TrainCommon.splitString(getTrainTerminatesName())) && 1854 !isLocalSwitcher() && 1855 !car.isCaboose() && 1856 !car.hasFred() && 1857 !car.isPassenger()) { 1858 if (debugFlag) { 1859 log.debug("Through car ({}) not allowed", car.toString()); 1860 } 1861 addLine(buildReport, Bundle.getMessage("trainDoesNotCarryOriginTerminal", 1862 getName(), car.getLocationName(), car.getDestinationName())); 1863 continue; 1864 } 1865 // check to see if moves are available 1866 if (getStatusCode() == CODE_BUILDING && rldest.getMaxCarMoves() - rldest.getCarMoves() <= 0) { 1867 setServiceStatus(Bundle.getMessage("trainNoMoves", 1868 getName(), getRoute().getName(), rldest.getId(), rldest.getName())); 1869 if (debugFlag) { 1870 log.debug("No available moves for destination {}", rldest.getName()); 1871 } 1872 addLine(buildReport, getServiceStatus()); 1873 continue; 1874 } 1875 if (debugFlag) { 1876 log.debug("Car ({}) can be dropped by train ({}) to ({}, {})", car.toString(), getName(), 1877 car.getDestinationName(), car.getDestinationTrackName()); 1878 } 1879 return true; 1880 } 1881 // check to see if train length is okay 1882 if (getStatusCode() == CODE_BUILDING && rldest.getTrainLength() + length > rldest.getMaxTrainLength()) { 1883 setServiceStatus(Bundle.getMessage("trainExceedsMaximumLength", 1884 getName(), getRoute().getName(), rldest.getId(), rldest.getMaxTrainLength(), 1885 Setup.getLengthUnit().toLowerCase(), rldest.getName(), car.toString(), 1886 rldest.getTrainLength() + length - rldest.getMaxTrainLength())); 1887 if (debugFlag) { 1888 log.debug("Car ({}) exceeds maximum train length {} when departing ({})", car.toString(), 1889 rldest.getMaxTrainLength(), rldest.getName()); 1890 } 1891 addLine(buildReport, getServiceStatus()); 1892 return false; 1893 } 1894 } 1895 addLine(buildReport, Bundle.getMessage("trainCanNotDeliverToDestination", 1896 getName(), car.toString(), car.getDestinationName(), car.getDestinationTrackName())); 1897 return false; 1898 } 1899 1900 private boolean isServicableTrack(PrintWriter buildReport, Car car, RouteLocation rldest, Track track) { 1901 if ((track.getTrainDirections() & rldest.getTrainDirection()) == 0 && !isLocalSwitcher()) { 1902 addLine(buildReport, Bundle.getMessage("buildCanNotDropRsUsingTrain", 1903 car.toString(), rldest.getTrainDirectionString(), track.getName())); 1904 return false; 1905 } 1906 if (!track.isDropTrainAccepted(this)) { 1907 addLine(buildReport, Bundle.getMessage("buildCanNotDropCarTrain", 1908 car.toString(), getName(), track.getTrackTypeName(), track.getLocation().getName(), 1909 track.getName())); 1910 return false; 1911 } 1912 return true; 1913 } 1914 1915 protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED; 1916 1917 private void addLine(PrintWriter buildReport, String string) { 1918 if (Setup.getRouterBuildReportLevel().equals(SEVEN)) { 1919 TrainCommon.addLine(buildReport, SEVEN, string); 1920 } 1921 } 1922 1923 protected void setServiceStatus(String status) { 1924 _serviceStatus = status; 1925 } 1926 1927 /** 1928 * Returns the statusCode of the "isServiceable(Car)" routine. There are two 1929 * statusCodes that need special consideration when the train is being built, 1930 * the moves in a train's route and the maximum train length. NOTE: The code 1931 * using getServiceStatus() currently assumes that if there's a service status 1932 * that the issue is either route moves or maximum train length. 1933 * 1934 * @return The statusCode. 1935 */ 1936 public String getServiceStatus() { 1937 return _serviceStatus; 1938 } 1939 1940 /** 1941 * @return The number of cars worked by this train 1942 */ 1943 public int getNumberCarsWorked() { 1944 int count = 0; 1945 for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) { 1946 if (rs.getRouteLocation() != null) { 1947 count++; 1948 } 1949 } 1950 return count; 1951 } 1952 1953 public void setNumberCarsRequested(int number) { 1954 _statusCarsRequested = number; 1955 } 1956 1957 public int getNumberCarsRequested() { 1958 return _statusCarsRequested; 1959 } 1960 1961 public void setTerminationDate(String date) { 1962 _statusTerminatedDate = date; 1963 } 1964 1965 public String getTerminationDate() { 1966 return _statusTerminatedDate; 1967 } 1968 1969 /** 1970 * Gets the number of cars in the train at the current location in the train's 1971 * route. 1972 * 1973 * @return The number of cars currently in the train 1974 */ 1975 public int getNumberCarsInTrain() { 1976 return getNumberCarsInTrain(getCurrentRouteLocation()); 1977 } 1978 1979 /** 1980 * Gets the number of cars in the train when train departs the route location. 1981 * 1982 * @param routeLocation The RouteLocation. 1983 * @return The number of cars in the train departing the route location. 1984 */ 1985 public int getNumberCarsInTrain(RouteLocation routeLocation) { 1986 int number = 0; 1987 Route route = getRoute(); 1988 if (route != null) { 1989 for (RouteLocation rl : route.getLocationsBySequenceList()) { 1990 for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) { 1991 if (rs.getRouteLocation() == rl) { 1992 number++; 1993 } 1994 if (rs.getRouteDestination() == rl) { 1995 number--; 1996 } 1997 } 1998 if (rl == routeLocation) { 1999 break; 2000 } 2001 } 2002 } 2003 return number; 2004 } 2005 2006 /** 2007 * Gets the number of empty cars in the train when train departs the route 2008 * location. 2009 * 2010 * @param routeLocation The RouteLocation. 2011 * @return The number of empty cars in the train departing the route location. 2012 */ 2013 public int getNumberEmptyCarsInTrain(RouteLocation routeLocation) { 2014 int number = 0; 2015 Route route = getRoute(); 2016 if (route != null) { 2017 for (RouteLocation rl : route.getLocationsBySequenceList()) { 2018 for (Car car : InstanceManager.getDefault(CarManager.class).getList(this)) { 2019 if (!car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) { 2020 continue; 2021 } 2022 if (car.getRouteLocation() == rl) { 2023 number++; 2024 } 2025 if (car.getRouteDestination() == rl) { 2026 number--; 2027 } 2028 } 2029 if (rl == routeLocation) { 2030 break; 2031 } 2032 } 2033 } 2034 2035 return number; 2036 } 2037 2038 public int getNumberLoadedCarsInTrain(RouteLocation routeLocation) { 2039 return getNumberCarsInTrain(routeLocation) - getNumberEmptyCarsInTrain(routeLocation); 2040 } 2041 2042 /** 2043 * Gets the number of cars pulled from a location 2044 * 2045 * @param routeLocation the location 2046 * @return number of pick ups 2047 */ 2048 public int getNumberCarsPickedUp(RouteLocation routeLocation) { 2049 int number = 0; 2050 for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) { 2051 if (rs.getRouteLocation() == routeLocation) { 2052 number++; 2053 } 2054 } 2055 return number; 2056 } 2057 2058 /** 2059 * Gets the number of cars delivered to a location 2060 * 2061 * @param routeLocation the location 2062 * @return number of set outs 2063 */ 2064 public int getNumberCarsSetout(RouteLocation routeLocation) { 2065 int number = 0; 2066 for (Car rs : InstanceManager.getDefault(CarManager.class).getList(this)) { 2067 if (rs.getRouteDestination() == routeLocation) { 2068 number++; 2069 } 2070 } 2071 return number; 2072 } 2073 2074 /** 2075 * Gets the train's length at the current location in the train's route. 2076 * 2077 * @return The train length at the train's current location 2078 */ 2079 public int getTrainLength() { 2080 return getTrainLength(getCurrentRouteLocation()); 2081 } 2082 2083 /** 2084 * Gets the train's length at the route location specified 2085 * 2086 * @param routeLocation The route location 2087 * @return The train length at the route location 2088 */ 2089 public int getTrainLength(RouteLocation routeLocation) { 2090 int length = 0; 2091 Route route = getRoute(); 2092 if (route != null) { 2093 for (RouteLocation rl : route.getLocationsBySequenceList()) { 2094 for (RollingStock rs : InstanceManager.getDefault(EngineManager.class).getList(this)) { 2095 if (rs.getRouteLocation() == rl) { 2096 length += rs.getTotalLength(); 2097 } 2098 if (rs.getRouteDestination() == rl) { 2099 length += -rs.getTotalLength(); 2100 } 2101 } 2102 for (RollingStock rs : InstanceManager.getDefault(CarManager.class).getList(this)) { 2103 if (rs.getRouteLocation() == rl) { 2104 length += rs.getTotalLength(); 2105 } 2106 if (rs.getRouteDestination() == rl) { 2107 length += -rs.getTotalLength(); 2108 } 2109 } 2110 if (rl == routeLocation) { 2111 break; 2112 } 2113 } 2114 } 2115 return length; 2116 } 2117 2118 /** 2119 * Get the train's weight at the current location. 2120 * 2121 * @return Train's weight in tons. 2122 */ 2123 public int getTrainWeight() { 2124 return getTrainWeight(getCurrentRouteLocation()); 2125 } 2126 2127 public int getTrainWeight(RouteLocation routeLocation) { 2128 int weight = 0; 2129 Route route = getRoute(); 2130 if (route != null) { 2131 for (RouteLocation rl : route.getLocationsBySequenceList()) { 2132 for (RollingStock rs : InstanceManager.getDefault(EngineManager.class).getList(this)) { 2133 if (rs.getRouteLocation() == rl) { 2134 weight += rs.getAdjustedWeightTons(); 2135 } 2136 if (rs.getRouteDestination() == rl) { 2137 weight += -rs.getAdjustedWeightTons(); 2138 } 2139 } 2140 for (Car car : InstanceManager.getDefault(CarManager.class).getList(this)) { 2141 if (car.getRouteLocation() == rl) { 2142 weight += car.getAdjustedWeightTons(); // weight depends 2143 // on car load 2144 } 2145 if (car.getRouteDestination() == rl) { 2146 weight += -car.getAdjustedWeightTons(); 2147 } 2148 } 2149 if (rl == routeLocation) { 2150 break; 2151 } 2152 } 2153 } 2154 return weight; 2155 } 2156 2157 /** 2158 * Gets the train's locomotive horsepower at the route location specified 2159 * 2160 * @param routeLocation The route location 2161 * @return The train's locomotive horsepower at the route location 2162 */ 2163 public int getTrainHorsePower(RouteLocation routeLocation) { 2164 int hp = 0; 2165 Route route = getRoute(); 2166 if (route != null) { 2167 for (RouteLocation rl : route.getLocationsBySequenceList()) { 2168 for (Engine eng : InstanceManager.getDefault(EngineManager.class).getList(this)) { 2169 if (eng.getRouteLocation() == rl) { 2170 hp += eng.getHpInteger(); 2171 } 2172 if (eng.getRouteDestination() == rl) { 2173 hp += -eng.getHpInteger(); 2174 } 2175 } 2176 if (rl == routeLocation) { 2177 break; 2178 } 2179 } 2180 } 2181 return hp; 2182 } 2183 2184 /** 2185 * Gets the current caboose road and number if there's one assigned to the 2186 * train. 2187 * 2188 * @return Road and number of caboose. 2189 */ 2190 public String getCabooseRoadAndNumber() { 2191 String cabooseRoadNumber = NONE; 2192 RouteLocation rl = getCurrentRouteLocation(); 2193 List<Car> cars = InstanceManager.getDefault(CarManager.class).getByTrainList(this); 2194 for (Car car : cars) { 2195 if (car.getRouteLocation() == rl && car.isCaboose()) { 2196 cabooseRoadNumber = 2197 car.getRoadName().split(TrainCommon.HYPHEN)[0] + " " + TrainCommon.splitString(car.getNumber()); 2198 } 2199 } 2200 return cabooseRoadNumber; 2201 } 2202 2203 public void setDescription(String description) { 2204 String old = _description; 2205 _description = description; 2206 if (!old.equals(description)) { 2207 setDirtyAndFirePropertyChange(DESCRIPTION_CHANGED_PROPERTY, old, description); 2208 } 2209 } 2210 2211 public String getRawDescription() { 2212 return _description; 2213 } 2214 2215 /** 2216 * Returns a formated string providing the train's description. {0} = lead 2217 * engine number, {1} = train's departure direction {2} = lead engine road {3} = 2218 * DCC address of lead engine. 2219 * 2220 * @return The train's description. 2221 */ 2222 public String getDescription() { 2223 try { 2224 String description = MessageFormat.format(getRawDescription(), new Object[]{getLeadEngineNumber(), 2225 getTrainDepartsDirection(), getLeadEngineRoadName(), getLeadEngineDccAddress()}); 2226 return description; 2227 } catch (IllegalArgumentException e) { 2228 return "ERROR IN FORMATTING: " + getRawDescription(); 2229 } 2230 } 2231 2232 public void setNumberEngines(String number) { 2233 String old = _numberEngines; 2234 _numberEngines = number; 2235 if (!old.equals(number)) { 2236 setDirtyAndFirePropertyChange("trainNmberEngines", old, number); // NOI18N 2237 } 2238 } 2239 2240 /** 2241 * Get the number of engines that this train requires. 2242 * 2243 * @return The number of engines that this train requires. 2244 */ 2245 public String getNumberEngines() { 2246 return _numberEngines; 2247 } 2248 2249 /** 2250 * Get the number of engines needed for the second set. 2251 * 2252 * @return The number of engines needed in route 2253 */ 2254 public String getSecondLegNumberEngines() { 2255 return _leg2Engines; 2256 } 2257 2258 public void setSecondLegNumberEngines(String number) { 2259 String old = _leg2Engines; 2260 _leg2Engines = number; 2261 if (!old.equals(number)) { 2262 setDirtyAndFirePropertyChange("trainNmberEngines", old, number); // NOI18N 2263 } 2264 } 2265 2266 /** 2267 * Get the number of engines needed for the third set. 2268 * 2269 * @return The number of engines needed in route 2270 */ 2271 public String getThirdLegNumberEngines() { 2272 return _leg3Engines; 2273 } 2274 2275 public void setThirdLegNumberEngines(String number) { 2276 String old = _leg3Engines; 2277 _leg3Engines = number; 2278 if (!old.equals(number)) { 2279 setDirtyAndFirePropertyChange("trainNmberEngines", old, number); // NOI18N 2280 } 2281 } 2282 2283 /** 2284 * Set the road name of engines servicing this train. 2285 * 2286 * @param road The road name of engines servicing this train. 2287 */ 2288 public void setEngineRoad(String road) { 2289 String old = _engineRoad; 2290 _engineRoad = road; 2291 if (!old.equals(road)) { 2292 setDirtyAndFirePropertyChange("trainEngineRoad", old, road); // NOI18N 2293 } 2294 } 2295 2296 /** 2297 * Get the road name of engines servicing this train. 2298 * 2299 * @return The road name of engines servicing this train. 2300 */ 2301 public String getEngineRoad() { 2302 return _engineRoad; 2303 } 2304 2305 /** 2306 * Set the road name of engines servicing this train 2nd leg. 2307 * 2308 * @param road The road name of engines servicing this train. 2309 */ 2310 public void setSecondLegEngineRoad(String road) { 2311 String old = _leg2Road; 2312 _leg2Road = road; 2313 if (!old.equals(road)) { 2314 setDirtyAndFirePropertyChange("trainEngineRoad", old, road); // NOI18N 2315 } 2316 } 2317 2318 /** 2319 * Get the road name of engines servicing this train 2nd leg. 2320 * 2321 * @return The road name of engines servicing this train. 2322 */ 2323 public String getSecondLegEngineRoad() { 2324 return _leg2Road; 2325 } 2326 2327 /** 2328 * Set the road name of engines servicing this train 3rd leg. 2329 * 2330 * @param road The road name of engines servicing this train. 2331 */ 2332 public void setThirdLegEngineRoad(String road) { 2333 String old = _leg3Road; 2334 _leg3Road = road; 2335 if (!old.equals(road)) { 2336 setDirtyAndFirePropertyChange("trainEngineRoad", old, road); // NOI18N 2337 } 2338 } 2339 2340 /** 2341 * Get the road name of engines servicing this train 3rd leg. 2342 * 2343 * @return The road name of engines servicing this train. 2344 */ 2345 public String getThirdLegEngineRoad() { 2346 return _leg3Road; 2347 } 2348 2349 /** 2350 * Set the model name of engines servicing this train. 2351 * 2352 * @param model The model name of engines servicing this train. 2353 */ 2354 public void setEngineModel(String model) { 2355 String old = _engineModel; 2356 _engineModel = model; 2357 if (!old.equals(model)) { 2358 setDirtyAndFirePropertyChange("trainEngineModel", old, model); // NOI18N 2359 } 2360 } 2361 2362 public String getEngineModel() { 2363 return _engineModel; 2364 } 2365 2366 /** 2367 * Set the model name of engines servicing this train's 2nd leg. 2368 * 2369 * @param model The model name of engines servicing this train. 2370 */ 2371 public void setSecondLegEngineModel(String model) { 2372 String old = _leg2Model; 2373 _leg2Model = model; 2374 if (!old.equals(model)) { 2375 setDirtyAndFirePropertyChange("trainEngineModel", old, model); // NOI18N 2376 } 2377 } 2378 2379 public String getSecondLegEngineModel() { 2380 return _leg2Model; 2381 } 2382 2383 /** 2384 * Set the model name of engines servicing this train's 3rd leg. 2385 * 2386 * @param model The model name of engines servicing this train. 2387 */ 2388 public void setThirdLegEngineModel(String model) { 2389 String old = _leg3Model; 2390 _leg3Model = model; 2391 if (!old.equals(model)) { 2392 setDirtyAndFirePropertyChange("trainEngineModel", old, model); // NOI18N 2393 } 2394 } 2395 2396 public String getThirdLegEngineModel() { 2397 return _leg3Model; 2398 } 2399 2400 protected void replaceModel(String oldModel, String newModel) { 2401 if (getEngineModel().equals(oldModel)) { 2402 setEngineModel(newModel); 2403 } 2404 if (getSecondLegEngineModel().equals(oldModel)) { 2405 setSecondLegEngineModel(newModel); 2406 } 2407 if (getThirdLegEngineModel().equals(oldModel)) { 2408 setThirdLegEngineModel(newModel); 2409 } 2410 } 2411 2412 /** 2413 * Set the road name of the caboose servicing this train. 2414 * 2415 * @param road The road name of the caboose servicing this train. 2416 */ 2417 public void setCabooseRoad(String road) { 2418 String old = _cabooseRoad; 2419 _cabooseRoad = road; 2420 if (!old.equals(road)) { 2421 setDirtyAndFirePropertyChange("trainCabooseRoad", old, road); // NOI18N 2422 } 2423 } 2424 2425 public String getCabooseRoad() { 2426 return _cabooseRoad; 2427 } 2428 2429 /** 2430 * Set the road name of the second leg caboose servicing this train. 2431 * 2432 * @param road The road name of the caboose servicing this train's 2nd leg. 2433 */ 2434 public void setSecondLegCabooseRoad(String road) { 2435 String old = _leg2CabooseRoad; 2436 _leg2CabooseRoad = road; 2437 if (!old.equals(road)) { 2438 setDirtyAndFirePropertyChange("trainCabooseRoad", old, road); // NOI18N 2439 } 2440 } 2441 2442 public String getSecondLegCabooseRoad() { 2443 return _leg2CabooseRoad; 2444 } 2445 2446 /** 2447 * Set the road name of the third leg caboose servicing this train. 2448 * 2449 * @param road The road name of the caboose servicing this train's 3rd leg. 2450 */ 2451 public void setThirdLegCabooseRoad(String road) { 2452 String old = _leg3CabooseRoad; 2453 _leg3CabooseRoad = road; 2454 if (!old.equals(road)) { 2455 setDirtyAndFirePropertyChange("trainCabooseRoad", old, road); // NOI18N 2456 } 2457 } 2458 2459 public String getThirdLegCabooseRoad() { 2460 return _leg3CabooseRoad; 2461 } 2462 2463 public void setSecondLegStartRouteLocation(RouteLocation rl) { 2464 _leg2Start = rl; 2465 } 2466 2467 public RouteLocation getSecondLegStartRouteLocation() { 2468 return _leg2Start; 2469 } 2470 2471 public String getSecondLegStartLocationName() { 2472 if (getSecondLegStartRouteLocation() == null) { 2473 return NONE; 2474 } 2475 return getSecondLegStartRouteLocation().getName(); 2476 } 2477 2478 public void setThirdLegStartRouteLocation(RouteLocation rl) { 2479 _leg3Start = rl; 2480 } 2481 2482 public RouteLocation getThirdLegStartRouteLocation() { 2483 return _leg3Start; 2484 } 2485 2486 public String getThirdLegStartLocationName() { 2487 if (getThirdLegStartRouteLocation() == null) { 2488 return NONE; 2489 } 2490 return getThirdLegStartRouteLocation().getName(); 2491 } 2492 2493 public void setSecondLegEndRouteLocation(RouteLocation rl) { 2494 _end2Leg = rl; 2495 } 2496 2497 public String getSecondLegEndLocationName() { 2498 if (getSecondLegEndRouteLocation() == null) { 2499 return NONE; 2500 } 2501 return getSecondLegEndRouteLocation().getName(); 2502 } 2503 2504 public RouteLocation getSecondLegEndRouteLocation() { 2505 return _end2Leg; 2506 } 2507 2508 public void setThirdLegEndRouteLocation(RouteLocation rl) { 2509 _leg3End = rl; 2510 } 2511 2512 public RouteLocation getThirdLegEndRouteLocation() { 2513 return _leg3End; 2514 } 2515 2516 public String getThirdLegEndLocationName() { 2517 if (getThirdLegEndRouteLocation() == null) { 2518 return NONE; 2519 } 2520 return getThirdLegEndRouteLocation().getName(); 2521 } 2522 2523 /** 2524 * Optional changes to train while en route. 2525 * 2526 * @param options NO_CABOOSE_OR_FRED, CHANGE_ENGINES, ADD_CABOOSE, 2527 * HELPER_ENGINES, REMOVE_CABOOSE 2528 */ 2529 public void setSecondLegOptions(int options) { 2530 int old = _leg2Options; 2531 _leg2Options = options; 2532 if (old != options) { 2533 setDirtyAndFirePropertyChange("trainLegOptions", old, options); // NOI18N 2534 } 2535 } 2536 2537 public int getSecondLegOptions() { 2538 return _leg2Options; 2539 } 2540 2541 /** 2542 * Optional changes to train while en route. 2543 * 2544 * @param options NO_CABOOSE_OR_FRED, CHANGE_ENGINES, ADD_CABOOSE, 2545 * HELPER_ENGINES, REMOVE_CABOOSE 2546 */ 2547 public void setThirdLegOptions(int options) { 2548 int old = _leg3Options; 2549 _leg3Options = options; 2550 if (old != options) { 2551 setDirtyAndFirePropertyChange("trainLegOptions", old, options); // NOI18N 2552 } 2553 } 2554 2555 public int getThirdLegOptions() { 2556 return _leg3Options; 2557 } 2558 2559 public void setComment(String comment) { 2560 String old = _comment; 2561 _comment = comment; 2562 if (!old.equals(comment)) { 2563 setDirtyAndFirePropertyChange("trainComment", old, comment); // NOI18N 2564 } 2565 } 2566 2567 public String getComment() { 2568 return TrainCommon.getTextColorString(getCommentWithColor()); 2569 } 2570 2571 public String getCommentWithColor() { 2572 return _comment; 2573 } 2574 2575 /** 2576 * Add a script to run before a train is built 2577 * 2578 * @param pathname The script's pathname 2579 */ 2580 public void addBuildScript(String pathname) { 2581 _buildScripts.add(pathname); 2582 setDirtyAndFirePropertyChange("addBuildScript", pathname, null); // NOI18N 2583 } 2584 2585 public void deleteBuildScript(String pathname) { 2586 _buildScripts.remove(pathname); 2587 setDirtyAndFirePropertyChange("deleteBuildScript", null, pathname); // NOI18N 2588 } 2589 2590 /** 2591 * Gets a list of pathnames (scripts) to run before this train is built 2592 * 2593 * @return A list of pathnames to run before this train is built 2594 */ 2595 public List<String> getBuildScripts() { 2596 return _buildScripts; 2597 } 2598 2599 /** 2600 * Add a script to run after a train is built 2601 * 2602 * @param pathname The script's pathname 2603 */ 2604 public void addAfterBuildScript(String pathname) { 2605 _afterBuildScripts.add(pathname); 2606 setDirtyAndFirePropertyChange("addAfterBuildScript", pathname, null); // NOI18N 2607 } 2608 2609 public void deleteAfterBuildScript(String pathname) { 2610 _afterBuildScripts.remove(pathname); 2611 setDirtyAndFirePropertyChange("deleteAfterBuildScript", null, pathname); // NOI18N 2612 } 2613 2614 /** 2615 * Gets a list of pathnames (scripts) to run after this train is built 2616 * 2617 * @return A list of pathnames to run after this train is built 2618 */ 2619 public List<String> getAfterBuildScripts() { 2620 return _afterBuildScripts; 2621 } 2622 2623 /** 2624 * Add a script to run when train is moved 2625 * 2626 * @param pathname The script's pathname 2627 */ 2628 public void addMoveScript(String pathname) { 2629 _moveScripts.add(pathname); 2630 setDirtyAndFirePropertyChange("addMoveScript", pathname, null); // NOI18N 2631 } 2632 2633 public void deleteMoveScript(String pathname) { 2634 _moveScripts.remove(pathname); 2635 setDirtyAndFirePropertyChange("deleteMoveScript", null, pathname); // NOI18N 2636 } 2637 2638 /** 2639 * Gets a list of pathnames (scripts) to run when this train moved 2640 * 2641 * @return A list of pathnames to run when this train moved 2642 */ 2643 public List<String> getMoveScripts() { 2644 return _moveScripts; 2645 } 2646 2647 /** 2648 * Add a script to run when train is terminated 2649 * 2650 * @param pathname The script's pathname 2651 */ 2652 public void addTerminationScript(String pathname) { 2653 _terminationScripts.add(pathname); 2654 setDirtyAndFirePropertyChange("addTerminationScript", pathname, null); // NOI18N 2655 } 2656 2657 public void deleteTerminationScript(String pathname) { 2658 _terminationScripts.remove(pathname); 2659 setDirtyAndFirePropertyChange("deleteTerminationScript", null, pathname); // NOI18N 2660 } 2661 2662 /** 2663 * Gets a list of pathnames (scripts) to run when this train terminates 2664 * 2665 * @return A list of pathnames to run when this train terminates 2666 */ 2667 public List<String> getTerminationScripts() { 2668 return _terminationScripts; 2669 } 2670 2671 /** 2672 * Gets the optional railroad name for this train. 2673 * 2674 * @return Train's railroad name. 2675 */ 2676 public String getRailroadName() { 2677 return _railroadName; 2678 } 2679 2680 /** 2681 * Overrides the default railroad name for this train. 2682 * 2683 * @param name The railroad name for this train. 2684 */ 2685 public void setRailroadName(String name) { 2686 String old = _railroadName; 2687 _railroadName = name; 2688 if (!old.equals(name)) { 2689 setDirtyAndFirePropertyChange("trainRailroadName", old, name); // NOI18N 2690 } 2691 } 2692 2693 public String getManifestLogoPathName() { 2694 return _logoPathName; 2695 } 2696 2697 /** 2698 * Overrides the default logo for this train. 2699 * 2700 * @param pathName file location for the logo. 2701 */ 2702 public void setManifestLogoPathName(String pathName) { 2703 _logoPathName = pathName; 2704 } 2705 2706 public boolean isShowArrivalAndDepartureTimesEnabled() { 2707 return _showTimes; 2708 } 2709 2710 public void setShowArrivalAndDepartureTimes(boolean enable) { 2711 boolean old = _showTimes; 2712 _showTimes = enable; 2713 if (old != enable) { 2714 setDirtyAndFirePropertyChange("showArrivalAndDepartureTimes", old ? "true" : "false", // NOI18N 2715 enable ? "true" : "false"); // NOI18N 2716 } 2717 } 2718 2719 public boolean isSendCarsToTerminalEnabled() { 2720 return _sendToTerminal; 2721 } 2722 2723 public void setSendCarsToTerminalEnabled(boolean enable) { 2724 boolean old = _sendToTerminal; 2725 _sendToTerminal = enable; 2726 if (old != enable) { 2727 setDirtyAndFirePropertyChange("send cars to terminal", old ? "true" : "false", enable ? "true" // NOI18N 2728 : "false"); // NOI18N 2729 } 2730 } 2731 2732 /** 2733 * Allow local moves if car has a custom load or Final Destination 2734 * 2735 * @return true if local move is allowed 2736 */ 2737 public boolean isAllowLocalMovesEnabled() { 2738 return _allowLocalMoves; 2739 } 2740 2741 public void setAllowLocalMovesEnabled(boolean enable) { 2742 boolean old = _allowLocalMoves; 2743 _allowLocalMoves = enable; 2744 if (old != enable) { 2745 setDirtyAndFirePropertyChange("allow local moves", old ? "true" : "false", enable ? "true" // NOI18N 2746 : "false"); // NOI18N 2747 } 2748 } 2749 2750 public boolean isAllowThroughCarsEnabled() { 2751 return _allowThroughCars; 2752 } 2753 2754 public void setAllowThroughCarsEnabled(boolean enable) { 2755 boolean old = _allowThroughCars; 2756 _allowThroughCars = enable; 2757 if (old != enable) { 2758 setDirtyAndFirePropertyChange("allow through cars", old ? "true" : "false", enable ? "true" // NOI18N 2759 : "false"); // NOI18N 2760 } 2761 } 2762 2763 public boolean isBuildTrainNormalEnabled() { 2764 return _buildNormal; 2765 } 2766 2767 public void setBuildTrainNormalEnabled(boolean enable) { 2768 boolean old = _buildNormal; 2769 _buildNormal = enable; 2770 if (old != enable) { 2771 setDirtyAndFirePropertyChange("build train normal", old ? "true" : "false", enable ? "true" // NOI18N 2772 : "false"); // NOI18N 2773 } 2774 } 2775 2776 /** 2777 * When true allow a turn to return cars to staging. A turn is a train that 2778 * departs and terminates at the same location. 2779 * 2780 * @return true if cars can return to staging 2781 */ 2782 public boolean isAllowReturnToStagingEnabled() { 2783 return _allowCarsReturnStaging; 2784 } 2785 2786 public void setAllowReturnToStagingEnabled(boolean enable) { 2787 boolean old = _allowCarsReturnStaging; 2788 _allowCarsReturnStaging = enable; 2789 if (old != enable) { 2790 setDirtyAndFirePropertyChange("allow cars to return to staging", old ? "true" : "false", // NOI18N 2791 enable ? "true" : "false"); // NOI18N 2792 } 2793 } 2794 2795 public boolean isServiceAllCarsWithFinalDestinationsEnabled() { 2796 return _serviceAllCarsWithFinalDestinations; 2797 } 2798 2799 public void setServiceAllCarsWithFinalDestinationsEnabled(boolean enable) { 2800 boolean old = _serviceAllCarsWithFinalDestinations; 2801 _serviceAllCarsWithFinalDestinations = enable; 2802 if (old != enable) { 2803 setDirtyAndFirePropertyChange("TrainServiceAllCarsWithFinalDestinations", old ? "true" : "false", // NOI18N 2804 enable ? "true" : "false"); // NOI18N 2805 } 2806 } 2807 2808 public boolean isBuildConsistEnabled() { 2809 return _buildConsist; 2810 } 2811 2812 public void setBuildConsistEnabled(boolean enable) { 2813 boolean old = _buildConsist; 2814 _buildConsist = enable; 2815 if (old != enable) { 2816 setDirtyAndFirePropertyChange("TrainBuildConsist", old ? "true" : "false", // NOI18N 2817 enable ? "true" : "false"); // NOI18N 2818 } 2819 } 2820 2821 public boolean isSendCarsWithCustomLoadsToStagingEnabled() { 2822 return _sendCarsWithCustomLoadsToStaging; 2823 } 2824 2825 public void setSendCarsWithCustomLoadsToStagingEnabled(boolean enable) { 2826 boolean old = _sendCarsWithCustomLoadsToStaging; 2827 _sendCarsWithCustomLoadsToStaging = enable; 2828 if (old != enable) { 2829 setDirtyAndFirePropertyChange("SendCarsWithCustomLoadsToStaging", old ? "true" : "false", // NOI18N 2830 enable ? "true" : "false"); // NOI18N 2831 } 2832 } 2833 2834 protected void setBuilt(boolean built) { 2835 boolean old = _built; 2836 _built = built; 2837 if (old != built) { 2838 setDirtyAndFirePropertyChange(BUILT_CHANGED_PROPERTY, old, built); // NOI18N 2839 } 2840 } 2841 2842 /** 2843 * Used to determine if this train has been built. 2844 * 2845 * @return true if the train was successfully built. 2846 */ 2847 public boolean isBuilt() { 2848 return _built; 2849 } 2850 2851 /** 2852 * Set true whenever the train's manifest has been modified. For example adding 2853 * or removing a car from a train, or changing the manifest format. Once the 2854 * manifest has been regenerated (modified == false), the old status for the 2855 * train is restored. 2856 * 2857 * @param modified True if train's manifest has been modified. 2858 */ 2859 public void setModified(boolean modified) { 2860 log.debug("Set modified {}", modified); 2861 if (!isBuilt()) { 2862 _modified = false; 2863 return; // there isn't a manifest to modify 2864 } 2865 boolean old = _modified; 2866 _modified = modified; 2867 if (modified) { 2868 setPrinted(false); 2869 } 2870 if (old != modified) { 2871 if (modified) { 2872 // scripts can call setModified() for a train 2873 if (getStatusCode() != CODE_RUN_SCRIPTS) { 2874 setOldStatusCode(getStatusCode()); 2875 } 2876 setStatusCode(CODE_MANIFEST_MODIFIED); 2877 } else { 2878 setStatusCode(getOldStatusCode()); // restore previous train 2879 // status 2880 } 2881 } 2882 setDirtyAndFirePropertyChange(TRAIN_MODIFIED_CHANGED_PROPERTY, null, modified); // NOI18N 2883 } 2884 2885 public boolean isModified() { 2886 return _modified; 2887 } 2888 2889 /** 2890 * Control flag used to decide if this train is to be built. 2891 * 2892 * @param build When true, build this train. 2893 */ 2894 public void setBuildEnabled(boolean build) { 2895 boolean old = _build; 2896 _build = build; 2897 if (old != build) { 2898 setDirtyAndFirePropertyChange(BUILD_CHANGED_PROPERTY, old, build); // NOI18N 2899 } 2900 } 2901 2902 /** 2903 * Used to determine if train is to be built. 2904 * 2905 * @return true if train is to be built. 2906 */ 2907 public boolean isBuildEnabled() { 2908 return _build; 2909 } 2910 2911 /** 2912 * Build this train if the build control flag is true. 2913 * 2914 * @return True only if train is successfully built. 2915 */ 2916 public boolean buildIfSelected() { 2917 if (isBuildEnabled() && !isBuilt()) { 2918 return build(); 2919 } 2920 log.debug("Train ({}) not selected or already built, skipping build", getName()); 2921 return false; 2922 } 2923 2924 /** 2925 * Build this train. Creates a train manifest. 2926 * 2927 * @return True if build successful. 2928 */ 2929 public synchronized boolean build() { 2930 reset(); 2931 // check to see if any other trains are building 2932 while (InstanceManager.getDefault(TrainManager.class).isAnyTrainBuilding()) { 2933 try { 2934 wait(100); // 100 msec 2935 } catch (InterruptedException e) { 2936 // TODO Auto-generated catch block 2937 log.error("Thread unexpectedly interrupted", e); 2938 } 2939 } 2940 // run before build scripts 2941 runScripts(getBuildScripts()); 2942 TrainBuilder tb = new TrainBuilder(); 2943 boolean results = tb.build(this); 2944 // run after build scripts 2945 runScripts(getAfterBuildScripts()); 2946 return results; 2947 } 2948 2949 /** 2950 * Run train scripts, waits for completion before returning. 2951 */ 2952 private synchronized void runScripts(List<String> scripts) { 2953 if (scripts.size() > 0) { 2954 // save the current status 2955 setOldStatusCode(getStatusCode()); 2956 setStatusCode(CODE_RUN_SCRIPTS); 2957 // create the python interpreter thread 2958 JmriScriptEngineManager.getDefault().initializeAllEngines(); 2959 // find the number of active threads 2960 ThreadGroup root = Thread.currentThread().getThreadGroup(); 2961 int numberOfThreads = root.activeCount(); 2962 // log.debug("Number of active threads: {}", numberOfThreads); 2963 for (String scriptPathname : scripts) { 2964 try { 2965 JmriScriptEngineManager.getDefault() 2966 .runScript(new File(jmri.util.FileUtil.getExternalFilename(scriptPathname))); 2967 } catch (Exception e) { 2968 log.error("Problem with script: {}", scriptPathname); 2969 } 2970 } 2971 // need to wait for scripts to complete or 4 seconds maximum 2972 int count = 0; 2973 while (root.activeCount() > numberOfThreads) { 2974 log.debug("Number of active threads: {}, at start: {}", root.activeCount(), numberOfThreads); 2975 try { 2976 wait(40); 2977 } catch (InterruptedException e) { 2978 Thread.currentThread().interrupt(); 2979 } 2980 if (count++ > 100) { 2981 break; // 4 seconds maximum 40*100 = 4000 2982 } 2983 } 2984 setStatusCode(getOldStatusCode()); 2985 } 2986 } 2987 2988 public boolean printBuildReport() { 2989 boolean isPreview = (InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled() || 2990 Setup.isBuildReportAlwaysPreviewEnabled()); 2991 return printBuildReport(isPreview); 2992 } 2993 2994 public boolean printBuildReport(boolean isPreview) { 2995 File buildFile = InstanceManager.getDefault(TrainManagerXml.class).getTrainBuildReportFile(getName()); 2996 if (!buildFile.exists()) { 2997 log.warn("Build file missing for train {}", getName()); 2998 return false; 2999 } 3000 3001 if (isPreview && Setup.isBuildReportEditorEnabled()) { 3002 TrainPrintUtilities.editReport(buildFile, getName()); 3003 } else { 3004 TrainPrintUtilities.printReport(buildFile, 3005 Bundle.getMessage("buildReport", getDescription()), 3006 isPreview, NONE, true, NONE, NONE, Setup.PORTRAIT, Setup.getBuildReportFontSize(), true); 3007 } 3008 return true; 3009 } 3010 3011 protected void setBuildFailed(boolean status) { 3012 boolean old = _buildFailed; 3013 _buildFailed = status; 3014 if (old != status) { 3015 setDirtyAndFirePropertyChange("buildFailed", old ? "true" : "false", status ? "true" : "false"); // NOI18N 3016 } 3017 } 3018 3019 /** 3020 * Returns true if the train build failed. Note that returning false doesn't 3021 * mean the build was successful. 3022 * 3023 * @return true if train build failed. 3024 */ 3025 public boolean isBuildFailed() { 3026 return _buildFailed; 3027 } 3028 3029 protected void setBuildFailedMessage(String message) { 3030 String old = _buildFailedMessage; 3031 _buildFailedMessage = message; 3032 if (!old.equals(message)) { 3033 setDirtyAndFirePropertyChange("buildFailedMessage", old, message); // NOI18N 3034 } 3035 } 3036 3037 protected String getBuildFailedMessage() { 3038 return _buildFailedMessage; 3039 } 3040 3041 /** 3042 * Print manifest for train if already built. 3043 * 3044 * @return true if print successful. 3045 */ 3046 public boolean printManifestIfBuilt() { 3047 if (isBuilt()) { 3048 boolean isPreview = InstanceManager.getDefault(TrainManager.class).isPrintPreviewEnabled(); 3049 return (printManifest(isPreview)); 3050 } else { 3051 log.debug("Need to build train ({}) before printing manifest", getName()); 3052 return false; 3053 } 3054 } 3055 3056 /** 3057 * Print manifest for train. 3058 * 3059 * @param isPreview True if preview. 3060 * @return true if print successful, false if train print file not found. 3061 */ 3062 public boolean printManifest(boolean isPreview) { 3063 if (isModified()) { 3064 new TrainManifest(this); 3065 try { 3066 new JsonManifest(this).build(); 3067 } catch (IOException ex) { 3068 log.error("Unable to create JSON manifest {}", ex.getLocalizedMessage()); 3069 } 3070 new TrainCsvManifest(this); 3071 } 3072 File file = InstanceManager.getDefault(TrainManagerXml.class).getTrainManifestFile(getName()); 3073 if (!file.exists()) { 3074 log.warn("Manifest file missing for train ({})", getName()); 3075 return false; 3076 } 3077 if (isPreview && Setup.isManifestEditorEnabled()) { 3078 TrainUtilities.openDesktop(file); 3079 return true; 3080 } 3081 String logoURL = Setup.NONE; 3082 if (!getManifestLogoPathName().equals(NONE)) { 3083 logoURL = FileUtil.getExternalFilename(getManifestLogoPathName()); 3084 } else if (!Setup.getManifestLogoURL().equals(Setup.NONE)) { 3085 logoURL = FileUtil.getExternalFilename(Setup.getManifestLogoURL()); 3086 } 3087 Location departs = InstanceManager.getDefault(LocationManager.class).getLocationByName(getTrainDepartsName()); 3088 String printerName = Location.NONE; 3089 if (departs != null) { 3090 printerName = departs.getDefaultPrinterName(); 3091 } 3092 // the train description shouldn't exceed half of the page width or the 3093 // page number will be overwritten 3094 String name = getDescription(); 3095 if (name.length() > TrainCommon.getManifestHeaderLineLength() / 2) { 3096 name = name.substring(0, TrainCommon.getManifestHeaderLineLength() / 2); 3097 } 3098 TrainPrintUtilities.printReport(file, name, isPreview, Setup.getFontName(), false, logoURL, printerName, 3099 Setup.getManifestOrientation(), Setup.getManifestFontSize(), Setup.isPrintPageHeaderEnabled()); 3100 if (!isPreview) { 3101 setPrinted(true); 3102 } 3103 return true; 3104 } 3105 3106 public boolean openFile() { 3107 File file = createCsvManifestFile(); 3108 if (file == null || !file.exists()) { 3109 log.warn("CSV manifest file missing for train {}", getName()); 3110 return false; 3111 } 3112 TrainUtilities.openDesktop(file); 3113 return true; 3114 } 3115 3116 public boolean runFile() { 3117 File file = createCsvManifestFile(); 3118 if (file == null || !file.exists()) { 3119 log.warn("CSV manifest file missing for train {}", getName()); 3120 return false; 3121 } 3122 // Set up to process the CSV file by the external Manifest program 3123 InstanceManager.getDefault(TrainCustomManifest.class).addCsvFile(file); 3124 if (!InstanceManager.getDefault(TrainCustomManifest.class).process()) { 3125 if (!InstanceManager.getDefault(TrainCustomManifest.class).excelFileExists()) { 3126 JmriJOptionPane.showMessageDialog(null, 3127 Bundle.getMessage("LoadDirectoryNameFileName", 3128 InstanceManager.getDefault(TrainCustomManifest.class).getDirectoryPathName(), 3129 InstanceManager.getDefault(TrainCustomManifest.class).getFileName()), 3130 Bundle.getMessage("ManifestCreatorNotFound"), JmriJOptionPane.ERROR_MESSAGE); 3131 } 3132 return false; 3133 } 3134 return true; 3135 } 3136 3137 public File createCsvManifestFile() { 3138 if (isModified()) { 3139 new TrainManifest(this); 3140 try { 3141 new JsonManifest(this).build(); 3142 } catch (IOException ex) { 3143 log.error("Unable to create JSON manifest {}", ex.getLocalizedMessage()); 3144 } 3145 new TrainCsvManifest(this); 3146 } 3147 File file = InstanceManager.getDefault(TrainManagerXml.class).getTrainCsvManifestFile(getName()); 3148 if (!file.exists()) { 3149 log.warn("CSV manifest file was not created for train ({})", getName()); 3150 return null; 3151 } 3152 return file; 3153 } 3154 3155 public void setPrinted(boolean printed) { 3156 boolean old = _printed; 3157 _printed = printed; 3158 if (old != printed) { 3159 setDirtyAndFirePropertyChange("trainPrinted", old ? "true" : "false", printed ? "true" : "false"); // NOI18N 3160 } 3161 } 3162 3163 /** 3164 * Used to determine if train manifest was printed. 3165 * 3166 * @return true if the train manifest was printed. 3167 */ 3168 public boolean isPrinted() { 3169 return _printed; 3170 } 3171 3172 /** 3173 * Sets the panel position for the train icon for the current route location. 3174 * 3175 * @return true if train coordinates can be set 3176 */ 3177 public boolean setTrainIconCoordinates() { 3178 if (Setup.isTrainIconCordEnabled() && getCurrentRouteLocation() != null && _trainIcon != null) { 3179 getCurrentRouteLocation().setTrainIconX(_trainIcon.getX()); 3180 getCurrentRouteLocation().setTrainIconY(_trainIcon.getY()); 3181 return true; 3182 } 3183 return false; 3184 } 3185 3186 /** 3187 * Terminate train. 3188 */ 3189 public void terminate() { 3190 while (isBuilt()) { 3191 move(); 3192 } 3193 } 3194 3195 /** 3196 * Move train to next location in the route. Will move engines, cars, and train 3197 * icon. Will also terminate a train after it arrives at its final destination. 3198 */ 3199 public void move() { 3200 log.debug("Move train ({})", getName()); 3201 if (getRoute() == null || getCurrentRouteLocation() == null) { 3202 setBuilt(false); // break terminate loop 3203 return; 3204 } 3205 if (!isBuilt()) { 3206 log.error("ERROR attempt to move train ({}) that hasn't been built", getName()); 3207 return; 3208 } 3209 RouteLocation rl = getCurrentRouteLocation(); 3210 RouteLocation rlNext = getNextRouteLocation(rl); 3211 3212 setCurrentLocation(rlNext); 3213 3214 // cars and engines will move via property change 3215 setDirtyAndFirePropertyChange(TRAIN_LOCATION_CHANGED_PROPERTY, rl, rlNext); 3216 moveTrainIcon(rlNext); 3217 updateStatus(rl, rlNext); 3218 // tell GUI that train has complete its move 3219 setDirtyAndFirePropertyChange(TRAIN_MOVE_COMPLETE_CHANGED_PROPERTY, rl, rlNext); 3220 } 3221 3222 /** 3223 * Move train to a location in the train's route. Code checks to see if the 3224 * location requested is part of the train's route and if the train hasn't 3225 * already visited the location. This command can only move the train forward in 3226 * its route. Note that you can not terminate the train using this command. See 3227 * move() or terminate(). 3228 * 3229 * @param locationName The name of the location to move this train. 3230 * @return true if train was able to move to the named location. 3231 */ 3232 public boolean move(String locationName) { 3233 log.info("Move train ({}) to location ({})", getName(), locationName); 3234 if (getRoute() == null || getCurrentRouteLocation() == null) { 3235 return false; 3236 } 3237 List<RouteLocation> routeList = getRoute().getLocationsBySequenceList(); 3238 for (int i = 0; i < routeList.size(); i++) { 3239 RouteLocation rl = routeList.get(i); 3240 if (getCurrentRouteLocation() == rl) { 3241 for (int j = i + 1; j < routeList.size(); j++) { 3242 rl = routeList.get(j); 3243 if (rl.getName().equals(locationName)) { 3244 log.debug("Found location ({}) moving train to this location", locationName); 3245 for (j = i + 1; j < routeList.size(); j++) { 3246 rl = routeList.get(j); 3247 move(); 3248 if (rl.getName().equals(locationName)) { 3249 return true; 3250 } 3251 } 3252 } 3253 } 3254 break; // done 3255 } 3256 } 3257 return false; 3258 } 3259 3260 /** 3261 * Moves the train to the specified route location 3262 * 3263 * @param rl route location 3264 * @return true if successful 3265 */ 3266 public boolean move(RouteLocation rl) { 3267 if (rl == null) { 3268 return false; 3269 } 3270 log.debug("Move train ({}) to location ({})", getName(), rl.getName()); 3271 if (getRoute() == null || getCurrentRouteLocation() == null) { 3272 return false; 3273 } 3274 boolean foundCurrent = false; 3275 for (RouteLocation xrl : getRoute().getLocationsBySequenceList()) { 3276 if (getCurrentRouteLocation() == xrl) { 3277 foundCurrent = true; 3278 } 3279 if (xrl == rl) { 3280 if (foundCurrent) { 3281 return true; // done 3282 } else { 3283 break; // train passed this location 3284 } 3285 } 3286 if (foundCurrent) { 3287 move(); 3288 } 3289 } 3290 return false; 3291 } 3292 3293 /** 3294 * Move train to the next location in the train's route. The location name 3295 * provided must be equal to the next location name in the train's route. 3296 * 3297 * @param locationName The next location name in the train's route. 3298 * @return true if successful. 3299 */ 3300 public boolean moveToNextLocation(String locationName) { 3301 if (getNextLocationName().equals(locationName)) { 3302 move(); 3303 return true; 3304 } 3305 return false; 3306 } 3307 3308 public void loadTrainIcon() { 3309 if (getCurrentRouteLocation() != null) { 3310 moveTrainIcon(getCurrentRouteLocation()); 3311 } 3312 } 3313 3314 private final boolean animation = true; // when true use animation for icon 3315 // moves 3316 TrainIconAnimation _ta; 3317 3318 /* 3319 * The train icon is moved to route location (rl) for this train 3320 */ 3321 protected void moveTrainIcon(RouteLocation rl) { 3322 // create train icon if at departure, if program has been restarted, or removed 3323 if (rl == getTrainDepartsRouteLocation() || _trainIcon == null || !_trainIcon.isActive()) { 3324 createTrainIcon(rl); 3325 } 3326 // is the lead engine still in train 3327 if (getLeadEngine() != null && getLeadEngine().getRouteDestination() == rl && rl != null) { 3328 log.debug("Engine ({}) arriving at destination {}", getLeadEngine().toString(), rl.getName()); 3329 } 3330 if (_trainIcon != null && _trainIcon.isActive()) { 3331 setTrainIconColor(); 3332 _trainIcon.setShowToolTip(true); 3333 String txt = null; 3334 if (getCurrentLocationName().equals(NONE)) { 3335 txt = getDescription() + " " + Bundle.getMessage("Terminated") + " (" + getTrainTerminatesName() + ")"; 3336 } else { 3337 txt = Bundle.getMessage("TrainAtNext", 3338 getDescription(), getCurrentLocationName(), getNextLocationName(), getTrainLength(), 3339 Setup.getLengthUnit().toLowerCase()); 3340 } 3341 _trainIcon.getToolTip().setText(txt); 3342 _trainIcon.getToolTip().setBackgroundColor(Color.white); 3343 // rl can be null when train is terminated. 3344 if (rl != null) { 3345 if (rl.getTrainIconX() != 0 || rl.getTrainIconY() != 0) { 3346 if (animation) { 3347 TrainIconAnimation ta = new TrainIconAnimation(_trainIcon, rl, _ta); 3348 ta.start(); // start the animation 3349 _ta = ta; 3350 } else { 3351 _trainIcon.setLocation(rl.getTrainIconX(), rl.getTrainIconY()); 3352 } 3353 } 3354 } 3355 } 3356 } 3357 3358 public String getIconName() { 3359 String name = getName(); 3360 if (isBuilt() && getLeadEngine() != null && Setup.isTrainIconAppendEnabled()) { 3361 name += " " + getLeadEngine().getNumber(); 3362 } 3363 return name; 3364 } 3365 3366 public String getLeadEngineNumber() { 3367 if (getLeadEngine() == null) { 3368 return NONE; 3369 } 3370 return getLeadEngine().getNumber(); 3371 } 3372 3373 public String getLeadEngineRoadName() { 3374 if (getLeadEngine() == null) { 3375 return NONE; 3376 } 3377 return getLeadEngine().getRoadName(); 3378 } 3379 3380 public String getLeadEngineRoadAndNumber() { 3381 if (getLeadEngine() == null) { 3382 return NONE; 3383 } 3384 return getLeadEngine().toString(); 3385 } 3386 3387 public String getLeadEngineDccAddress() { 3388 if (getLeadEngine() == null) { 3389 return NONE; 3390 } 3391 return getLeadEngine().getDccAddress(); 3392 } 3393 3394 /** 3395 * Gets the lead engine, will create it if the program has been restarted 3396 * 3397 * @return lead engine for this train 3398 */ 3399 public Engine getLeadEngine() { 3400 if (_leadEngine == null && !_leadEngineId.equals(NONE)) { 3401 _leadEngine = InstanceManager.getDefault(EngineManager.class).getById(_leadEngineId); 3402 } 3403 return _leadEngine; 3404 } 3405 3406 public void setLeadEngine(Engine engine) { 3407 if (engine == null) { 3408 _leadEngineId = NONE; 3409 } 3410 _leadEngine = engine; 3411 } 3412 3413 /** 3414 * Returns the lead engine in a train's route. There can be up to two changes in 3415 * the lead engine for a train. 3416 * 3417 * @param routeLocation where in the train's route to find the lead engine. 3418 * @return lead engine 3419 */ 3420 public Engine getLeadEngine(RouteLocation routeLocation) { 3421 Engine lead = null; 3422 for (RouteLocation rl : getRoute().getLocationsBySequenceList()) { 3423 for (Engine engine : InstanceManager.getDefault(EngineManager.class).getByTrainList(this)) { 3424 if (engine.getRouteLocation() == rl && (engine.getConsist() == null || engine.isLead())) { 3425 lead = engine; 3426 break; 3427 } 3428 } 3429 if (rl == routeLocation) { 3430 break; 3431 } 3432 } 3433 return lead; 3434 } 3435 3436 protected TrainIcon _trainIcon = null; 3437 3438 public TrainIcon getTrainIcon() { 3439 return _trainIcon; 3440 } 3441 3442 public void createTrainIcon(RouteLocation rl) { 3443 if (_trainIcon != null && _trainIcon.isActive()) { 3444 _trainIcon.remove(); 3445 } 3446 // if there's a panel specified, get it and place icon 3447 if (!Setup.getPanelName().isEmpty()) { 3448 Editor editor = InstanceManager.getDefault(EditorManager.class).getTargetFrame(Setup.getPanelName()); 3449 if (editor != null) { 3450 try { 3451 _trainIcon = editor.addTrainIcon(getIconName()); 3452 } catch (Exception e) { 3453 log.error("Error placing train ({}) icon on panel ({})", getName(), Setup.getPanelName(), e); 3454 return; 3455 } 3456 _trainIcon.setTrain(this); 3457 if (getIconName().length() > 9) { 3458 _trainIcon.setFont(_trainIcon.getFont().deriveFont(8.f)); 3459 } 3460 if (rl != null) { 3461 _trainIcon.setLocation(rl.getTrainIconX(), rl.getTrainIconY()); 3462 } 3463 // add throttle if there's a throttle manager 3464 if (jmri.InstanceManager.getNullableDefault(jmri.ThrottleManager.class) != null) { 3465 // add throttle if JMRI loco roster entry exist 3466 RosterEntry entry = null; 3467 if (getLeadEngine() != null) { 3468 // first try and find a match based on loco road number 3469 entry = getLeadEngine().getRosterEntry(); 3470 } 3471 if (entry != null) { 3472 _trainIcon.setRosterEntry(entry); 3473 if (getLeadEngine().getConsist() != null) { 3474 _trainIcon.setConsistNumber(getLeadEngine().getConsist().getConsistNumber()); 3475 } 3476 } else { 3477 log.debug("Loco roster entry not found for train ({})", getName()); 3478 } 3479 } 3480 } 3481 } 3482 } 3483 3484 private void setTrainIconColor() { 3485 // Terminated train? 3486 if (getCurrentLocationName().equals(NONE)) { 3487 _trainIcon.setLocoColor(Setup.getTrainIconColorTerminate()); 3488 return; 3489 } 3490 // local train serving only one location? 3491 if (isLocalSwitcher()) { 3492 _trainIcon.setLocoColor(Setup.getTrainIconColorLocal()); 3493 return; 3494 } 3495 // set color based on train direction at current location 3496 if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.NORTH) { 3497 _trainIcon.setLocoColor(Setup.getTrainIconColorNorth()); 3498 } 3499 if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.SOUTH) { 3500 _trainIcon.setLocoColor(Setup.getTrainIconColorSouth()); 3501 } 3502 if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.EAST) { 3503 _trainIcon.setLocoColor(Setup.getTrainIconColorEast()); 3504 } 3505 if (getCurrentRouteLocation().getTrainDirection() == RouteLocation.WEST) { 3506 _trainIcon.setLocoColor(Setup.getTrainIconColorWest()); 3507 } 3508 } 3509 3510 private void updateStatus(RouteLocation old, RouteLocation next) { 3511 if (next != null) { 3512 setStatusCode(CODE_TRAIN_EN_ROUTE); 3513 // run move scripts 3514 runScripts(getMoveScripts()); 3515 } else { 3516 log.debug("Train ({}) terminated", getName()); 3517 setTerminationDate(TrainCommon.getDate(false)); 3518 setStatusCode(CODE_TERMINATED); 3519 setBuilt(false); 3520 // run termination scripts 3521 runScripts(getTerminationScripts()); 3522 } 3523 } 3524 3525 /** 3526 * Sets the print status for switch lists 3527 * 3528 * @param status UNKNOWN PRINTED 3529 */ 3530 public void setSwitchListStatus(String status) { 3531 String old = _switchListStatus; 3532 _switchListStatus = status; 3533 if (!old.equals(status)) { 3534 setDirtyAndFirePropertyChange("switch list train status", old, status); // NOI18N 3535 } 3536 } 3537 3538 public String getSwitchListStatus() { 3539 return _switchListStatus; 3540 } 3541 3542 /** 3543 * Resets the train, removes engines and cars from this train. 3544 * 3545 * @return true if reset successful 3546 */ 3547 public boolean reset() { 3548 // is this train in route? 3549 if (isTrainEnRoute()) { 3550 log.info("Train ({}) has started its route, can not be reset", getName()); 3551 return false; 3552 } 3553 setCurrentLocation(null); 3554 setDepartureTrack(null); 3555 setTerminationTrack(null); 3556 setBuilt(false); 3557 setBuildFailed(false); 3558 setBuildFailedMessage(NONE); 3559 setPrinted(false); 3560 setModified(false); 3561 // remove cars and engines from this train via property change 3562 setStatusCode(CODE_TRAIN_RESET); 3563 // remove train icon 3564 if (_trainIcon != null && _trainIcon.isActive()) { 3565 _trainIcon.remove(); 3566 } 3567 return true; 3568 } 3569 3570 public void dispose() { 3571 if (getRoute() != null) { 3572 getRoute().removePropertyChangeListener(this); 3573 } 3574 InstanceManager.getDefault(CarRoads.class).removePropertyChangeListener(this); 3575 InstanceManager.getDefault(CarTypes.class).removePropertyChangeListener(this); 3576 InstanceManager.getDefault(EngineTypes.class).removePropertyChangeListener(this); 3577 InstanceManager.getDefault(CarOwners.class).removePropertyChangeListener(this); 3578 InstanceManager.getDefault(EngineModels.class).removePropertyChangeListener(this); 3579 3580 setDirtyAndFirePropertyChange(DISPOSE_CHANGED_PROPERTY, null, "Dispose"); // NOI18N 3581 } 3582 3583 /** 3584 * Construct this Entry from XML. This member has to remain synchronized with 3585 * the detailed DTD in operations-trains.dtd 3586 * 3587 * @param e Consist XML element 3588 */ 3589 public Train(Element e) { 3590 org.jdom2.Attribute a; 3591 if ((a = e.getAttribute(Xml.ID)) != null) { 3592 _id = a.getValue(); 3593 } else { 3594 log.warn("no id attribute in train element when reading operations"); 3595 } 3596 if ((a = e.getAttribute(Xml.NAME)) != null) { 3597 _name = a.getValue(); 3598 } 3599 if ((a = e.getAttribute(Xml.DESCRIPTION)) != null) { 3600 _description = a.getValue(); 3601 } 3602 if ((a = e.getAttribute(Xml.DEPART_HOUR)) != null) { 3603 String hour = a.getValue(); 3604 if ((a = e.getAttribute(Xml.DEPART_MINUTE)) != null) { 3605 String minute = a.getValue(); 3606 _departureTime = hour + ":" + minute; 3607 } 3608 } 3609 3610 // Trains table row color 3611 Element eRowColor = e.getChild(Xml.ROW_COLOR); 3612 if (eRowColor != null && (a = eRowColor.getAttribute(Xml.NAME)) != null) { 3613 _tableRowColorName = a.getValue().toLowerCase(); 3614 } 3615 if (eRowColor != null && (a = eRowColor.getAttribute(Xml.RESET_ROW_COLOR)) != null) { 3616 _tableRowColorResetName = a.getValue().toLowerCase(); 3617 } 3618 3619 Element eRoute = e.getChild(Xml.ROUTE); 3620 if (eRoute != null) { 3621 if ((a = eRoute.getAttribute(Xml.ID)) != null) { 3622 setRoute(InstanceManager.getDefault(RouteManager.class).getRouteById(a.getValue())); 3623 } 3624 if (eRoute.getChild(Xml.SKIPS) != null) { 3625 List<Element> skips = eRoute.getChild(Xml.SKIPS).getChildren(Xml.LOCATION); 3626 String[] locs = new String[skips.size()]; 3627 for (int i = 0; i < skips.size(); i++) { 3628 Element loc = skips.get(i); 3629 if ((a = loc.getAttribute(Xml.ID)) != null) { 3630 locs[i] = a.getValue(); 3631 } 3632 } 3633 setTrainSkipsLocations(locs); 3634 } 3635 } else { 3636 // old format 3637 // try and first get the route by id then by name 3638 if ((a = e.getAttribute(Xml.ROUTE_ID)) != null) { 3639 setRoute(InstanceManager.getDefault(RouteManager.class).getRouteById(a.getValue())); 3640 } else if ((a = e.getAttribute(Xml.ROUTE)) != null) { 3641 setRoute(InstanceManager.getDefault(RouteManager.class).getRouteByName(a.getValue())); 3642 } 3643 if ((a = e.getAttribute(Xml.SKIP)) != null) { 3644 String locationIds = a.getValue(); 3645 String[] locs = locationIds.split("%%"); // NOI18N 3646 // log.debug("Train skips: {}", locationIds); 3647 setTrainSkipsLocations(locs); 3648 } 3649 } 3650 // new way of reading car types using elements 3651 if (e.getChild(Xml.TYPES) != null) { 3652 List<Element> carTypes = e.getChild(Xml.TYPES).getChildren(Xml.CAR_TYPE); 3653 String[] types = new String[carTypes.size()]; 3654 for (int i = 0; i < carTypes.size(); i++) { 3655 Element type = carTypes.get(i); 3656 if ((a = type.getAttribute(Xml.NAME)) != null) { 3657 types[i] = a.getValue(); 3658 } 3659 } 3660 setTypeNames(types); 3661 List<Element> locoTypes = e.getChild(Xml.TYPES).getChildren(Xml.LOCO_TYPE); 3662 types = new String[locoTypes.size()]; 3663 for (int i = 0; i < locoTypes.size(); i++) { 3664 Element type = locoTypes.get(i); 3665 if ((a = type.getAttribute(Xml.NAME)) != null) { 3666 types[i] = a.getValue(); 3667 } 3668 } 3669 setTypeNames(types); 3670 } // old way of reading car types up to version 2.99.6 3671 else if ((a = e.getAttribute(Xml.CAR_TYPES)) != null) { 3672 String names = a.getValue(); 3673 String[] types = names.split("%%"); // NOI18N 3674 // log.debug("Car types: {}", names); 3675 setTypeNames(types); 3676 } 3677 // old misspelled format 3678 if ((a = e.getAttribute(Xml.CAR_ROAD_OPERATION)) != null) { 3679 _carRoadOption = a.getValue(); 3680 } 3681 if ((a = e.getAttribute(Xml.CAR_ROAD_OPTION)) != null) { 3682 _carRoadOption = a.getValue(); 3683 } 3684 // new way of reading car roads using elements 3685 if (e.getChild(Xml.CAR_ROADS) != null) { 3686 List<Element> carRoads = e.getChild(Xml.CAR_ROADS).getChildren(Xml.CAR_ROAD); 3687 String[] roads = new String[carRoads.size()]; 3688 for (int i = 0; i < carRoads.size(); i++) { 3689 Element road = carRoads.get(i); 3690 if ((a = road.getAttribute(Xml.NAME)) != null) { 3691 roads[i] = a.getValue(); 3692 } 3693 } 3694 setCarRoadNames(roads); 3695 } // old way of reading car roads up to version 2.99.6 3696 else if ((a = e.getAttribute(Xml.CAR_ROADS)) != null) { 3697 String names = a.getValue(); 3698 String[] roads = names.split("%%"); // NOI18N 3699 log.debug("Train ({}) {} car roads: {}", getName(), getCarRoadOption(), names); 3700 setCarRoadNames(roads); 3701 } 3702 3703 if ((a = e.getAttribute(Xml.LOCO_ROAD_OPTION)) != null) { 3704 _locoRoadOption = a.getValue(); 3705 } 3706 // new way of reading engine roads using elements 3707 if (e.getChild(Xml.LOCO_ROADS) != null) { 3708 List<Element> locoRoads = e.getChild(Xml.LOCO_ROADS).getChildren(Xml.LOCO_ROAD); 3709 String[] roads = new String[locoRoads.size()]; 3710 for (int i = 0; i < locoRoads.size(); i++) { 3711 Element road = locoRoads.get(i); 3712 if ((a = road.getAttribute(Xml.NAME)) != null) { 3713 roads[i] = a.getValue(); 3714 } 3715 } 3716 setLocoRoadNames(roads); 3717 } 3718 3719 if ((a = e.getAttribute(Xml.CAR_LOAD_OPTION)) != null) { 3720 _loadOption = a.getValue(); 3721 } 3722 if ((a = e.getAttribute(Xml.CAR_OWNER_OPTION)) != null) { 3723 _ownerOption = a.getValue(); 3724 } 3725 if ((a = e.getAttribute(Xml.BUILT_START_YEAR)) != null) { 3726 _builtStartYear = a.getValue(); 3727 } 3728 if ((a = e.getAttribute(Xml.BUILT_END_YEAR)) != null) { 3729 _builtEndYear = a.getValue(); 3730 } 3731 // new way of reading car loads using elements 3732 if (e.getChild(Xml.CAR_LOADS) != null) { 3733 List<Element> carLoads = e.getChild(Xml.CAR_LOADS).getChildren(Xml.CAR_LOAD); 3734 String[] loads = new String[carLoads.size()]; 3735 for (int i = 0; i < carLoads.size(); i++) { 3736 Element load = carLoads.get(i); 3737 if ((a = load.getAttribute(Xml.NAME)) != null) { 3738 loads[i] = a.getValue(); 3739 } 3740 } 3741 setLoadNames(loads); 3742 } // old way of reading car loads up to version 2.99.6 3743 else if ((a = e.getAttribute(Xml.CAR_LOADS)) != null) { 3744 String names = a.getValue(); 3745 String[] loads = names.split("%%"); // NOI18N 3746 log.debug("Train ({}) {} car loads: {}", getName(), getLoadOption(), names); 3747 setLoadNames(loads); 3748 } 3749 // new way of reading car owners using elements 3750 if (e.getChild(Xml.CAR_OWNERS) != null) { 3751 List<Element> carOwners = e.getChild(Xml.CAR_OWNERS).getChildren(Xml.CAR_OWNER); 3752 String[] owners = new String[carOwners.size()]; 3753 for (int i = 0; i < carOwners.size(); i++) { 3754 Element owner = carOwners.get(i); 3755 if ((a = owner.getAttribute(Xml.NAME)) != null) { 3756 owners[i] = a.getValue(); 3757 } 3758 } 3759 setOwnerNames(owners); 3760 } // old way of reading car owners up to version 2.99.6 3761 else if ((a = e.getAttribute(Xml.CAR_OWNERS)) != null) { 3762 String names = a.getValue(); 3763 String[] owners = names.split("%%"); // NOI18N 3764 log.debug("Train ({}) {} car owners: {}", getName(), getOwnerOption(), names); 3765 setOwnerNames(owners); 3766 } 3767 3768 if ((a = e.getAttribute(Xml.NUMBER_ENGINES)) != null) { 3769 _numberEngines = a.getValue(); 3770 } 3771 if ((a = e.getAttribute(Xml.LEG2_ENGINES)) != null) { 3772 _leg2Engines = a.getValue(); 3773 } 3774 if ((a = e.getAttribute(Xml.LEG3_ENGINES)) != null) { 3775 _leg3Engines = a.getValue(); 3776 } 3777 if ((a = e.getAttribute(Xml.ENGINE_ROAD)) != null) { 3778 _engineRoad = a.getValue(); 3779 } 3780 if ((a = e.getAttribute(Xml.LEG2_ROAD)) != null) { 3781 _leg2Road = a.getValue(); 3782 } 3783 if ((a = e.getAttribute(Xml.LEG3_ROAD)) != null) { 3784 _leg3Road = a.getValue(); 3785 } 3786 if ((a = e.getAttribute(Xml.ENGINE_MODEL)) != null) { 3787 _engineModel = a.getValue(); 3788 } 3789 if ((a = e.getAttribute(Xml.LEG2_MODEL)) != null) { 3790 _leg2Model = a.getValue(); 3791 } 3792 if ((a = e.getAttribute(Xml.LEG3_MODEL)) != null) { 3793 _leg3Model = a.getValue(); 3794 } 3795 if ((a = e.getAttribute(Xml.REQUIRES)) != null) { 3796 try { 3797 _requires = Integer.parseInt(a.getValue()); 3798 } catch (NumberFormatException ee) { 3799 log.error("Requires ({}) isn't a valid number for train ({})", a.getValue(), getName()); 3800 } 3801 } 3802 if ((a = e.getAttribute(Xml.CABOOSE_ROAD)) != null) { 3803 _cabooseRoad = a.getValue(); 3804 } 3805 if ((a = e.getAttribute(Xml.LEG2_CABOOSE_ROAD)) != null) { 3806 _leg2CabooseRoad = a.getValue(); 3807 } 3808 if ((a = e.getAttribute(Xml.LEG3_CABOOSE_ROAD)) != null) { 3809 _leg3CabooseRoad = a.getValue(); 3810 } 3811 if ((a = e.getAttribute(Xml.LEG2_OPTIONS)) != null) { 3812 try { 3813 _leg2Options = Integer.parseInt(a.getValue()); 3814 } catch (NumberFormatException ee) { 3815 log.error("Leg 2 options ({}) isn't a valid number for train ({})", a.getValue(), getName()); 3816 } 3817 } 3818 if ((a = e.getAttribute(Xml.LEG3_OPTIONS)) != null) { 3819 try { 3820 _leg3Options = Integer.parseInt(a.getValue()); 3821 } catch (NumberFormatException ee) { 3822 log.error("Leg 3 options ({}) isn't a valid number for train ({})", a.getValue(), getName()); 3823 } 3824 } 3825 if ((a = e.getAttribute(Xml.BUILD_NORMAL)) != null) { 3826 _buildNormal = a.getValue().equals(Xml.TRUE); 3827 } 3828 if ((a = e.getAttribute(Xml.TO_TERMINAL)) != null) { 3829 _sendToTerminal = a.getValue().equals(Xml.TRUE); 3830 } 3831 if ((a = e.getAttribute(Xml.ALLOW_LOCAL_MOVES)) != null) { 3832 _allowLocalMoves = a.getValue().equals(Xml.TRUE); 3833 } 3834 if ((a = e.getAttribute(Xml.ALLOW_THROUGH_CARS)) != null) { 3835 _allowThroughCars = a.getValue().equals(Xml.TRUE); 3836 } 3837 if ((a = e.getAttribute(Xml.ALLOW_RETURN)) != null) { 3838 _allowCarsReturnStaging = a.getValue().equals(Xml.TRUE); 3839 } 3840 if ((a = e.getAttribute(Xml.SERVICE_ALL)) != null) { 3841 _serviceAllCarsWithFinalDestinations = a.getValue().equals(Xml.TRUE); 3842 } 3843 if ((a = e.getAttribute(Xml.BUILD_CONSIST)) != null) { 3844 _buildConsist = a.getValue().equals(Xml.TRUE); 3845 } 3846 if ((a = e.getAttribute(Xml.SEND_CUSTOM_STAGING)) != null) { 3847 _sendCarsWithCustomLoadsToStaging = a.getValue().equals(Xml.TRUE); 3848 } 3849 if ((a = e.getAttribute(Xml.BUILT)) != null) { 3850 _built = a.getValue().equals(Xml.TRUE); 3851 } 3852 if ((a = e.getAttribute(Xml.BUILD)) != null) { 3853 _build = a.getValue().equals(Xml.TRUE); 3854 } 3855 if ((a = e.getAttribute(Xml.BUILD_FAILED)) != null) { 3856 _buildFailed = a.getValue().equals(Xml.TRUE); 3857 } 3858 if ((a = e.getAttribute(Xml.BUILD_FAILED_MESSAGE)) != null) { 3859 _buildFailedMessage = a.getValue(); 3860 } 3861 if ((a = e.getAttribute(Xml.PRINTED)) != null) { 3862 _printed = a.getValue().equals(Xml.TRUE); 3863 } 3864 if ((a = e.getAttribute(Xml.MODIFIED)) != null) { 3865 _modified = a.getValue().equals(Xml.TRUE); 3866 } 3867 if ((a = e.getAttribute(Xml.SWITCH_LIST_STATUS)) != null) { 3868 _switchListStatus = a.getValue(); 3869 } 3870 if ((a = e.getAttribute(Xml.LEAD_ENGINE)) != null) { 3871 _leadEngineId = a.getValue(); 3872 } 3873 if ((a = e.getAttribute(Xml.TERMINATION_DATE)) != null) { 3874 _statusTerminatedDate = a.getValue(); 3875 } 3876 if ((a = e.getAttribute(Xml.REQUESTED_CARS)) != null) { 3877 try { 3878 _statusCarsRequested = Integer.parseInt(a.getValue()); 3879 } catch (NumberFormatException ee) { 3880 log.error("Status cars requested ({}) isn't a valid number for train ({})", a.getValue(), getName()); 3881 } 3882 } 3883 if ((a = e.getAttribute(Xml.STATUS_CODE)) != null) { 3884 try { 3885 _statusCode = Integer.parseInt(a.getValue()); 3886 } catch (NumberFormatException ee) { 3887 log.error("Status code ({}) isn't a valid number for train ({})", a.getValue(), getName()); 3888 } 3889 } else if ((a = e.getAttribute(Xml.STATUS)) != null) { 3890 // attempt to recover status code 3891 String status = a.getValue(); 3892 if (status.startsWith(BUILD_FAILED)) { 3893 _statusCode = CODE_BUILD_FAILED; 3894 } else if (status.startsWith(BUILT)) { 3895 _statusCode = CODE_BUILT; 3896 } else if (status.startsWith(PARTIAL_BUILT)) { 3897 _statusCode = CODE_PARTIAL_BUILT; 3898 } else if (status.startsWith(TERMINATED)) { 3899 String[] splitStatus = status.split(" "); 3900 if (splitStatus.length > 1) { 3901 _statusTerminatedDate = splitStatus[1]; 3902 } 3903 _statusCode = CODE_TERMINATED; 3904 } else if (status.startsWith(TRAIN_EN_ROUTE)) { 3905 _statusCode = CODE_TRAIN_EN_ROUTE; 3906 } else if (status.startsWith(TRAIN_RESET)) { 3907 _statusCode = CODE_TRAIN_RESET; 3908 } else { 3909 _statusCode = CODE_UNKNOWN; 3910 } 3911 } 3912 if ((a = e.getAttribute(Xml.OLD_STATUS_CODE)) != null) { 3913 try { 3914 _oldStatusCode = Integer.parseInt(a.getValue()); 3915 } catch (NumberFormatException ee) { 3916 log.error("Old status code ({}) isn't a valid number for train ({})", a.getValue(), getName()); 3917 } 3918 } else { 3919 _oldStatusCode = getStatusCode(); // use current status code if one 3920 // wasn't saved 3921 } 3922 if ((a = e.getAttribute(Xml.COMMENT)) != null) { 3923 _comment = a.getValue(); 3924 } 3925 if (getRoute() != null) { 3926 if ((a = e.getAttribute(Xml.CURRENT)) != null) { 3927 _current = getRoute().getLocationById(a.getValue()); 3928 } 3929 if ((a = e.getAttribute(Xml.LEG2_START)) != null) { 3930 _leg2Start = getRoute().getLocationById(a.getValue()); 3931 } 3932 if ((a = e.getAttribute(Xml.LEG3_START)) != null) { 3933 _leg3Start = getRoute().getLocationById(a.getValue()); 3934 } 3935 if ((a = e.getAttribute(Xml.LEG2_END)) != null) { 3936 _end2Leg = getRoute().getLocationById(a.getValue()); 3937 } 3938 if ((a = e.getAttribute(Xml.LEG3_END)) != null) { 3939 _leg3End = getRoute().getLocationById(a.getValue()); 3940 } 3941 if ((a = e.getAttribute(Xml.DEPARTURE_TRACK)) != null) { 3942 Location location = InstanceManager.getDefault(LocationManager.class) 3943 .getLocationByName(getTrainDepartsName()); 3944 if (location != null) { 3945 _departureTrack = location.getTrackById(a.getValue()); 3946 } else { 3947 log.error("Departure location not found for track {}", a.getValue()); 3948 } 3949 } 3950 if ((a = e.getAttribute(Xml.TERMINATION_TRACK)) != null) { 3951 Location location = InstanceManager.getDefault(LocationManager.class) 3952 .getLocationByName(getTrainTerminatesName()); 3953 if (location != null) { 3954 _terminationTrack = location.getTrackById(a.getValue()); 3955 } else { 3956 log.error("Termiation location not found for track {}", a.getValue()); 3957 } 3958 } 3959 } 3960 3961 // check for scripts 3962 if (e.getChild(Xml.SCRIPTS) != null) { 3963 List<Element> lb = e.getChild(Xml.SCRIPTS).getChildren(Xml.BUILD); 3964 for (Element es : lb) { 3965 if ((a = es.getAttribute(Xml.NAME)) != null) { 3966 addBuildScript(a.getValue()); 3967 } 3968 } 3969 List<Element> lab = e.getChild(Xml.SCRIPTS).getChildren(Xml.AFTER_BUILD); 3970 for (Element es : lab) { 3971 if ((a = es.getAttribute(Xml.NAME)) != null) { 3972 addAfterBuildScript(a.getValue()); 3973 } 3974 } 3975 List<Element> lm = e.getChild(Xml.SCRIPTS).getChildren(Xml.MOVE); 3976 for (Element es : lm) { 3977 if ((a = es.getAttribute(Xml.NAME)) != null) { 3978 addMoveScript(a.getValue()); 3979 } 3980 } 3981 List<Element> lt = e.getChild(Xml.SCRIPTS).getChildren(Xml.TERMINATE); 3982 for (Element es : lt) { 3983 if ((a = es.getAttribute(Xml.NAME)) != null) { 3984 addTerminationScript(a.getValue()); 3985 } 3986 } 3987 } 3988 // check for optional railroad name and logo 3989 if ((e.getChild(Xml.RAIL_ROAD) != null) && (a = e.getChild(Xml.RAIL_ROAD).getAttribute(Xml.NAME)) != null) { 3990 String name = a.getValue(); 3991 setRailroadName(name); 3992 } 3993 if ((e.getChild(Xml.MANIFEST_LOGO) != null)) { 3994 if ((a = e.getChild(Xml.MANIFEST_LOGO).getAttribute(Xml.NAME)) != null) { 3995 setManifestLogoPathName(a.getValue()); 3996 } 3997 } 3998 if ((a = e.getAttribute(Xml.SHOW_TIMES)) != null) { 3999 _showTimes = a.getValue().equals(Xml.TRUE); 4000 } 4001 4002 addPropertyChangeListerners(); 4003 } 4004 4005 private void addPropertyChangeListerners() { 4006 InstanceManager.getDefault(CarRoads.class).addPropertyChangeListener(this); 4007 InstanceManager.getDefault(CarTypes.class).addPropertyChangeListener(this); 4008 InstanceManager.getDefault(EngineTypes.class).addPropertyChangeListener(this); 4009 InstanceManager.getDefault(CarOwners.class).addPropertyChangeListener(this); 4010 InstanceManager.getDefault(EngineModels.class).addPropertyChangeListener(this); 4011 } 4012 4013 /** 4014 * Create an XML element to represent this Entry. This member has to remain 4015 * synchronized with the detailed DTD in operations-trains.dtd. 4016 * 4017 * @return Contents in a JDOM Element 4018 */ 4019 public Element store() { 4020 Element e = new Element(Xml.TRAIN); 4021 e.setAttribute(Xml.ID, getId()); 4022 e.setAttribute(Xml.NAME, getName()); 4023 e.setAttribute(Xml.DESCRIPTION, getRawDescription()); 4024 e.setAttribute(Xml.DEPART_HOUR, getDepartureTimeHour()); 4025 e.setAttribute(Xml.DEPART_MINUTE, getDepartureTimeMinute()); 4026 4027 Element eRowColor = new Element(Xml.ROW_COLOR); 4028 eRowColor.setAttribute(Xml.NAME, getTableRowColorName()); 4029 eRowColor.setAttribute(Xml.RESET_ROW_COLOR, getTableRowColorNameReset()); 4030 e.addContent(eRowColor); 4031 4032 Element eRoute = new Element(Xml.ROUTE); 4033 if (getRoute() != null) { 4034 eRoute.setAttribute(Xml.NAME, getRoute().getName()); 4035 eRoute.setAttribute(Xml.ID, getRoute().getId()); 4036 e.addContent(eRoute); 4037 // build list of locations that this train skips 4038 String[] locationIds = getTrainSkipsLocations(); 4039 if (locationIds.length > 0) { 4040 Element eSkips = new Element(Xml.SKIPS); 4041 for (String id : locationIds) { 4042 Element eLoc = new Element(Xml.LOCATION); 4043 RouteLocation rl = getRoute().getLocationById(id); 4044 if (rl != null) { 4045 eLoc.setAttribute(Xml.NAME, rl.getName()); 4046 eLoc.setAttribute(Xml.ID, id); 4047 eSkips.addContent(eLoc); 4048 } 4049 } 4050 eRoute.addContent(eSkips); 4051 } 4052 } 4053 // build list of locations that this train skips 4054 if (getCurrentRouteLocation() != null) { 4055 e.setAttribute(Xml.CURRENT, getCurrentRouteLocation().getId()); 4056 } 4057 if (getDepartureTrack() != null) { 4058 e.setAttribute(Xml.DEPARTURE_TRACK, getDepartureTrack().getId()); 4059 } 4060 if (getTerminationTrack() != null) { 4061 e.setAttribute(Xml.TERMINATION_TRACK, getTerminationTrack().getId()); 4062 } 4063 e.setAttribute(Xml.BUILT_START_YEAR, getBuiltStartYear()); 4064 e.setAttribute(Xml.BUILT_END_YEAR, getBuiltEndYear()); 4065 e.setAttribute(Xml.NUMBER_ENGINES, getNumberEngines()); 4066 e.setAttribute(Xml.ENGINE_ROAD, getEngineRoad()); 4067 e.setAttribute(Xml.ENGINE_MODEL, getEngineModel()); 4068 e.setAttribute(Xml.REQUIRES, Integer.toString(getRequirements())); 4069 e.setAttribute(Xml.CABOOSE_ROAD, getCabooseRoad()); 4070 e.setAttribute(Xml.BUILD_NORMAL, isBuildTrainNormalEnabled() ? Xml.TRUE : Xml.FALSE); 4071 e.setAttribute(Xml.TO_TERMINAL, isSendCarsToTerminalEnabled() ? Xml.TRUE : Xml.FALSE); 4072 e.setAttribute(Xml.ALLOW_LOCAL_MOVES, isAllowLocalMovesEnabled() ? Xml.TRUE : Xml.FALSE); 4073 e.setAttribute(Xml.ALLOW_RETURN, isAllowReturnToStagingEnabled() ? Xml.TRUE : Xml.FALSE); 4074 e.setAttribute(Xml.ALLOW_THROUGH_CARS, isAllowThroughCarsEnabled() ? Xml.TRUE : Xml.FALSE); 4075 e.setAttribute(Xml.SERVICE_ALL, isServiceAllCarsWithFinalDestinationsEnabled() ? Xml.TRUE : Xml.FALSE); 4076 e.setAttribute(Xml.SEND_CUSTOM_STAGING, isSendCarsWithCustomLoadsToStagingEnabled() ? Xml.TRUE : Xml.FALSE); 4077 e.setAttribute(Xml.BUILD_CONSIST, isBuildConsistEnabled() ? Xml.TRUE : Xml.FALSE); 4078 e.setAttribute(Xml.BUILT, isBuilt() ? Xml.TRUE : Xml.FALSE); 4079 e.setAttribute(Xml.BUILD, isBuildEnabled() ? Xml.TRUE : Xml.FALSE); 4080 e.setAttribute(Xml.BUILD_FAILED, isBuildFailed() ? Xml.TRUE : Xml.FALSE); 4081 e.setAttribute(Xml.BUILD_FAILED_MESSAGE, getBuildFailedMessage()); 4082 e.setAttribute(Xml.PRINTED, isPrinted() ? Xml.TRUE : Xml.FALSE); 4083 e.setAttribute(Xml.MODIFIED, isModified() ? Xml.TRUE : Xml.FALSE); 4084 e.setAttribute(Xml.SWITCH_LIST_STATUS, getSwitchListStatus()); 4085 if (getLeadEngine() != null) { 4086 e.setAttribute(Xml.LEAD_ENGINE, getLeadEngine().getId()); 4087 } 4088 e.setAttribute(Xml.STATUS, getStatus()); 4089 e.setAttribute(Xml.TERMINATION_DATE, getTerminationDate()); 4090 e.setAttribute(Xml.REQUESTED_CARS, Integer.toString(getNumberCarsRequested())); 4091 e.setAttribute(Xml.STATUS_CODE, Integer.toString(getStatusCode())); 4092 e.setAttribute(Xml.OLD_STATUS_CODE, Integer.toString(getOldStatusCode())); 4093 e.setAttribute(Xml.COMMENT, getCommentWithColor()); 4094 e.setAttribute(Xml.SHOW_TIMES, isShowArrivalAndDepartureTimesEnabled() ? Xml.TRUE : Xml.FALSE); 4095 // build list of car types for this train 4096 String[] types = getTypeNames(); 4097 // new way of saving car types 4098 Element eTypes = new Element(Xml.TYPES); 4099 for (String type : types) { 4100 // don't save types that have been deleted by user 4101 if (InstanceManager.getDefault(EngineTypes.class).containsName(type)) { 4102 Element eType = new Element(Xml.LOCO_TYPE); 4103 eType.setAttribute(Xml.NAME, type); 4104 eTypes.addContent(eType); 4105 } else if (InstanceManager.getDefault(CarTypes.class).containsName(type)) { 4106 Element eType = new Element(Xml.CAR_TYPE); 4107 eType.setAttribute(Xml.NAME, type); 4108 eTypes.addContent(eType); 4109 } 4110 } 4111 e.addContent(eTypes); 4112 // save list of car roads for this train 4113 if (!getCarRoadOption().equals(ALL_ROADS)) { 4114 e.setAttribute(Xml.CAR_ROAD_OPTION, getCarRoadOption()); 4115 String[] roads = getCarRoadNames(); 4116 // new way of saving road names 4117 Element eRoads = new Element(Xml.CAR_ROADS); 4118 for (String road : roads) { 4119 Element eRoad = new Element(Xml.CAR_ROAD); 4120 eRoad.setAttribute(Xml.NAME, road); 4121 eRoads.addContent(eRoad); 4122 } 4123 e.addContent(eRoads); 4124 } 4125 // save list of engine roads for this train 4126 if (!getLocoRoadOption().equals(ALL_ROADS)) { 4127 e.setAttribute(Xml.LOCO_ROAD_OPTION, getLocoRoadOption()); 4128 String[] roads = getLocoRoadNames(); 4129 Element eRoads = new Element(Xml.LOCO_ROADS); 4130 for (String road : roads) { 4131 Element eRoad = new Element(Xml.LOCO_ROAD); 4132 eRoad.setAttribute(Xml.NAME, road); 4133 eRoads.addContent(eRoad); 4134 } 4135 e.addContent(eRoads); 4136 } 4137 // save list of car loads for this train 4138 if (!getLoadOption().equals(ALL_LOADS)) { 4139 e.setAttribute(Xml.CAR_LOAD_OPTION, getLoadOption()); 4140 String[] loads = getLoadNames(); 4141 // new way of saving car loads 4142 Element eLoads = new Element(Xml.CAR_LOADS); 4143 for (String load : loads) { 4144 Element eLoad = new Element(Xml.CAR_LOAD); 4145 eLoad.setAttribute(Xml.NAME, load); 4146 eLoads.addContent(eLoad); 4147 } 4148 e.addContent(eLoads); 4149 } 4150 // save list of car owners for this train 4151 if (!getOwnerOption().equals(ALL_OWNERS)) { 4152 e.setAttribute(Xml.CAR_OWNER_OPTION, getOwnerOption()); 4153 String[] owners = getOwnerNames(); 4154 // new way of saving car owners 4155 Element eOwners = new Element(Xml.CAR_OWNERS); 4156 for (String owner : owners) { 4157 Element eOwner = new Element(Xml.CAR_OWNER); 4158 eOwner.setAttribute(Xml.NAME, owner); 4159 eOwners.addContent(eOwner); 4160 } 4161 e.addContent(eOwners); 4162 } 4163 // save list of scripts for this train 4164 if (getBuildScripts().size() > 0 || 4165 getAfterBuildScripts().size() > 0 || 4166 getMoveScripts().size() > 0 || 4167 getTerminationScripts().size() > 0) { 4168 Element es = new Element(Xml.SCRIPTS); 4169 if (getBuildScripts().size() > 0) { 4170 for (String scriptPathname : getBuildScripts()) { 4171 Element em = new Element(Xml.BUILD); 4172 em.setAttribute(Xml.NAME, scriptPathname); 4173 es.addContent(em); 4174 } 4175 } 4176 if (getAfterBuildScripts().size() > 0) { 4177 for (String scriptPathname : getAfterBuildScripts()) { 4178 Element em = new Element(Xml.AFTER_BUILD); 4179 em.setAttribute(Xml.NAME, scriptPathname); 4180 es.addContent(em); 4181 } 4182 } 4183 if (getMoveScripts().size() > 0) { 4184 for (String scriptPathname : getMoveScripts()) { 4185 Element em = new Element(Xml.MOVE); 4186 em.setAttribute(Xml.NAME, scriptPathname); 4187 es.addContent(em); 4188 } 4189 } 4190 // save list of termination scripts for this train 4191 if (getTerminationScripts().size() > 0) { 4192 for (String scriptPathname : getTerminationScripts()) { 4193 Element et = new Element(Xml.TERMINATE); 4194 et.setAttribute(Xml.NAME, scriptPathname); 4195 es.addContent(et); 4196 } 4197 } 4198 e.addContent(es); 4199 } 4200 if (!getRailroadName().equals(NONE)) { 4201 Element r = new Element(Xml.RAIL_ROAD); 4202 r.setAttribute(Xml.NAME, getRailroadName()); 4203 e.addContent(r); 4204 } 4205 if (!getManifestLogoPathName().equals(NONE)) { 4206 Element l = new Element(Xml.MANIFEST_LOGO); 4207 l.setAttribute(Xml.NAME, getManifestLogoPathName()); 4208 e.addContent(l); 4209 } 4210 4211 if (getSecondLegOptions() != NO_CABOOSE_OR_FRED) { 4212 e.setAttribute(Xml.LEG2_OPTIONS, Integer.toString(getSecondLegOptions())); 4213 e.setAttribute(Xml.LEG2_ENGINES, getSecondLegNumberEngines()); 4214 e.setAttribute(Xml.LEG2_ROAD, getSecondLegEngineRoad()); 4215 e.setAttribute(Xml.LEG2_MODEL, getSecondLegEngineModel()); 4216 e.setAttribute(Xml.LEG2_CABOOSE_ROAD, getSecondLegCabooseRoad()); 4217 if (getSecondLegStartRouteLocation() != null) { 4218 e.setAttribute(Xml.LEG2_START, getSecondLegStartRouteLocation().getId()); 4219 } 4220 if (getSecondLegEndRouteLocation() != null) { 4221 e.setAttribute(Xml.LEG2_END, getSecondLegEndRouteLocation().getId()); 4222 } 4223 } 4224 if (getThirdLegOptions() != NO_CABOOSE_OR_FRED) { 4225 e.setAttribute(Xml.LEG3_OPTIONS, Integer.toString(getThirdLegOptions())); 4226 e.setAttribute(Xml.LEG3_ENGINES, getThirdLegNumberEngines()); 4227 e.setAttribute(Xml.LEG3_ROAD, getThirdLegEngineRoad()); 4228 e.setAttribute(Xml.LEG3_MODEL, getThirdLegEngineModel()); 4229 e.setAttribute(Xml.LEG3_CABOOSE_ROAD, getThirdLegCabooseRoad()); 4230 if (getThirdLegStartRouteLocation() != null) { 4231 e.setAttribute(Xml.LEG3_START, getThirdLegStartRouteLocation().getId()); 4232 } 4233 if (getThirdLegEndRouteLocation() != null) { 4234 e.setAttribute(Xml.LEG3_END, getThirdLegEndRouteLocation().getId()); 4235 } 4236 } 4237 return e; 4238 } 4239 4240 @Override 4241 public void propertyChange(java.beans.PropertyChangeEvent e) { 4242 if (Control.SHOW_PROPERTY) { 4243 log.debug("Train ({}) sees property change: ({}) old: ({}) new: ({})", getName(), e.getPropertyName(), 4244 e.getOldValue(), e.getNewValue()); 4245 } 4246 if (e.getPropertyName().equals(Route.DISPOSE)) { 4247 setRoute(null); 4248 } 4249 if (e.getPropertyName().equals(CarTypes.CARTYPES_NAME_CHANGED_PROPERTY) || 4250 e.getPropertyName().equals(CarTypes.CARTYPES_CHANGED_PROPERTY) || 4251 e.getPropertyName().equals(EngineTypes.ENGINETYPES_NAME_CHANGED_PROPERTY)) { 4252 replaceType((String) e.getOldValue(), (String) e.getNewValue()); 4253 } 4254 if (e.getPropertyName().equals(CarRoads.CARROADS_NAME_CHANGED_PROPERTY)) { 4255 replaceRoad((String) e.getOldValue(), (String) e.getNewValue()); 4256 } 4257 if (e.getPropertyName().equals(CarOwners.CAROWNERS_NAME_CHANGED_PROPERTY)) { 4258 replaceOwner((String) e.getOldValue(), (String) e.getNewValue()); 4259 } 4260 if (e.getPropertyName().equals(EngineModels.ENGINEMODELS_NAME_CHANGED_PROPERTY)) { 4261 replaceModel((String) e.getOldValue(), (String) e.getNewValue()); 4262 } 4263 // forward route departure time property changes 4264 if (e.getPropertyName().equals(RouteLocation.DEPARTURE_TIME_CHANGED_PROPERTY)) { 4265 setDirtyAndFirePropertyChange(DEPARTURETIME_CHANGED_PROPERTY, e.getOldValue(), e.getNewValue()); 4266 } 4267 // forward any property changes in this train's route 4268 if (e.getSource().getClass().equals(Route.class)) { 4269 setDirtyAndFirePropertyChange(e.getPropertyName(), e.getOldValue(), e.getNewValue()); 4270 } 4271 } 4272 4273 protected void setDirtyAndFirePropertyChange(String p, Object old, Object n) { 4274 InstanceManager.getDefault(TrainManagerXml.class).setDirty(true); 4275 firePropertyChange(p, old, n); 4276 } 4277 4278 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(Train.class); 4279 4280}