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