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