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