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