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