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