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