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