001package jmri.jmrit.operations.trains.trainbuilder; 002 003import java.io.*; 004import java.nio.charset.StandardCharsets; 005import java.time.LocalDateTime; 006import java.time.ZoneId; 007import java.util.*; 008 009import jmri.InstanceManager; 010import jmri.Version; 011import jmri.jmrit.operations.locations.Location; 012import jmri.jmrit.operations.locations.Track; 013import jmri.jmrit.operations.locations.schedules.ScheduleItem; 014import jmri.jmrit.operations.rollingstock.RollingStock; 015import jmri.jmrit.operations.rollingstock.cars.*; 016import jmri.jmrit.operations.rollingstock.engines.Engine; 017import jmri.jmrit.operations.router.Router; 018import jmri.jmrit.operations.routes.RouteLocation; 019import jmri.jmrit.operations.setup.Setup; 020import jmri.jmrit.operations.trains.*; 021import jmri.jmrit.operations.trains.schedules.TrainSchedule; 022import jmri.jmrit.operations.trains.schedules.TrainScheduleManager; 023import jmri.util.swing.JmriJOptionPane; 024 025import org.apache.commons.lang3.StringUtils; 026 027/** 028 * Methods to support the TrainBuilder class. 029 * 030 * @author Daniel Boudreau Copyright (C) 2021, 2026 031 */ 032public class TrainBuilderBase extends TrainCommon { 033 034 // report levels 035 protected static final String ONE = Setup.BUILD_REPORT_MINIMAL; 036 protected static final String THREE = Setup.BUILD_REPORT_NORMAL; 037 protected static final String FIVE = Setup.BUILD_REPORT_DETAILED; 038 protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED; 039 040 protected static final int DISPLAY_CAR_LIMIT_20 = 20; // build exception out 041 // of staging 042 protected static final int DISPLAY_CAR_LIMIT_50 = 50; 043 protected static final int DISPLAY_CAR_LIMIT_100 = 100; 044 045 protected static final boolean USE_BUNIT = true; 046 protected static final String TIMING = "timing of trains"; 047 048 private PrintWriter _buildReport; // build report for this train 049 private LocalDateTime _startTime; // when the build report started 050 private Train _train; // the train being built 051 private List<Engine> _engineList; // engines for this train, modified during build 052 protected int _engineIndex; // index for engineList 053 private List<Car> _carList; // cars for this train, modified during the build 054 protected int _carIndex; // index for carList 055 private List<RouteLocation> _routeList; // ordered list of locations 056 private Location _departLocation; // train departs this location 057 private Track _departStageTrack; // departure staging track (null if not staging) 058 private Location _terminateLocation; // train terminates at this location 059 private Track _terminateStageTrack; // terminate staging track (null if not staging) 060 061 int _numberCars = 0; // number of cars moved by this train 062 int _completedMoves; // the number of pick up car moves for a location 063 int _reqNumOfMoves; // the requested number of car moves for a location 064 065 Engine _lastEngine; // last engine found from getEngine 066 Engine _secondLeadEngine; // lead engine 2nd part of train's route 067 Engine _thirdLeadEngine; // lead engine 3rd part of the train's route 068 069 Hashtable<String, Integer> _numOfBlocks; // Number of blocks of cars 070 // departing staging. 071 List<Car> _notRoutable = new ArrayList<>(); // cars that couldn't be routed 072 List<Location> _modifiedLocations = new ArrayList<>(); // modified locations 073 int _warnings = 0; // the number of warnings in the build report 074 075 // managers 076 TrainManager trainManager = InstanceManager.getDefault(TrainManager.class); 077 TrainScheduleManager trainScheduleManager = InstanceManager.getDefault(TrainScheduleManager.class); 078 CarLoads carLoads = InstanceManager.getDefault(CarLoads.class); 079 Router router = InstanceManager.getDefault(Router.class); 080 081 protected PrintWriter getBuildReport() { 082 return _buildReport; 083 } 084 085 protected void setBuildReport(PrintWriter printWriter) { 086 _buildReport = printWriter; 087 } 088 089 protected LocalDateTime getStartTime() { 090 return _startTime; 091 } 092 093 protected void setStartTime(LocalDateTime date) { 094 _startTime = date; 095 } 096 097 protected Train getTrain() { 098 return _train; 099 } 100 101 protected void setTrain(Train train) { 102 _train = train; 103 } 104 105 protected List<Engine> getEngineList() { 106 return _engineList; 107 } 108 109 protected void setEngineList(List<Engine> list) { 110 _engineList = list; 111 } 112 113 protected List<Car> getCarList() { 114 return _carList; 115 } 116 117 protected void setCarList(List<Car> list) { 118 _carList = list; 119 } 120 121 protected List<RouteLocation> getRouteList() { 122 return _routeList; 123 } 124 125 protected void setRouteList(List<RouteLocation> list) { 126 _routeList = list; 127 } 128 129 protected void remove(Car car) { 130 // remove this car from the list 131 if (getCarList().remove(car)) { 132 _carIndex--; 133 } 134 } 135 136 protected void remove(Engine engine) { 137 // remove this engine from the list 138 if (getEngineList().remove(engine)) { 139 _engineIndex--; 140 } 141 } 142 143 /** 144 * Will also set the termination track if returning to staging 145 * 146 * @param track departure track from staging 147 */ 148 protected void setDepartureStagingTrack(Track track) { 149 if ((getTerminateStagingTrack() == null || getTerminateStagingTrack() == _departStageTrack) && 150 getDepartureLocation() == getTerminateLocation() && 151 Setup.isBuildAggressive() && 152 Setup.isStagingTrackImmediatelyAvail()) { 153 setTerminateStagingTrack(track); // use the same track 154 } 155 _departStageTrack = track; 156 } 157 158 protected Location getDepartureLocation() { 159 return _departLocation; 160 } 161 162 protected void setDepartureLocation(Location location) { 163 _departLocation = location; 164 } 165 166 protected Track getDepartureStagingTrack() { 167 return _departStageTrack; 168 } 169 170 protected void setTerminateStagingTrack(Track track) { 171 _terminateStageTrack = track; 172 } 173 174 protected Location getTerminateLocation() { 175 return _terminateLocation; 176 } 177 178 protected void setTerminateLocation(Location location) { 179 _terminateLocation = location; 180 } 181 182 protected Track getTerminateStagingTrack() { 183 return _terminateStageTrack; 184 } 185 186 protected void createBuildReportFile() throws BuildFailedException { 187 // backup the train's previous build report file 188 InstanceManager.getDefault(TrainManagerXml.class).savePreviousBuildStatusFile(getTrain().getName()); 189 190 // create build report file 191 File file = InstanceManager.getDefault(TrainManagerXml.class).createTrainBuildReportFile(getTrain().getName()); 192 try { 193 setBuildReport(new PrintWriter( 194 new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)), 195 true)); 196 } catch (IOException e) { 197 log.error("Can not open build report file: {}", e.getLocalizedMessage()); 198 throw new BuildFailedException(e); 199 } 200 } 201 202 /** 203 * Creates the build report header information lines. Build report date, 204 * JMRI version, train schedule, build report display levels, setup comment. 205 */ 206 protected void showBuildReportInfo() { 207 addLine(ONE, Bundle.getMessage("BuildReportMsg", getTrain().getName(), getDate(getStartTime()))); 208 addLine(ONE, 209 Bundle.getMessage("BuildReportVersion", Version.name())); 210 if (!trainScheduleManager.getTrainScheduleActiveId().equals(TrainScheduleManager.NONE)) { 211 if (trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY)) { 212 addLine(ONE, Bundle.getMessage("buildActiveSchedule", Bundle.getMessage("Any"))); 213 } else { 214 TrainSchedule sch = trainScheduleManager.getActiveSchedule(); 215 if (sch != null) { 216 addLine(ONE, Bundle.getMessage("buildActiveSchedule", sch.getName())); 217 } 218 } 219 } 220 // show the various build detail levels 221 addLine(THREE, Bundle.getMessage("buildReportLevelThree")); 222 addLine(FIVE, Bundle.getMessage("buildReportLevelFive")); 223 addLine(SEVEN, Bundle.getMessage("buildReportLevelSeven")); 224 225 if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED)) { 226 addLine(SEVEN, Bundle.getMessage("buildRouterReportLevelDetailed")); 227 } else if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) { 228 addLine(SEVEN, Bundle.getMessage("buildRouterReportLevelVeryDetailed")); 229 } 230 231 if (!Setup.getComment().trim().isEmpty()) { 232 addLine(ONE, BLANK_LINE); 233 addLine(ONE, Setup.getComment()); 234 } 235 } 236 237 protected void setUpRoute() throws BuildFailedException { 238 if (getTrain().getRoute() == null) { 239 throw new BuildFailedException( 240 Bundle.getMessage("buildErrorRoute", getTrain().getName())); 241 } 242 // get the train's route 243 setRouteList(getTrain().getRoute().getLocationsBySequenceList()); 244 if (getRouteList().size() < 1) { 245 throw new BuildFailedException( 246 Bundle.getMessage("buildErrorNeedRoute", getTrain().getName())); 247 } 248 // train departs 249 setDepartureLocation(locationManager.getLocationByName(getTrain().getTrainDepartsName())); 250 if (getDepartureLocation() == null) { 251 throw new BuildFailedException( 252 Bundle.getMessage("buildErrorNeedDepLoc", getTrain().getName())); 253 } 254 // train terminates 255 setTerminateLocation(locationManager.getLocationByName(getTrain().getTrainTerminatesName())); 256 if (getTerminateLocation() == null) { 257 throw new BuildFailedException(Bundle.getMessage("buildErrorNeedTermLoc", getTrain().getName())); 258 } 259 } 260 261 /** 262 * show train build options when in detailed mode 263 */ 264 protected void showTrainBuildOptions() { 265 ResourceBundle rb = ResourceBundle.getBundle("jmri.jmrit.operations.setup.JmritOperationsSetupBundle"); 266 addLine(FIVE, BLANK_LINE); 267 addLine(FIVE, Bundle.getMessage("MenuItemBuildOptions") + ":"); 268 if (Setup.isBuildAggressive()) { 269 if (Setup.isBuildOnTime()) { 270 addLine(FIVE, Bundle.getMessage("BuildModeOnTime")); 271 } else { 272 addLine(FIVE, Bundle.getMessage("BuildModeAggressive")); 273 } 274 addLine(FIVE, Bundle.getMessage("BuildNumberPasses", Setup.getNumberPasses())); 275 if (Setup.isStagingTrackImmediatelyAvail() && getDepartureLocation().isStaging()) { 276 addLine(FIVE, Bundle.getMessage("BuildStagingTrackAvail")); 277 } 278 } else { 279 addLine(FIVE, Bundle.getMessage("BuildModeNormal")); 280 } 281 // show switcher options 282 if (getTrain().isLocalSwitcher()) { 283 addLine(FIVE, BLANK_LINE); 284 addLine(FIVE, rb.getString("BorderLayoutSwitcherService") + ":"); 285 if (Setup.isLocalInterchangeMovesEnabled()) { 286 addLine(FIVE, rb.getString("AllowLocalInterchange")); 287 } else { 288 addLine(FIVE, rb.getString("NoAllowLocalInterchange")); 289 } 290 if (Setup.isLocalSpurMovesEnabled()) { 291 addLine(FIVE, rb.getString("AllowLocalSpur")); 292 } else { 293 addLine(FIVE, rb.getString("NoAllowLocalSpur")); 294 } 295 if (Setup.isLocalYardMovesEnabled()) { 296 addLine(FIVE, rb.getString("AllowLocalYard")); 297 } else { 298 addLine(FIVE, rb.getString("NoAllowLocalYard")); 299 } 300 } 301 // show staging options 302 if (getDepartureLocation().isStaging() || getTerminateLocation().isStaging()) { 303 addLine(FIVE, BLANK_LINE); 304 addLine(FIVE, Bundle.getMessage("buildStagingOptions")); 305 306 if (Setup.isStagingTrainCheckEnabled() && getTerminateLocation().isStaging()) { 307 addLine(FIVE, Bundle.getMessage("buildOptionRestrictStaging")); 308 } 309 if (Setup.isStagingTrackImmediatelyAvail() && getTerminateLocation().isStaging()) { 310 addLine(FIVE, rb.getString("StagingAvailable")); 311 } 312 if (Setup.isStagingAllowReturnEnabled() && 313 getDepartureLocation().isStaging() && 314 getTerminateLocation().isStaging() && 315 getDepartureLocation() == getTerminateLocation()) { 316 addLine(FIVE, rb.getString("AllowCarsToReturn")); 317 } 318 if (Setup.isStagingPromptFromEnabled() && getDepartureLocation().isStaging()) { 319 addLine(FIVE, rb.getString("PromptFromStaging")); 320 } 321 if (Setup.isStagingPromptToEnabled() && getTerminateLocation().isStaging()) { 322 addLine(FIVE, rb.getString("PromptToStaging")); 323 } 324 if (Setup.isStagingTryNormalBuildEnabled() && getDepartureLocation().isStaging()) { 325 addLine(FIVE, rb.getString("TryNormalStaging")); 326 } 327 } 328 329 // Car routing options 330 addLine(FIVE, BLANK_LINE); 331 addLine(FIVE, Bundle.getMessage("buildCarRoutingOptions")); 332 333 // warn if car routing is disabled 334 if (!Setup.isCarRoutingEnabled()) { 335 addLine(FIVE, Bundle.getMessage("RoutingDisabled")); 336 _warnings++; 337 } else { 338 if (Setup.isCarRoutingViaYardsEnabled()) { 339 addLine(FIVE, Bundle.getMessage("RoutingViaYardsEnabled")); 340 } 341 if (Setup.isCarRoutingViaStagingEnabled()) { 342 addLine(FIVE, Bundle.getMessage("RoutingViaStagingEnabled")); 343 } 344 if (Setup.isOnlyActiveTrainsEnabled()) { 345 addLine(FIVE, Bundle.getMessage("OnlySelectedTrains")); 346 _warnings++; 347 // list the selected trains 348 for (Train train : trainManager.getTrainsByNameList()) { 349 if (train.isBuildEnabled()) { 350 addLine(SEVEN, 351 Bundle.getMessage("buildTrainNameAndDesc", train.getName(), train.getDescription())); 352 } 353 } 354 if (!getTrain().isBuildEnabled()) { 355 addLine(FIVE, Bundle.getMessage("buildTrainNotSelected", getTrain().getName())); 356 } 357 } else { 358 addLine(FIVE, rb.getString("AllTrains")); 359 } 360 if (Setup.isCheckCarDestinationEnabled()) { 361 addLine(FIVE, Bundle.getMessage("CheckCarDestination")); 362 } 363 } 364 } 365 366 /* 367 * Show the enabled and disabled build options for this train. 368 */ 369 protected void showSpecificTrainBuildOptions() { 370 addLine(FIVE, BLANK_LINE); 371 addLine(FIVE, 372 Bundle.getMessage("buildOptionsForTrain", getTrain().getName())); 373 showSpecificTrainBuildOptions(true); 374 addLine(FIVE, BLANK_LINE); 375 addLine(FIVE, Bundle.getMessage("buildDisabledOptionsForTrain", getTrain().getName())); 376 showSpecificTrainBuildOptions(false); 377 } 378 379 /* 380 * Enabled when true lists selected build options for this train. Enabled 381 * when false list disabled build options for this train. 382 */ 383 private void showSpecificTrainBuildOptions(boolean enabled) { 384 385 if (getTrain().isBuildTrainNormalEnabled() ^ !enabled) { 386 addLine(FIVE, Bundle.getMessage("NormalModeWhenBuilding")); 387 } 388 if (getTrain().isSendCarsToTerminalEnabled() ^ !enabled) { 389 addLine(FIVE, Bundle.getMessage("SendToTerminal", getTerminateLocation().getName())); 390 } 391 if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) ^ !enabled && 392 getDepartureLocation().isStaging() && 393 getDepartureLocation() == getTerminateLocation()) { 394 addLine(FIVE, Bundle.getMessage("AllowCarsToReturn")); 395 } 396 if (getTrain().isAllowLocalMovesEnabled() ^ !enabled) { 397 addLine(FIVE, Bundle.getMessage("AllowLocalMoves")); 398 } 399 if (getTrain().isAllowThroughCarsEnabled() ^ !enabled && getDepartureLocation() != getTerminateLocation()) { 400 addLine(FIVE, Bundle.getMessage("AllowThroughCars")); 401 } 402 if (getTrain().isServiceAllCarsWithFinalDestinationsEnabled() ^ !enabled) { 403 addLine(FIVE, Bundle.getMessage("ServiceAllCars")); 404 } 405 if (getTrain().isSendCarsWithCustomLoadsToStagingEnabled() ^ !enabled) { 406 addLine(FIVE, Bundle.getMessage("SendCustomToStaging")); 407 } 408 if (getTrain().isBuildConsistEnabled() ^ !enabled) { 409 addLine(FIVE, Bundle.getMessage("BuildConsist")); 410 if (enabled) { 411 addLine(SEVEN, Bundle.getMessage("BuildConsistHPT", Setup.getHorsePowerPerTon())); 412 } 413 } 414 } 415 416 /** 417 * Adds to the build report what the train will service. Road and owner 418 * names, built dates, and engine types. 419 */ 420 protected void showTrainServices() { 421 // show road names that this train will service 422 if (!getTrain().getLocoRoadOption().equals(Train.ALL_ROADS)) { 423 addLine(FIVE, Bundle.getMessage("buildTrainLocoRoads", getTrain().getName(), 424 getTrain().getLocoRoadOption(), formatStringToCommaSeparated(getTrain().getLocoRoadNames()))); 425 } 426 // show owner names that this train will service 427 if (!getTrain().getOwnerOption().equals(Train.ALL_OWNERS)) { 428 addLine(FIVE, Bundle.getMessage("buildTrainOwners", getTrain().getName(), getTrain().getOwnerOption(), 429 formatStringToCommaSeparated(getTrain().getOwnerNames()))); 430 } 431 // show built dates serviced 432 if (!getTrain().getBuiltStartYear().equals(Train.NONE)) { 433 addLine(FIVE, 434 Bundle.getMessage("buildTrainBuiltAfter", getTrain().getName(), getTrain().getBuiltStartYear())); 435 } 436 if (!getTrain().getBuiltEndYear().equals(Train.NONE)) { 437 addLine(FIVE, 438 Bundle.getMessage("buildTrainBuiltBefore", getTrain().getName(), getTrain().getBuiltEndYear())); 439 } 440 441 // show engine types that this train will service 442 if (!getTrain().getNumberEngines().equals("0")) { 443 addLine(FIVE, Bundle.getMessage("buildTrainServicesEngineTypes", getTrain().getName())); 444 addLine(FIVE, formatStringToCommaSeparated(getTrain().getLocoTypeNames())); 445 } 446 } 447 448 /** 449 * Show and initialize the train's route. Determines the number of car moves 450 * requested for this train. Also adjust the number of car moves if the 451 * random car moves option was selected. 452 * 453 * @throws BuildFailedException if random variable isn't an integer 454 */ 455 protected void showAndInitializeTrainRoute() throws BuildFailedException { 456 int requestedCarMoves = 0; // how many cars were asked to be moved 457 // TODO: DAB control minimal build by each train 458 addLine(ONE, BLANK_LINE); 459 addLine(THREE, 460 Bundle.getMessage("buildTrainRoute", getTrain().getName(), getTrain().getRoute().getName())); 461 462 // get the number of requested car moves for this train 463 for (RouteLocation rl : getRouteList()) { 464 // check to see if there's a location for each stop in the route 465 // this checks for a deleted location 466 Location location = locationManager.getLocationByName(rl.getName()); 467 if (location == null || rl.getLocation() == null) { 468 throw new BuildFailedException( 469 Bundle.getMessage("buildErrorLocMissing", getTrain().getRoute().getName())); 470 } 471 // train doesn't drop or pick up cars from staging locations found 472 // in middle of a route 473 if (location.isStaging() && 474 rl != getTrain().getTrainDepartsRouteLocation() && 475 rl != getTrain().getTrainTerminatesRouteLocation()) { 476 addLine(ONE, 477 Bundle.getMessage("buildLocStaging", rl.getName())); 478 // don't allow car moves for this location 479 rl.setCarMoves(rl.getMaxCarMoves()); 480 } else if (getTrain().isLocationSkipped(rl)) { 481 // if a location is skipped, no car drops or pick ups 482 addLine(THREE, 483 Bundle.getMessage("buildLocSkippedMaxTrain", rl.getId(), rl.getName(), 484 rl.getTrainDirectionString(), getTrain().getName(), rl.getMaxTrainLength(), 485 Setup.getLengthUnit().toLowerCase())); 486 // don't allow car moves for this location 487 rl.setCarMoves(rl.getMaxCarMoves()); 488 } else { 489 // we're going to use this location, so initialize 490 rl.setCarMoves(0); // clear the number of moves 491 // add up the total number of car moves requested 492 requestedCarMoves += rl.getMaxCarMoves(); 493 // show the type of moves allowed at this location 494 if (!rl.isDropAllowed() && !rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) { 495 addLine(THREE, 496 Bundle.getMessage("buildLocNoDropsOrPickups", rl.getId(), 497 location.isStaging() ? Bundle.getMessage("Staging") : Bundle.getMessage("Location"), 498 rl.getName(), 499 rl.getTrainDirectionString(), rl.getMaxTrainLength(), 500 Setup.getLengthUnit().toLowerCase())); 501 } else if (rl == getTrain().getTrainTerminatesRouteLocation()) { 502 addLine(THREE, Bundle.getMessage("buildLocTerminates", rl.getId(), 503 location.isStaging() ? Bundle.getMessage("Staging") : Bundle.getMessage("Location"), 504 rl.getName(), rl.getTrainDirectionString(), rl.getMaxCarMoves(), 505 rl.isPickUpAllowed() ? Bundle.getMessage("Pickups").toLowerCase() + ", " : "", 506 rl.isDropAllowed() ? Bundle.getMessage("Drop").toLowerCase() + ", " : "", 507 rl.isLocalMovesAllowed() ? Bundle.getMessage("LocalMoves").toLowerCase() + ", " : "")); 508 } else { 509 addLine(THREE, Bundle.getMessage("buildLocRequestMoves", rl.getId(), 510 location.isStaging() ? Bundle.getMessage("Staging") : Bundle.getMessage("Location"), 511 rl.getName(), rl.getTrainDirectionString(), rl.getMaxCarMoves(), 512 rl.isPickUpAllowed() ? Bundle.getMessage("Pickups").toLowerCase() + ", " : "", 513 rl.isDropAllowed() ? Bundle.getMessage("Drop").toLowerCase() + ", " : "", 514 rl.isLocalMovesAllowed() ? Bundle.getMessage("LocalMoves").toLowerCase() + ", " : "", 515 rl.getMaxTrainLength(), Setup.getLengthUnit().toLowerCase())); 516 } 517 } 518 rl.setTrainWeight(0); // clear the total train weight 519 rl.setTrainLength(0); // and length 520 } 521 522 // check for random moves in the train's route 523 for (RouteLocation rl : getRouteList()) { 524 if (rl.getRandomControl().equals(RouteLocation.DISABLED)) { 525 continue; 526 } 527 if (rl.getCarMoves() == 0 && rl.getMaxCarMoves() > 0) { 528 log.debug("Location ({}) has random control value {} and maximum moves {}", rl.getName(), 529 rl.getRandomControl(), rl.getMaxCarMoves()); 530 try { 531 int value = Integer.parseInt(rl.getRandomControl()); 532 // now adjust the number of available moves for this 533 // location 534 double random = Math.random(); 535 log.debug("random {}", random); 536 int moves = (int) (random * ((rl.getMaxCarMoves() * value / 100) + 1)); 537 log.debug("Reducing number of moves for location ({}) by {}", rl.getName(), moves); 538 rl.setCarMoves(moves); 539 requestedCarMoves = requestedCarMoves - moves; 540 addLine(FIVE, 541 Bundle.getMessage("buildRouteRandomControl", rl.getName(), rl.getId(), 542 rl.getRandomControl(), rl.getMaxCarMoves(), rl.getMaxCarMoves() - moves)); 543 } catch (NumberFormatException e) { 544 throw new BuildFailedException(Bundle.getMessage("buildErrorRandomControl", 545 getTrain().getRoute().getName(), rl.getName(), rl.getRandomControl())); 546 } 547 } 548 } 549 550 int numMoves = requestedCarMoves; // number of car moves 551 if (!getTrain().isLocalSwitcher()) { 552 requestedCarMoves = requestedCarMoves / 2; // only need half as many 553 // cars to meet requests 554 } 555 addLine(ONE, Bundle.getMessage("buildRouteRequest", getTrain().getRoute().getName(), 556 Integer.toString(requestedCarMoves), Integer.toString(numMoves))); 557 558 getTrain().setNumberCarsRequested(requestedCarMoves); // save number of car 559 // moves requested 560 } 561 562 /** 563 * reports if local switcher 564 */ 565 protected void showIfLocalSwitcher() { 566 if (getTrain().isLocalSwitcher()) { 567 addLine(THREE, BLANK_LINE); 568 addLine(THREE, Bundle.getMessage("buildTrainIsSwitcher", getTrain().getName(), 569 TrainCommon.splitString(getTrain().getTrainDepartsName()))); 570 } 571 } 572 573 /** 574 * Show how many engines are required for this train, and if a certain road 575 * name for the engine is requested. Show if there are any engine changes in 576 * the route, or if helper engines are needed. There can be up to 2 engine 577 * changes or helper requests. Show if caboose or FRED is needed for train, 578 * and if there's a road name requested. There can be up to 2 caboose 579 * changes in the route. 580 */ 581 protected void showTrainRequirements() { 582 addLine(ONE, BLANK_LINE); 583 addLine(ONE, Bundle.getMessage("TrainRequirements")); 584 if (getTrain().isBuildConsistEnabled() && Setup.getHorsePowerPerTon() > 0) { 585 addLine(ONE, 586 Bundle.getMessage("buildTrainReqConsist", Setup.getHorsePowerPerTon(), 587 getTrain().getNumberEngines())); 588 } else if (getTrain().getNumberEngines().equals("0")) { 589 addLine(ONE, Bundle.getMessage("buildTrainReq0Engine")); 590 } else if (getTrain().getNumberEngines().equals("1")) { 591 addLine(ONE, Bundle.getMessage("buildTrainReq1Engine", getTrain().getTrainDepartsName(), 592 getTrain().getEngineModel(), getTrain().getEngineRoad())); 593 } else { 594 addLine(ONE, 595 Bundle.getMessage("buildTrainReqEngine", getTrain().getTrainDepartsName(), 596 getTrain().getNumberEngines(), 597 getTrain().getEngineModel(), getTrain().getEngineRoad())); 598 } 599 // show any required loco changes 600 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 601 addLine(ONE, 602 Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(), 603 getTrain().getSecondLegStartRouteLocation().getId(), 604 getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(), 605 getTrain().getSecondLegEngineRoad())); 606 } 607 if ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) { 608 addLine(ONE, 609 Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(), 610 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 611 getTrain().getSecondLegEngineRoad())); 612 } 613 if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES) { 614 addLine(ONE, 615 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(), 616 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(), 617 getTrain().getSecondLegEngineRoad())); 618 } 619 if ((getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 620 addLine(ONE, 621 Bundle.getMessage("buildTrainHelperEngines", getTrain().getSecondLegNumberEngines(), 622 getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEndLocationName(), 623 getTrain().getSecondLegEngineModel(), getTrain().getSecondLegEngineRoad())); 624 } 625 626 if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) { 627 addLine(ONE, 628 Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(), 629 getTrain().getThirdLegStartRouteLocation().getId(), 630 getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(), 631 getTrain().getThirdLegEngineRoad())); 632 } 633 if ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) { 634 addLine(ONE, 635 Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(), 636 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 637 getTrain().getThirdLegEngineRoad())); 638 } 639 if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES) { 640 addLine(ONE, 641 Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(), 642 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(), 643 getTrain().getThirdLegEngineRoad())); 644 } 645 if ((getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) { 646 addLine(ONE, 647 Bundle.getMessage("buildTrainHelperEngines", getTrain().getThirdLegNumberEngines(), 648 getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEndLocationName(), 649 getTrain().getThirdLegEngineModel(), getTrain().getThirdLegEngineRoad())); 650 } 651 // show caboose or FRED requirements 652 if (getTrain().isCabooseNeeded()) { 653 addLine(ONE, Bundle.getMessage("buildTrainRequiresCaboose", getTrain().getTrainDepartsName(), 654 getTrain().getCabooseRoad())); 655 } 656 // show any caboose changes in the train's route 657 if ((getTrain().getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 658 (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 659 addLine(ONE, 660 Bundle.getMessage("buildCabooseChange", getTrain().getSecondLegStartRouteLocation())); 661 } 662 if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 663 (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 664 addLine(ONE, Bundle.getMessage("buildCabooseChange", getTrain().getThirdLegStartRouteLocation())); 665 } 666 if (getTrain().isFredNeeded()) { 667 addLine(ONE, 668 Bundle.getMessage("buildTrainRequiresFRED", getTrain().getTrainDepartsName(), 669 getTrain().getCabooseRoad())); 670 } 671 } 672 673 protected void showTrainCarRoads() { 674 if (!getTrain().getCarRoadOption().equals(Train.ALL_ROADS)) { 675 addLine(FIVE, BLANK_LINE); 676 addLine(FIVE, Bundle.getMessage("buildTrainRoads", getTrain().getName(), 677 getTrain().getCarRoadOption(), formatStringToCommaSeparated(getTrain().getCarRoadNames()))); 678 } 679 } 680 681 protected void showTrainCabooseRoads() { 682 if (!getTrain().getCabooseRoadOption().equals(Train.ALL_ROADS)) { 683 addLine(FIVE, BLANK_LINE); 684 addLine(FIVE, Bundle.getMessage("buildTrainCabooseRoads", getTrain().getName(), 685 getTrain().getCabooseRoadOption(), formatStringToCommaSeparated(getTrain().getCabooseRoadNames()))); 686 } 687 } 688 689 protected void showTrainCarTypes() { 690 addLine(FIVE, BLANK_LINE); 691 addLine(FIVE, Bundle.getMessage("buildTrainServicesCarTypes", getTrain().getName())); 692 addLine(FIVE, formatStringToCommaSeparated(getTrain().getCarTypeNames())); 693 } 694 695 protected void showTrainLoadNames() { 696 if (!getTrain().getLoadOption().equals(Train.ALL_LOADS)) { 697 addLine(FIVE, Bundle.getMessage("buildTrainLoads", getTrain().getName(), getTrain().getLoadOption(), 698 formatStringToCommaSeparated(getTrain().getLoadNames()))); 699 } 700 } 701 702 /** 703 * Ask which staging track the train is to depart on. 704 * 705 * @return The departure track the user selected. 706 */ 707 protected Track promptFromStagingDialog() { 708 List<Track> tracksIn = getDepartureLocation().getTracksByNameList(null); 709 List<Track> validTracks = new ArrayList<>(); 710 // only show valid tracks 711 for (Track track : tracksIn) { 712 if (checkDepartureStagingTrack(track)) { 713 validTracks.add(track); 714 } 715 } 716 if (validTracks.size() > 1) { 717 // need an object array for dialog window 718 Object[] tracks = new Object[validTracks.size()]; 719 for (int i = 0; i < validTracks.size(); i++) { 720 tracks[i] = validTracks.get(i); 721 } 722 723 Track selected = (Track) JmriJOptionPane.showInputDialog(null, 724 Bundle.getMessage("TrainDepartingStaging", getTrain().getName(), getDepartureLocation().getName()), 725 Bundle.getMessage("SelectDepartureTrack"), JmriJOptionPane.QUESTION_MESSAGE, null, tracks, null); 726 if (selected != null) { 727 addLine(FIVE, Bundle.getMessage("buildUserSelectedDeparture", selected.getName(), 728 selected.getLocation().getName())); 729 } else { 730 addLine(FIVE, Bundle.getMessage("buildUserCanceledDeparture")); 731 } 732 return selected; 733 } else if (validTracks.size() == 1) { 734 Track track = validTracks.get(0); 735 addLine(FIVE, 736 Bundle.getMessage("buildOnlyOneDepartureTrack", track.getName(), track.getLocation().getName())); 737 return track; 738 } 739 return null; // no tracks available 740 } 741 742 /** 743 * Ask which staging track the train is to terminate on. 744 * 745 * @return The termination track selected by the user. 746 */ 747 protected Track promptToStagingDialog() { 748 List<Track> tracksIn = getTerminateLocation().getTracksByNameList(null); 749 List<Track> validTracks = new ArrayList<>(); 750 // only show valid tracks 751 for (Track track : tracksIn) { 752 if (checkTerminateStagingTrack(track)) { 753 validTracks.add(track); 754 } 755 } 756 if (validTracks.size() > 1) { 757 // need an object array for dialog window 758 Object[] tracks = new Object[validTracks.size()]; 759 for (int i = 0; i < validTracks.size(); i++) { 760 tracks[i] = validTracks.get(i); 761 } 762 763 Track selected = (Track) JmriJOptionPane.showInputDialog(null, 764 Bundle.getMessage("TrainTerminatingStaging", getTrain().getName(), 765 getTerminateLocation().getName()), 766 Bundle.getMessage("SelectArrivalTrack"), JmriJOptionPane.QUESTION_MESSAGE, null, tracks, null); 767 if (selected != null) { 768 addLine(FIVE, Bundle.getMessage("buildUserSelectedArrival", selected.getName(), 769 selected.getLocation().getName())); 770 } 771 return selected; 772 } else if (validTracks.size() == 1) { 773 return validTracks.get(0); 774 } 775 return null; // no tracks available 776 } 777 778 /** 779 * Removes the remaining cabooses and cars with FRED from consideration. 780 * 781 * @throws BuildFailedException code check if car being removed is in 782 * staging 783 */ 784 protected void removeCaboosesAndCarsWithFred() throws BuildFailedException { 785 addLine(SEVEN, BLANK_LINE); 786 addLine(SEVEN, Bundle.getMessage("buildRemoveCarsNotNeeded")); 787 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 788 Car car = getCarList().get(_carIndex); 789 if (car.isCaboose() || car.hasFred()) { 790 addLine(SEVEN, 791 Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(), 792 car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 793 // code check, should never be staging 794 if (car.getTrack() == getDepartureStagingTrack()) { 795 throw new BuildFailedException("ERROR: Attempt to removed car with FRED or Caboose from staging"); // NOI18N 796 } 797 remove(car); // remove this car from the list 798 } 799 } 800 } 801 802 /** 803 * Save the car's final destination and schedule id in case of train reset 804 */ 805 protected void saveCarFinalDestinations() { 806 for (Car car : getCarList()) { 807 car.setPreviousFinalDestination(car.getFinalDestination()); 808 car.setPreviousFinalDestinationTrack(car.getFinalDestinationTrack()); 809 car.setPreviousScheduleId(car.getScheduleItemId()); 810 } 811 } 812 813 /** 814 * Creates the carList. Only cars that can be serviced by this train are in 815 * the list. 816 * 817 * @throws BuildFailedException if car is marked as missing and is in 818 * staging 819 */ 820 protected void createCarList() throws BuildFailedException { 821 // get list of cars for this route 822 setCarList(carManager.getAvailableTrainList(getTrain())); 823 addLine(SEVEN, BLANK_LINE); 824 addLine(SEVEN, Bundle.getMessage("buildRemoveCars")); 825 boolean showCar = true; 826 int carListSize = getCarList().size(); 827 // now remove cars that the train can't service 828 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 829 Car car = getCarList().get(_carIndex); 830 // only show the first 100 cars removed due to wrong car type for 831 // train 832 if (showCar && carListSize - getCarList().size() == DISPLAY_CAR_LIMIT_100) { 833 showCar = false; 834 addLine(FIVE, 835 Bundle.getMessage("buildOnlyFirstXXXCars", DISPLAY_CAR_LIMIT_100, Bundle.getMessage("Type"))); 836 } 837 // remove cars that don't have a track assignment 838 if (car.getTrack() == null) { 839 _warnings++; 840 addLine(ONE, 841 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 842 remove(car); 843 continue; 844 } 845 // remove cars that have been reported as missing 846 if (car.isLocationUnknown()) { 847 addLine(SEVEN, Bundle.getMessage("buildExcludeCarLocUnknown", car.toString(), 848 car.getLocationName(), car.getTrackName())); 849 if (car.getTrack() == getDepartureStagingTrack()) { 850 throw new BuildFailedException(Bundle.getMessage("buildErrorLocationUnknown", car.getLocationName(), 851 car.getTrackName(), car.toString())); 852 } 853 remove(car); 854 continue; 855 } 856 // remove cars that are out of service 857 if (car.isOutOfService()) { 858 addLine(SEVEN, Bundle.getMessage("buildExcludeCarOutOfService", car.toString(), 859 car.getLocationName(), car.getTrackName())); 860 if (car.getTrack() == getDepartureStagingTrack()) { 861 throw new BuildFailedException( 862 Bundle.getMessage("buildErrorLocationOutOfService", car.getLocationName(), 863 car.getTrackName(), car.toString())); 864 } 865 remove(car); 866 continue; 867 } 868 // does car have a destination that is part of this train's route? 869 if (car.getDestination() != null) { 870 RouteLocation rld = getTrain().getRoute().getLastLocationByName(car.getDestinationName()); 871 if (rld == null) { 872 addLine(SEVEN, Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 873 car.getDestinationName(), car.getDestinationTrackName(), getTrain().getRoute().getName())); 874 // Code check, programming ERROR if car departing staging 875 if (car.getLocation() == getDepartureLocation() && getDepartureStagingTrack() != null) { 876 throw new BuildFailedException(Bundle.getMessage("buildErrorCarNotPartRoute", car.toString())); 877 } 878 remove(car); // remove this car from the list 879 continue; 880 } 881 } 882 // remove cars with FRED that have a destination that isn't the 883 // terminal 884 if (car.hasFred() && car.getDestination() != null && car.getDestination() != getTerminateLocation()) { 885 addLine(FIVE, 886 Bundle.getMessage("buildExcludeCarWrongDest", car.toString(), car.getTypeName(), 887 car.getTypeExtensions(), car.getDestinationName())); 888 remove(car); 889 continue; 890 } 891 892 // remove cabooses that have a destination that isn't the terminal, 893 // and no caboose changes in the train's route 894 if (car.isCaboose() && 895 car.getDestination() != null && 896 car.getDestination() != getTerminateLocation() && 897 (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE + Train.REMOVE_CABOOSE) == 0 && 898 (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE + Train.REMOVE_CABOOSE) == 0) { 899 addLine(FIVE, 900 Bundle.getMessage("buildExcludeCarWrongDest", car.toString(), car.getTypeName(), 901 car.getTypeExtensions(), car.getDestinationName())); 902 remove(car); 903 continue; 904 } 905 906 // is car at interchange or spur and is this train allowed to pull? 907 if (!checkPickupInterchangeOrSpur(car)) { 908 remove(car); 909 continue; 910 } 911 912 // is car at interchange with destination restrictions? 913 if (!checkPickupInterchangeDestinationRestrictions(car)) { 914 remove(car); 915 continue; 916 } 917 // note that for trains departing staging the engine and car roads, 918 // types, owners, and built date were already checked. 919 920 if (!car.isCaboose() && !getTrain().isCarRoadNameAccepted(car.getRoadName()) || 921 car.isCaboose() && !getTrain().isCabooseRoadNameAccepted(car.getRoadName())) { 922 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(), 923 car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getTypeExtensions(), 924 car.getRoadName())); 925 remove(car); 926 continue; 927 } 928 if (!getTrain().isTypeNameAccepted(car.getTypeName())) { 929 // only show lead cars when excluding car type 930 if (showCar && (car.getKernel() == null || car.isLead())) { 931 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongType", car.toString(), 932 car.getLocationName(), car.getTrackName(), car.getTypeName())); 933 } 934 remove(car); 935 continue; 936 } 937 if (!getTrain().isOwnerNameAccepted(car.getOwnerName())) { 938 addLine(SEVEN, 939 Bundle.getMessage("buildExcludeCarOwnerAtLoc", car.toString(), car.getOwnerName(), 940 car.getLocationName(), car.getTrackName())); 941 remove(car); 942 continue; 943 } 944 if (!getTrain().isBuiltDateAccepted(car.getBuilt())) { 945 addLine(SEVEN, 946 Bundle.getMessage("buildExcludeCarBuiltAtLoc", car.toString(), car.getBuilt(), 947 car.getLocationName(), car.getTrackName())); 948 remove(car); 949 continue; 950 } 951 952 // all cars in staging must be accepted, so don't exclude if in 953 // staging 954 // note that a car's load can change when departing staging 955 // a car's wait value is ignored when departing staging 956 // a car's pick up day is ignored when departing staging 957 if (getDepartureStagingTrack() == null || car.getTrack() != getDepartureStagingTrack()) { 958 if (!car.isCaboose() && 959 !car.isPassenger() && 960 !getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 961 addLine(SEVEN, Bundle.getMessage("buildExcludeCarLoadAtLoc", car.toString(), 962 car.getTypeName(), car.getLoadName())); 963 remove(car); 964 continue; 965 } 966 // remove cars with FRED if not needed by train 967 if (car.hasFred() && !getTrain().isFredNeeded()) { 968 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWithFredAtLoc", car.toString(), 969 car.getTypeName(), (car.getLocationName() + ", " + car.getTrackName()))); 970 remove(car); // remove this car from the list 971 continue; 972 } 973 // does the car have a pick up day? 974 if (!car.getPickupScheduleId().equals(Car.NONE)) { 975 if (trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY) || 976 car.getPickupScheduleId().equals(trainScheduleManager.getTrainScheduleActiveId())) { 977 car.setPickupScheduleId(Car.NONE); 978 } else { 979 TrainSchedule sch = trainScheduleManager.getScheduleById(car.getPickupScheduleId()); 980 if (sch != null) { 981 addLine(SEVEN, 982 Bundle.getMessage("buildExcludeCarSchedule", car.toString(), car.getTypeName(), 983 car.getLocationName(), car.getTrackName(), sch.getName())); 984 remove(car); 985 continue; 986 } 987 } 988 } 989 // does car have a wait count? 990 if (car.getWait() > 0) { 991 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWait", car.toString(), 992 car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait())); 993 if (getTrain().isServiceable(car)) { 994 addLine(SEVEN, Bundle.getMessage("buildTrainCanServiceWait", getTrain().getName(), 995 car.toString(), car.getWait() - 1)); 996 car.setWait(car.getWait() - 1); // decrement wait count 997 // a car's load changes when the wait count reaches 0 998 String oldLoad = car.getLoadName(); 999 if (car.getTrack().isSpur()) { 1000 car.updateLoad(car.getTrack()); // has the wait 1001 // count reached 0? 1002 } 1003 if (!oldLoad.equals(car.getLoadName())) { 1004 addLine(SEVEN, 1005 Bundle.getMessage("buildCarLoadChangedWait", car.toString(), car.getTypeName(), 1006 oldLoad, car.getLoadName(), car.getFinalDestinationName(), 1007 car.getFinalDestinationTrackName())); 1008 } 1009 } 1010 remove(car); 1011 continue; 1012 } 1013 } 1014 } 1015 } 1016 1017 /** 1018 * Adjust car list to only have cars from one staging track 1019 * 1020 * @throws BuildFailedException if all cars departing staging can't be used 1021 */ 1022 protected void adjustCarsInStaging() throws BuildFailedException { 1023 if (!getTrain().isDepartingStaging()) { 1024 return; // not departing staging 1025 } 1026 int numCarsFromStaging = 0; 1027 _numOfBlocks = new Hashtable<>(); 1028 addLine(SEVEN, BLANK_LINE); 1029 addLine(SEVEN, Bundle.getMessage("buildRemoveCarsStaging")); 1030 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 1031 Car car = getCarList().get(_carIndex); 1032 if (car.getLocation() == getDepartureLocation()) { 1033 if (car.getTrack() == getDepartureStagingTrack()) { 1034 numCarsFromStaging++; 1035 // populate car blocking hashtable 1036 // don't block cabooses, cars with FRED, or passenger. Only 1037 // block lead cars in 1038 // kernel 1039 if (!car.isCaboose() && 1040 !car.hasFred() && 1041 !car.isPassenger() && 1042 (car.getKernel() == null || car.isLead())) { 1043 log.debug("Car {} last location id: {}", car.toString(), car.getLastLocationId()); 1044 Integer number = 1; 1045 if (_numOfBlocks.containsKey(car.getLastLocationId())) { 1046 number = _numOfBlocks.get(car.getLastLocationId()) + 1; 1047 _numOfBlocks.remove(car.getLastLocationId()); 1048 } 1049 _numOfBlocks.put(car.getLastLocationId(), number); 1050 } 1051 } else { 1052 addLine(SEVEN, Bundle.getMessage("buildExcludeCarAtLoc", car.toString(), 1053 car.getTypeName(), car.getLocationName(), car.getTrackName())); 1054 remove(car); 1055 } 1056 } 1057 } 1058 // show how many cars are departing from staging 1059 addLine(FIVE, BLANK_LINE); 1060 addLine(FIVE, Bundle.getMessage("buildDepartingStagingCars", 1061 getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName(), 1062 numCarsFromStaging)); 1063 // and list them 1064 for (Car car : getCarList()) { 1065 if (car.getTrack() == getDepartureStagingTrack()) { 1066 addLine(SEVEN, Bundle.getMessage("buildStagingCarAtLoc", car.toString(), 1067 car.getTypeName(), car.getLoadType().toLowerCase(), car.getLoadName())); 1068 } 1069 } 1070 // error if all of the cars from staging aren't available 1071 if (!Setup.isBuildOnTime() && numCarsFromStaging != getDepartureStagingTrack().getNumberCars()) { 1072 throw new BuildFailedException( 1073 Bundle.getMessage("buildErrorNotAllCars", getDepartureStagingTrack().getName(), 1074 Integer.toString(getDepartureStagingTrack().getNumberCars() - numCarsFromStaging))); 1075 } 1076 log.debug("Staging departure track ({}) has {} cars and {} blocks", getDepartureStagingTrack().getName(), 1077 numCarsFromStaging, _numOfBlocks.size()); // NOI18N 1078 } 1079 1080 /** 1081 * List available cars by location. Removes non-lead kernel cars from the 1082 * car list. 1083 * 1084 * @throws BuildFailedException if kernel doesn't have lead or cars aren't 1085 * on the same track. 1086 */ 1087 protected void showCarsByLocation() throws BuildFailedException { 1088 // show how many cars were found 1089 addLine(FIVE, BLANK_LINE); 1090 addLine(ONE, 1091 Bundle.getMessage("buildFoundCars", Integer.toString(getCarList().size()), getTrain().getName())); 1092 // only show cars once using the train's route 1093 List<String> locationNames = new ArrayList<>(); 1094 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 1095 if (locationNames.contains(rl.getName())) { 1096 continue; 1097 } 1098 locationNames.add(rl.getName()); 1099 int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getCarList())); 1100 if (rl.getLocation().isStaging()) { 1101 addLine(FIVE, 1102 Bundle.getMessage("buildCarsInStaging", count, rl.getName())); 1103 } else { 1104 addLine(FIVE, 1105 Bundle.getMessage("buildCarsAtLocation", count, rl.getName())); 1106 } 1107 // now go through the car list and remove non-lead cars in kernels, 1108 // destinations 1109 // that aren't part of this route 1110 int carCount = 0; 1111 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 1112 Car car = getCarList().get(_carIndex); 1113 if (!car.getLocationName().equals(rl.getName())) { 1114 continue; 1115 } 1116 // only print out the first DISPLAY_CAR_LIMIT cars for each 1117 // location 1118 if (carCount < DISPLAY_CAR_LIMIT_50 && (car.getKernel() == null || car.isLead())) { 1119 if (car.getLoadPriority().equals(CarLoad.PRIORITY_LOW) && 1120 car.getTrack().getTrackPriority().equals(Track.PRIORITY_NORMAL)) { 1121 addLine(SEVEN, 1122 Bundle.getMessage("buildCarAtLocWithMoves", car.toString(), car.getTypeName(), 1123 car.getTypeExtensions(), car.getLocationName(), car.getTrackName(), 1124 car.getMoves())); 1125 } else { 1126 addLine(SEVEN, 1127 Bundle.getMessage("buildCarAtLocWithMovesPriority", car.toString(), car.getTypeName(), 1128 car.getTypeExtensions(), car.getLocationName(), car.getTrackName(), 1129 car.getTrack().getTrackPriority(), car.getMoves(), 1130 car.getLoadType().toLowerCase(), car.getLoadName(), 1131 car.getLoadPriority())); 1132 } 1133 if (car.isLead()) { 1134 addLine(SEVEN, 1135 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1136 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1137 Setup.getLengthUnit().toLowerCase())); 1138 // list all of the cars in the kernel now 1139 for (Car k : car.getKernel().getCars()) { 1140 if (!k.isLead()) { 1141 addLine(SEVEN, 1142 Bundle.getMessage("buildCarPartOfKernel", k.toString(), k.getKernelName(), 1143 k.getKernel().getSize(), k.getKernel().getTotalLength(), 1144 Setup.getLengthUnit().toLowerCase())); 1145 } 1146 } 1147 } 1148 carCount++; 1149 if (carCount == DISPLAY_CAR_LIMIT_50) { 1150 addLine(SEVEN, 1151 Bundle.getMessage("buildOnlyFirstXXXCars", carCount, rl.getName())); 1152 } 1153 } 1154 // report car in kernel but lead has been removed 1155 if (car.getKernel() != null && !getCarList().contains(car.getKernel().getLead())) { 1156 addLine(SEVEN, 1157 Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), 1158 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1159 Setup.getLengthUnit().toLowerCase())); 1160 } 1161 // use only the lead car in a kernel for building trains 1162 if (car.getKernel() != null) { 1163 checkKernel(car); // kernel needs lead car and all cars on 1164 // the same track 1165 if (!car.isLead()) { 1166 remove(car); // remove this car from the list 1167 continue; 1168 } 1169 } 1170 if (getTrain().equals(car.getTrain())) { 1171 addLine(FIVE, Bundle.getMessage("buildCarAlreadyAssigned", car.toString())); 1172 } 1173 } 1174 addLine(SEVEN, BLANK_LINE); 1175 } 1176 } 1177 1178 protected void sortCarsOnFifoLifoTracks() { 1179 addLine(SEVEN, Bundle.getMessage("buildSortCarsByLastDate")); 1180 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 1181 Car car = getCarList().get(_carIndex); 1182 if (car.getTrack().getServiceOrder().equals(Track.NORMAL) || car.getTrack().isStaging()) { 1183 continue; 1184 } 1185 addLine(SEVEN, 1186 Bundle.getMessage("buildTrackModePriority", car.toString(), car.getTrack().getTrackTypeName(), 1187 car.getLocationName(), car.getTrackName(), car.getTrack().getServiceOrder(), 1188 car.getLastDate())); 1189 Car bestCar = car; 1190 for (int i = _carIndex + 1; i < getCarList().size(); i++) { 1191 Car testCar = getCarList().get(i); 1192 if (testCar.getTrack() == car.getTrack() && 1193 bestCar.getLoadPriority().equals(testCar.getLoadPriority())) { 1194 log.debug("{} car ({}) last moved date: {}", car.getTrack().getTrackTypeName(), testCar.toString(), 1195 testCar.getLastDate()); // NOI18N 1196 if (car.getTrack().getServiceOrder().equals(Track.FIFO)) { 1197 if (bestCar.getLastMoveDate().isAfter(testCar.getLastMoveDate())) { 1198 bestCar = testCar; 1199 log.debug("New best car ({})", bestCar.toString()); 1200 } 1201 } else if (car.getTrack().getServiceOrder().equals(Track.LIFO)) { 1202 if (bestCar.getLastMoveDate().isBefore(testCar.getLastMoveDate())) { 1203 bestCar = testCar; 1204 log.debug("New best car ({})", bestCar.toString()); 1205 } 1206 } 1207 } 1208 } 1209 if (car != bestCar) { 1210 addLine(SEVEN, 1211 Bundle.getMessage("buildTrackModeCarPriority", car.getTrack().getTrackTypeName(), 1212 car.getTrackName(), car.getTrack().getServiceOrder(), bestCar.toString(), 1213 bestCar.getLastDate(), car.toString(), car.getLastDate())); 1214 getCarList().remove(bestCar); // change sort 1215 getCarList().add(_carIndex, bestCar); 1216 } 1217 } 1218 addLine(SEVEN, BLANK_LINE); 1219 } 1220 1221 /** 1222 * Verifies that all cars in the kernel have the same departure track. Also 1223 * checks to see if the kernel has a lead car and the lead car is in 1224 * service. 1225 * 1226 * @throws BuildFailedException 1227 */ 1228 private void checkKernel(Car car) throws BuildFailedException { 1229 boolean foundLeadCar = false; 1230 for (Car c : car.getKernel().getCars()) { 1231 // check that lead car exists 1232 if (c.isLead() && !c.isOutOfService()) { 1233 foundLeadCar = true; 1234 } 1235 // check to see that all cars have the same location and track 1236 if (car.getLocation() != c.getLocation() || 1237 c.getTrack() == null || 1238 !car.getTrack().getSplitName().equals(c.getTrack().getSplitName())) { 1239 throw new BuildFailedException(Bundle.getMessage("buildErrorCarKernelLocation", c.toString(), 1240 car.getKernelName(), c.getLocationName(), c.getTrackName(), car.toString(), 1241 car.getLocationName(), car.getTrackName())); 1242 } 1243 } 1244 // code check, all kernels should have a lead car 1245 if (foundLeadCar == false) { 1246 throw new BuildFailedException(Bundle.getMessage("buildErrorCarKernelNoLead", car.getKernelName())); 1247 } 1248 } 1249 1250 /* 1251 * For blocking cars out of staging 1252 */ 1253 protected String getLargestBlock() { 1254 Enumeration<String> en = _numOfBlocks.keys(); 1255 String largestBlock = ""; 1256 int maxCars = 0; 1257 while (en.hasMoreElements()) { 1258 String locId = en.nextElement(); 1259 if (_numOfBlocks.get(locId) > maxCars) { 1260 largestBlock = locId; 1261 maxCars = _numOfBlocks.get(locId); 1262 } 1263 } 1264 return largestBlock; 1265 } 1266 1267 /** 1268 * Returns the routeLocation with the most available moves. Used for 1269 * blocking a train out of staging. 1270 * 1271 * @param blockRouteList The route for this train, modified by deleting 1272 * RouteLocations serviced 1273 * @param blockId Where these cars were originally picked up from. 1274 * @return The location in the route with the most available moves. 1275 */ 1276 protected RouteLocation getLocationWithMaximumMoves(List<RouteLocation> blockRouteList, String blockId) { 1277 RouteLocation rlMax = null; 1278 int maxMoves = 0; 1279 for (RouteLocation rl : blockRouteList) { 1280 if (rl == getTrain().getTrainDepartsRouteLocation()) { 1281 continue; 1282 } 1283 if (rl.getMaxCarMoves() - rl.getCarMoves() > maxMoves) { 1284 maxMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 1285 rlMax = rl; 1286 } 1287 // if two locations have the same number of moves, return the one 1288 // that doesn't match the block id 1289 if (rl.getMaxCarMoves() - rl.getCarMoves() == maxMoves && !rl.getLocation().getId().equals(blockId)) { 1290 rlMax = rl; 1291 } 1292 } 1293 return rlMax; 1294 } 1295 1296 /** 1297 * Temporally remove cars from staging track if train returning to the same 1298 * staging track to free up track space. 1299 */ 1300 protected void makeAdjustmentsIfDepartingStaging() { 1301 if (getTrain().isDepartingStaging()) { 1302 _reqNumOfMoves = 0; 1303 // Move cars out of staging after working other locations 1304 // if leaving and returning to staging on the same track, temporary pull cars off the track 1305 if (getDepartureStagingTrack() == getTerminateStagingTrack()) { 1306 if (!getTrain().isAllowReturnToStagingEnabled() && !Setup.isStagingAllowReturnEnabled()) { 1307 // takes care of cars in a kernel by getting all cars 1308 for (Car car : carManager.getList()) { 1309 // don't remove caboose or car with FRED already 1310 // assigned to train 1311 if (car.getTrack() == getDepartureStagingTrack() && car.getRouteDestination() == null) { 1312 car.setLocation(car.getLocation(), null); 1313 } 1314 } 1315 } else { 1316 // since all cars can return to staging, the track space is 1317 // consumed for now 1318 addLine(THREE, BLANK_LINE); 1319 addLine(THREE, Bundle.getMessage("buildWarnDepartStaging", 1320 getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName())); 1321 addLine(THREE, BLANK_LINE); 1322 } 1323 } 1324 addLine(THREE, 1325 Bundle.getMessage("buildDepartStagingAggressive", 1326 getDepartureStagingTrack().getLocation().getName())); 1327 } 1328 } 1329 1330 /** 1331 * Restores cars departing staging track assignment. 1332 */ 1333 protected void restoreCarsIfDepartingStaging() { 1334 if (getTrain().isDepartingStaging() && 1335 getDepartureStagingTrack() == getTerminateStagingTrack() && 1336 !getTrain().isAllowReturnToStagingEnabled() && 1337 !Setup.isStagingAllowReturnEnabled()) { 1338 // restore departure track for cars departing staging 1339 for (Car car : getCarList()) { 1340 if (car.getLocation() == getDepartureStagingTrack().getLocation() && car.getTrack() == null) { 1341 car.setLocation(getDepartureStagingTrack().getLocation(), getDepartureStagingTrack(), 1342 RollingStock.FORCE); // force 1343 if (car.getKernel() != null) { 1344 for (Car k : car.getKernel().getCars()) { 1345 k.setLocation(getDepartureStagingTrack().getLocation(), getDepartureStagingTrack(), 1346 RollingStock.FORCE); // force 1347 } 1348 } 1349 } 1350 } 1351 } 1352 } 1353 1354 protected void showLoadGenerationOptionsStaging() { 1355 if (getDepartureStagingTrack() != null && 1356 _reqNumOfMoves > 0 && 1357 (getDepartureStagingTrack().isAddCustomLoadsEnabled() || 1358 getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled() || 1359 getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled())) { 1360 addLine(FIVE, Bundle.getMessage("buildCustomLoadOptions", getDepartureStagingTrack().getName())); 1361 if (getDepartureStagingTrack().isAddCustomLoadsEnabled()) { 1362 addLine(FIVE, Bundle.getMessage("buildLoadCarLoads")); 1363 } 1364 if (getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled()) { 1365 addLine(FIVE, Bundle.getMessage("buildLoadAnyCarLoads")); 1366 } 1367 if (getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled()) { 1368 addLine(FIVE, Bundle.getMessage("buildLoadsStaging")); 1369 } 1370 addLine(FIVE, BLANK_LINE); 1371 } 1372 } 1373 1374 /** 1375 * Checks to see if all cars on a staging track have been given a 1376 * destination. Throws exception if there's a car without a destination. 1377 * 1378 * @throws BuildFailedException if car on staging track not assigned to 1379 * train 1380 */ 1381 protected void checkStuckCarsInStaging() throws BuildFailedException { 1382 if (!getTrain().isDepartingStaging()) { 1383 return; 1384 } 1385 int carCount = 0; 1386 StringBuffer buf = new StringBuffer(); 1387 // confirm that all cars in staging are departing 1388 for (Car car : getCarList()) { 1389 // build failure if car departing staging without a destination or 1390 // train 1391 if (car.getTrack() == getDepartureStagingTrack() && 1392 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 1393 if (car.getKernel() != null) { 1394 for (Car c : car.getKernel().getCars()) { 1395 carCount++; 1396 addCarToStuckStagingList(c, buf, carCount); 1397 } 1398 } else { 1399 carCount++; 1400 addCarToStuckStagingList(car, buf, carCount); 1401 } 1402 } 1403 } 1404 if (carCount > 0) { 1405 log.debug("{} cars stuck in staging", carCount); 1406 String msg = Bundle.getMessage("buildStagingCouldNotFindDest", carCount, 1407 getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName()); 1408 throw new BuildFailedException(msg + buf.toString(), BuildFailedException.STAGING); 1409 } 1410 } 1411 1412 /** 1413 * Creates a list of up to 20 cars stuck in staging. 1414 * 1415 * @param car The car to add to the list 1416 * @param buf StringBuffer 1417 * @param carCount how many cars in the list 1418 */ 1419 private void addCarToStuckStagingList(Car car, StringBuffer buf, int carCount) { 1420 if (carCount <= DISPLAY_CAR_LIMIT_20) { 1421 buf.append(NEW_LINE + " " + car.toString()); 1422 } else if (carCount == DISPLAY_CAR_LIMIT_20 + 1) { 1423 buf.append(NEW_LINE + 1424 Bundle.getMessage("buildOnlyFirstXXXCars", DISPLAY_CAR_LIMIT_20, 1425 getDepartureStagingTrack().getName())); 1426 } 1427 } 1428 1429 /** 1430 * Used to determine if a car on a staging track doesn't have a destination 1431 * or train 1432 * 1433 * @return true if at least one car doesn't have a destination or train. 1434 * false if all cars have a destination. 1435 */ 1436 protected boolean isCarStuckStaging() { 1437 if (getTrain().isDepartingStaging()) { 1438 // confirm that all cars in staging are departing 1439 for (Car car : getCarList()) { 1440 if (car.getTrack() == getDepartureStagingTrack() && 1441 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 1442 return true; 1443 } 1444 } 1445 } 1446 return false; 1447 } 1448 1449 protected void finishAddRsToTrain(RollingStock rs, RouteLocation rl, RouteLocation rld, int length, 1450 int weightTons) { 1451 // notify that locations have been modified when build done 1452 // allows automation actions to run properly 1453 if (!_modifiedLocations.contains(rl.getLocation())) { 1454 _modifiedLocations.add(rl.getLocation()); 1455 } 1456 if (!_modifiedLocations.contains(rld.getLocation())) { 1457 _modifiedLocations.add(rld.getLocation()); 1458 } 1459 rs.setTrain(getTrain()); 1460 rs.setRouteLocation(rl); 1461 rs.setRouteDestination(rld); 1462 // now adjust train length and weight for each location that the rolling 1463 // stock is in the train 1464 boolean inTrain = false; 1465 for (RouteLocation routeLocation : getRouteList()) { 1466 if (rl == routeLocation) { 1467 inTrain = true; 1468 } 1469 if (rld == routeLocation) { 1470 break; // done 1471 } 1472 if (inTrain) { 1473 routeLocation.setTrainLength(routeLocation.getTrainLength() + length); 1474 routeLocation.setTrainWeight(routeLocation.getTrainWeight() + weightTons); 1475 } 1476 } 1477 } 1478 1479 /** 1480 * Determine if rolling stock can be picked up based on train direction at 1481 * the route location. 1482 * 1483 * @param rs The rolling stock 1484 * @param rl The rolling stock's route location 1485 * @throws BuildFailedException if coding issue 1486 * @return true if there isn't a problem 1487 */ 1488 protected boolean checkPickUpTrainDirection(RollingStock rs, RouteLocation rl) throws BuildFailedException { 1489 // Code Check, car or engine should have a track assignment 1490 if (rs.getTrack() == null) { 1491 throw new BuildFailedException( 1492 Bundle.getMessage("buildWarningRsNoTrack", rs.toString(), rs.getLocationName())); 1493 } 1494 // ignore local switcher direction 1495 if (getTrain().isLocalSwitcher()) { 1496 return true; 1497 } 1498 if ((rl.getTrainDirection() & 1499 rs.getLocation().getTrainDirections() & 1500 rs.getTrack().getTrainDirections()) != 0) { 1501 return true; 1502 } 1503 1504 // Only track direction can cause the following message. Location 1505 // direction has already been checked 1506 addLine(SEVEN, 1507 Bundle.getMessage("buildRsCanNotPickupUsingTrain", rs.toString(), rl.getTrainDirectionString(), 1508 rs.getTrackName(), rs.getLocationName(), rl.getId())); 1509 return false; 1510 } 1511 1512 /** 1513 * Used to report a problem picking up the rolling stock due to train 1514 * direction. 1515 * 1516 * @param rl The route location 1517 * @return true if there isn't a problem 1518 */ 1519 protected boolean checkPickUpTrainDirection(RouteLocation rl) { 1520 // ignore local switcher direction 1521 if (getTrain().isLocalSwitcher()) { 1522 return true; 1523 } 1524 if ((rl.getTrainDirection() & rl.getLocation().getTrainDirections()) != 0) { 1525 return true; 1526 } 1527 1528 addLine(ONE, Bundle.getMessage("buildLocDirection", rl.getName(), rl.getTrainDirectionString())); 1529 return false; 1530 } 1531 1532 /** 1533 * Determines if car can be pulled from an interchange or spur. Needed for 1534 * quick service tracks. 1535 * 1536 * @param car the car being pulled 1537 * @return true if car can be pulled, otherwise false. 1538 */ 1539 protected boolean checkPickupInterchangeOrSpur(Car car) { 1540 if (car.getTrack().isInterchange()) { 1541 // don't service a car at interchange and has been dropped off 1542 // by this train 1543 if (car.getTrack().getPickupOption().equals(Track.ANY) && 1544 car.getLastRouteId().equals(getTrain().getRoute().getId())) { 1545 addLine(SEVEN, Bundle.getMessage("buildExcludeCarDropByTrain", car.toString(), 1546 car.getTypeName(), getTrain().getRoute().getName(), car.getLocationName(), car.getTrackName())); 1547 return false; 1548 } 1549 } 1550 // is car at interchange or spur and is this train allowed to pull? 1551 if (car.getTrack().isInterchange() || car.getTrack().isSpur()) { 1552 if (car.getTrack().getPickupOption().equals(Track.TRAINS) || 1553 car.getTrack().getPickupOption().equals(Track.EXCLUDE_TRAINS)) { 1554 if (car.getTrack().isPickupTrainAccepted(getTrain())) { 1555 log.debug("Car ({}) can be picked up by this train", car.toString()); 1556 } else { 1557 addLine(SEVEN, 1558 Bundle.getMessage("buildExcludeCarByTrain", car.toString(), car.getTypeName(), 1559 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName())); 1560 return false; 1561 } 1562 } else if (car.getTrack().getPickupOption().equals(Track.ROUTES) || 1563 car.getTrack().getPickupOption().equals(Track.EXCLUDE_ROUTES)) { 1564 if (car.getTrack().isPickupRouteAccepted(getTrain().getRoute())) { 1565 log.debug("Car ({}) can be picked up by this route", car.toString()); 1566 } else { 1567 addLine(SEVEN, 1568 Bundle.getMessage("buildExcludeCarByRoute", car.toString(), car.getTypeName(), 1569 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName())); 1570 return false; 1571 } 1572 } 1573 } 1574 return true; 1575 } 1576 1577 /** 1578 * Checks to see if an interchange track has destination restrictions. 1579 * Returns true if there's at least one destination in the train's route 1580 * that can service the car departing the interchange. 1581 * 1582 * @param car the car being evaluated 1583 * @return true if car can be pulled 1584 */ 1585 protected boolean checkPickupInterchangeDestinationRestrictions(Car car) { 1586 if (!car.getTrack().isInterchange() || 1587 car.getTrack().getDestinationOption().equals(Track.ALL_DESTINATIONS) || 1588 car.getFinalDestination() != null) { 1589 return true; 1590 } 1591 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 1592 if (car.getTrack().isDestinationAccepted(rl.getLocation())) { 1593 return true; 1594 } 1595 } 1596 addLine(SEVEN, Bundle.getMessage("buildExcludeCarByInterchange", car.toString(), 1597 car.getTypeName(), car.getTrackTypeName(), car.getLocationName(), car.getTrackName())); 1598 return false; 1599 } 1600 1601 /** 1602 * Checks to see if train length would be exceeded if this car was added to 1603 * the train. 1604 * 1605 * @param car the car in question 1606 * @param rl the departure route location for this car 1607 * @param rld the destination route location for this car 1608 * @return true if car can be added to train 1609 */ 1610 protected boolean checkTrainLength(Car car, RouteLocation rl, RouteLocation rld) { 1611 // car can be a kernel so get total length 1612 int length = car.getTotalKernelLength(); 1613 boolean carInTrain = false; 1614 for (RouteLocation rlt : getRouteList()) { 1615 if (rl == rlt) { 1616 carInTrain = true; 1617 } 1618 if (rld == rlt) { 1619 break; 1620 } 1621 if (carInTrain && rlt.getTrainLength() + length > rlt.getMaxTrainLength()) { 1622 addLine(FIVE, 1623 Bundle.getMessage("buildCanNotPickupCarLength", car.toString(), length, 1624 Setup.getLengthUnit().toLowerCase(), rlt.getMaxTrainLength(), 1625 Setup.getLengthUnit().toLowerCase(), 1626 rlt.getTrainLength() + length - rlt.getMaxTrainLength(), rlt.getName(), rlt.getId())); 1627 return false; 1628 } 1629 } 1630 return true; 1631 } 1632 1633 protected boolean checkDropTrainDirection(RollingStock rs, RouteLocation rld, Track track) { 1634 // local? 1635 if (getTrain().isLocalSwitcher()) { 1636 return true; 1637 } 1638 // this location only services trains with these directions 1639 int serviceTrainDir = rld.getLocation().getTrainDirections(); 1640 if (track != null) { 1641 serviceTrainDir = serviceTrainDir & track.getTrainDirections(); 1642 } 1643 1644 // is this a car going to alternate track? Check to see if direct move 1645 // from alternate to FD track is possible 1646 if ((rld.getTrainDirection() & serviceTrainDir) != 0 && 1647 rs != null && 1648 track != null && 1649 Car.class.isInstance(rs)) { 1650 Car car = (Car) rs; 1651 if (car.getFinalDestinationTrack() != null && 1652 track == car.getFinalDestinationTrack().getAlternateTrack() && 1653 (track.getTrainDirections() & car.getFinalDestinationTrack().getTrainDirections()) == 0) { 1654 addLine(SEVEN, 1655 Bundle.getMessage("buildCanNotDropRsUsingTrain4", car.getFinalDestinationTrack().getName(), 1656 formatStringToCommaSeparated( 1657 Setup.getDirectionStrings(car.getFinalDestinationTrack().getTrainDirections())), 1658 car.getFinalDestinationTrack().getAlternateTrack().getName(), 1659 formatStringToCommaSeparated(Setup.getDirectionStrings( 1660 car.getFinalDestinationTrack().getAlternateTrack().getTrainDirections())))); 1661 return false; 1662 } 1663 } 1664 1665 if ((rld.getTrainDirection() & serviceTrainDir) != 0) { 1666 return true; 1667 } 1668 if (rs == null || track == null) { 1669 addLine(SEVEN, 1670 Bundle.getMessage("buildDestinationDoesNotService", rld.getName(), rld.getTrainDirectionString())); 1671 } else { 1672 addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain", rs.toString(), 1673 rld.getTrainDirectionString(), track.getName())); 1674 } 1675 return false; 1676 } 1677 1678 protected boolean checkDropTrainDirection(RouteLocation rld) { 1679 return (checkDropTrainDirection(null, rld, null)); 1680 } 1681 1682 /** 1683 * Determinate if rolling stock can be dropped by this train to the track 1684 * specified. 1685 * 1686 * @param rs the rolling stock to be set out. 1687 * @param track the destination track. 1688 * @return true if able to drop. 1689 */ 1690 protected boolean checkTrainCanDrop(RollingStock rs, Track track) { 1691 if (track.isInterchange() || track.isSpur()) { 1692 if (track.getDropOption().equals(Track.TRAINS) || track.getDropOption().equals(Track.EXCLUDE_TRAINS)) { 1693 if (track.isDropTrainAccepted(getTrain())) { 1694 log.debug("Rolling stock ({}) can be droped by train to track ({})", rs.toString(), 1695 track.getName()); 1696 } else { 1697 addLine(SEVEN, 1698 Bundle.getMessage("buildCanNotDropTrain", rs.toString(), getTrain().getName(), 1699 track.getTrackTypeName(), track.getLocation().getName(), track.getName())); 1700 return false; 1701 } 1702 } 1703 if (track.getDropOption().equals(Track.ROUTES) || track.getDropOption().equals(Track.EXCLUDE_ROUTES)) { 1704 if (track.isDropRouteAccepted(getTrain().getRoute())) { 1705 log.debug("Rolling stock ({}) can be droped by route to track ({})", rs.toString(), 1706 track.getName()); 1707 } else { 1708 addLine(SEVEN, 1709 Bundle.getMessage("buildCanNotDropRoute", rs.toString(), getTrain().getRoute().getName(), 1710 track.getTrackTypeName(), track.getLocation().getName(), track.getName())); 1711 return false; 1712 } 1713 } 1714 } 1715 return true; 1716 } 1717 1718 /** 1719 * Check departure staging track to see if engines and cars are available to 1720 * a new train. Also confirms that the engine and car type, load, road, etc. 1721 * are accepted by the train. 1722 * 1723 * @param departStageTrack The staging track 1724 * @return true is there are engines and cars available. 1725 */ 1726 protected boolean checkDepartureStagingTrack(Track departStageTrack) { 1727 addLine(THREE, 1728 Bundle.getMessage("buildStagingHas", departStageTrack.getName(), 1729 Integer.toString(departStageTrack.getNumberEngines()), 1730 Integer.toString(departStageTrack.getNumberCars()))); 1731 // does this staging track service this train? 1732 if (!departStageTrack.isPickupTrainAccepted(getTrain())) { 1733 addLine(THREE, Bundle.getMessage("buildStagingNotTrain", departStageTrack.getName())); 1734 return false; 1735 } 1736 if (departStageTrack.getNumberRS() == 0 && getTrain().getTrainDepartsRouteLocation().getMaxCarMoves() > 0) { 1737 addLine(THREE, Bundle.getMessage("buildStagingEmpty", departStageTrack.getName())); 1738 return false; 1739 } 1740 if (departStageTrack.getUsedLength() > getTrain().getTrainDepartsRouteLocation().getMaxTrainLength()) { 1741 addLine(THREE, 1742 Bundle.getMessage("buildStagingTrainTooLong", departStageTrack.getName(), 1743 departStageTrack.getUsedLength(), Setup.getLengthUnit().toLowerCase(), 1744 getTrain().getTrainDepartsRouteLocation().getMaxTrainLength())); 1745 return false; 1746 } 1747 if (departStageTrack.getNumberCars() > getTrain().getTrainDepartsRouteLocation().getMaxCarMoves()) { 1748 addLine(THREE, Bundle.getMessage("buildStagingTooManyCars", departStageTrack.getName(), 1749 departStageTrack.getNumberCars(), getTrain().getTrainDepartsRouteLocation().getMaxCarMoves())); 1750 return false; 1751 } 1752 // does the staging track have the right number of locomotives? 1753 if (!getTrain().getNumberEngines().equals("0") && 1754 getNumberEngines(getTrain().getNumberEngines()) != departStageTrack.getNumberEngines()) { 1755 addLine(THREE, Bundle.getMessage("buildStagingNotEngines", departStageTrack.getName(), 1756 departStageTrack.getNumberEngines(), getTrain().getNumberEngines())); 1757 return false; 1758 } 1759 // is the staging track direction correct for this train? 1760 if ((departStageTrack.getTrainDirections() & 1761 getTrain().getTrainDepartsRouteLocation().getTrainDirection()) == 0) { 1762 addLine(THREE, Bundle.getMessage("buildStagingNotDirection", departStageTrack.getName())); 1763 return false; 1764 } 1765 1766 // check engines on staging track 1767 if (!checkStagingEngines(departStageTrack)) { 1768 return false; 1769 } 1770 1771 // check for car road, load, owner, built, Caboose or FRED needed 1772 if (!checkStagingCarTypeRoadLoadOwnerBuiltCabooseOrFRED(departStageTrack)) { 1773 return false; 1774 } 1775 1776 // determine if staging track is in a pool (multiple trains on one 1777 // staging track) 1778 if (!checkStagingPool(departStageTrack)) { 1779 return false; 1780 } 1781 addLine(FIVE, 1782 Bundle.getMessage("buildTrainCanDepartTrack", getTrain().getName(), departStageTrack.getName())); 1783 return true; 1784 } 1785 1786 /** 1787 * Used to determine if engines on staging track are acceptable to the train 1788 * being built. 1789 * 1790 * @param departStageTrack Depart staging track 1791 * @return true if engines on staging track meet train requirement 1792 */ 1793 private boolean checkStagingEngines(Track departStageTrack) { 1794 if (departStageTrack.getNumberEngines() > 0) { 1795 for (Engine eng : engineManager.getList(departStageTrack)) { 1796 // clones are are already assigned to a train 1797 if (eng.isClone()) { 1798 continue; 1799 } 1800 // has engine been assigned to another train? 1801 if (eng.getRouteLocation() != null) { 1802 addLine(THREE, Bundle.getMessage("buildStagingDepart", departStageTrack.getName(), 1803 eng.getTrainName())); 1804 return false; 1805 } 1806 if (eng.getTrain() != null && eng.getTrain() != getTrain()) { 1807 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineTrain", 1808 departStageTrack.getName(), eng.toString(), eng.getTrainName())); 1809 return false; 1810 } 1811 // does the train accept the engine type from the staging 1812 // track? 1813 if (!getTrain().isTypeNameAccepted(eng.getTypeName())) { 1814 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineType", 1815 departStageTrack.getName(), eng.toString(), eng.getTypeName(), getTrain().getName())); 1816 return false; 1817 } 1818 // does the train accept the engine model from the staging 1819 // track? 1820 if (!getTrain().getEngineModel().equals(Train.NONE) && 1821 !getTrain().getEngineModel().equals(eng.getModel())) { 1822 addLine(THREE, 1823 Bundle.getMessage("buildStagingDepartEngineModel", departStageTrack.getName(), 1824 eng.toString(), eng.getModel(), getTrain().getName())); 1825 return false; 1826 } 1827 // does the engine road match the train requirements? 1828 if (!getTrain().getCarRoadOption().equals(Train.ALL_ROADS) && 1829 !getTrain().getEngineRoad().equals(Train.NONE) && 1830 !getTrain().getEngineRoad().equals(eng.getRoadName())) { 1831 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineRoad", 1832 departStageTrack.getName(), eng.toString(), eng.getRoadName(), getTrain().getName())); 1833 return false; 1834 } 1835 // does the train accept the engine road from the staging 1836 // track? 1837 if (getTrain().getEngineRoad().equals(Train.NONE) && 1838 !getTrain().isLocoRoadNameAccepted(eng.getRoadName())) { 1839 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineRoad", 1840 departStageTrack.getName(), eng.toString(), eng.getRoadName(), getTrain().getName())); 1841 return false; 1842 } 1843 // does the train accept the engine owner from the staging 1844 // track? 1845 if (!getTrain().isOwnerNameAccepted(eng.getOwnerName())) { 1846 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineOwner", 1847 departStageTrack.getName(), eng.toString(), eng.getOwnerName(), getTrain().getName())); 1848 return false; 1849 } 1850 // does the train accept the engine built date from the 1851 // staging track? 1852 if (!getTrain().isBuiltDateAccepted(eng.getBuilt())) { 1853 addLine(THREE, 1854 Bundle.getMessage("buildStagingDepartEngineBuilt", departStageTrack.getName(), 1855 eng.toString(), eng.getBuilt(), getTrain().getName())); 1856 return false; 1857 } 1858 } 1859 } 1860 return true; 1861 } 1862 1863 /** 1864 * Checks to see if all cars in staging can be serviced by the train being 1865 * built. Also searches for caboose or car with FRED. 1866 * 1867 * @param departStageTrack Departure staging track 1868 * @return True if okay 1869 */ 1870 private boolean checkStagingCarTypeRoadLoadOwnerBuiltCabooseOrFRED(Track departStageTrack) { 1871 boolean foundCaboose = false; 1872 boolean foundFRED = false; 1873 if (departStageTrack.getNumberCars() > 0) { 1874 for (Car car : carManager.getList(departStageTrack)) { 1875 // clones are are already assigned to a train 1876 if (car.isClone()) { 1877 continue; 1878 } 1879 // ignore non-lead cars in kernels 1880 if (car.getKernel() != null && !car.isLead()) { 1881 continue; // ignore non-lead cars 1882 } 1883 // has car been assigned to another train? 1884 if (car.getRouteLocation() != null) { 1885 log.debug("Car ({}) has route location ({})", car.toString(), car.getRouteLocation().getName()); 1886 addLine(THREE, 1887 Bundle.getMessage("buildStagingDepart", departStageTrack.getName(), car.getTrainName())); 1888 return false; 1889 } 1890 if (car.getTrain() != null && car.getTrain() != getTrain()) { 1891 addLine(THREE, Bundle.getMessage("buildStagingDepartCarTrain", 1892 departStageTrack.getName(), car.toString(), car.getTrainName())); 1893 return false; 1894 } 1895 // does the train accept the car type from the staging track? 1896 if (!getTrain().isTypeNameAccepted(car.getTypeName())) { 1897 addLine(THREE, 1898 Bundle.getMessage("buildStagingDepartCarType", departStageTrack.getName(), car.toString(), 1899 car.getTypeName(), getTrain().getName())); 1900 return false; 1901 } 1902 // does the train accept the car road from the staging track? 1903 if (!car.isCaboose() && !getTrain().isCarRoadNameAccepted(car.getRoadName())) { 1904 addLine(THREE, 1905 Bundle.getMessage("buildStagingDepartCarRoad", departStageTrack.getName(), car.toString(), 1906 car.getRoadName(), getTrain().getName())); 1907 return false; 1908 } 1909 // does the train accept the car load from the staging track? 1910 if (!car.isCaboose() && 1911 !car.isPassenger() && 1912 (!car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1913 !departStageTrack.isAddCustomLoadsEnabled() && 1914 !departStageTrack.isAddCustomLoadsAnySpurEnabled() && 1915 !departStageTrack.isAddCustomLoadsAnyStagingTrackEnabled()) && 1916 !getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1917 addLine(THREE, 1918 Bundle.getMessage("buildStagingDepartCarLoad", departStageTrack.getName(), car.toString(), 1919 car.getLoadName(), getTrain().getName())); 1920 return false; 1921 } 1922 // does the train accept the car owner from the staging track? 1923 if (!getTrain().isOwnerNameAccepted(car.getOwnerName())) { 1924 addLine(THREE, Bundle.getMessage("buildStagingDepartCarOwner", 1925 departStageTrack.getName(), car.toString(), car.getOwnerName(), getTrain().getName())); 1926 return false; 1927 } 1928 // does the train accept the car built date from the staging 1929 // track? 1930 if (!getTrain().isBuiltDateAccepted(car.getBuilt())) { 1931 addLine(THREE, Bundle.getMessage("buildStagingDepartCarBuilt", 1932 departStageTrack.getName(), car.toString(), car.getBuilt(), getTrain().getName())); 1933 return false; 1934 } 1935 // does the car have a destination serviced by this train? 1936 if (car.getDestination() != null) { 1937 log.debug("Car ({}) has a destination ({}, {})", car.toString(), car.getDestinationName(), 1938 car.getDestinationTrackName()); 1939 if (!getTrain().isServiceable(car)) { 1940 addLine(THREE, 1941 Bundle.getMessage("buildStagingDepartCarDestination", departStageTrack.getName(), 1942 car.toString(), car.getDestinationName(), getTrain().getName())); 1943 return false; 1944 } 1945 } 1946 // is this car a caboose with the correct road for this train? 1947 if (car.isCaboose() && 1948 (getTrain().getCabooseRoad().equals(Train.NONE) || 1949 getTrain().getCabooseRoad().equals(car.getRoadName()))) { 1950 foundCaboose = true; 1951 } 1952 // is this car have a FRED with the correct road for this train? 1953 if (car.hasFred() && 1954 (getTrain().getCabooseRoad().equals(Train.NONE) || 1955 getTrain().getCabooseRoad().equals(car.getRoadName()))) { 1956 foundFRED = true; 1957 } 1958 } 1959 } 1960 // does the train require a caboose and did we find one from staging? 1961 if (getTrain().isCabooseNeeded() && !foundCaboose) { 1962 addLine(THREE, 1963 Bundle.getMessage("buildStagingNoCaboose", departStageTrack.getName(), 1964 getTrain().getCabooseRoad())); 1965 return false; 1966 } 1967 // does the train require a car with FRED and did we find one from 1968 // staging? 1969 if (getTrain().isFredNeeded() && !foundFRED) { 1970 addLine(THREE, 1971 Bundle.getMessage("buildStagingNoCarFRED", departStageTrack.getName(), 1972 getTrain().getCabooseRoad())); 1973 return false; 1974 } 1975 return true; 1976 } 1977 1978 /** 1979 * Used to determine if staging track in a pool is the appropriated one for 1980 * departure. Staging tracks in a pool can operate in one of two ways FIFO 1981 * or LIFO. In FIFO mode (First in First out), the program selects a staging 1982 * track from the pool that has cars with the earliest arrival date. In LIFO 1983 * mode (Last in First out), the program selects a staging track from the 1984 * pool that has cars with the latest arrival date. 1985 * 1986 * @param departStageTrack the track being tested 1987 * @return true if departure on this staging track is possible 1988 */ 1989 private boolean checkStagingPool(Track departStageTrack) { 1990 if (departStageTrack.getPool() == null || 1991 departStageTrack.getServiceOrder().equals(Track.NORMAL) || 1992 departStageTrack.getNumberCars() == 0) { 1993 return true; 1994 } 1995 1996 addLine(SEVEN, Bundle.getMessage("buildStagingTrackPool", departStageTrack.getName(), 1997 departStageTrack.getPool().getName(), departStageTrack.getPool().getSize(), 1998 departStageTrack.getServiceOrder())); 1999 2000 List<Car> carList = carManager.getAvailableTrainList(getTrain()); 2001 LocalDateTime carDepartStageTrackDate = null; 2002 for (Car car : carList) { 2003 if (car.getTrack() == departStageTrack) { 2004 carDepartStageTrackDate = car.getLastMoveDate(); 2005 break; // use 1st car found 2006 } 2007 } 2008 // next check isn't really necessary, null is never returned 2009 if (carDepartStageTrackDate == null) { 2010 return true; // no cars with found date 2011 } 2012 2013 for (Track track : departStageTrack.getPool().getTracks()) { 2014 if (track == departStageTrack || track.getNumberCars() == 0) { 2015 continue; 2016 } 2017 // determine dates cars arrived into staging 2018 LocalDateTime carOtherStageTrackDate = null; 2019 2020 for (Car car : carList) { 2021 if (car.getTrack() == track) { 2022 carOtherStageTrackDate = car.getLastMoveDate(); 2023 break; // use 1st car found 2024 } 2025 } 2026 if (carOtherStageTrackDate != null) { 2027 if (departStageTrack.getServiceOrder().equals(Track.LIFO)) { 2028 if (carDepartStageTrackDate.isBefore(carOtherStageTrackDate)) { 2029 addLine(SEVEN, 2030 Bundle.getMessage("buildStagingCarsBefore", departStageTrack.getName(), 2031 track.getName())); 2032 return false; 2033 } 2034 } else { 2035 if (carOtherStageTrackDate.isBefore(carDepartStageTrackDate)) { 2036 addLine(SEVEN, Bundle.getMessage("buildStagingCarsBefore", track.getName(), 2037 departStageTrack.getName())); 2038 return false; 2039 } 2040 } 2041 } 2042 } 2043 return true; 2044 } 2045 2046 /** 2047 * Checks to see if staging track can accept train. 2048 * 2049 * @param terminateStageTrack the staging track 2050 * @return true if staging track is empty, not reserved, and accepts car and 2051 * engine types, roads, and loads. 2052 */ 2053 protected boolean checkTerminateStagingTrack(Track terminateStageTrack) { 2054 if (!terminateStageTrack.isDropTrainAccepted(getTrain())) { 2055 addLine(FIVE, Bundle.getMessage("buildStagingNotTrain", terminateStageTrack.getName())); 2056 return false; 2057 } 2058 // In normal mode, find a completely empty track. In aggressive mode, a 2059 // track that scheduled to depart is okay 2060 if (((!Setup.isBuildAggressive() || 2061 !Setup.isStagingTrackImmediatelyAvail() || 2062 terminateStageTrack.isQuickServiceEnabled()) && 2063 terminateStageTrack.getNumberRS() != 0) || 2064 (terminateStageTrack.getNumberRS() != terminateStageTrack.getPickupRS()) && 2065 terminateStageTrack.getNumberRS() != 0) { 2066 addLine(FIVE, 2067 Bundle.getMessage("buildStagingTrackOccupied", terminateStageTrack.getName(), 2068 terminateStageTrack.getNumberEngines(), terminateStageTrack.getNumberCars())); 2069 if (terminateStageTrack.getIgnoreUsedLengthPercentage() == Track.IGNORE_0) { 2070 return false; 2071 } else { 2072 addLine(FIVE, 2073 Bundle.getMessage("buildTrackHasPlannedPickups", terminateStageTrack.getName(), 2074 terminateStageTrack.getIgnoreUsedLengthPercentage(), terminateStageTrack.getLength(), 2075 Setup.getLengthUnit().toLowerCase(), terminateStageTrack.getUsedLength(), 2076 terminateStageTrack.getReserved(), 2077 terminateStageTrack.getReservedLengthSetouts(), 2078 terminateStageTrack.getReservedLengthSetouts() - terminateStageTrack.getReserved(), 2079 terminateStageTrack.getAvailableTrackSpace())); 2080 } 2081 } 2082 if ((!Setup.isBuildOnTime() || !terminateStageTrack.isQuickServiceEnabled()) && 2083 terminateStageTrack.getDropRS() != 0) { 2084 addLine(FIVE, Bundle.getMessage("buildStagingTrackReserved", terminateStageTrack.getName(), 2085 terminateStageTrack.getDropRS())); 2086 return false; 2087 } 2088 if (terminateStageTrack.getPickupRS() > 0) { 2089 addLine(FIVE, Bundle.getMessage("buildStagingTrackDepart", terminateStageTrack.getName())); 2090 } 2091 // if track is setup to accept a specific train or route, then ignore 2092 // other track restrictions 2093 if (terminateStageTrack.getDropOption().equals(Track.TRAINS) || 2094 terminateStageTrack.getDropOption().equals(Track.ROUTES)) { 2095 addLine(SEVEN, 2096 Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), 2097 terminateStageTrack.getName())); 2098 return true; // train can drop to this track, ignore other track 2099 // restrictions 2100 } 2101 if (!Setup.isStagingTrainCheckEnabled()) { 2102 addLine(SEVEN, 2103 Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), 2104 terminateStageTrack.getName())); 2105 return true; 2106 } else if (!checkTerminateStagingTrackRestrictions(terminateStageTrack)) { 2107 addLine(SEVEN, 2108 Bundle.getMessage("buildStagingTrackRestriction", terminateStageTrack.getName(), 2109 getTrain().getName())); 2110 addLine(SEVEN, Bundle.getMessage("buildOptionRestrictStaging")); 2111 return false; 2112 } 2113 return true; 2114 } 2115 2116 private boolean checkTerminateStagingTrackRestrictions(Track terminateStageTrack) { 2117 // check go see if location/track will accept the train's car and engine 2118 // types 2119 for (String name : getTrain().getTypeNames()) { 2120 if (!getTerminateLocation().acceptsTypeName(name)) { 2121 addLine(FIVE, 2122 Bundle.getMessage("buildDestinationType", getTerminateLocation().getName(), name)); 2123 return false; 2124 } 2125 if (!terminateStageTrack.isTypeNameAccepted(name)) { 2126 addLine(FIVE, 2127 Bundle.getMessage("buildStagingTrackType", terminateStageTrack.getLocation().getName(), 2128 terminateStageTrack.getName(), name)); 2129 return false; 2130 } 2131 } 2132 // check go see if track will accept the train's car roads 2133 if (getTrain().getCarRoadOption().equals(Train.ALL_ROADS) && 2134 !terminateStageTrack.getRoadOption().equals(Track.ALL_ROADS)) { 2135 addLine(FIVE, Bundle.getMessage("buildStagingTrackAllRoads", terminateStageTrack.getName())); 2136 return false; 2137 } 2138 // now determine if roads accepted by train are also accepted by staging 2139 // track 2140 // TODO should we be checking caboose and loco road names? 2141 for (String road : InstanceManager.getDefault(CarRoads.class).getNames()) { 2142 if (getTrain().isCarRoadNameAccepted(road)) { 2143 if (!terminateStageTrack.isRoadNameAccepted(road)) { 2144 addLine(FIVE, 2145 Bundle.getMessage("buildStagingTrackRoad", terminateStageTrack.getLocation().getName(), 2146 terminateStageTrack.getName(), road, "")); 2147 return false; 2148 } 2149 } 2150 } 2151 2152 // determine if staging will accept loads carried by train 2153 if (getTrain().getLoadOption().equals(Train.ALL_LOADS) && 2154 !terminateStageTrack.getLoadOption().equals(Track.ALL_LOADS)) { 2155 addLine(FIVE, Bundle.getMessage("buildStagingTrackAllLoads", terminateStageTrack.getName())); 2156 return false; 2157 } 2158 // get all of the types and loads that a train can carry, and determine 2159 // if staging will accept 2160 for (String type : getTrain().getTypeNames()) { 2161 for (String load : carLoads.getNames(type)) { 2162 if (getTrain().isLoadNameAccepted(load, type)) { 2163 if (!terminateStageTrack.isLoadNameAndCarTypeAccepted(load, type)) { 2164 addLine(FIVE, 2165 Bundle.getMessage("buildStagingTrackLoad", terminateStageTrack.getLocation().getName(), 2166 terminateStageTrack.getName(), type + CarLoad.SPLIT_CHAR + load)); 2167 return false; 2168 } 2169 } 2170 } 2171 } 2172 addLine(SEVEN, 2173 Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), terminateStageTrack.getName())); 2174 return true; 2175 } 2176 2177 boolean routeToTrackFound; 2178 2179 protected boolean checkBasicMoves(Car car, Track track) { 2180 if (car.getTrack() == track) { 2181 return false; 2182 } 2183 // don't allow local move to track with a "similar" name 2184 if (car.getSplitLocationName().equals(track.getLocation().getSplitName()) && 2185 car.getSplitTrackName().equals(track.getSplitName())) { 2186 return false; 2187 } 2188 if (track.isStaging() && car.getLocation() == track.getLocation()) { 2189 return false; // don't use same staging location 2190 } 2191 // is the car's destination the terminal and is that allowed? 2192 if (!checkThroughCarsAllowed(car, track.getLocation().getName())) { 2193 return false; 2194 } 2195 if (!checkLocalMovesAllowed(car, track)) { 2196 return false; 2197 } 2198 return true; 2199 } 2200 2201 /** 2202 * Used when generating a car load from staging. 2203 * 2204 * @param car the car. 2205 * @param track the car's destination track that has the schedule. 2206 * @return ScheduleItem si if match found, null otherwise. 2207 * @throws BuildFailedException if schedule doesn't have any line items 2208 */ 2209 protected ScheduleItem getScheduleItem(Car car, Track track) throws BuildFailedException { 2210 if (track.getSchedule() == null) { 2211 return null; 2212 } 2213 if (!track.isTypeNameAccepted(car.getTypeName())) { 2214 log.debug("Track ({}) doesn't service car type ({})", track.getName(), car.getTypeName()); 2215 if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 2216 addLine(SEVEN, 2217 Bundle.getMessage("buildSpurNotThisType", track.getLocation().getName(), track.getName(), 2218 track.getScheduleName(), car.getTypeName())); 2219 } 2220 return null; 2221 } 2222 ScheduleItem si = null; 2223 if (track.getScheduleMode() == Track.SEQUENTIAL) { 2224 si = track.getCurrentScheduleItem(); 2225 // code check 2226 if (si == null) { 2227 throw new BuildFailedException(Bundle.getMessage("buildErrorNoScheduleItem", track.getScheduleItemId(), 2228 track.getScheduleName(), track.getName(), track.getLocation().getName())); 2229 } 2230 return checkScheduleItem(si, car, track); 2231 } 2232 log.debug("Track ({}) in match mode", track.getName()); 2233 // go through entire schedule looking for a match 2234 for (int i = 0; i < track.getSchedule().getSize(); i++) { 2235 si = track.getNextScheduleItem(); 2236 // code check 2237 if (si == null) { 2238 throw new BuildFailedException(Bundle.getMessage("buildErrorNoScheduleItem", track.getScheduleItemId(), 2239 track.getScheduleName(), track.getName(), track.getLocation().getName())); 2240 } 2241 si = checkScheduleItem(si, car, track); 2242 if (si != null) { 2243 break; 2244 } 2245 } 2246 return si; 2247 } 2248 2249 /** 2250 * Used when generating a car load from staging. Checks a schedule item to 2251 * see if the car type matches, and the train and track can service the 2252 * schedule item's load. This code doesn't check to see if the car's load 2253 * can be serviced by the schedule. Instead a schedule item is returned that 2254 * allows the program to assign a custom load to the car that matches a 2255 * schedule item. Therefore, schedule items that don't request a custom load 2256 * are ignored. 2257 * 2258 * @param si the schedule item 2259 * @param car the car to check 2260 * @param track the destination track 2261 * @return Schedule item si if okay, null otherwise. 2262 */ 2263 private ScheduleItem checkScheduleItem(ScheduleItem si, Car car, Track track) { 2264 if (!car.getTypeName().equals(si.getTypeName()) || 2265 si.getReceiveLoadName().equals(ScheduleItem.NONE) || 2266 si.getReceiveLoadName().equals(carLoads.getDefaultEmptyName()) || 2267 si.getReceiveLoadName().equals(carLoads.getDefaultLoadName())) { 2268 log.debug("Not using track ({}) schedule request type ({}) road ({}) load ({})", track.getName(), 2269 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()); // NOI18N 2270 if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 2271 addLine(SEVEN, 2272 Bundle.getMessage("buildSpurScheduleNotUsed", track.getLocation().getName(), track.getName(), 2273 track.getScheduleName(), si.getId(), track.getScheduleModeName().toLowerCase(), 2274 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName())); 2275 } 2276 return null; 2277 } 2278 if (!si.getRoadName().equals(ScheduleItem.NONE) && !car.getRoadName().equals(si.getRoadName())) { 2279 log.debug("Not using track ({}) schedule request type ({}) road ({}) load ({})", track.getName(), 2280 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()); // NOI18N 2281 if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 2282 addLine(SEVEN, 2283 Bundle.getMessage("buildSpurScheduleNotUsed", track.getLocation().getName(), track.getName(), 2284 track.getScheduleName(), si.getId(), track.getScheduleModeName().toLowerCase(), 2285 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName())); 2286 } 2287 return null; 2288 } 2289 if (!getTrain().isLoadNameAccepted(si.getReceiveLoadName(), si.getTypeName())) { 2290 addLine(SEVEN, Bundle.getMessage("buildTrainNotNewLoad", getTrain().getName(), 2291 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 2292 return null; 2293 } 2294 // does the departure track allow this load? 2295 if (!car.getTrack().isLoadNameAndCarTypeShipped(si.getReceiveLoadName(), car.getTypeName())) { 2296 addLine(SEVEN, 2297 Bundle.getMessage("buildTrackNotLoadSchedule", car.getTrackName(), si.getReceiveLoadName(), 2298 track.getLocation().getName(), track.getName(), si.getId())); 2299 return null; 2300 } 2301 if (!si.getSetoutTrainScheduleId().equals(ScheduleItem.NONE) && 2302 !trainScheduleManager.getTrainScheduleActiveId().equals(si.getSetoutTrainScheduleId())) { 2303 log.debug("Schedule item isn't active"); 2304 // build the status message 2305 TrainSchedule aSch = trainScheduleManager.getScheduleById(trainScheduleManager.getTrainScheduleActiveId()); 2306 TrainSchedule tSch = trainScheduleManager.getScheduleById(si.getSetoutTrainScheduleId()); 2307 String aName = ""; 2308 String tName = ""; 2309 if (aSch != null) { 2310 aName = aSch.getName(); 2311 } 2312 if (tSch != null) { 2313 tName = tSch.getName(); 2314 } 2315 addLine(SEVEN, 2316 Bundle.getMessage("buildScheduleNotActive", track.getName(), si.getId(), tName, aName)); 2317 2318 return null; 2319 } 2320 if (!si.getRandom().equals(ScheduleItem.NONE)) { 2321 if (!si.doRandom()) { 2322 addLine(SEVEN, 2323 Bundle.getMessage("buildScheduleRandom", track.getLocation().getName(), track.getName(), 2324 track.getScheduleName(), si.getId(), si.getReceiveLoadName(), si.getRandom(), 2325 si.getCalculatedRandom())); 2326 return null; 2327 } 2328 } 2329 log.debug("Found track ({}) schedule item id ({}) for car ({})", track.getName(), si.getId(), car.toString()); 2330 return si; 2331 } 2332 2333 protected void showCarServiceOrder(Car car) { 2334 if (!car.getTrack().getServiceOrder().equals(Track.NORMAL) && !car.getTrack().isStaging()) { 2335 addLine(SEVEN, 2336 Bundle.getMessage("buildTrackModePriority", car.toString(), car.getTrack().getTrackTypeName(), 2337 car.getLocationName(), car.getTrackName(), car.getTrack().getServiceOrder(), 2338 car.getLastDate())); 2339 } 2340 } 2341 2342 /** 2343 * Returns a list containing two tracks. The 1st track found for the car, 2344 * the 2nd track is the car's final destination if an alternate track was 2345 * used for the car. 2nd track can be null. 2346 * 2347 * @param car The car needing a destination track 2348 * @param rld the RouteLocation destination 2349 * @return List containing up to two tracks. No tracks if none found. 2350 */ 2351 protected List<Track> getTracksAtDestination(Car car, RouteLocation rld) { 2352 List<Track> tracks = new ArrayList<>(); 2353 Location testDestination = rld.getLocation(); 2354 // first report if there are any alternate tracks 2355 for (Track track : testDestination.getTracksByNameList(null)) { 2356 if (track.isAlternate()) { 2357 addLine(SEVEN, Bundle.getMessage("buildTrackIsAlternate", car.toString(), 2358 track.getTrackTypeName(), track.getLocation().getName(), track.getName())); 2359 } 2360 } 2361 // now find a track for this car 2362 for (Track testTrack : testDestination.getTracksByMoves(null)) { 2363 // normally don't move car to a track with the same name at the same 2364 // location 2365 if (car.getSplitLocationName().equals(testTrack.getLocation().getSplitName()) && 2366 car.getSplitTrackName().equals(testTrack.getSplitName()) && 2367 !car.isPassenger() && 2368 !car.isCaboose() && 2369 !car.hasFred()) { 2370 addLine(SEVEN, 2371 Bundle.getMessage("buildCanNotDropCarSameTrack", car.toString(), testTrack.getName())); 2372 continue; 2373 } 2374 // Can the train service this track? 2375 if (!checkDropTrainDirection(car, rld, testTrack)) { 2376 continue; 2377 } 2378 // drop to interchange or spur? 2379 if (!checkTrainCanDrop(car, testTrack)) { 2380 continue; 2381 } 2382 // report if track has planned pickups 2383 if (testTrack.getIgnoreUsedLengthPercentage() > Track.IGNORE_0) { 2384 addLine(SEVEN, 2385 Bundle.getMessage("buildTrackHasPlannedPickups", testTrack.getName(), 2386 testTrack.getIgnoreUsedLengthPercentage(), testTrack.getLength(), 2387 Setup.getLengthUnit().toLowerCase(), testTrack.getUsedLength(), testTrack.getReserved(), 2388 testTrack.getReservedLengthSetouts(), 2389 testTrack.getReservedLengthPickups(), 2390 testTrack.getAvailableTrackSpace())); 2391 } 2392 String status = car.checkDestination(testDestination, testTrack); 2393 // Can be a caboose or car with FRED with a custom load 2394 // is the destination a spur with a schedule demanding this car's 2395 // custom load? 2396 if (status.equals(Track.OKAY) && 2397 !testTrack.getScheduleId().equals(Track.NONE) && 2398 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 2399 !car.getLoadName().equals(carLoads.getDefaultLoadName())) { 2400 addLine(FIVE, 2401 Bundle.getMessage("buildSpurScheduleLoad", testTrack.getName(), car.getLoadName())); 2402 } 2403 // check to see if alternate track is available if track full 2404 if (status.startsWith(Track.LENGTH)) { 2405 addLine(SEVEN, 2406 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), testTrack.getTrackTypeName(), 2407 testTrack.getLocation().getName(), testTrack.getName(), status)); 2408 if (checkForAlternate(car, testTrack)) { 2409 // send car to alternate track 2410 tracks.add(testTrack.getAlternateTrack()); 2411 tracks.add(testTrack); // car's final destination 2412 break; // done with this destination 2413 } 2414 continue; 2415 } 2416 // check for train timing 2417 if (status.equals(Track.OKAY)) { 2418 status = checkReserved(getTrain(), rld, car, testTrack, true); 2419 if (status.equals(TIMING) && checkForAlternate(car, testTrack)) { 2420 // send car to alternate track 2421 tracks.add(testTrack.getAlternateTrack()); 2422 tracks.add(testTrack); // car's final destination 2423 break; // done with this destination 2424 } 2425 } 2426 // okay to drop car? 2427 if (!status.equals(Track.OKAY)) { 2428 addLine(SEVEN, 2429 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), testTrack.getTrackTypeName(), 2430 testTrack.getLocation().getName(), testTrack.getName(), status)); 2431 continue; 2432 } 2433 if (!checkForLocalMove(car, testTrack)) { 2434 continue; 2435 } 2436 tracks.add(testTrack); 2437 tracks.add(null); // no final destination for this car 2438 break; // done with this destination 2439 } 2440 return tracks; 2441 } 2442 2443 /** 2444 * Checks to see if track has an alternate and can be used 2445 * 2446 * @param car the car being dropped 2447 * @param track the destination track 2448 * @return true if track has an alternate and can be used 2449 */ 2450 protected boolean checkForAlternate(Car car, Track track) { 2451 if (track.getAlternateTrack() != null && 2452 car.getTrack() != track.getAlternateTrack() && 2453 checkTrainCanDrop(car, track.getAlternateTrack())) { 2454 addLine(SEVEN, 2455 Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), 2456 track.getName(), track.getAlternateTrack().getName())); 2457 String status = car.checkDestination(track.getLocation(), track.getAlternateTrack()); 2458 if (status.equals(Track.OKAY)) { 2459 return true; 2460 } 2461 addLine(SEVEN, 2462 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 2463 track.getAlternateTrack().getTrackTypeName(), 2464 track.getLocation().getName(), track.getAlternateTrack().getName(), 2465 status)); 2466 } 2467 return false; 2468 } 2469 2470 /** 2471 * Used to determine if car could be set out at earlier location in the 2472 * train's route. 2473 * 2474 * @param car The car 2475 * @param trackTemp The destination track for this car 2476 * @param rld Where in the route the destination track was found 2477 * @param start Where to begin the check 2478 * @param routeEnd Where to stop the check 2479 * @return The best RouteLocation to drop off the car 2480 */ 2481 protected RouteLocation checkForEarlierDrop(Car car, Track trackTemp, RouteLocation rld, int start, int routeEnd) { 2482 for (int m = start; m < routeEnd; m++) { 2483 RouteLocation rle = getRouteList().get(m); 2484 if (rle == rld) { 2485 break; 2486 } 2487 car.setRouteDestinationTiming(rle); // for timing 2488 if (rle.getName().equals(rld.getName()) && 2489 (rle.getCarMoves() < rle.getMaxCarMoves()) && 2490 rle.isDropAllowed() && 2491 checkDropTrainDirection(car, rle, trackTemp) && 2492 trackTemp.isRollingStockAccepted(car).equals(Track.OKAY)) { 2493 log.debug("Found an earlier drop for car ({}) destination ({})", car.toString(), rle.getName()); // NOI18N 2494 return rle; // earlier drop in train's route 2495 } 2496 } 2497 return rld; 2498 } 2499 2500 /* 2501 * Determines if rolling stock can be delivered to track when considering 2502 * timing of car pulls by other trains. 2503 */ 2504 protected String checkReserved(Train train, RouteLocation rld, Car car, Track destTrack, boolean printMsg) { 2505 // car returning to same track? 2506 if (car.getTrack() != destTrack) { 2507 // car can be a kernel so get total length 2508 int length = car.getTotalKernelLength(); 2509 log.debug("Car length: {}, available track space: {}, reserved: {}", length, 2510 destTrack.getAvailableTrackSpace(), destTrack.getReserved()); 2511 if (length > destTrack.getAvailableTrackSpace() + 2512 destTrack.getReserved()) { 2513 boolean returned = false; 2514 String trainExpectedArrival = train.getExpectedArrivalTime(rld, true); 2515 int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival); 2516 int reservedReturned = 0; 2517 // does this car already have this destination? 2518 if (car.getDestinationTrack() == destTrack) { 2519 reservedReturned = -car.getTotalKernelLength(); 2520 } 2521 // get a list of cars on this track 2522 List<Car> cars = carManager.getList(destTrack); 2523 for (Car kar : cars) { 2524 if (kar.getTrain() != null && kar.getTrain() != train) { 2525 int carPullTime = convertStringTime(kar.getPickupTime()); 2526 if (trainArrivalTimeMinutes < carPullTime) { 2527 // don't print if checking redirect to alternate 2528 if (printMsg) { 2529 addLine(SEVEN, 2530 Bundle.getMessage("buildCarTrainTiming", kar.toString(), 2531 kar.getTrack().getTrackTypeName(), kar.getLocationName(), 2532 kar.getTrackName(), kar.getTrainName(), kar.getPickupTime(), 2533 getTrain().getName(), trainExpectedArrival)); 2534 } 2535 reservedReturned += kar.getTotalLength(); 2536 returned = true; 2537 } 2538 } 2539 } 2540 if (returned && length > destTrack.getAvailableTrackSpace() - reservedReturned) { 2541 if (printMsg) { 2542 addLine(SEVEN, 2543 Bundle.getMessage("buildWarnTrainTiming", car.toString(), destTrack.getTrackTypeName(), 2544 destTrack.getLocation().getName(), destTrack.getName(), getTrain().getName(), 2545 destTrack.getAvailableTrackSpace() - reservedReturned, 2546 Setup.getLengthUnit().toLowerCase())); 2547 } 2548 return TIMING; 2549 } 2550 } 2551 } 2552 return Track.OKAY; 2553 } 2554 2555 /** 2556 * Checks to see if local move is allowed for this car 2557 * 2558 * @param car the car being moved 2559 * @param testTrack the destination track for this car 2560 * @return false if local move not allowed 2561 */ 2562 private boolean checkForLocalMove(Car car, Track testTrack) { 2563 if (getTrain().isLocalSwitcher()) { 2564 // No local moves from spur to spur 2565 if (!Setup.isLocalSpurMovesEnabled() && testTrack.isSpur() && car.getTrack().isSpur()) { 2566 addLine(SEVEN, 2567 Bundle.getMessage("buildNoSpurToSpurMove", car.getTrackName(), testTrack.getName())); 2568 return false; 2569 } 2570 // No local moves from yard to yard, except for cabooses and cars 2571 // with FRED 2572 if (!Setup.isLocalYardMovesEnabled() && 2573 testTrack.isYard() && 2574 car.getTrack().isYard() && 2575 !car.isCaboose() && 2576 !car.hasFred()) { 2577 addLine(SEVEN, 2578 Bundle.getMessage("buildNoYardToYardMove", car.getTrackName(), testTrack.getName())); 2579 return false; 2580 } 2581 // No local moves from interchange to interchange 2582 if (!Setup.isLocalInterchangeMovesEnabled() && 2583 testTrack.isInterchange() && 2584 car.getTrack().isInterchange()) { 2585 addLine(SEVEN, 2586 Bundle.getMessage("buildNoInterchangeToInterchangeMove", car.getTrackName(), 2587 testTrack.getName())); 2588 return false; 2589 } 2590 } 2591 return true; 2592 } 2593 2594 protected Track tryStaging(Car car, RouteLocation rldSave) throws BuildFailedException { 2595 // local switcher working staging? 2596 if (getTrain().isLocalSwitcher() && 2597 !car.isPassenger() && 2598 !car.isCaboose() && 2599 !car.hasFred() && 2600 car.getTrack() == getTerminateStagingTrack()) { 2601 addLine(SEVEN, 2602 Bundle.getMessage("buildCanNotDropCarSameTrack", car.toString(), car.getTrack().getName())); 2603 return null; 2604 } 2605 // no need to check train and track direction into staging, already done 2606 String status = car.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 2607 if (status.equals(Track.OKAY)) { 2608 return getTerminateStagingTrack(); 2609 // only generate a new load if there aren't any other tracks 2610 // available for this car 2611 } else if (status.startsWith(Track.LOAD) && 2612 car.getTrack() == getDepartureStagingTrack() && 2613 car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 2614 rldSave == null && 2615 (getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 2616 getDepartureStagingTrack().isAddCustomLoadsEnabled() || 2617 getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled())) { 2618 // try and generate a load for this car into staging 2619 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack())) { 2620 return getTerminateStagingTrack(); 2621 } 2622 } 2623 addLine(SEVEN, 2624 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 2625 getTerminateStagingTrack().getTrackTypeName(), 2626 getTerminateStagingTrack().getLocation().getName(), getTerminateStagingTrack().getName(), 2627 status)); 2628 return null; 2629 } 2630 2631 /** 2632 * Returns true if car can be picked up later in a train's route 2633 * 2634 * @param car the car 2635 * @param rl car's route location 2636 * @param rld car's route location destination 2637 * @return true if car can be picked up later in a train's route 2638 * @throws BuildFailedException if coding issue 2639 */ 2640 protected boolean checkForLaterPickUp(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 2641 // is there another pick up location in the route? 2642 if (rl == rld || !rld.getName().equals(car.getLocationName())) { 2643 return false; 2644 } 2645 // last route location in the route? 2646 if (rld == getTrain().getTrainTerminatesRouteLocation() && !car.isLocalMove()) { 2647 return false; 2648 } 2649 // don't delay adding a caboose, passenger car, or car with FRED 2650 if (car.isCaboose() || car.isPassenger() || car.hasFred()) { 2651 return false; 2652 } 2653 // no later pick up if car is departing staging 2654 if (car.getLocation().isStaging()) { 2655 return false; 2656 } 2657 if (!checkPickUpTrainDirection(car, rld)) { 2658 addLine(SEVEN, 2659 Bundle.getMessage("buildNoPickupLaterDirection", car.toString(), rld.getName(), rld.getId())); 2660 return false; 2661 } 2662 if (!rld.isPickUpAllowed() && !rld.isLocalMovesAllowed() || 2663 !rld.isPickUpAllowed() && rld.isLocalMovesAllowed() && !car.isLocalMove()) { 2664 addLine(SEVEN, 2665 Bundle.getMessage("buildNoPickupLater", car.toString(), rld.getName(), rld.getId())); 2666 return false; 2667 } 2668 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 2669 addLine(SEVEN, 2670 Bundle.getMessage("buildNoPickupLaterMoves", car.toString(), rld.getName(), rld.getId())); 2671 return false; 2672 } 2673 // is the track full? If so, pull immediately, prevents overloading 2674 if (checkForPickUps(car, rl, false)) { 2675 addLine(SEVEN, Bundle.getMessage("buildNoPickupLaterTrack", car.toString(), rld.getName(), 2676 car.getTrackName(), rld.getId(), car.getTrack().getLength() - car.getTrack().getUsedLength(), 2677 Setup.getLengthUnit().toLowerCase(), car.getTrackName())); 2678 return false; 2679 } 2680 // are there any other cars being pull from the same track, route location, and train? 2681 if (checkForPickUps(car, rl, true)) { 2682 addLine(SEVEN, Bundle.getMessage("buildAlreadyPickups", car.toString(), rld.getName(), 2683 car.getTrackName(), rld.getId(), car.getTrack().getTrackTypeName(), rl.getName(), 2684 car.getTrack().getName(), getTrain().getName())); 2685 return false; 2686 } 2687 addLine(SEVEN, 2688 Bundle.getMessage("buildPickupLaterOkay", car.toString(), rld.getName(), rld.getId())); 2689 return true; 2690 } 2691 2692 /* 2693 * checks to see if the train being built already has car pick ups at the 2694 * same track, route location rl, and train, and there's a track space 2695 * issue. 2696 * 2697 * return true if there are already pick ups from the car's track 2698 */ 2699 private boolean checkForPickUps(Car car, RouteLocation rl, boolean isCheckForCars) { 2700 if (!car.isLocalMove() && rl.isDropAllowed()) { 2701 int length = 0; 2702 if (isCheckForCars) { 2703 for (Car c : carManager.getByTrainList(getTrain())) { 2704 if (car.getTrack() == c.getTrack() && rl == c.getRouteLocation()) { 2705 length += c.getTotalKernelLength(); 2706 } 2707 } 2708 } 2709 if (car.getTrack().getLength() - car.getTrack().getUsedLength() < car.getTotalKernelLength() + length) { 2710 return true; 2711 } 2712 } 2713 return false; 2714 } 2715 2716 /** 2717 * Returns true is cars are allowed to travel from origin to terminal 2718 * 2719 * @param car The car 2720 * @param destinationName Destination name for this car 2721 * @return true if through cars are allowed. false if not. 2722 */ 2723 protected boolean checkThroughCarsAllowed(Car car, String destinationName) { 2724 if (!getTrain().isAllowThroughCarsEnabled() && 2725 !getTrain().isLocalSwitcher() && 2726 !car.isCaboose() && 2727 !car.hasFred() && 2728 !car.isPassenger() && 2729 car.getSplitLocationName().equals(getDepartureLocation().getSplitName()) && 2730 splitString(destinationName).equals(getTerminateLocation().getSplitName()) && 2731 !getDepartureLocation().getSplitName().equals(getTerminateLocation().getSplitName())) { 2732 addLine(FIVE, Bundle.getMessage("buildThroughTrafficNotAllow", getDepartureLocation().getName(), 2733 getTerminateLocation().getName())); 2734 return false; // through cars not allowed 2735 } 2736 return true; // through cars allowed 2737 } 2738 2739 private boolean checkLocalMovesAllowed(Car car, Track track) { 2740 if (!getTrain().isLocalSwitcher() && 2741 !getTrain().isAllowLocalMovesEnabled() && 2742 car.getSplitLocationName().equals(track.getLocation().getSplitName())) { 2743 addLine(SEVEN, 2744 Bundle.getMessage("buildNoLocalMoveToTrack", car.getLocationName(), car.getTrackName(), 2745 track.getLocation().getName(), track.getName(), getTrain().getName())); 2746 return false; 2747 } 2748 return true; 2749 } 2750 2751 /** 2752 * Creates a car load for a car departing staging and eventually terminating 2753 * into staging. 2754 * 2755 * @param car the car! 2756 * @param stageTrack the staging track the car will terminate to 2757 * @return true if a load was generated this this car. 2758 * @throws BuildFailedException if coding check fails 2759 */ 2760 protected boolean generateLoadCarDepartingAndTerminatingIntoStaging(Car car, Track stageTrack) 2761 throws BuildFailedException { 2762 addLine(SEVEN, BLANK_LINE); 2763 // code check 2764 if (stageTrack == null || !stageTrack.isStaging()) { 2765 throw new BuildFailedException("ERROR coding issue, staging track null or not staging"); 2766 } 2767 if (!stageTrack.isTypeNameAccepted(car.getTypeName())) { 2768 addLine(SEVEN, 2769 Bundle.getMessage("buildStagingTrackType", stageTrack.getLocation().getName(), stageTrack.getName(), 2770 car.getTypeName())); 2771 return false; 2772 } 2773 if (!stageTrack.isRoadNameAndLoadTypeAccepted(car.getRoadName(), car.getLoadType())) { 2774 addLine(SEVEN, 2775 Bundle.getMessage("buildStagingTrackRoad", stageTrack.getLocation().getName(), stageTrack.getName(), 2776 car.getRoadName(), car.getLoadType())); 2777 return false; 2778 } 2779 // Departing and returning to same location in staging? 2780 if (!getTrain().isAllowReturnToStagingEnabled() && 2781 !Setup.isStagingAllowReturnEnabled() && 2782 !car.isCaboose() && 2783 !car.hasFred() && 2784 !car.isPassenger() && 2785 car.getSplitLocationName().equals(stageTrack.getLocation().getSplitName())) { 2786 addLine(SEVEN, 2787 Bundle.getMessage("buildNoReturnStaging", car.toString(), stageTrack.getLocation().getName())); 2788 return false; 2789 } 2790 // figure out which loads the car can use 2791 List<String> loads = carLoads.getNames(car.getTypeName()); 2792 // remove the default names 2793 loads.remove(carLoads.getDefaultEmptyName()); 2794 loads.remove(carLoads.getDefaultLoadName()); 2795 if (loads.size() == 0) { 2796 log.debug("No custom loads for car type ({}) ignoring staging track ({})", car.getTypeName(), 2797 stageTrack.getName()); 2798 return false; 2799 } 2800 addLine(SEVEN, 2801 Bundle.getMessage("buildSearchTrackLoadStaging", car.toString(), car.getTypeName(), 2802 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 2803 stageTrack.getLocation().getName(), stageTrack.getName())); 2804 String oldLoad = car.getLoadName(); // save car's "E" load 2805 for (int i = loads.size() - 1; i >= 0; i--) { 2806 String load = loads.get(i); 2807 log.debug("Try custom load ({}) for car ({})", load, car.toString()); 2808 if (!car.getTrack().isLoadNameAndCarTypeShipped(load, car.getTypeName()) || 2809 !stageTrack.isLoadNameAndCarTypeAccepted(load, car.getTypeName()) || 2810 !getTrain().isLoadNameAccepted(load, car.getTypeName())) { 2811 // report why the load was rejected and remove it from consideration 2812 if (!car.getTrack().isLoadNameAndCarTypeShipped(load, car.getTypeName())) { 2813 addLine(SEVEN, 2814 Bundle.getMessage("buildTrackNotNewLoad", car.getTrackName(), load, 2815 stageTrack.getLocation().getName(), stageTrack.getName())); 2816 } 2817 if (!stageTrack.isLoadNameAndCarTypeAccepted(load, car.getTypeName())) { 2818 addLine(SEVEN, 2819 Bundle.getMessage("buildDestTrackNoLoad", stageTrack.getLocation().getName(), 2820 stageTrack.getName(), car.toString(), load)); 2821 } 2822 if (!getTrain().isLoadNameAccepted(load, car.getTypeName())) { 2823 addLine(SEVEN, 2824 Bundle.getMessage("buildTrainNotNewLoad", getTrain().getName(), load, 2825 stageTrack.getLocation().getName(), stageTrack.getName())); 2826 } 2827 loads.remove(i); 2828 continue; 2829 } 2830 car.setLoadName(load); 2831 // does the car have a home division? 2832 if (car.getDivision() != null) { 2833 addLine(SEVEN, 2834 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 2835 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 2836 car.getLocationName(), 2837 car.getTrackName(), car.getTrack().getDivisionName())); 2838 // load type empty must return to car's home division 2839 // or load type load from foreign division must return to car's 2840 // home division 2841 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && 2842 car.getDivision() != stageTrack.getDivision() || 2843 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 2844 car.getTrack().getDivision() != car.getDivision() && 2845 car.getDivision() != stageTrack.getDivision()) { 2846 addLine(SEVEN, 2847 Bundle.getMessage("buildNoDivisionTrack", stageTrack.getTrackTypeName(), 2848 stageTrack.getLocation().getName(), stageTrack.getName(), 2849 stageTrack.getDivisionName(), car.toString(), 2850 car.getLoadType().toLowerCase(), car.getLoadName())); 2851 loads.remove(i); 2852 continue; 2853 } 2854 } 2855 } 2856 // do we need to test all car loads? 2857 boolean loadRestrictions = isLoadRestrictions(); 2858 // now determine if the loads can be routed to the staging track 2859 for (int i = loads.size() - 1; i >= 0; i--) { 2860 String load = loads.get(i); 2861 car.setLoadName(load); 2862 if (!router.isCarRouteable(car, getTrain(), stageTrack, getBuildReport())) { 2863 loads.remove(i); // no remove this load 2864 addLine(SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 2865 stageTrack.getLocation().getName(), stageTrack.getName(), load)); 2866 if (!loadRestrictions) { 2867 loads.clear(); // no loads can be routed 2868 break; 2869 } 2870 } else if (!loadRestrictions) { 2871 break; // done all loads can be routed 2872 } 2873 } 2874 // Use random loads rather that the first one that works to create 2875 // interesting loads 2876 if (loads.size() > 0) { 2877 int rnd = (int) (Math.random() * loads.size()); 2878 car.setLoadName(loads.get(rnd)); 2879 // check to see if car is now accepted by staging 2880 String status = car.checkDestination(stageTrack.getLocation(), stageTrack); 2881 if (status.equals(Track.OKAY) || 2882 (status.startsWith(Track.LENGTH) && stageTrack != getTerminateStagingTrack())) { 2883 car.setLoadGeneratedFromStaging(true); 2884 car.setFinalDestination(stageTrack.getLocation()); 2885 // don't set track assignment unless the car is going to this 2886 // train's staging 2887 if (stageTrack == getTerminateStagingTrack()) { 2888 car.setFinalDestinationTrack(stageTrack); 2889 } else { 2890 // don't assign the track, that will be done later 2891 car.setFinalDestinationTrack(null); 2892 } 2893 car.updateKernel(); // is car part of kernel? 2894 addLine(SEVEN, 2895 Bundle.getMessage("buildAddingScheduleLoad", loads.size(), car.getLoadName(), car.toString())); 2896 return true; 2897 } 2898 addLine(SEVEN, 2899 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), stageTrack.getTrackTypeName(), 2900 stageTrack.getLocation().getName(), stageTrack.getName(), status)); 2901 } 2902 car.setLoadName(oldLoad); // restore load and report failure 2903 addLine(SEVEN, Bundle.getMessage("buildUnableNewLoadStaging", car.toString(), car.getTrackName(), 2904 stageTrack.getLocation().getName(), stageTrack.getName())); 2905 return false; 2906 } 2907 2908 /** 2909 * Checks to see if there are any load restrictions for trains, 2910 * interchanges, and yards if routing through yards is enabled. 2911 * 2912 * @return true if there are load restrictions. 2913 */ 2914 private boolean isLoadRestrictions() { 2915 boolean restrictions = isLoadRestrictionsTrain() || isLoadRestrictions(Track.INTERCHANGE); 2916 if (Setup.isCarRoutingViaYardsEnabled()) { 2917 restrictions = restrictions || isLoadRestrictions(Track.YARD); 2918 } 2919 return restrictions; 2920 } 2921 2922 private boolean isLoadRestrictions(String type) { 2923 for (Track track : locationManager.getTracks(type)) { 2924 if (!track.getLoadOption().equals(Track.ALL_LOADS)) { 2925 return true; 2926 } 2927 } 2928 return false; 2929 } 2930 2931 private boolean isLoadRestrictionsTrain() { 2932 for (Train train : trainManager.getList()) { 2933 if (!train.getLoadOption().equals(Train.ALL_LOADS)) { 2934 return true; 2935 } 2936 } 2937 return false; 2938 } 2939 2940 /** 2941 * report any cars left at route location 2942 * 2943 * @param rl route location 2944 */ 2945 protected void showCarsNotMoved(RouteLocation rl) { 2946 if (_carIndex < 0) { 2947 _carIndex = 0; 2948 } 2949 // cars up this point have build report messages, only show the cars 2950 // that aren't 2951 // in the build report 2952 int numberCars = 0; 2953 for (int i = _carIndex; i < getCarList().size(); i++) { 2954 if (numberCars == DISPLAY_CAR_LIMIT_100) { 2955 addLine(FIVE, Bundle.getMessage("buildOnlyFirstXXXCars", numberCars, rl.getName())); 2956 break; 2957 } 2958 Car car = getCarList().get(i); 2959 // find a car at this location that hasn't been given a destination 2960 if (!car.getLocationName().equals(rl.getName()) || car.getRouteDestination() != null) { 2961 continue; 2962 } 2963 if (numberCars == 0) { 2964 addLine(SEVEN, 2965 Bundle.getMessage("buildMovesCompleted", rl.getMaxCarMoves(), rl.getName())); 2966 } 2967 addLine(SEVEN, Bundle.getMessage("buildCarIgnored", car.toString(), car.getTypeName(), 2968 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName())); 2969 numberCars++; 2970 } 2971 addLine(SEVEN, BLANK_LINE); 2972 } 2973 2974 /** 2975 * Remove rolling stock from train 2976 * 2977 * @param rs the rolling stock to be removed 2978 */ 2979 protected void removeRollingStockFromTrain(RollingStock rs) { 2980 // adjust train length and weight for each location that the rolling 2981 // stock is in the train 2982 boolean inTrain = false; 2983 for (RouteLocation routeLocation : getRouteList()) { 2984 if (rs.getRouteLocation() == routeLocation) { 2985 inTrain = true; 2986 } 2987 if (rs.getRouteDestination() == routeLocation) { 2988 break; 2989 } 2990 if (inTrain) { 2991 routeLocation.setTrainLength(routeLocation.getTrainLength() - rs.getTotalLength()); // includes 2992 // couplers 2993 routeLocation.setTrainWeight(routeLocation.getTrainWeight() - rs.getAdjustedWeightTons()); 2994 } 2995 } 2996 rs.reset(); // remove this rolling stock from the train 2997 } 2998 2999 /** 3000 * Lists cars that couldn't be routed. 3001 */ 3002 protected void showCarsNotRoutable() { 3003 // any cars unable to route? 3004 if (_notRoutable.size() > 0) { 3005 addLine(ONE, BLANK_LINE); 3006 addLine(ONE, Bundle.getMessage("buildCarsNotRoutable")); 3007 for (Car car : _notRoutable) { 3008 _warnings++; 3009 addLine(ONE, 3010 Bundle.getMessage("buildCarNotRoutable", car.toString(), car.getLocationName(), 3011 car.getTrackName(), car.getPreviousFinalDestinationName(), 3012 car.getPreviousFinalDestinationTrackName())); 3013 } 3014 addLine(ONE, BLANK_LINE); 3015 } 3016 } 3017 3018 protected void finshBuildReport() { 3019 // done building 3020 if (_warnings > 0) { 3021 addLine(ONE, Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings)); 3022 } 3023 addLine(FIVE, 3024 Bundle.getMessage("buildTime", getTrain().getName(), LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() - 3025 getStartTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())); 3026 } 3027 3028 /** 3029 * build has failed due to cars in staging not having destinations this 3030 * routine removes those cars from the staging track by user request. 3031 */ 3032 protected void removeCarsFromStaging() { 3033 // Code check, only called if train was departing staging 3034 if (getDepartureStagingTrack() == null) { 3035 log.error("Error, called when cars in staging not assigned to train"); 3036 return; 3037 } 3038 for (Car car : getCarList()) { 3039 // remove cars from departure staging track that haven't been 3040 // assigned to this train 3041 if (car.getTrack() == getDepartureStagingTrack() && car.getTrain() == null) { 3042 // remove track from kernel 3043 if (car.getKernel() != null) { 3044 for (Car c : car.getKernel().getCars()) 3045 c.setLocation(car.getLocation(), null); 3046 } else { 3047 car.setLocation(car.getLocation(), null); 3048 } 3049 } 3050 } 3051 } 3052 3053 protected int countRollingStockAt(RouteLocation rl, List<RollingStock> list) { 3054 int count = 0; 3055 for (RollingStock rs : list) { 3056 if (rs.getLocationName().equals(rl.getName())) { 3057 count++; 3058 } 3059 } 3060 return count; 3061 } 3062 3063 /* 3064 * lists the tracks that aren't in quick service mode 3065 */ 3066 protected void showTracksNotQuickService() { 3067 if (Setup.isBuildOnTime()) { 3068 addLine(FIVE, BLANK_LINE); 3069 addLine(FIVE, Bundle.getMessage("buildTracksNotQuickService")); 3070 for (Track track : locationManager.getTracks(null)) { 3071 if (!track.isQuickServiceEnabled()) { 3072 addLine(SEVEN, Bundle.getMessage("buildTrackNotQuick", 3073 StringUtils.capitalize(track.getTrackTypeName()), track.getLocation().getName(), 3074 track.getName())); 3075 } 3076 } 3077 } 3078 } 3079 3080 protected boolean checkRouteLocation(RouteLocation rl) { 3081 if (getTrain().isLocationSkipped(rl)) { 3082 addLine(ONE, 3083 Bundle.getMessage("buildLocSkipped", rl.getName(), rl.getId(), getTrain().getName())); 3084 return false; 3085 } 3086 if (!rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) { 3087 addLine(ONE, 3088 Bundle.getMessage("buildLocNoPickups", getTrain().getRoute().getName(), rl.getId(), rl.getName())); 3089 return false; 3090 } 3091 // no pick ups from staging unless at the start of the train's route 3092 if (rl != getTrain().getTrainDepartsRouteLocation() && rl.getLocation().isStaging()) { 3093 addLine(ONE, Bundle.getMessage("buildNoPickupsFromStaging", rl.getName())); 3094 return false; 3095 } 3096 // the next check provides a build report message if there's an 3097 // issue with the train direction 3098 if (!checkPickUpTrainDirection(rl)) { 3099 return false; 3100 } 3101 return true; 3102 } 3103 3104 /** 3105 * Checks to see if rolling stock is departing a quick service track and is 3106 * allowed to be pulled by this train. To pull, the route location must be 3107 * different than the one used to deliver the rolling stock. To service the 3108 * rolling stock, the train must arrive after the rolling stock's clone is 3109 * set out by this train or by another train. 3110 * 3111 * @param rs the rolling stock 3112 * @param rl the route location pulling the rolling stock 3113 * @return true if rolling stock can be pulled 3114 */ 3115 protected boolean checkQuickServiceDeparting(RollingStock rs, RouteLocation rl) { 3116 if (rs.getTrack().isQuickServiceEnabled()) { 3117 RollingStock clone = null; 3118 if (Car.class.isInstance(rs)) { 3119 clone = carManager.getClone(rs); 3120 } 3121 if (Engine.class.isInstance(rs)) { 3122 clone = engineManager.getClone(rs); 3123 } 3124 if (clone != null) { 3125 // was the rolling stock delivered using this route location? 3126 if (rs.getRouteDestination() == rl) { 3127 addLine(FIVE, 3128 Bundle.getMessage("buildRouteLocation", rs.toString(), rs.getTrack().getTrackTypeName(), 3129 rs.getLocationName(), rs.getTrackName(), getTrain().getName(), rl.getName(), 3130 rl.getId())); 3131 addLine(FIVE, BLANK_LINE); 3132 return false; 3133 } 3134 3135 // determine when the train arrives 3136 String trainExpectedArrival = getTrain().getExpectedArrivalTime(rl, true); 3137 int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival); 3138 // determine when the clone is going to be delivered 3139 int cloneSetoutTimeMinutes = convertStringTime(clone.getSetoutTime()); 3140 // in aggressive mode the dwell time is 0 3141 int dwellTime = Setup.getDwellTime(); 3142 if (cloneSetoutTimeMinutes + dwellTime > trainArrivalTimeMinutes) { 3143 String earliest = convertMinutesTime(cloneSetoutTimeMinutes + dwellTime); 3144 addLine(FIVE, Bundle.getMessage("buildDeliveryTiming", rs.toString(), 3145 clone.getSetoutTime(), rs.getTrack().getTrackTypeName(), rs.getLocationName(), 3146 rs.getTrackName(), clone.getTrainName(), clone.getRouteDestination().getId(), 3147 getTrain().getName(), trainExpectedArrival, dwellTime, earliest)); 3148 addLine(FIVE, BLANK_LINE); 3149 return false; 3150 } else { 3151 addLine(SEVEN, Bundle.getMessage("buildCloneDeliveryTiming", clone.toString(), 3152 clone.getSetoutTime(), rs.getTrack().getTrackTypeName(), rs.getLocationName(), 3153 rs.getTrackName(), clone.getTrainName(), clone.getRouteDestination().getId(), 3154 getTrain().getName(), trainExpectedArrival, dwellTime, rs.toString())); 3155 } 3156 } 3157 } 3158 return true; 3159 } 3160 3161 /* 3162 * Engine methods start here 3163 */ 3164 3165 /** 3166 * Used to determine the number of engines requested by the user. 3167 * 3168 * @param requestEngines Can be a number, AUTO or AUTO HPT. 3169 * @return the number of engines requested by user. 3170 */ 3171 protected int getNumberEngines(String requestEngines) { 3172 int numberEngines = 0; 3173 if (requestEngines.equals(Train.AUTO)) { 3174 numberEngines = getAutoEngines(); 3175 } else if (requestEngines.equals(Train.AUTO_HPT)) { 3176 numberEngines = 1; // get one loco for now, check HP requirements 3177 // after train is built 3178 } else { 3179 numberEngines = Integer.parseInt(requestEngines); 3180 } 3181 return numberEngines; 3182 } 3183 3184 /** 3185 * Returns the number of engines needed for this train, minimum 1, maximum 3186 * user specified in setup. Based on maximum allowable train length and 3187 * grade between locations, and the maximum cars that the train can have at 3188 * the maximum train length. One engine per sixteen 40' cars for 1% grade. 3189 * 3190 * @return The number of engines needed 3191 */ 3192 private int getAutoEngines() { 3193 double numberEngines = 1; 3194 int moves = 0; 3195 int carLength = 40 + Car.COUPLERS; // typical 40' car 3196 3197 // adjust if length in meters 3198 if (!Setup.getLengthUnit().equals(Setup.FEET)) { 3199 carLength = 12 + Car.COUPLERS; // typical car in meters 3200 } 3201 3202 for (RouteLocation rl : getRouteList()) { 3203 if (rl.isPickUpAllowed() && rl != getTrain().getTrainTerminatesRouteLocation()) { 3204 moves += rl.getMaxCarMoves(); // assume all moves are pick ups 3205 double carDivisor = 16; // number of 40' cars per engine 1% grade 3206 // change engine requirements based on grade 3207 if (rl.getGrade() > 1) { 3208 carDivisor = carDivisor / rl.getGrade(); 3209 } 3210 log.debug("Maximum train length {} for location ({})", rl.getMaxTrainLength(), rl.getName()); 3211 if (rl.getMaxTrainLength() / (carDivisor * carLength) > numberEngines) { 3212 numberEngines = rl.getMaxTrainLength() / (carDivisor * carLength); 3213 // round up to next whole integer 3214 numberEngines = Math.ceil(numberEngines); 3215 // determine if there's enough car pick ups at this point to 3216 // reach the max train length 3217 if (numberEngines > moves / carDivisor) { 3218 // no reduce based on moves 3219 numberEngines = Math.ceil(moves / carDivisor); 3220 } 3221 } 3222 } 3223 } 3224 int nE = (int) numberEngines; 3225 if (getTrain().isLocalSwitcher()) { 3226 nE = 1; // only one engine if switcher 3227 } 3228 addLine(ONE, 3229 Bundle.getMessage("buildAutoBuildMsg", Integer.toString(nE))); 3230 if (nE > Setup.getMaxNumberEngines()) { 3231 addLine(THREE, Bundle.getMessage("buildMaximumNumberEngines", Setup.getMaxNumberEngines())); 3232 nE = Setup.getMaxNumberEngines(); 3233 } 3234 return nE; 3235 } 3236 3237 protected void addLine(String level, String string) { 3238 addLine(getBuildReport(), level, string); 3239 } 3240 3241 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilderBase.class); 3242 3243}