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