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