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