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