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