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 } 717 return selected; 718 } else if (validTracks.size() == 1) { 719 Track track = validTracks.get(0); 720 addLine(FIVE, 721 Bundle.getMessage("buildOnlyOneDepartureTrack", track.getName(), track.getLocation().getName())); 722 return track; 723 } 724 return null; // no tracks available 725 } 726 727 /** 728 * Ask which staging track the train is to terminate on. 729 * 730 * @return The termination track selected by the user. 731 */ 732 protected Track promptToStagingDialog() { 733 List<Track> tracksIn = getTerminateLocation().getTracksByNameList(null); 734 List<Track> validTracks = new ArrayList<>(); 735 // only show valid tracks 736 for (Track track : tracksIn) { 737 if (checkTerminateStagingTrack(track)) { 738 validTracks.add(track); 739 } 740 } 741 if (validTracks.size() > 1) { 742 // need an object array for dialog window 743 Object[] tracks = new Object[validTracks.size()]; 744 for (int i = 0; i < validTracks.size(); i++) { 745 tracks[i] = validTracks.get(i); 746 } 747 748 Track selected = (Track) JmriJOptionPane.showInputDialog(null, 749 Bundle.getMessage("TrainTerminatingStaging", getTrain().getName(), 750 getTerminateLocation().getName()), 751 Bundle.getMessage("SelectArrivalTrack"), JmriJOptionPane.QUESTION_MESSAGE, null, tracks, null); 752 if (selected != null) { 753 addLine(FIVE, Bundle.getMessage("buildUserSelectedArrival", selected.getName(), 754 selected.getLocation().getName())); 755 } 756 return selected; 757 } else if (validTracks.size() == 1) { 758 return validTracks.get(0); 759 } 760 return null; // no tracks available 761 } 762 763 /** 764 * Removes the remaining cabooses and cars with FRED from consideration. 765 * 766 * @throws BuildFailedException code check if car being removed is in 767 * staging 768 */ 769 protected void removeCaboosesAndCarsWithFred() throws BuildFailedException { 770 addLine(SEVEN, BLANK_LINE); 771 addLine(SEVEN, Bundle.getMessage("buildRemoveCarsNotNeeded")); 772 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 773 Car car = getCarList().get(_carIndex); 774 if (car.isCaboose() || car.hasFred()) { 775 addLine(SEVEN, 776 Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(), 777 car.getTypeExtensions(), car.getLocationName(), car.getTrackName())); 778 // code check, should never be staging 779 if (car.getTrack() == getDepartureStagingTrack()) { 780 throw new BuildFailedException("ERROR: Attempt to removed car with FRED or Caboose from staging"); // NOI18N 781 } 782 remove(car); // remove this car from the list 783 } 784 } 785 addLine(SEVEN, BLANK_LINE); 786 } 787 788 /** 789 * Save the car's final destination and schedule id in case of train reset 790 */ 791 protected void saveCarFinalDestinations() { 792 for (Car car : getCarList()) { 793 car.setPreviousFinalDestination(car.getFinalDestination()); 794 car.setPreviousFinalDestinationTrack(car.getFinalDestinationTrack()); 795 car.setPreviousScheduleId(car.getScheduleItemId()); 796 } 797 } 798 799 /** 800 * Creates the carList. Only cars that can be serviced by this train are in 801 * the list. 802 * 803 * @throws BuildFailedException if car is marked as missing and is in 804 * staging 805 */ 806 protected void createCarList() throws BuildFailedException { 807 // get list of cars for this route 808 setCarList(carManager.getAvailableTrainList(getTrain())); 809 addLine(SEVEN, BLANK_LINE); 810 addLine(SEVEN, Bundle.getMessage("buildRemoveCars")); 811 boolean showCar = true; 812 int carListSize = getCarList().size(); 813 // now remove cars that the train can't service 814 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 815 Car car = getCarList().get(_carIndex); 816 // only show the first 100 cars removed due to wrong car type for 817 // train 818 if (showCar && carListSize - getCarList().size() == DISPLAY_CAR_LIMIT_100) { 819 showCar = false; 820 addLine(FIVE, 821 Bundle.getMessage("buildOnlyFirstXXXCars", DISPLAY_CAR_LIMIT_100, Bundle.getMessage("Type"))); 822 } 823 // remove cars that don't have a track assignment 824 if (car.getTrack() == null) { 825 _warnings++; 826 addLine(ONE, 827 Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName())); 828 remove(car); 829 continue; 830 } 831 // remove cars that have been reported as missing 832 if (car.isLocationUnknown()) { 833 addLine(SEVEN, Bundle.getMessage("buildExcludeCarLocUnknown", car.toString(), 834 car.getLocationName(), car.getTrackName())); 835 if (car.getTrack() == getDepartureStagingTrack()) { 836 throw new BuildFailedException(Bundle.getMessage("buildErrorLocationUnknown", car.getLocationName(), 837 car.getTrackName(), car.toString())); 838 } 839 remove(car); 840 continue; 841 } 842 // remove cars that are out of service 843 if (car.isOutOfService()) { 844 addLine(SEVEN, Bundle.getMessage("buildExcludeCarOutOfService", car.toString(), 845 car.getLocationName(), car.getTrackName())); 846 if (car.getTrack() == getDepartureStagingTrack()) { 847 throw new BuildFailedException( 848 Bundle.getMessage("buildErrorLocationOutOfService", car.getLocationName(), 849 car.getTrackName(), car.toString())); 850 } 851 remove(car); 852 continue; 853 } 854 // does car have a destination that is part of this train's route? 855 if (car.getDestination() != null) { 856 RouteLocation rld = getTrain().getRoute().getLastLocationByName(car.getDestinationName()); 857 if (rld == null) { 858 addLine(SEVEN, Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(), 859 car.getDestinationName(), car.getDestinationTrackName(), getTrain().getRoute().getName())); 860 // Code check, programming ERROR if car departing staging 861 if (car.getLocation() == getDepartureLocation() && getDepartureStagingTrack() != null) { 862 throw new BuildFailedException(Bundle.getMessage("buildErrorCarNotPartRoute", car.toString())); 863 } 864 remove(car); // remove this car from the list 865 continue; 866 } 867 } 868 // remove cars with FRED that have a destination that isn't the 869 // terminal 870 if (car.hasFred() && car.getDestination() != null && car.getDestination() != getTerminateLocation()) { 871 addLine(FIVE, 872 Bundle.getMessage("buildExcludeCarWrongDest", car.toString(), car.getTypeName(), 873 car.getTypeExtensions(), car.getDestinationName())); 874 remove(car); 875 continue; 876 } 877 878 // remove cabooses that have a destination that isn't the terminal, 879 // and no caboose changes in the train's route 880 if (car.isCaboose() && 881 car.getDestination() != null && 882 car.getDestination() != getTerminateLocation() && 883 (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE + Train.REMOVE_CABOOSE) == 0 && 884 (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE + Train.REMOVE_CABOOSE) == 0) { 885 addLine(FIVE, 886 Bundle.getMessage("buildExcludeCarWrongDest", car.toString(), car.getTypeName(), 887 car.getTypeExtensions(), car.getDestinationName())); 888 remove(car); 889 continue; 890 } 891 892 // is car at interchange or spur and is this train allowed to pull? 893 if (!checkPickupInterchangeOrSpur(car)) { 894 remove(car); 895 continue; 896 } 897 898 // is car at interchange with destination restrictions? 899 if (!checkPickupInterchangeDestinationRestrictions(car)) { 900 remove(car); 901 continue; 902 } 903 // note that for trains departing staging the engine and car roads, 904 // types, owners, and built date were already checked. 905 906 if (!car.isCaboose() && !getTrain().isCarRoadNameAccepted(car.getRoadName()) || 907 car.isCaboose() && !getTrain().isCabooseRoadNameAccepted(car.getRoadName())) { 908 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(), 909 car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getTypeExtensions(), 910 car.getRoadName())); 911 remove(car); 912 continue; 913 } 914 if (!getTrain().isTypeNameAccepted(car.getTypeName())) { 915 // only show lead cars when excluding car type 916 if (showCar && (car.getKernel() == null || car.isLead())) { 917 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongType", car.toString(), 918 car.getLocationName(), car.getTrackName(), car.getTypeName())); 919 } 920 remove(car); 921 continue; 922 } 923 if (!getTrain().isOwnerNameAccepted(car.getOwnerName())) { 924 addLine(SEVEN, 925 Bundle.getMessage("buildExcludeCarOwnerAtLoc", car.toString(), car.getOwnerName(), 926 car.getLocationName(), car.getTrackName())); 927 remove(car); 928 continue; 929 } 930 if (!getTrain().isBuiltDateAccepted(car.getBuilt())) { 931 addLine(SEVEN, 932 Bundle.getMessage("buildExcludeCarBuiltAtLoc", car.toString(), car.getBuilt(), 933 car.getLocationName(), car.getTrackName())); 934 remove(car); 935 continue; 936 } 937 938 // all cars in staging must be accepted, so don't exclude if in 939 // staging 940 // note that a car's load can change when departing staging 941 // a car's wait value is ignored when departing staging 942 // a car's pick up day is ignored when departing staging 943 if (getDepartureStagingTrack() == null || car.getTrack() != getDepartureStagingTrack()) { 944 if (!car.isCaboose() && 945 !car.isPassenger() && 946 !getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 947 addLine(SEVEN, Bundle.getMessage("buildExcludeCarLoadAtLoc", car.toString(), 948 car.getTypeName(), car.getLoadName())); 949 remove(car); 950 continue; 951 } 952 // remove cars with FRED if not needed by train 953 if (car.hasFred() && !getTrain().isFredNeeded()) { 954 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWithFredAtLoc", car.toString(), 955 car.getTypeName(), (car.getLocationName() + ", " + car.getTrackName()))); 956 remove(car); // remove this car from the list 957 continue; 958 } 959 // does the car have a pick up day? 960 if (!car.getPickupScheduleId().equals(Car.NONE)) { 961 if (trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY) || 962 car.getPickupScheduleId().equals(trainScheduleManager.getTrainScheduleActiveId())) { 963 car.setPickupScheduleId(Car.NONE); 964 } else { 965 TrainSchedule sch = trainScheduleManager.getScheduleById(car.getPickupScheduleId()); 966 if (sch != null) { 967 addLine(SEVEN, 968 Bundle.getMessage("buildExcludeCarSchedule", car.toString(), car.getTypeName(), 969 car.getLocationName(), car.getTrackName(), sch.getName())); 970 remove(car); 971 continue; 972 } 973 } 974 } 975 // does car have a wait count? 976 if (car.getWait() > 0) { 977 addLine(SEVEN, Bundle.getMessage("buildExcludeCarWait", car.toString(), 978 car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait())); 979 if (getTrain().isServiceable(car)) { 980 addLine(SEVEN, Bundle.getMessage("buildTrainCanServiceWait", getTrain().getName(), 981 car.toString(), car.getWait() - 1)); 982 car.setWait(car.getWait() - 1); // decrement wait count 983 // a car's load changes when the wait count reaches 0 984 String oldLoad = car.getLoadName(); 985 if (car.getTrack().isSpur()) { 986 car.updateLoad(car.getTrack()); // has the wait 987 // count reached 0? 988 } 989 String newLoad = car.getLoadName(); 990 if (!oldLoad.equals(newLoad)) { 991 addLine(SEVEN, Bundle.getMessage("buildCarLoadChangedWait", car.toString(), 992 car.getTypeName(), oldLoad, newLoad)); 993 } 994 } 995 remove(car); 996 continue; 997 } 998 } 999 } 1000 } 1001 1002 /** 1003 * Adjust car list to only have cars from one staging track 1004 * 1005 * @throws BuildFailedException if all cars departing staging can't be used 1006 */ 1007 protected void adjustCarsInStaging() throws BuildFailedException { 1008 if (!getTrain().isDepartingStaging()) { 1009 return; // not departing staging 1010 } 1011 int numCarsFromStaging = 0; 1012 _numOfBlocks = new Hashtable<>(); 1013 addLine(SEVEN, BLANK_LINE); 1014 addLine(SEVEN, Bundle.getMessage("buildRemoveCarsStaging")); 1015 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 1016 Car car = getCarList().get(_carIndex); 1017 if (car.getLocation() == getDepartureLocation()) { 1018 if (car.getTrack() == getDepartureStagingTrack()) { 1019 numCarsFromStaging++; 1020 // populate car blocking hashtable 1021 // don't block cabooses, cars with FRED, or passenger. Only 1022 // block lead cars in 1023 // kernel 1024 if (!car.isCaboose() && 1025 !car.hasFred() && 1026 !car.isPassenger() && 1027 (car.getKernel() == null || car.isLead())) { 1028 log.debug("Car {} last location id: {}", car.toString(), car.getLastLocationId()); 1029 Integer number = 1; 1030 if (_numOfBlocks.containsKey(car.getLastLocationId())) { 1031 number = _numOfBlocks.get(car.getLastLocationId()) + 1; 1032 _numOfBlocks.remove(car.getLastLocationId()); 1033 } 1034 _numOfBlocks.put(car.getLastLocationId(), number); 1035 } 1036 } else { 1037 addLine(SEVEN, Bundle.getMessage("buildExcludeCarAtLoc", car.toString(), 1038 car.getTypeName(), car.getLocationName(), car.getTrackName())); 1039 remove(car); 1040 } 1041 } 1042 } 1043 // show how many cars are departing from staging 1044 addLine(FIVE, BLANK_LINE); 1045 addLine(FIVE, Bundle.getMessage("buildDepartingStagingCars", 1046 getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName(), 1047 numCarsFromStaging)); 1048 // and list them 1049 for (Car car : getCarList()) { 1050 if (car.getTrack() == getDepartureStagingTrack()) { 1051 addLine(SEVEN, Bundle.getMessage("buildStagingCarAtLoc", car.toString(), 1052 car.getTypeName(), car.getLoadType().toLowerCase(), car.getLoadName())); 1053 } 1054 } 1055 // error if all of the cars from staging aren't available 1056 if (!Setup.isBuildOnTime() && numCarsFromStaging != getDepartureStagingTrack().getNumberCars()) { 1057 throw new BuildFailedException( 1058 Bundle.getMessage("buildErrorNotAllCars", getDepartureStagingTrack().getName(), 1059 Integer.toString(getDepartureStagingTrack().getNumberCars() - numCarsFromStaging))); 1060 } 1061 log.debug("Staging departure track ({}) has {} cars and {} blocks", getDepartureStagingTrack().getName(), 1062 numCarsFromStaging, _numOfBlocks.size()); // NOI18N 1063 } 1064 1065 /** 1066 * List available cars by location. Removes non-lead kernel cars from the 1067 * car list. 1068 * 1069 * @throws BuildFailedException if kernel doesn't have lead or cars aren't 1070 * on the same track. 1071 */ 1072 protected void showCarsByLocation() throws BuildFailedException { 1073 // show how many cars were found 1074 addLine(FIVE, BLANK_LINE); 1075 addLine(ONE, 1076 Bundle.getMessage("buildFoundCars", Integer.toString(getCarList().size()), getTrain().getName())); 1077 // only show cars once using the train's route 1078 List<String> locationNames = new ArrayList<>(); 1079 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 1080 if (locationNames.contains(rl.getName())) { 1081 continue; 1082 } 1083 locationNames.add(rl.getName()); 1084 int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getCarList())); 1085 if (rl.getLocation().isStaging()) { 1086 addLine(FIVE, 1087 Bundle.getMessage("buildCarsInStaging", count, rl.getName())); 1088 } else { 1089 addLine(FIVE, 1090 Bundle.getMessage("buildCarsAtLocation", count, rl.getName())); 1091 } 1092 // now go through the car list and remove non-lead cars in kernels, 1093 // destinations 1094 // that aren't part of this route 1095 int carCount = 0; 1096 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 1097 Car car = getCarList().get(_carIndex); 1098 if (!car.getLocationName().equals(rl.getName())) { 1099 continue; 1100 } 1101 // only print out the first DISPLAY_CAR_LIMIT cars for each 1102 // location 1103 if (carCount < DISPLAY_CAR_LIMIT_50 && (car.getKernel() == null || car.isLead())) { 1104 if (car.getLoadPriority().equals(CarLoad.PRIORITY_LOW) && 1105 car.getTrack().getTrackPriority().equals(Track.PRIORITY_NORMAL)) { 1106 addLine(SEVEN, 1107 Bundle.getMessage("buildCarAtLocWithMoves", car.toString(), car.getTypeName(), 1108 car.getTypeExtensions(), car.getLocationName(), car.getTrackName(), 1109 car.getMoves())); 1110 } else { 1111 addLine(SEVEN, 1112 Bundle.getMessage("buildCarAtLocWithMovesPriority", car.toString(), car.getTypeName(), 1113 car.getTypeExtensions(), car.getLocationName(), car.getTrackName(), 1114 car.getTrack().getTrackPriority(), car.getMoves(), 1115 car.getLoadType().toLowerCase(), car.getLoadName(), 1116 car.getLoadPriority())); 1117 } 1118 if (car.isLead()) { 1119 addLine(SEVEN, 1120 Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(), 1121 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1122 Setup.getLengthUnit().toLowerCase())); 1123 // list all of the cars in the kernel now 1124 for (Car k : car.getKernel().getCars()) { 1125 if (!k.isLead()) { 1126 addLine(SEVEN, 1127 Bundle.getMessage("buildCarPartOfKernel", k.toString(), k.getKernelName(), 1128 k.getKernel().getSize(), k.getKernel().getTotalLength(), 1129 Setup.getLengthUnit().toLowerCase())); 1130 } 1131 } 1132 } 1133 carCount++; 1134 if (carCount == DISPLAY_CAR_LIMIT_50) { 1135 addLine(SEVEN, 1136 Bundle.getMessage("buildOnlyFirstXXXCars", carCount, rl.getName())); 1137 } 1138 } 1139 // report car in kernel but lead has been removed 1140 if (car.getKernel() != null && !getCarList().contains(car.getKernel().getLead())) { 1141 addLine(SEVEN, 1142 Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), 1143 car.getKernel().getSize(), car.getKernel().getTotalLength(), 1144 Setup.getLengthUnit().toLowerCase())); 1145 } 1146 // use only the lead car in a kernel for building trains 1147 if (car.getKernel() != null) { 1148 checkKernel(car); // kernel needs lead car and all cars on 1149 // the same track 1150 if (!car.isLead()) { 1151 remove(car); // remove this car from the list 1152 continue; 1153 } 1154 } 1155 if (getTrain().equals(car.getTrain())) { 1156 addLine(FIVE, Bundle.getMessage("buildCarAlreadyAssigned", car.toString())); 1157 } 1158 } 1159 addLine(SEVEN, BLANK_LINE); 1160 } 1161 } 1162 1163 protected void sortCarsOnFifoLifoTracks() { 1164 addLine(SEVEN, Bundle.getMessage("buildSortCarsByLastDate")); 1165 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 1166 Car car = getCarList().get(_carIndex); 1167 if (car.getTrack().getServiceOrder().equals(Track.NORMAL) || car.getTrack().isStaging()) { 1168 continue; 1169 } 1170 addLine(SEVEN, 1171 Bundle.getMessage("buildTrackModePriority", car.toString(), car.getTrack().getTrackTypeName(), 1172 car.getLocationName(), car.getTrackName(), car.getTrack().getServiceOrder(), 1173 car.getLastDate())); 1174 Car bestCar = car; 1175 for (int i = _carIndex + 1; i < getCarList().size(); i++) { 1176 Car testCar = getCarList().get(i); 1177 if (testCar.getTrack() == car.getTrack() && 1178 bestCar.getLoadPriority().equals(testCar.getLoadPriority())) { 1179 log.debug("{} car ({}) last moved date: {}", car.getTrack().getTrackTypeName(), testCar.toString(), 1180 testCar.getLastDate()); // NOI18N 1181 if (car.getTrack().getServiceOrder().equals(Track.FIFO)) { 1182 if (bestCar.getLastMoveDate().after(testCar.getLastMoveDate())) { 1183 bestCar = testCar; 1184 log.debug("New best car ({})", bestCar.toString()); 1185 } 1186 } else if (car.getTrack().getServiceOrder().equals(Track.LIFO)) { 1187 if (bestCar.getLastMoveDate().before(testCar.getLastMoveDate())) { 1188 bestCar = testCar; 1189 log.debug("New best car ({})", bestCar.toString()); 1190 } 1191 } 1192 } 1193 } 1194 if (car != bestCar) { 1195 addLine(SEVEN, 1196 Bundle.getMessage("buildTrackModeCarPriority", car.getTrack().getTrackTypeName(), 1197 car.getTrackName(), car.getTrack().getServiceOrder(), bestCar.toString(), 1198 bestCar.getLastDate(), car.toString(), car.getLastDate())); 1199 getCarList().remove(bestCar); // change sort 1200 getCarList().add(_carIndex, bestCar); 1201 } 1202 } 1203 addLine(SEVEN, BLANK_LINE); 1204 } 1205 1206 /** 1207 * Verifies that all cars in the kernel have the same departure track. Also 1208 * checks to see if the kernel has a lead car and the lead car is in 1209 * service. 1210 * 1211 * @throws BuildFailedException 1212 */ 1213 private void checkKernel(Car car) throws BuildFailedException { 1214 boolean foundLeadCar = false; 1215 for (Car c : car.getKernel().getCars()) { 1216 // check that lead car exists 1217 if (c.isLead() && !c.isOutOfService()) { 1218 foundLeadCar = true; 1219 } 1220 // check to see that all cars have the same location and track 1221 if (car.getLocation() != c.getLocation() || 1222 c.getTrack() == null || 1223 !car.getTrack().getSplitName().equals(c.getTrack().getSplitName())) { 1224 throw new BuildFailedException(Bundle.getMessage("buildErrorCarKernelLocation", c.toString(), 1225 car.getKernelName(), c.getLocationName(), c.getTrackName(), car.toString(), 1226 car.getLocationName(), car.getTrackName())); 1227 } 1228 } 1229 // code check, all kernels should have a lead car 1230 if (foundLeadCar == false) { 1231 throw new BuildFailedException(Bundle.getMessage("buildErrorCarKernelNoLead", car.getKernelName())); 1232 } 1233 } 1234 1235 /* 1236 * For blocking cars out of staging 1237 */ 1238 protected String getLargestBlock() { 1239 Enumeration<String> en = _numOfBlocks.keys(); 1240 String largestBlock = ""; 1241 int maxCars = 0; 1242 while (en.hasMoreElements()) { 1243 String locId = en.nextElement(); 1244 if (_numOfBlocks.get(locId) > maxCars) { 1245 largestBlock = locId; 1246 maxCars = _numOfBlocks.get(locId); 1247 } 1248 } 1249 return largestBlock; 1250 } 1251 1252 /** 1253 * Returns the routeLocation with the most available moves. Used for 1254 * blocking a train out of staging. 1255 * 1256 * @param blockRouteList The route for this train, modified by deleting 1257 * RouteLocations serviced 1258 * @param blockId Where these cars were originally picked up from. 1259 * @return The location in the route with the most available moves. 1260 */ 1261 protected RouteLocation getLocationWithMaximumMoves(List<RouteLocation> blockRouteList, String blockId) { 1262 RouteLocation rlMax = null; 1263 int maxMoves = 0; 1264 for (RouteLocation rl : blockRouteList) { 1265 if (rl == getTrain().getTrainDepartsRouteLocation()) { 1266 continue; 1267 } 1268 if (rl.getMaxCarMoves() - rl.getCarMoves() > maxMoves) { 1269 maxMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 1270 rlMax = rl; 1271 } 1272 // if two locations have the same number of moves, return the one 1273 // that doesn't match the block id 1274 if (rl.getMaxCarMoves() - rl.getCarMoves() == maxMoves && !rl.getLocation().getId().equals(blockId)) { 1275 rlMax = rl; 1276 } 1277 } 1278 return rlMax; 1279 } 1280 1281 /** 1282 * Temporally remove cars from staging track if train returning to the same 1283 * staging track to free up track space. 1284 */ 1285 protected void makeAdjustmentsIfDepartingStaging() { 1286 if (getTrain().isDepartingStaging()) { 1287 _reqNumOfMoves = 0; 1288 // Move cars out of staging after working other locations 1289 // if leaving and returning to staging on the same track, temporary pull cars off the track 1290 if (getDepartureStagingTrack() == getTerminateStagingTrack()) { 1291 if (!getTrain().isAllowReturnToStagingEnabled() && !Setup.isStagingAllowReturnEnabled()) { 1292 // takes care of cars in a kernel by getting all cars 1293 for (Car car : carManager.getList()) { 1294 // don't remove caboose or car with FRED already 1295 // assigned to train 1296 if (car.getTrack() == getDepartureStagingTrack() && car.getRouteDestination() == null) { 1297 car.setLocation(car.getLocation(), null); 1298 } 1299 } 1300 } else { 1301 // since all cars can return to staging, the track space is 1302 // consumed for now 1303 addLine(THREE, BLANK_LINE); 1304 addLine(THREE, Bundle.getMessage("buildWarnDepartStaging", 1305 getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName())); 1306 addLine(THREE, BLANK_LINE); 1307 } 1308 } 1309 addLine(THREE, 1310 Bundle.getMessage("buildDepartStagingAggressive", 1311 getDepartureStagingTrack().getLocation().getName())); 1312 } 1313 } 1314 1315 /** 1316 * Restores cars departing staging track assignment. 1317 */ 1318 protected void restoreCarsIfDepartingStaging() { 1319 if (getTrain().isDepartingStaging() && 1320 getDepartureStagingTrack() == getTerminateStagingTrack() && 1321 !getTrain().isAllowReturnToStagingEnabled() && 1322 !Setup.isStagingAllowReturnEnabled()) { 1323 // restore departure track for cars departing staging 1324 for (Car car : getCarList()) { 1325 if (car.getLocation() == getDepartureStagingTrack().getLocation() && car.getTrack() == null) { 1326 car.setLocation(getDepartureStagingTrack().getLocation(), getDepartureStagingTrack(), 1327 RollingStock.FORCE); // force 1328 if (car.getKernel() != null) { 1329 for (Car k : car.getKernel().getCars()) { 1330 k.setLocation(getDepartureStagingTrack().getLocation(), getDepartureStagingTrack(), 1331 RollingStock.FORCE); // force 1332 } 1333 } 1334 } 1335 } 1336 } 1337 } 1338 1339 protected void showLoadGenerationOptionsStaging() { 1340 if (getDepartureStagingTrack() != null && 1341 _reqNumOfMoves > 0 && 1342 (getDepartureStagingTrack().isAddCustomLoadsEnabled() || 1343 getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled() || 1344 getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled())) { 1345 addLine(FIVE, Bundle.getMessage("buildCustomLoadOptions", getDepartureStagingTrack().getName())); 1346 if (getDepartureStagingTrack().isAddCustomLoadsEnabled()) { 1347 addLine(FIVE, Bundle.getMessage("buildLoadCarLoads")); 1348 } 1349 if (getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled()) { 1350 addLine(FIVE, Bundle.getMessage("buildLoadAnyCarLoads")); 1351 } 1352 if (getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled()) { 1353 addLine(FIVE, Bundle.getMessage("buildLoadsStaging")); 1354 } 1355 addLine(FIVE, BLANK_LINE); 1356 } 1357 } 1358 1359 /** 1360 * Checks to see if all cars on a staging track have been given a 1361 * destination. Throws exception if there's a car without a destination. 1362 * 1363 * @throws BuildFailedException if car on staging track not assigned to 1364 * train 1365 */ 1366 protected void checkStuckCarsInStaging() throws BuildFailedException { 1367 if (!getTrain().isDepartingStaging()) { 1368 return; 1369 } 1370 int carCount = 0; 1371 StringBuffer buf = new StringBuffer(); 1372 // confirm that all cars in staging are departing 1373 for (Car car : getCarList()) { 1374 // build failure if car departing staging without a destination or 1375 // train 1376 if (car.getTrack() == getDepartureStagingTrack() && 1377 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 1378 if (car.getKernel() != null) { 1379 for (Car c : car.getKernel().getCars()) { 1380 carCount++; 1381 addCarToStuckStagingList(c, buf, carCount); 1382 } 1383 } else { 1384 carCount++; 1385 addCarToStuckStagingList(car, buf, carCount); 1386 } 1387 } 1388 } 1389 if (carCount > 0) { 1390 log.debug("{} cars stuck in staging", carCount); 1391 String msg = Bundle.getMessage("buildStagingCouldNotFindDest", carCount, 1392 getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName()); 1393 throw new BuildFailedException(msg + buf.toString(), BuildFailedException.STAGING); 1394 } 1395 } 1396 1397 /** 1398 * Creates a list of up to 20 cars stuck in staging. 1399 * 1400 * @param car The car to add to the list 1401 * @param buf StringBuffer 1402 * @param carCount how many cars in the list 1403 */ 1404 private void addCarToStuckStagingList(Car car, StringBuffer buf, int carCount) { 1405 if (carCount <= DISPLAY_CAR_LIMIT_20) { 1406 buf.append(NEW_LINE + " " + car.toString()); 1407 } else if (carCount == DISPLAY_CAR_LIMIT_20 + 1) { 1408 buf.append(NEW_LINE + 1409 Bundle.getMessage("buildOnlyFirstXXXCars", DISPLAY_CAR_LIMIT_20, 1410 getDepartureStagingTrack().getName())); 1411 } 1412 } 1413 1414 /** 1415 * Used to determine if a car on a staging track doesn't have a destination 1416 * or train 1417 * 1418 * @return true if at least one car doesn't have a destination or train. 1419 * false if all cars have a destination. 1420 */ 1421 protected boolean isCarStuckStaging() { 1422 if (getTrain().isDepartingStaging()) { 1423 // confirm that all cars in staging are departing 1424 for (Car car : getCarList()) { 1425 if (car.getTrack() == getDepartureStagingTrack() && 1426 (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) { 1427 return true; 1428 } 1429 } 1430 } 1431 return false; 1432 } 1433 1434 protected void finishAddRsToTrain(RollingStock rs, RouteLocation rl, RouteLocation rld, int length, 1435 int weightTons) { 1436 // notify that locations have been modified when build done 1437 // allows automation actions to run properly 1438 if (!_modifiedLocations.contains(rl.getLocation())) { 1439 _modifiedLocations.add(rl.getLocation()); 1440 } 1441 if (!_modifiedLocations.contains(rld.getLocation())) { 1442 _modifiedLocations.add(rld.getLocation()); 1443 } 1444 rs.setTrain(getTrain()); 1445 rs.setRouteLocation(rl); 1446 rs.setRouteDestination(rld); 1447 // now adjust train length and weight for each location that the rolling 1448 // stock is in the train 1449 boolean inTrain = false; 1450 for (RouteLocation routeLocation : getRouteList()) { 1451 if (rl == routeLocation) { 1452 inTrain = true; 1453 } 1454 if (rld == routeLocation) { 1455 break; // done 1456 } 1457 if (inTrain) { 1458 routeLocation.setTrainLength(routeLocation.getTrainLength() + length); 1459 routeLocation.setTrainWeight(routeLocation.getTrainWeight() + weightTons); 1460 } 1461 } 1462 } 1463 1464 /** 1465 * Determine if rolling stock can be picked up based on train direction at 1466 * the route location. 1467 * 1468 * @param rs The rolling stock 1469 * @param rl The rolling stock's route location 1470 * @throws BuildFailedException if coding issue 1471 * @return true if there isn't a problem 1472 */ 1473 protected boolean checkPickUpTrainDirection(RollingStock rs, RouteLocation rl) throws BuildFailedException { 1474 // Code Check, car or engine should have a track assignment 1475 if (rs.getTrack() == null) { 1476 throw new BuildFailedException( 1477 Bundle.getMessage("buildWarningRsNoTrack", rs.toString(), rs.getLocationName())); 1478 } 1479 // ignore local switcher direction 1480 if (getTrain().isLocalSwitcher()) { 1481 return true; 1482 } 1483 if ((rl.getTrainDirection() & 1484 rs.getLocation().getTrainDirections() & 1485 rs.getTrack().getTrainDirections()) != 0) { 1486 return true; 1487 } 1488 1489 // Only track direction can cause the following message. Location 1490 // direction has already been checked 1491 addLine(SEVEN, 1492 Bundle.getMessage("buildRsCanNotPickupUsingTrain", rs.toString(), rl.getTrainDirectionString(), 1493 rs.getTrackName(), rs.getLocationName(), rl.getId())); 1494 return false; 1495 } 1496 1497 /** 1498 * Used to report a problem picking up the rolling stock due to train 1499 * direction. 1500 * 1501 * @param rl The route location 1502 * @return true if there isn't a problem 1503 */ 1504 protected boolean checkPickUpTrainDirection(RouteLocation rl) { 1505 // ignore local switcher direction 1506 if (getTrain().isLocalSwitcher()) { 1507 return true; 1508 } 1509 if ((rl.getTrainDirection() & rl.getLocation().getTrainDirections()) != 0) { 1510 return true; 1511 } 1512 1513 addLine(ONE, Bundle.getMessage("buildLocDirection", rl.getName(), rl.getTrainDirectionString())); 1514 return false; 1515 } 1516 1517 /** 1518 * Determines if car can be pulled from an interchange or spur. Needed for 1519 * quick service tracks. 1520 * 1521 * @param car the car being pulled 1522 * @return true if car can be pulled, otherwise false. 1523 */ 1524 protected boolean checkPickupInterchangeOrSpur(Car car) { 1525 if (car.getTrack().isInterchange()) { 1526 // don't service a car at interchange and has been dropped off 1527 // by this train 1528 if (car.getTrack().getPickupOption().equals(Track.ANY) && 1529 car.getLastRouteId().equals(getTrain().getRoute().getId())) { 1530 addLine(SEVEN, Bundle.getMessage("buildExcludeCarDropByTrain", car.toString(), 1531 car.getTypeName(), getTrain().getRoute().getName(), car.getLocationName(), car.getTrackName())); 1532 return false; 1533 } 1534 } 1535 // is car at interchange or spur and is this train allowed to pull? 1536 if (car.getTrack().isInterchange() || car.getTrack().isSpur()) { 1537 if (car.getTrack().getPickupOption().equals(Track.TRAINS) || 1538 car.getTrack().getPickupOption().equals(Track.EXCLUDE_TRAINS)) { 1539 if (car.getTrack().isPickupTrainAccepted(getTrain())) { 1540 log.debug("Car ({}) can be picked up by this train", car.toString()); 1541 } else { 1542 addLine(SEVEN, 1543 Bundle.getMessage("buildExcludeCarByTrain", car.toString(), car.getTypeName(), 1544 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName())); 1545 return false; 1546 } 1547 } else if (car.getTrack().getPickupOption().equals(Track.ROUTES) || 1548 car.getTrack().getPickupOption().equals(Track.EXCLUDE_ROUTES)) { 1549 if (car.getTrack().isPickupRouteAccepted(getTrain().getRoute())) { 1550 log.debug("Car ({}) can be picked up by this route", car.toString()); 1551 } else { 1552 addLine(SEVEN, 1553 Bundle.getMessage("buildExcludeCarByRoute", car.toString(), car.getTypeName(), 1554 car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName())); 1555 return false; 1556 } 1557 } 1558 } 1559 return true; 1560 } 1561 1562 /** 1563 * Checks to see if an interchange track has destination restrictions. 1564 * Returns true if there's at least one destination in the train's route 1565 * that can service the car departing the interchange. 1566 * 1567 * @param car the car being evaluated 1568 * @return true if car can be pulled 1569 */ 1570 protected boolean checkPickupInterchangeDestinationRestrictions(Car car) { 1571 if (!car.getTrack().isInterchange() || 1572 car.getTrack().getDestinationOption().equals(Track.ALL_DESTINATIONS) || 1573 car.getFinalDestination() != null) { 1574 return true; 1575 } 1576 for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) { 1577 if (car.getTrack().isDestinationAccepted(rl.getLocation())) { 1578 return true; 1579 } 1580 } 1581 addLine(SEVEN, Bundle.getMessage("buildExcludeCarByInterchange", car.toString(), 1582 car.getTypeName(), car.getTrackType(), car.getLocationName(), car.getTrackName())); 1583 return false; 1584 } 1585 1586 /** 1587 * Checks to see if train length would be exceeded if this car was added to 1588 * the train. 1589 * 1590 * @param car the car in question 1591 * @param rl the departure route location for this car 1592 * @param rld the destination route location for this car 1593 * @return true if car can be added to train 1594 */ 1595 protected boolean checkTrainLength(Car car, RouteLocation rl, RouteLocation rld) { 1596 // car can be a kernel so get total length 1597 int length = car.getTotalKernelLength(); 1598 boolean carInTrain = false; 1599 for (RouteLocation rlt : getRouteList()) { 1600 if (rl == rlt) { 1601 carInTrain = true; 1602 } 1603 if (rld == rlt) { 1604 break; 1605 } 1606 if (carInTrain && rlt.getTrainLength() + length > rlt.getMaxTrainLength()) { 1607 addLine(FIVE, 1608 Bundle.getMessage("buildCanNotPickupCarLength", car.toString(), length, 1609 Setup.getLengthUnit().toLowerCase(), rlt.getMaxTrainLength(), 1610 Setup.getLengthUnit().toLowerCase(), 1611 rlt.getTrainLength() + length - rlt.getMaxTrainLength(), rlt.getName(), rlt.getId())); 1612 return false; 1613 } 1614 } 1615 return true; 1616 } 1617 1618 protected boolean checkDropTrainDirection(RollingStock rs, RouteLocation rld, Track track) { 1619 // local? 1620 if (getTrain().isLocalSwitcher()) { 1621 return true; 1622 } 1623 // this location only services trains with these directions 1624 int serviceTrainDir = rld.getLocation().getTrainDirections(); 1625 if (track != null) { 1626 serviceTrainDir = serviceTrainDir & track.getTrainDirections(); 1627 } 1628 1629 // is this a car going to alternate track? Check to see if direct move 1630 // from alternate to FD track is possible 1631 if ((rld.getTrainDirection() & serviceTrainDir) != 0 && 1632 rs != null && 1633 track != null && 1634 Car.class.isInstance(rs)) { 1635 Car car = (Car) rs; 1636 if (car.getFinalDestinationTrack() != null && 1637 track == car.getFinalDestinationTrack().getAlternateTrack() && 1638 (track.getTrainDirections() & car.getFinalDestinationTrack().getTrainDirections()) == 0) { 1639 addLine(SEVEN, 1640 Bundle.getMessage("buildCanNotDropRsUsingTrain4", car.getFinalDestinationTrack().getName(), 1641 formatStringToCommaSeparated( 1642 Setup.getDirectionStrings(car.getFinalDestinationTrack().getTrainDirections())), 1643 car.getFinalDestinationTrack().getAlternateTrack().getName(), 1644 formatStringToCommaSeparated(Setup.getDirectionStrings( 1645 car.getFinalDestinationTrack().getAlternateTrack().getTrainDirections())))); 1646 return false; 1647 } 1648 } 1649 1650 if ((rld.getTrainDirection() & serviceTrainDir) != 0) { 1651 return true; 1652 } 1653 if (rs == null || track == null) { 1654 addLine(SEVEN, 1655 Bundle.getMessage("buildDestinationDoesNotService", rld.getName(), rld.getTrainDirectionString())); 1656 } else { 1657 addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain", rs.toString(), 1658 rld.getTrainDirectionString(), track.getName())); 1659 } 1660 return false; 1661 } 1662 1663 protected boolean checkDropTrainDirection(RouteLocation rld) { 1664 return (checkDropTrainDirection(null, rld, null)); 1665 } 1666 1667 /** 1668 * Determinate if rolling stock can be dropped by this train to the track 1669 * specified. 1670 * 1671 * @param rs the rolling stock to be set out. 1672 * @param track the destination track. 1673 * @return true if able to drop. 1674 */ 1675 protected boolean checkTrainCanDrop(RollingStock rs, Track track) { 1676 if (track.isInterchange() || track.isSpur()) { 1677 if (track.getDropOption().equals(Track.TRAINS) || track.getDropOption().equals(Track.EXCLUDE_TRAINS)) { 1678 if (track.isDropTrainAccepted(getTrain())) { 1679 log.debug("Rolling stock ({}) can be droped by train to track ({})", rs.toString(), 1680 track.getName()); 1681 } else { 1682 addLine(SEVEN, 1683 Bundle.getMessage("buildCanNotDropTrain", rs.toString(), getTrain().getName(), 1684 track.getTrackTypeName(), track.getLocation().getName(), track.getName())); 1685 return false; 1686 } 1687 } 1688 if (track.getDropOption().equals(Track.ROUTES) || track.getDropOption().equals(Track.EXCLUDE_ROUTES)) { 1689 if (track.isDropRouteAccepted(getTrain().getRoute())) { 1690 log.debug("Rolling stock ({}) can be droped by route to track ({})", rs.toString(), 1691 track.getName()); 1692 } else { 1693 addLine(SEVEN, 1694 Bundle.getMessage("buildCanNotDropRoute", rs.toString(), getTrain().getRoute().getName(), 1695 track.getTrackTypeName(), track.getLocation().getName(), track.getName())); 1696 return false; 1697 } 1698 } 1699 } 1700 return true; 1701 } 1702 1703 /** 1704 * Check departure staging track to see if engines and cars are available to 1705 * a new train. Also confirms that the engine and car type, load, road, etc. 1706 * are accepted by the train. 1707 * 1708 * @param departStageTrack The staging track 1709 * @return true is there are engines and cars available. 1710 */ 1711 protected boolean checkDepartureStagingTrack(Track departStageTrack) { 1712 addLine(THREE, 1713 Bundle.getMessage("buildStagingHas", departStageTrack.getName(), 1714 Integer.toString(departStageTrack.getNumberEngines()), 1715 Integer.toString(departStageTrack.getNumberCars()))); 1716 // does this staging track service this train? 1717 if (!departStageTrack.isPickupTrainAccepted(getTrain())) { 1718 addLine(THREE, Bundle.getMessage("buildStagingNotTrain", departStageTrack.getName())); 1719 return false; 1720 } 1721 if (departStageTrack.getNumberRS() == 0 && getTrain().getTrainDepartsRouteLocation().getMaxCarMoves() > 0) { 1722 addLine(THREE, Bundle.getMessage("buildStagingEmpty", departStageTrack.getName())); 1723 return false; 1724 } 1725 if (departStageTrack.getUsedLength() > getTrain().getTrainDepartsRouteLocation().getMaxTrainLength()) { 1726 addLine(THREE, 1727 Bundle.getMessage("buildStagingTrainTooLong", departStageTrack.getName(), 1728 departStageTrack.getUsedLength(), Setup.getLengthUnit().toLowerCase(), 1729 getTrain().getTrainDepartsRouteLocation().getMaxTrainLength())); 1730 return false; 1731 } 1732 if (departStageTrack.getNumberCars() > getTrain().getTrainDepartsRouteLocation().getMaxCarMoves()) { 1733 addLine(THREE, Bundle.getMessage("buildStagingTooManyCars", departStageTrack.getName(), 1734 departStageTrack.getNumberCars(), getTrain().getTrainDepartsRouteLocation().getMaxCarMoves())); 1735 return false; 1736 } 1737 // does the staging track have the right number of locomotives? 1738 if (!getTrain().getNumberEngines().equals("0") && 1739 getNumberEngines(getTrain().getNumberEngines()) != departStageTrack.getNumberEngines()) { 1740 addLine(THREE, Bundle.getMessage("buildStagingNotEngines", departStageTrack.getName(), 1741 departStageTrack.getNumberEngines(), getTrain().getNumberEngines())); 1742 return false; 1743 } 1744 // is the staging track direction correct for this train? 1745 if ((departStageTrack.getTrainDirections() & 1746 getTrain().getTrainDepartsRouteLocation().getTrainDirection()) == 0) { 1747 addLine(THREE, Bundle.getMessage("buildStagingNotDirection", departStageTrack.getName())); 1748 return false; 1749 } 1750 1751 // check engines on staging track 1752 if (!checkStagingEngines(departStageTrack)) { 1753 return false; 1754 } 1755 1756 // check for car road, load, owner, built, Caboose or FRED needed 1757 if (!checkStagingCarTypeRoadLoadOwnerBuiltCabooseOrFRED(departStageTrack)) { 1758 return false; 1759 } 1760 1761 // determine if staging track is in a pool (multiple trains on one 1762 // staging track) 1763 if (!checkStagingPool(departStageTrack)) { 1764 return false; 1765 } 1766 addLine(FIVE, 1767 Bundle.getMessage("buildTrainCanDepartTrack", getTrain().getName(), departStageTrack.getName())); 1768 return true; 1769 } 1770 1771 /** 1772 * Used to determine if engines on staging track are acceptable to the train 1773 * being built. 1774 * 1775 * @param departStageTrack Depart staging track 1776 * @return true if engines on staging track meet train requirement 1777 */ 1778 private boolean checkStagingEngines(Track departStageTrack) { 1779 if (departStageTrack.getNumberEngines() > 0) { 1780 for (Engine eng : engineManager.getList(departStageTrack)) { 1781 // clones are are already assigned to a train 1782 if (eng.isClone()) { 1783 continue; 1784 } 1785 // has engine been assigned to another train? 1786 if (eng.getRouteLocation() != null) { 1787 addLine(THREE, Bundle.getMessage("buildStagingDepart", departStageTrack.getName(), 1788 eng.getTrainName())); 1789 return false; 1790 } 1791 if (eng.getTrain() != null && eng.getTrain() != getTrain()) { 1792 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineTrain", 1793 departStageTrack.getName(), eng.toString(), eng.getTrainName())); 1794 return false; 1795 } 1796 // does the train accept the engine type from the staging 1797 // track? 1798 if (!getTrain().isTypeNameAccepted(eng.getTypeName())) { 1799 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineType", 1800 departStageTrack.getName(), eng.toString(), eng.getTypeName(), getTrain().getName())); 1801 return false; 1802 } 1803 // does the train accept the engine model from the staging 1804 // track? 1805 if (!getTrain().getEngineModel().equals(Train.NONE) && 1806 !getTrain().getEngineModel().equals(eng.getModel())) { 1807 addLine(THREE, 1808 Bundle.getMessage("buildStagingDepartEngineModel", departStageTrack.getName(), 1809 eng.toString(), eng.getModel(), getTrain().getName())); 1810 return false; 1811 } 1812 // does the engine road match the train requirements? 1813 if (!getTrain().getCarRoadOption().equals(Train.ALL_ROADS) && 1814 !getTrain().getEngineRoad().equals(Train.NONE) && 1815 !getTrain().getEngineRoad().equals(eng.getRoadName())) { 1816 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineRoad", 1817 departStageTrack.getName(), eng.toString(), eng.getRoadName(), getTrain().getName())); 1818 return false; 1819 } 1820 // does the train accept the engine road from the staging 1821 // track? 1822 if (getTrain().getEngineRoad().equals(Train.NONE) && 1823 !getTrain().isLocoRoadNameAccepted(eng.getRoadName())) { 1824 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineRoad", 1825 departStageTrack.getName(), eng.toString(), eng.getRoadName(), getTrain().getName())); 1826 return false; 1827 } 1828 // does the train accept the engine owner from the staging 1829 // track? 1830 if (!getTrain().isOwnerNameAccepted(eng.getOwnerName())) { 1831 addLine(THREE, Bundle.getMessage("buildStagingDepartEngineOwner", 1832 departStageTrack.getName(), eng.toString(), eng.getOwnerName(), getTrain().getName())); 1833 return false; 1834 } 1835 // does the train accept the engine built date from the 1836 // staging track? 1837 if (!getTrain().isBuiltDateAccepted(eng.getBuilt())) { 1838 addLine(THREE, 1839 Bundle.getMessage("buildStagingDepartEngineBuilt", departStageTrack.getName(), 1840 eng.toString(), eng.getBuilt(), getTrain().getName())); 1841 return false; 1842 } 1843 } 1844 } 1845 return true; 1846 } 1847 1848 /** 1849 * Checks to see if all cars in staging can be serviced by the train being 1850 * built. Also searches for caboose or car with FRED. 1851 * 1852 * @param departStageTrack Departure staging track 1853 * @return True if okay 1854 */ 1855 private boolean checkStagingCarTypeRoadLoadOwnerBuiltCabooseOrFRED(Track departStageTrack) { 1856 boolean foundCaboose = false; 1857 boolean foundFRED = false; 1858 if (departStageTrack.getNumberCars() > 0) { 1859 for (Car car : carManager.getList(departStageTrack)) { 1860 // clones are are already assigned to a train 1861 if (car.isClone()) { 1862 continue; 1863 } 1864 // ignore non-lead cars in kernels 1865 if (car.getKernel() != null && !car.isLead()) { 1866 continue; // ignore non-lead cars 1867 } 1868 // has car been assigned to another train? 1869 if (car.getRouteLocation() != null) { 1870 log.debug("Car ({}) has route location ({})", car.toString(), car.getRouteLocation().getName()); 1871 addLine(THREE, 1872 Bundle.getMessage("buildStagingDepart", departStageTrack.getName(), car.getTrainName())); 1873 return false; 1874 } 1875 if (car.getTrain() != null && car.getTrain() != getTrain()) { 1876 addLine(THREE, Bundle.getMessage("buildStagingDepartCarTrain", 1877 departStageTrack.getName(), car.toString(), car.getTrainName())); 1878 return false; 1879 } 1880 // does the train accept the car type from the staging track? 1881 if (!getTrain().isTypeNameAccepted(car.getTypeName())) { 1882 addLine(THREE, 1883 Bundle.getMessage("buildStagingDepartCarType", departStageTrack.getName(), car.toString(), 1884 car.getTypeName(), getTrain().getName())); 1885 return false; 1886 } 1887 // does the train accept the car road from the staging track? 1888 if (!car.isCaboose() && !getTrain().isCarRoadNameAccepted(car.getRoadName())) { 1889 addLine(THREE, 1890 Bundle.getMessage("buildStagingDepartCarRoad", departStageTrack.getName(), car.toString(), 1891 car.getRoadName(), getTrain().getName())); 1892 return false; 1893 } 1894 // does the train accept the car load from the staging track? 1895 if (!car.isCaboose() && 1896 !car.isPassenger() && 1897 (!car.getLoadName().equals(carLoads.getDefaultEmptyName()) || 1898 !departStageTrack.isAddCustomLoadsEnabled() && 1899 !departStageTrack.isAddCustomLoadsAnySpurEnabled() && 1900 !departStageTrack.isAddCustomLoadsAnyStagingTrackEnabled()) && 1901 !getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) { 1902 addLine(THREE, 1903 Bundle.getMessage("buildStagingDepartCarLoad", departStageTrack.getName(), car.toString(), 1904 car.getLoadName(), getTrain().getName())); 1905 return false; 1906 } 1907 // does the train accept the car owner from the staging track? 1908 if (!getTrain().isOwnerNameAccepted(car.getOwnerName())) { 1909 addLine(THREE, Bundle.getMessage("buildStagingDepartCarOwner", 1910 departStageTrack.getName(), car.toString(), car.getOwnerName(), getTrain().getName())); 1911 return false; 1912 } 1913 // does the train accept the car built date from the staging 1914 // track? 1915 if (!getTrain().isBuiltDateAccepted(car.getBuilt())) { 1916 addLine(THREE, Bundle.getMessage("buildStagingDepartCarBuilt", 1917 departStageTrack.getName(), car.toString(), car.getBuilt(), getTrain().getName())); 1918 return false; 1919 } 1920 // does the car have a destination serviced by this train? 1921 if (car.getDestination() != null) { 1922 log.debug("Car ({}) has a destination ({}, {})", car.toString(), car.getDestinationName(), 1923 car.getDestinationTrackName()); 1924 if (!getTrain().isServiceable(car)) { 1925 addLine(THREE, 1926 Bundle.getMessage("buildStagingDepartCarDestination", departStageTrack.getName(), 1927 car.toString(), car.getDestinationName(), getTrain().getName())); 1928 return false; 1929 } 1930 } 1931 // is this car a caboose with the correct road for this train? 1932 if (car.isCaboose() && 1933 (getTrain().getCabooseRoad().equals(Train.NONE) || 1934 getTrain().getCabooseRoad().equals(car.getRoadName()))) { 1935 foundCaboose = true; 1936 } 1937 // is this car have a FRED with the correct road for this train? 1938 if (car.hasFred() && 1939 (getTrain().getCabooseRoad().equals(Train.NONE) || 1940 getTrain().getCabooseRoad().equals(car.getRoadName()))) { 1941 foundFRED = true; 1942 } 1943 } 1944 } 1945 // does the train require a caboose and did we find one from staging? 1946 if (getTrain().isCabooseNeeded() && !foundCaboose) { 1947 addLine(THREE, 1948 Bundle.getMessage("buildStagingNoCaboose", departStageTrack.getName(), 1949 getTrain().getCabooseRoad())); 1950 return false; 1951 } 1952 // does the train require a car with FRED and did we find one from 1953 // staging? 1954 if (getTrain().isFredNeeded() && !foundFRED) { 1955 addLine(THREE, 1956 Bundle.getMessage("buildStagingNoCarFRED", departStageTrack.getName(), 1957 getTrain().getCabooseRoad())); 1958 return false; 1959 } 1960 return true; 1961 } 1962 1963 /** 1964 * Used to determine if staging track in a pool is the appropriated one for 1965 * departure. Staging tracks in a pool can operate in one of two ways FIFO 1966 * or LIFO. In FIFO mode (First in First out), the program selects a staging 1967 * track from the pool that has cars with the earliest arrival date. In LIFO 1968 * mode (Last in First out), the program selects a staging track from the 1969 * pool that has cars with the latest arrival date. 1970 * 1971 * @param departStageTrack the track being tested 1972 * @return true if departure on this staging track is possible 1973 */ 1974 private boolean checkStagingPool(Track departStageTrack) { 1975 if (departStageTrack.getPool() == null || 1976 departStageTrack.getServiceOrder().equals(Track.NORMAL) || 1977 departStageTrack.getNumberCars() == 0) { 1978 return true; 1979 } 1980 1981 addLine(SEVEN, Bundle.getMessage("buildStagingTrackPool", departStageTrack.getName(), 1982 departStageTrack.getPool().getName(), departStageTrack.getPool().getSize(), 1983 departStageTrack.getServiceOrder())); 1984 1985 List<Car> carList = carManager.getAvailableTrainList(getTrain()); 1986 Date carDepartStageTrackDate = null; 1987 for (Car car : carList) { 1988 if (car.getTrack() == departStageTrack) { 1989 carDepartStageTrackDate = car.getLastMoveDate(); 1990 break; // use 1st car found 1991 } 1992 } 1993 // next check isn't really necessary, null is never returned 1994 if (carDepartStageTrackDate == null) { 1995 return true; // no cars with found date 1996 } 1997 1998 for (Track track : departStageTrack.getPool().getTracks()) { 1999 if (track == departStageTrack || track.getNumberCars() == 0) { 2000 continue; 2001 } 2002 // determine dates cars arrived into staging 2003 Date carOtherStageTrackDate = null; 2004 2005 for (Car car : carList) { 2006 if (car.getTrack() == track) { 2007 carOtherStageTrackDate = car.getLastMoveDate(); 2008 break; // use 1st car found 2009 } 2010 } 2011 if (carOtherStageTrackDate != null) { 2012 if (departStageTrack.getServiceOrder().equals(Track.LIFO)) { 2013 if (carDepartStageTrackDate.before(carOtherStageTrackDate)) { 2014 addLine(SEVEN, 2015 Bundle.getMessage("buildStagingCarsBefore", departStageTrack.getName(), 2016 track.getName())); 2017 return false; 2018 } 2019 } else { 2020 if (carOtherStageTrackDate.before(carDepartStageTrackDate)) { 2021 addLine(SEVEN, Bundle.getMessage("buildStagingCarsBefore", track.getName(), 2022 departStageTrack.getName())); 2023 return false; 2024 } 2025 } 2026 } 2027 } 2028 return true; 2029 } 2030 2031 /** 2032 * Checks to see if staging track can accept train. 2033 * 2034 * @param terminateStageTrack the staging track 2035 * @return true if staging track is empty, not reserved, and accepts car and 2036 * engine types, roads, and loads. 2037 */ 2038 protected boolean checkTerminateStagingTrack(Track terminateStageTrack) { 2039 if (!terminateStageTrack.isDropTrainAccepted(getTrain())) { 2040 addLine(FIVE, Bundle.getMessage("buildStagingNotTrain", terminateStageTrack.getName())); 2041 return false; 2042 } 2043 // In normal mode, find a completely empty track. In aggressive mode, a 2044 // track that scheduled to depart is okay 2045 if (((!Setup.isBuildAggressive() || 2046 !Setup.isStagingTrackImmediatelyAvail() || 2047 terminateStageTrack.isQuickServiceEnabled()) && 2048 terminateStageTrack.getNumberRS() != 0) || 2049 (terminateStageTrack.getNumberRS() != terminateStageTrack.getPickupRS()) && 2050 terminateStageTrack.getNumberRS() != 0) { 2051 addLine(FIVE, 2052 Bundle.getMessage("buildStagingTrackOccupied", terminateStageTrack.getName(), 2053 terminateStageTrack.getNumberEngines(), terminateStageTrack.getNumberCars())); 2054 if (terminateStageTrack.getIgnoreUsedLengthPercentage() == Track.IGNORE_0) { 2055 return false; 2056 } else { 2057 addLine(FIVE, 2058 Bundle.getMessage("buildTrackHasPlannedPickups", terminateStageTrack.getName(), 2059 terminateStageTrack.getIgnoreUsedLengthPercentage(), terminateStageTrack.getLength(), 2060 Setup.getLengthUnit().toLowerCase(), terminateStageTrack.getUsedLength(), 2061 terminateStageTrack.getReserved(), 2062 terminateStageTrack.getReservedLengthSetouts(), 2063 terminateStageTrack.getReservedLengthSetouts() - terminateStageTrack.getReserved(), 2064 terminateStageTrack.getAvailableTrackSpace())); 2065 } 2066 } 2067 if ((!Setup.isBuildOnTime() || !terminateStageTrack.isQuickServiceEnabled()) && 2068 terminateStageTrack.getDropRS() != 0) { 2069 addLine(FIVE, Bundle.getMessage("buildStagingTrackReserved", terminateStageTrack.getName(), 2070 terminateStageTrack.getDropRS())); 2071 return false; 2072 } 2073 if (terminateStageTrack.getPickupRS() > 0) { 2074 addLine(FIVE, Bundle.getMessage("buildStagingTrackDepart", terminateStageTrack.getName())); 2075 } 2076 // if track is setup to accept a specific train or route, then ignore 2077 // other track restrictions 2078 if (terminateStageTrack.getDropOption().equals(Track.TRAINS) || 2079 terminateStageTrack.getDropOption().equals(Track.ROUTES)) { 2080 addLine(SEVEN, 2081 Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), 2082 terminateStageTrack.getName())); 2083 return true; // train can drop to this track, ignore other track 2084 // restrictions 2085 } 2086 if (!Setup.isStagingTrainCheckEnabled()) { 2087 addLine(SEVEN, 2088 Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), 2089 terminateStageTrack.getName())); 2090 return true; 2091 } else if (!checkTerminateStagingTrackRestrictions(terminateStageTrack)) { 2092 addLine(SEVEN, 2093 Bundle.getMessage("buildStagingTrackRestriction", terminateStageTrack.getName(), 2094 getTrain().getName())); 2095 addLine(SEVEN, Bundle.getMessage("buildOptionRestrictStaging")); 2096 return false; 2097 } 2098 return true; 2099 } 2100 2101 private boolean checkTerminateStagingTrackRestrictions(Track terminateStageTrack) { 2102 // check go see if location/track will accept the train's car and engine 2103 // types 2104 for (String name : getTrain().getTypeNames()) { 2105 if (!getTerminateLocation().acceptsTypeName(name)) { 2106 addLine(FIVE, 2107 Bundle.getMessage("buildDestinationType", getTerminateLocation().getName(), name)); 2108 return false; 2109 } 2110 if (!terminateStageTrack.isTypeNameAccepted(name)) { 2111 addLine(FIVE, 2112 Bundle.getMessage("buildStagingTrackType", terminateStageTrack.getLocation().getName(), 2113 terminateStageTrack.getName(), name)); 2114 return false; 2115 } 2116 } 2117 // check go see if track will accept the train's car roads 2118 if (getTrain().getCarRoadOption().equals(Train.ALL_ROADS) && 2119 !terminateStageTrack.getRoadOption().equals(Track.ALL_ROADS)) { 2120 addLine(FIVE, Bundle.getMessage("buildStagingTrackAllRoads", terminateStageTrack.getName())); 2121 return false; 2122 } 2123 // now determine if roads accepted by train are also accepted by staging 2124 // track 2125 // TODO should we be checking caboose and loco road names? 2126 for (String road : InstanceManager.getDefault(CarRoads.class).getNames()) { 2127 if (getTrain().isCarRoadNameAccepted(road)) { 2128 if (!terminateStageTrack.isRoadNameAccepted(road)) { 2129 addLine(FIVE, 2130 Bundle.getMessage("buildStagingTrackRoad", terminateStageTrack.getLocation().getName(), 2131 terminateStageTrack.getName(), road)); 2132 return false; 2133 } 2134 } 2135 } 2136 2137 // determine if staging will accept loads carried by train 2138 if (getTrain().getLoadOption().equals(Train.ALL_LOADS) && 2139 !terminateStageTrack.getLoadOption().equals(Track.ALL_LOADS)) { 2140 addLine(FIVE, Bundle.getMessage("buildStagingTrackAllLoads", terminateStageTrack.getName())); 2141 return false; 2142 } 2143 // get all of the types and loads that a train can carry, and determine 2144 // if staging will accept 2145 for (String type : getTrain().getTypeNames()) { 2146 for (String load : carLoads.getNames(type)) { 2147 if (getTrain().isLoadNameAccepted(load, type)) { 2148 if (!terminateStageTrack.isLoadNameAndCarTypeAccepted(load, type)) { 2149 addLine(FIVE, 2150 Bundle.getMessage("buildStagingTrackLoad", terminateStageTrack.getLocation().getName(), 2151 terminateStageTrack.getName(), type + CarLoad.SPLIT_CHAR + load)); 2152 return false; 2153 } 2154 } 2155 } 2156 } 2157 addLine(SEVEN, 2158 Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), terminateStageTrack.getName())); 2159 return true; 2160 } 2161 2162 boolean routeToTrackFound; 2163 2164 protected boolean checkBasicMoves(Car car, Track track) { 2165 if (car.getTrack() == track) { 2166 return false; 2167 } 2168 // don't allow local move to track with a "similar" name 2169 if (car.getSplitLocationName().equals(track.getLocation().getSplitName()) && 2170 car.getSplitTrackName().equals(track.getSplitName())) { 2171 return false; 2172 } 2173 if (track.isStaging() && car.getLocation() == track.getLocation()) { 2174 return false; // don't use same staging location 2175 } 2176 // is the car's destination the terminal and is that allowed? 2177 if (!checkThroughCarsAllowed(car, track.getLocation().getName())) { 2178 return false; 2179 } 2180 if (!checkLocalMovesAllowed(car, track)) { 2181 return false; 2182 } 2183 return true; 2184 } 2185 2186 /** 2187 * Used when generating a car load from staging. 2188 * 2189 * @param car the car. 2190 * @param track the car's destination track that has the schedule. 2191 * @return ScheduleItem si if match found, null otherwise. 2192 * @throws BuildFailedException if schedule doesn't have any line items 2193 */ 2194 protected ScheduleItem getScheduleItem(Car car, Track track) throws BuildFailedException { 2195 if (track.getSchedule() == null) { 2196 return null; 2197 } 2198 if (!track.isTypeNameAccepted(car.getTypeName())) { 2199 log.debug("Track ({}) doesn't service car type ({})", track.getName(), car.getTypeName()); 2200 if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 2201 addLine(SEVEN, 2202 Bundle.getMessage("buildSpurNotThisType", track.getLocation().getName(), track.getName(), 2203 track.getScheduleName(), car.getTypeName())); 2204 } 2205 return null; 2206 } 2207 ScheduleItem si = null; 2208 if (track.getScheduleMode() == Track.SEQUENTIAL) { 2209 si = track.getCurrentScheduleItem(); 2210 // code check 2211 if (si == null) { 2212 throw new BuildFailedException(Bundle.getMessage("buildErrorNoScheduleItem", track.getScheduleItemId(), 2213 track.getScheduleName(), track.getName(), track.getLocation().getName())); 2214 } 2215 return checkScheduleItem(si, car, track); 2216 } 2217 log.debug("Track ({}) in match mode", track.getName()); 2218 // go through entire schedule looking for a match 2219 for (int i = 0; i < track.getSchedule().getSize(); i++) { 2220 si = track.getNextScheduleItem(); 2221 // code check 2222 if (si == null) { 2223 throw new BuildFailedException(Bundle.getMessage("buildErrorNoScheduleItem", track.getScheduleItemId(), 2224 track.getScheduleName(), track.getName(), track.getLocation().getName())); 2225 } 2226 si = checkScheduleItem(si, car, track); 2227 if (si != null) { 2228 break; 2229 } 2230 } 2231 return si; 2232 } 2233 2234 /** 2235 * Used when generating a car load from staging. Checks a schedule item to 2236 * see if the car type matches, and the train and track can service the 2237 * schedule item's load. This code doesn't check to see if the car's load 2238 * can be serviced by the schedule. Instead a schedule item is returned that 2239 * allows the program to assign a custom load to the car that matches a 2240 * schedule item. Therefore, schedule items that don't request a custom load 2241 * are ignored. 2242 * 2243 * @param si the schedule item 2244 * @param car the car to check 2245 * @param track the destination track 2246 * @return Schedule item si if okay, null otherwise. 2247 */ 2248 private ScheduleItem checkScheduleItem(ScheduleItem si, Car car, Track track) { 2249 if (!car.getTypeName().equals(si.getTypeName()) || 2250 si.getReceiveLoadName().equals(ScheduleItem.NONE) || 2251 si.getReceiveLoadName().equals(carLoads.getDefaultEmptyName()) || 2252 si.getReceiveLoadName().equals(carLoads.getDefaultLoadName())) { 2253 log.debug("Not using track ({}) schedule request type ({}) road ({}) load ({})", track.getName(), 2254 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()); // NOI18N 2255 if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 2256 addLine(SEVEN, 2257 Bundle.getMessage("buildSpurScheduleNotUsed", track.getLocation().getName(), track.getName(), 2258 track.getScheduleName(), si.getId(), track.getScheduleModeName().toLowerCase(), 2259 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName())); 2260 } 2261 return null; 2262 } 2263 if (!si.getRoadName().equals(ScheduleItem.NONE) && !car.getRoadName().equals(si.getRoadName())) { 2264 log.debug("Not using track ({}) schedule request type ({}) road ({}) load ({})", track.getName(), 2265 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()); // NOI18N 2266 if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 2267 addLine(SEVEN, 2268 Bundle.getMessage("buildSpurScheduleNotUsed", track.getLocation().getName(), track.getName(), 2269 track.getScheduleName(), si.getId(), track.getScheduleModeName().toLowerCase(), 2270 si.getTypeName(), si.getRoadName(), si.getReceiveLoadName())); 2271 } 2272 return null; 2273 } 2274 if (!getTrain().isLoadNameAccepted(si.getReceiveLoadName(), si.getTypeName())) { 2275 addLine(SEVEN, Bundle.getMessage("buildTrainNotNewLoad", getTrain().getName(), 2276 si.getReceiveLoadName(), track.getLocation().getName(), track.getName())); 2277 return null; 2278 } 2279 // does the departure track allow this load? 2280 if (!car.getTrack().isLoadNameAndCarTypeShipped(si.getReceiveLoadName(), car.getTypeName())) { 2281 addLine(SEVEN, 2282 Bundle.getMessage("buildTrackNotLoadSchedule", car.getTrackName(), si.getReceiveLoadName(), 2283 track.getLocation().getName(), track.getName(), si.getId())); 2284 return null; 2285 } 2286 if (!si.getSetoutTrainScheduleId().equals(ScheduleItem.NONE) && 2287 !trainScheduleManager.getTrainScheduleActiveId().equals(si.getSetoutTrainScheduleId())) { 2288 log.debug("Schedule item isn't active"); 2289 // build the status message 2290 TrainSchedule aSch = trainScheduleManager.getScheduleById(trainScheduleManager.getTrainScheduleActiveId()); 2291 TrainSchedule tSch = trainScheduleManager.getScheduleById(si.getSetoutTrainScheduleId()); 2292 String aName = ""; 2293 String tName = ""; 2294 if (aSch != null) { 2295 aName = aSch.getName(); 2296 } 2297 if (tSch != null) { 2298 tName = tSch.getName(); 2299 } 2300 addLine(SEVEN, 2301 Bundle.getMessage("buildScheduleNotActive", track.getName(), si.getId(), tName, aName)); 2302 2303 return null; 2304 } 2305 if (!si.getRandom().equals(ScheduleItem.NONE)) { 2306 if (!si.doRandom()) { 2307 addLine(SEVEN, 2308 Bundle.getMessage("buildScheduleRandom", track.getLocation().getName(), track.getName(), 2309 track.getScheduleName(), si.getId(), si.getReceiveLoadName(), si.getRandom(), 2310 si.getCalculatedRandom())); 2311 return null; 2312 } 2313 } 2314 log.debug("Found track ({}) schedule item id ({}) for car ({})", track.getName(), si.getId(), car.toString()); 2315 return si; 2316 } 2317 2318 protected void showCarServiceOrder(Car car) { 2319 if (!car.getTrack().getServiceOrder().equals(Track.NORMAL) && !car.getTrack().isStaging()) { 2320 addLine(SEVEN, 2321 Bundle.getMessage("buildTrackModePriority", car.toString(), car.getTrack().getTrackTypeName(), 2322 car.getLocationName(), car.getTrackName(), car.getTrack().getServiceOrder(), 2323 car.getLastDate())); 2324 } 2325 } 2326 2327 /** 2328 * Returns a list containing two tracks. The 1st track found for the car, 2329 * the 2nd track is the car's final destination if an alternate track was 2330 * used for the car. 2nd track can be null. 2331 * 2332 * @param car The car needing a destination track 2333 * @param rld the RouteLocation destination 2334 * @return List containing up to two tracks. No tracks if none found. 2335 */ 2336 protected List<Track> getTracksAtDestination(Car car, RouteLocation rld) { 2337 List<Track> tracks = new ArrayList<>(); 2338 Location testDestination = rld.getLocation(); 2339 // first report if there are any alternate tracks 2340 for (Track track : testDestination.getTracksByNameList(null)) { 2341 if (track.isAlternate()) { 2342 addLine(SEVEN, Bundle.getMessage("buildTrackIsAlternate", car.toString(), 2343 track.getTrackTypeName(), track.getLocation().getName(), track.getName())); 2344 } 2345 } 2346 // now find a track for this car 2347 for (Track testTrack : testDestination.getTracksByMoves(null)) { 2348 // normally don't move car to a track with the same name at the same 2349 // location 2350 if (car.getSplitLocationName().equals(testTrack.getLocation().getSplitName()) && 2351 car.getSplitTrackName().equals(testTrack.getSplitName()) && 2352 !car.isPassenger() && 2353 !car.isCaboose() && 2354 !car.hasFred()) { 2355 addLine(SEVEN, 2356 Bundle.getMessage("buildCanNotDropCarSameTrack", car.toString(), testTrack.getName())); 2357 continue; 2358 } 2359 // Can the train service this track? 2360 if (!checkDropTrainDirection(car, rld, testTrack)) { 2361 continue; 2362 } 2363 // drop to interchange or spur? 2364 if (!checkTrainCanDrop(car, testTrack)) { 2365 continue; 2366 } 2367 // report if track has planned pickups 2368 if (testTrack.getIgnoreUsedLengthPercentage() > Track.IGNORE_0) { 2369 addLine(SEVEN, 2370 Bundle.getMessage("buildTrackHasPlannedPickups", testTrack.getName(), 2371 testTrack.getIgnoreUsedLengthPercentage(), testTrack.getLength(), 2372 Setup.getLengthUnit().toLowerCase(), testTrack.getUsedLength(), testTrack.getReserved(), 2373 testTrack.getReservedLengthSetouts(), 2374 testTrack.getReservedLengthPickups(), 2375 testTrack.getAvailableTrackSpace())); 2376 } 2377 String status = car.checkDestination(testDestination, testTrack); 2378 // Can be a caboose or car with FRED with a custom load 2379 // is the destination a spur with a schedule demanding this car's 2380 // custom load? 2381 if (status.equals(Track.OKAY) && 2382 !testTrack.getScheduleId().equals(Track.NONE) && 2383 !car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 2384 !car.getLoadName().equals(carLoads.getDefaultLoadName())) { 2385 addLine(FIVE, 2386 Bundle.getMessage("buildSpurScheduleLoad", testTrack.getName(), car.getLoadName())); 2387 } 2388 // check to see if alternate track is available if track full 2389 if (status.startsWith(Track.LENGTH)) { 2390 addLine(SEVEN, 2391 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), testTrack.getTrackTypeName(), 2392 testTrack.getLocation().getName(), testTrack.getName(), status)); 2393 if (checkForAlternate(car, testTrack)) { 2394 // send car to alternate track 2395 tracks.add(testTrack.getAlternateTrack()); 2396 tracks.add(testTrack); // car's final destination 2397 break; // done with this destination 2398 } 2399 continue; 2400 } 2401 // check for train timing 2402 if (status.equals(Track.OKAY)) { 2403 status = checkReserved(getTrain(), rld, car, testTrack, true); 2404 if (status.equals(TIMING) && checkForAlternate(car, testTrack)) { 2405 // send car to alternate track 2406 tracks.add(testTrack.getAlternateTrack()); 2407 tracks.add(testTrack); // car's final destination 2408 break; // done with this destination 2409 } 2410 } 2411 // okay to drop car? 2412 if (!status.equals(Track.OKAY)) { 2413 addLine(SEVEN, 2414 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), testTrack.getTrackTypeName(), 2415 testTrack.getLocation().getName(), testTrack.getName(), status)); 2416 continue; 2417 } 2418 if (!checkForLocalMove(car, testTrack)) { 2419 continue; 2420 } 2421 tracks.add(testTrack); 2422 tracks.add(null); // no final destination for this car 2423 break; // done with this destination 2424 } 2425 return tracks; 2426 } 2427 2428 /** 2429 * Checks to see if track has an alternate and can be used 2430 * 2431 * @param car the car being dropped 2432 * @param testTrack the destination track 2433 * @return true if track has an alternate and can be used 2434 */ 2435 protected boolean checkForAlternate(Car car, Track testTrack) { 2436 if (testTrack.getAlternateTrack() != null && 2437 car.getTrack() != testTrack.getAlternateTrack() && 2438 checkTrainCanDrop(car, testTrack.getAlternateTrack())) { 2439 addLine(SEVEN, 2440 Bundle.getMessage("buildTrackFullHasAlternate", testTrack.getLocation().getName(), 2441 testTrack.getName(), testTrack.getAlternateTrack().getName())); 2442 String status = car.checkDestination(testTrack.getLocation(), testTrack.getAlternateTrack()); 2443 if (status.equals(Track.OKAY)) { 2444 return true; 2445 } 2446 addLine(SEVEN, 2447 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 2448 testTrack.getAlternateTrack().getTrackTypeName(), 2449 testTrack.getLocation().getName(), testTrack.getAlternateTrack().getName(), 2450 status)); 2451 } 2452 return false; 2453 } 2454 2455 /** 2456 * Used to determine if car could be set out at earlier location in the 2457 * train's route. 2458 * 2459 * @param car The car 2460 * @param trackTemp The destination track for this car 2461 * @param rld Where in the route the destination track was found 2462 * @param start Where to begin the check 2463 * @param routeEnd Where to stop the check 2464 * @return The best RouteLocation to drop off the car 2465 */ 2466 protected RouteLocation checkForEarlierDrop(Car car, Track trackTemp, RouteLocation rld, int start, int routeEnd) { 2467 for (int m = start; m < routeEnd; m++) { 2468 RouteLocation rle = getRouteList().get(m); 2469 if (rle == rld) { 2470 break; 2471 } 2472 if (rle.getName().equals(rld.getName()) && 2473 (rle.getCarMoves() < rle.getMaxCarMoves()) && 2474 rle.isDropAllowed() && 2475 checkDropTrainDirection(car, rle, trackTemp)) { 2476 log.debug("Found an earlier drop for car ({}) destination ({})", car.toString(), rle.getName()); // NOI18N 2477 return rle; // earlier drop in train's route 2478 } 2479 } 2480 return rld; 2481 } 2482 2483 /* 2484 * Determines if rolling stock can be delivered to track when considering 2485 * timing of car pulls by other trains. 2486 */ 2487 protected String checkReserved(Train train, RouteLocation rld, Car car, Track destTrack, boolean printMsg) { 2488 // car returning to same track? 2489 if (car.getTrack() != destTrack) { 2490 // car can be a kernel so get total length 2491 int length = car.getTotalKernelLength(); 2492 log.debug("Car length: {}, available track space: {}, reserved: {}", length, 2493 destTrack.getAvailableTrackSpace(), destTrack.getReserved()); 2494 if (length > destTrack.getAvailableTrackSpace() + 2495 destTrack.getReserved()) { 2496 boolean returned = false; 2497 String trainExpectedArrival = train.getExpectedArrivalTime(rld, true); 2498 int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival); 2499 int reservedReturned = 0; 2500 // does this car already have this destination? 2501 if (car.getDestinationTrack() == destTrack) { 2502 reservedReturned = -car.getTotalKernelLength(); 2503 } 2504 // get a list of cars on this track 2505 List<Car> cars = carManager.getList(destTrack); 2506 for (Car kar : cars) { 2507 if (kar.getTrain() != null && kar.getTrain() != train) { 2508 int carPullTime = convertStringTime(kar.getPickupTime()); 2509 if (trainArrivalTimeMinutes < carPullTime) { 2510 // don't print if checking redirect to alternate 2511 if (printMsg) { 2512 addLine(SEVEN, 2513 Bundle.getMessage("buildCarTrainTiming", kar.toString(), 2514 kar.getTrack().getTrackTypeName(), kar.getLocationName(), 2515 kar.getTrackName(), kar.getTrainName(), kar.getPickupTime(), 2516 getTrain().getName(), trainExpectedArrival)); 2517 } 2518 reservedReturned += kar.getTotalLength(); 2519 returned = true; 2520 } 2521 } 2522 } 2523 if (returned && length > destTrack.getAvailableTrackSpace() - reservedReturned) { 2524 if (printMsg) { 2525 addLine(SEVEN, 2526 Bundle.getMessage("buildWarnTrainTiming", car.toString(), destTrack.getTrackTypeName(), 2527 destTrack.getLocation().getName(), destTrack.getName(), getTrain().getName(), 2528 destTrack.getAvailableTrackSpace() - reservedReturned, 2529 Setup.getLengthUnit().toLowerCase())); 2530 } 2531 return TIMING; 2532 } 2533 } 2534 } 2535 return Track.OKAY; 2536 } 2537 2538 /** 2539 * Checks to see if local move is allowed for this car 2540 * 2541 * @param car the car being moved 2542 * @param testTrack the destination track for this car 2543 * @return false if local move not allowed 2544 */ 2545 private boolean checkForLocalMove(Car car, Track testTrack) { 2546 if (getTrain().isLocalSwitcher()) { 2547 // No local moves from spur to spur 2548 if (!Setup.isLocalSpurMovesEnabled() && testTrack.isSpur() && car.getTrack().isSpur()) { 2549 addLine(SEVEN, 2550 Bundle.getMessage("buildNoSpurToSpurMove", car.getTrackName(), testTrack.getName())); 2551 return false; 2552 } 2553 // No local moves from yard to yard, except for cabooses and cars 2554 // with FRED 2555 if (!Setup.isLocalYardMovesEnabled() && 2556 testTrack.isYard() && 2557 car.getTrack().isYard() && 2558 !car.isCaboose() && 2559 !car.hasFred()) { 2560 addLine(SEVEN, 2561 Bundle.getMessage("buildNoYardToYardMove", car.getTrackName(), testTrack.getName())); 2562 return false; 2563 } 2564 // No local moves from interchange to interchange 2565 if (!Setup.isLocalInterchangeMovesEnabled() && 2566 testTrack.isInterchange() && 2567 car.getTrack().isInterchange()) { 2568 addLine(SEVEN, 2569 Bundle.getMessage("buildNoInterchangeToInterchangeMove", car.getTrackName(), 2570 testTrack.getName())); 2571 return false; 2572 } 2573 } 2574 return true; 2575 } 2576 2577 protected Track tryStaging(Car car, RouteLocation rldSave) throws BuildFailedException { 2578 // local switcher working staging? 2579 if (getTrain().isLocalSwitcher() && 2580 !car.isPassenger() && 2581 !car.isCaboose() && 2582 !car.hasFred() && 2583 car.getTrack() == getTerminateStagingTrack()) { 2584 addLine(SEVEN, 2585 Bundle.getMessage("buildCanNotDropCarSameTrack", car.toString(), car.getTrack().getName())); 2586 return null; 2587 } 2588 // no need to check train and track direction into staging, already done 2589 String status = car.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack()); 2590 if (status.equals(Track.OKAY)) { 2591 return getTerminateStagingTrack(); 2592 // only generate a new load if there aren't any other tracks 2593 // available for this car 2594 } else if (status.startsWith(Track.LOAD) && 2595 car.getTrack() == getDepartureStagingTrack() && 2596 car.getLoadName().equals(carLoads.getDefaultEmptyName()) && 2597 rldSave == null && 2598 (getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled() || 2599 getDepartureStagingTrack().isAddCustomLoadsEnabled() || 2600 getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled())) { 2601 // try and generate a load for this car into staging 2602 if (generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack())) { 2603 return getTerminateStagingTrack(); 2604 } 2605 } 2606 addLine(SEVEN, 2607 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), 2608 getTerminateStagingTrack().getTrackTypeName(), 2609 getTerminateStagingTrack().getLocation().getName(), getTerminateStagingTrack().getName(), 2610 status)); 2611 return null; 2612 } 2613 2614 /** 2615 * Returns true if car can be picked up later in a train's route 2616 * 2617 * @param car the car 2618 * @param rl car's route location 2619 * @param rld car's route location destination 2620 * @return true if car can be picked up later in a train's route 2621 * @throws BuildFailedException if coding issue 2622 */ 2623 protected boolean checkForLaterPickUp(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException { 2624 // is there another pick up location in the route? 2625 if (rl == rld || !rld.getName().equals(car.getLocationName())) { 2626 return false; 2627 } 2628 // last route location in the route? 2629 if (rld == getTrain().getTrainTerminatesRouteLocation() && !car.isLocalMove()) { 2630 return false; 2631 } 2632 // don't delay adding a caboose, passenger car, or car with FRED 2633 if (car.isCaboose() || car.isPassenger() || car.hasFred()) { 2634 return false; 2635 } 2636 // no later pick up if car is departing staging 2637 if (car.getLocation().isStaging()) { 2638 return false; 2639 } 2640 if (!checkPickUpTrainDirection(car, rld)) { 2641 addLine(SEVEN, 2642 Bundle.getMessage("buildNoPickupLaterDirection", car.toString(), rld.getName(), rld.getId())); 2643 return false; 2644 } 2645 if (!rld.isPickUpAllowed() && !rld.isLocalMovesAllowed() || 2646 !rld.isPickUpAllowed() && rld.isLocalMovesAllowed() && !car.isLocalMove()) { 2647 addLine(SEVEN, 2648 Bundle.getMessage("buildNoPickupLater", car.toString(), rld.getName(), rld.getId())); 2649 return false; 2650 } 2651 if (rld.getCarMoves() >= rld.getMaxCarMoves()) { 2652 addLine(SEVEN, 2653 Bundle.getMessage("buildNoPickupLaterMoves", car.toString(), rld.getName(), rld.getId())); 2654 return false; 2655 } 2656 // is the track full? If so, pull immediately, prevents overloading 2657 if (checkForPickUps(car, rl, false)) { 2658 addLine(SEVEN, Bundle.getMessage("buildNoPickupLaterTrack", car.toString(), rld.getName(), 2659 car.getTrackName(), rld.getId(), car.getTrack().getLength() - car.getTrack().getUsedLength(), 2660 Setup.getLengthUnit().toLowerCase())); 2661 return false; 2662 } 2663 // are there any other cars being pull from the same track, route location, and train? 2664 if (checkForPickUps(car, rl, true)) { 2665 addLine(SEVEN, Bundle.getMessage("buildAlreadyPickups", car.toString(), rld.getName(), 2666 car.getTrackName(), rld.getId(), car.getTrack().getTrackTypeName(), rl.getName(), 2667 car.getTrack().getName(), getTrain().getName())); 2668 return false; 2669 } 2670 addLine(SEVEN, 2671 Bundle.getMessage("buildPickupLaterOkay", car.toString(), rld.getName(), rld.getId())); 2672 return true; 2673 } 2674 2675 /* 2676 * checks to see if the train being built already has car pick ups at the 2677 * same track, route location rl, and train, and there's a track space 2678 * issue. 2679 * 2680 * return true if there are already pick ups from the car's track 2681 */ 2682 private boolean checkForPickUps(Car car, RouteLocation rl, boolean isCheckForCars) { 2683 if (!car.isLocalMove() && rl.isDropAllowed()) { 2684 int length = 0; 2685 if (isCheckForCars) { 2686 for (Car c : carManager.getByTrainList(getTrain())) { 2687 if (car.getTrack() == c.getTrack() && rl == c.getRouteLocation()) { 2688 length += c.getTotalKernelLength(); 2689 } 2690 } 2691 } 2692 if (car.getTrack().getLength() - car.getTrack().getUsedLength() < car.getTotalKernelLength() + length) { 2693 return true; 2694 } 2695 } 2696 return false; 2697 } 2698 2699 /** 2700 * Returns true is cars are allowed to travel from origin to terminal 2701 * 2702 * @param car The car 2703 * @param destinationName Destination name for this car 2704 * @return true if through cars are allowed. false if not. 2705 */ 2706 protected boolean checkThroughCarsAllowed(Car car, String destinationName) { 2707 if (!getTrain().isAllowThroughCarsEnabled() && 2708 !getTrain().isLocalSwitcher() && 2709 !car.isCaboose() && 2710 !car.hasFred() && 2711 !car.isPassenger() && 2712 car.getSplitLocationName().equals(getDepartureLocation().getSplitName()) && 2713 splitString(destinationName).equals(getTerminateLocation().getSplitName()) && 2714 !getDepartureLocation().getSplitName().equals(getTerminateLocation().getSplitName())) { 2715 addLine(FIVE, Bundle.getMessage("buildThroughTrafficNotAllow", getDepartureLocation().getName(), 2716 getTerminateLocation().getName())); 2717 return false; // through cars not allowed 2718 } 2719 return true; // through cars allowed 2720 } 2721 2722 private boolean checkLocalMovesAllowed(Car car, Track track) { 2723 if (!getTrain().isLocalSwitcher() && 2724 !getTrain().isAllowLocalMovesEnabled() && 2725 car.getSplitLocationName().equals(track.getLocation().getSplitName())) { 2726 addLine(SEVEN, 2727 Bundle.getMessage("buildNoLocalMoveToTrack", car.getLocationName(), car.getTrackName(), 2728 track.getLocation().getName(), track.getName(), getTrain().getName())); 2729 return false; 2730 } 2731 return true; 2732 } 2733 2734 /** 2735 * Creates a car load for a car departing staging and eventually terminating 2736 * into staging. 2737 * 2738 * @param car the car! 2739 * @param stageTrack the staging track the car will terminate to 2740 * @return true if a load was generated this this car. 2741 * @throws BuildFailedException if coding check fails 2742 */ 2743 protected boolean generateLoadCarDepartingAndTerminatingIntoStaging(Car car, Track stageTrack) 2744 throws BuildFailedException { 2745 addLine(SEVEN, BLANK_LINE); 2746 // code check 2747 if (stageTrack == null || !stageTrack.isStaging()) { 2748 throw new BuildFailedException("ERROR coding issue, staging track null or not staging"); 2749 } 2750 if (!stageTrack.isTypeNameAccepted(car.getTypeName())) { 2751 addLine(SEVEN, 2752 Bundle.getMessage("buildStagingTrackType", stageTrack.getLocation().getName(), stageTrack.getName(), 2753 car.getTypeName())); 2754 return false; 2755 } 2756 if (!stageTrack.isRoadNameAccepted(car.getRoadName())) { 2757 addLine(SEVEN, 2758 Bundle.getMessage("buildStagingTrackRoad", stageTrack.getLocation().getName(), stageTrack.getName(), 2759 car.getRoadName())); 2760 return false; 2761 } 2762 // Departing and returning to same location in staging? 2763 if (!getTrain().isAllowReturnToStagingEnabled() && 2764 !Setup.isStagingAllowReturnEnabled() && 2765 !car.isCaboose() && 2766 !car.hasFred() && 2767 !car.isPassenger() && 2768 car.getSplitLocationName().equals(stageTrack.getLocation().getSplitName())) { 2769 addLine(SEVEN, 2770 Bundle.getMessage("buildNoReturnStaging", car.toString(), stageTrack.getLocation().getName())); 2771 return false; 2772 } 2773 // figure out which loads the car can use 2774 List<String> loads = carLoads.getNames(car.getTypeName()); 2775 // remove the default names 2776 loads.remove(carLoads.getDefaultEmptyName()); 2777 loads.remove(carLoads.getDefaultLoadName()); 2778 if (loads.size() == 0) { 2779 log.debug("No custom loads for car type ({}) ignoring staging track ({})", car.getTypeName(), 2780 stageTrack.getName()); 2781 return false; 2782 } 2783 addLine(SEVEN, 2784 Bundle.getMessage("buildSearchTrackLoadStaging", car.toString(), car.getTypeName(), 2785 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(), 2786 stageTrack.getLocation().getName(), stageTrack.getName())); 2787 String oldLoad = car.getLoadName(); // save car's "E" load 2788 for (int i = loads.size() - 1; i >= 0; i--) { 2789 String load = loads.get(i); 2790 log.debug("Try custom load ({}) for car ({})", load, car.toString()); 2791 if (!car.getTrack().isLoadNameAndCarTypeShipped(load, car.getTypeName()) || 2792 !stageTrack.isLoadNameAndCarTypeAccepted(load, car.getTypeName()) || 2793 !getTrain().isLoadNameAccepted(load, car.getTypeName())) { 2794 // report why the load was rejected and remove it from consideration 2795 if (!car.getTrack().isLoadNameAndCarTypeShipped(load, car.getTypeName())) { 2796 addLine(SEVEN, 2797 Bundle.getMessage("buildTrackNotNewLoad", car.getTrackName(), load, 2798 stageTrack.getLocation().getName(), stageTrack.getName())); 2799 } 2800 if (!stageTrack.isLoadNameAndCarTypeAccepted(load, car.getTypeName())) { 2801 addLine(SEVEN, 2802 Bundle.getMessage("buildDestTrackNoLoad", stageTrack.getLocation().getName(), 2803 stageTrack.getName(), car.toString(), load)); 2804 } 2805 if (!getTrain().isLoadNameAccepted(load, car.getTypeName())) { 2806 addLine(SEVEN, 2807 Bundle.getMessage("buildTrainNotNewLoad", getTrain().getName(), load, 2808 stageTrack.getLocation().getName(), stageTrack.getName())); 2809 } 2810 loads.remove(i); 2811 continue; 2812 } 2813 car.setLoadName(load); 2814 // does the car have a home division? 2815 if (car.getDivision() != null) { 2816 addLine(SEVEN, 2817 Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(), 2818 car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(), 2819 car.getLocationName(), 2820 car.getTrackName(), car.getTrack().getDivisionName())); 2821 // load type empty must return to car's home division 2822 // or load type load from foreign division must return to car's 2823 // home division 2824 if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && 2825 car.getDivision() != stageTrack.getDivision() || 2826 car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) && 2827 car.getTrack().getDivision() != car.getDivision() && 2828 car.getDivision() != stageTrack.getDivision()) { 2829 addLine(SEVEN, 2830 Bundle.getMessage("buildNoDivisionTrack", stageTrack.getTrackTypeName(), 2831 stageTrack.getLocation().getName(), stageTrack.getName(), 2832 stageTrack.getDivisionName(), car.toString(), 2833 car.getLoadType().toLowerCase(), car.getLoadName())); 2834 loads.remove(i); 2835 continue; 2836 } 2837 } 2838 } 2839 // do we need to test all car loads? 2840 boolean loadRestrictions = isLoadRestrictions(); 2841 // now determine if the loads can be routed to the staging track 2842 for (int i = loads.size() - 1; i >= 0; i--) { 2843 String load = loads.get(i); 2844 car.setLoadName(load); 2845 if (!router.isCarRouteable(car, getTrain(), stageTrack, getBuildReport())) { 2846 loads.remove(i); // no remove this load 2847 addLine(SEVEN, Bundle.getMessage("buildStagingTrackNotReachable", 2848 stageTrack.getLocation().getName(), stageTrack.getName(), load)); 2849 if (!loadRestrictions) { 2850 loads.clear(); // no loads can be routed 2851 break; 2852 } 2853 } else if (!loadRestrictions) { 2854 break; // done all loads can be routed 2855 } 2856 } 2857 // Use random loads rather that the first one that works to create 2858 // interesting loads 2859 if (loads.size() > 0) { 2860 int rnd = (int) (Math.random() * loads.size()); 2861 car.setLoadName(loads.get(rnd)); 2862 // check to see if car is now accepted by staging 2863 String status = car.checkDestination(stageTrack.getLocation(), stageTrack); 2864 if (status.equals(Track.OKAY) || 2865 (status.startsWith(Track.LENGTH) && stageTrack != getTerminateStagingTrack())) { 2866 car.setLoadGeneratedFromStaging(true); 2867 car.setFinalDestination(stageTrack.getLocation()); 2868 // don't set track assignment unless the car is going to this 2869 // train's staging 2870 if (stageTrack == getTerminateStagingTrack()) { 2871 car.setFinalDestinationTrack(stageTrack); 2872 } else { 2873 // don't assign the track, that will be done later 2874 car.setFinalDestinationTrack(null); 2875 } 2876 car.updateKernel(); // is car part of kernel? 2877 addLine(SEVEN, 2878 Bundle.getMessage("buildAddingScheduleLoad", loads.size(), car.getLoadName(), car.toString())); 2879 return true; 2880 } 2881 addLine(SEVEN, 2882 Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), stageTrack.getTrackTypeName(), 2883 stageTrack.getLocation().getName(), stageTrack.getName(), status)); 2884 } 2885 car.setLoadName(oldLoad); // restore load and report failure 2886 addLine(SEVEN, Bundle.getMessage("buildUnableNewLoadStaging", car.toString(), car.getTrackName(), 2887 stageTrack.getLocation().getName(), stageTrack.getName())); 2888 return false; 2889 } 2890 2891 /** 2892 * Checks to see if there are any load restrictions for trains, 2893 * interchanges, and yards if routing through yards is enabled. 2894 * 2895 * @return true if there are load restrictions. 2896 */ 2897 private boolean isLoadRestrictions() { 2898 boolean restrictions = isLoadRestrictionsTrain() || isLoadRestrictions(Track.INTERCHANGE); 2899 if (Setup.isCarRoutingViaYardsEnabled()) { 2900 restrictions = restrictions || isLoadRestrictions(Track.YARD); 2901 } 2902 return restrictions; 2903 } 2904 2905 private boolean isLoadRestrictions(String type) { 2906 for (Track track : locationManager.getTracks(type)) { 2907 if (!track.getLoadOption().equals(Track.ALL_LOADS)) { 2908 return true; 2909 } 2910 } 2911 return false; 2912 } 2913 2914 private boolean isLoadRestrictionsTrain() { 2915 for (Train train : trainManager.getList()) { 2916 if (!train.getLoadOption().equals(Train.ALL_LOADS)) { 2917 return true; 2918 } 2919 } 2920 return false; 2921 } 2922 2923 /** 2924 * report any cars left at route location 2925 * 2926 * @param rl route location 2927 */ 2928 protected void showCarsNotMoved(RouteLocation rl) { 2929 if (_carIndex < 0) { 2930 _carIndex = 0; 2931 } 2932 // cars up this point have build report messages, only show the cars 2933 // that aren't 2934 // in the build report 2935 int numberCars = 0; 2936 for (int i = _carIndex; i < getCarList().size(); i++) { 2937 if (numberCars == DISPLAY_CAR_LIMIT_100) { 2938 addLine(FIVE, Bundle.getMessage("buildOnlyFirstXXXCars", numberCars, rl.getName())); 2939 break; 2940 } 2941 Car car = getCarList().get(i); 2942 // find a car at this location that hasn't been given a destination 2943 if (!car.getLocationName().equals(rl.getName()) || car.getRouteDestination() != null) { 2944 continue; 2945 } 2946 if (numberCars == 0) { 2947 addLine(SEVEN, 2948 Bundle.getMessage("buildMovesCompleted", rl.getMaxCarMoves(), rl.getName())); 2949 } 2950 addLine(SEVEN, Bundle.getMessage("buildCarIgnored", car.toString(), car.getTypeName(), 2951 car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName())); 2952 numberCars++; 2953 } 2954 addLine(SEVEN, BLANK_LINE); 2955 } 2956 2957 /** 2958 * Remove rolling stock from train 2959 * 2960 * @param rs the rolling stock to be removed 2961 */ 2962 protected void removeRollingStockFromTrain(RollingStock rs) { 2963 // adjust train length and weight for each location that the rolling 2964 // stock is in the train 2965 boolean inTrain = false; 2966 for (RouteLocation routeLocation : getRouteList()) { 2967 if (rs.getRouteLocation() == routeLocation) { 2968 inTrain = true; 2969 } 2970 if (rs.getRouteDestination() == routeLocation) { 2971 break; 2972 } 2973 if (inTrain) { 2974 routeLocation.setTrainLength(routeLocation.getTrainLength() - rs.getTotalLength()); // includes 2975 // couplers 2976 routeLocation.setTrainWeight(routeLocation.getTrainWeight() - rs.getAdjustedWeightTons()); 2977 } 2978 } 2979 rs.reset(); // remove this rolling stock from the train 2980 } 2981 2982 /** 2983 * Lists cars that couldn't be routed. 2984 */ 2985 protected void showCarsNotRoutable() { 2986 // any cars unable to route? 2987 if (_notRoutable.size() > 0) { 2988 addLine(ONE, BLANK_LINE); 2989 addLine(ONE, Bundle.getMessage("buildCarsNotRoutable")); 2990 for (Car car : _notRoutable) { 2991 _warnings++; 2992 addLine(ONE, 2993 Bundle.getMessage("buildCarNotRoutable", car.toString(), car.getLocationName(), 2994 car.getTrackName(), car.getPreviousFinalDestinationName(), 2995 car.getPreviousFinalDestinationTrackName())); 2996 } 2997 addLine(ONE, BLANK_LINE); 2998 } 2999 } 3000 3001 /** 3002 * build has failed due to cars in staging not having destinations this 3003 * routine removes those cars from the staging track by user request. 3004 */ 3005 protected void removeCarsFromStaging() { 3006 // Code check, only called if train was departing staging 3007 if (getDepartureStagingTrack() == null) { 3008 log.error("Error, called when cars in staging not assigned to train"); 3009 return; 3010 } 3011 for (Car car : getCarList()) { 3012 // remove cars from departure staging track that haven't been 3013 // assigned to this train 3014 if (car.getTrack() == getDepartureStagingTrack() && car.getTrain() == null) { 3015 // remove track from kernel 3016 if (car.getKernel() != null) { 3017 for (Car c : car.getKernel().getCars()) 3018 c.setLocation(car.getLocation(), null); 3019 } else { 3020 car.setLocation(car.getLocation(), null); 3021 } 3022 } 3023 } 3024 } 3025 3026 protected int countRollingStockAt(RouteLocation rl, List<RollingStock> list) { 3027 int count = 0; 3028 for (RollingStock rs : list) { 3029 if (rs.getLocationName().equals(rl.getName())) { 3030 count++; 3031 } 3032 } 3033 return count; 3034 } 3035 3036 /* 3037 * lists the tracks that aren't in quick service mode 3038 */ 3039 protected void showTracksNotQuickService() { 3040 if (Setup.isBuildOnTime()) { 3041 addLine(FIVE, Bundle.getMessage("buildTracksNotQuickService")); 3042 for (Track track : locationManager.getTracks(null)) { 3043 if (!track.isQuickServiceEnabled()) { 3044 addLine(SEVEN, Bundle.getMessage("buildTrackNotQuick", 3045 StringUtils.capitalize(track.getTrackTypeName()), track.getLocation().getName(), 3046 track.getName())); 3047 } 3048 } 3049 addLine(FIVE, BLANK_LINE); 3050 } 3051 } 3052 3053 /** 3054 * Checks to see if rolling stock is departing a quick service track and is 3055 * allowed to be pulled by this train. To pull, the route location must be 3056 * different than the one used to deliver the rolling stock. To service the 3057 * rolling stock, the train must arrive after the rolling stock's clone is 3058 * set out by this train or by another train. 3059 * 3060 * @param rs the rolling stock 3061 * @param rl the route location pulling the rolling stock 3062 * @return true if rolling stock can be pulled 3063 */ 3064 protected boolean checkQuickServiceDeparting(RollingStock rs, RouteLocation rl) { 3065 if (rs.getTrack().isQuickServiceEnabled()) { 3066 RollingStock clone = null; 3067 if (Car.class.isInstance(rs)) { 3068 clone = carManager.getClone(rs); 3069 } 3070 if (Engine.class.isInstance(rs)) { 3071 clone = engineManager.getClone(rs); 3072 } 3073 if (clone != null) { 3074 // was the rolling stock delivered using this route location? 3075 if (rs.getRouteDestination() == rl) { 3076 addLine(FIVE, 3077 Bundle.getMessage("buildRouteLocation", rs.toString(), 3078 rs.getTrack().getTrackTypeName(), 3079 rs.getLocationName(), rs.getTrackName(), getTrain().getName(), rl.getName(), 3080 rl.getId())); 3081 addLine(FIVE, BLANK_LINE); 3082 return false; 3083 } 3084 3085 // determine when the train arrives 3086 String trainExpectedArrival = getTrain().getExpectedArrivalTime(rl, true); 3087 int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival); 3088 // determine when the clone is going to be delivered 3089 int cloneSetoutTimeMinutes = convertStringTime(clone.getSetoutTime()); 3090 // in aggressive mode the dwell time is 0 3091 int dwellTime = 0; 3092 if (Setup.isBuildOnTime()) { 3093 dwellTime = Setup.getDwellTime(); 3094 } 3095 if (cloneSetoutTimeMinutes + dwellTime > trainArrivalTimeMinutes) { 3096 String earliest = convertMinutesTime(cloneSetoutTimeMinutes + dwellTime); 3097 addLine(FIVE, Bundle.getMessage("buildDeliveryTiming", rs.toString(), 3098 clone.getSetoutTime(), rs.getTrack().getTrackTypeName(), rs.getLocationName(), 3099 rs.getTrackName(), clone.getTrainName(), getTrain().getName(), trainExpectedArrival, 3100 dwellTime, earliest)); 3101 addLine(FIVE, BLANK_LINE); 3102 return false; 3103 } else { 3104 addLine(SEVEN, Bundle.getMessage("buildCloneDeliveryTiming", clone.toString(), 3105 clone.getSetoutTime(), rs.getTrack().getTrackTypeName(), rs.getLocationName(), 3106 rs.getTrackName(), clone.getTrainName(), getTrain().getName(), trainExpectedArrival, 3107 dwellTime, rs.toString())); 3108 } 3109 } 3110 } 3111 return true; 3112 } 3113 3114 /* 3115 * Engine methods start here 3116 */ 3117 3118 /** 3119 * Used to determine the number of engines requested by the user. 3120 * 3121 * @param requestEngines Can be a number, AUTO or AUTO HPT. 3122 * @return the number of engines requested by user. 3123 */ 3124 protected int getNumberEngines(String requestEngines) { 3125 int numberEngines = 0; 3126 if (requestEngines.equals(Train.AUTO)) { 3127 numberEngines = getAutoEngines(); 3128 } else if (requestEngines.equals(Train.AUTO_HPT)) { 3129 numberEngines = 1; // get one loco for now, check HP requirements 3130 // after train is built 3131 } else { 3132 numberEngines = Integer.parseInt(requestEngines); 3133 } 3134 return numberEngines; 3135 } 3136 3137 /** 3138 * Returns the number of engines needed for this train, minimum 1, maximum 3139 * user specified in setup. Based on maximum allowable train length and 3140 * grade between locations, and the maximum cars that the train can have at 3141 * the maximum train length. One engine per sixteen 40' cars for 1% grade. 3142 * 3143 * @return The number of engines needed 3144 */ 3145 private int getAutoEngines() { 3146 double numberEngines = 1; 3147 int moves = 0; 3148 int carLength = 40 + Car.COUPLERS; // typical 40' car 3149 3150 // adjust if length in meters 3151 if (!Setup.getLengthUnit().equals(Setup.FEET)) { 3152 carLength = 12 + Car.COUPLERS; // typical car in meters 3153 } 3154 3155 for (RouteLocation rl : getRouteList()) { 3156 if (rl.isPickUpAllowed() && rl != getTrain().getTrainTerminatesRouteLocation()) { 3157 moves += rl.getMaxCarMoves(); // assume all moves are pick ups 3158 double carDivisor = 16; // number of 40' cars per engine 1% grade 3159 // change engine requirements based on grade 3160 if (rl.getGrade() > 1) { 3161 carDivisor = carDivisor / rl.getGrade(); 3162 } 3163 log.debug("Maximum train length {} for location ({})", rl.getMaxTrainLength(), rl.getName()); 3164 if (rl.getMaxTrainLength() / (carDivisor * carLength) > numberEngines) { 3165 numberEngines = rl.getMaxTrainLength() / (carDivisor * carLength); 3166 // round up to next whole integer 3167 numberEngines = Math.ceil(numberEngines); 3168 // determine if there's enough car pick ups at this point to 3169 // reach the max train length 3170 if (numberEngines > moves / carDivisor) { 3171 // no reduce based on moves 3172 numberEngines = Math.ceil(moves / carDivisor); 3173 } 3174 } 3175 } 3176 } 3177 int nE = (int) numberEngines; 3178 if (getTrain().isLocalSwitcher()) { 3179 nE = 1; // only one engine if switcher 3180 } 3181 addLine(ONE, 3182 Bundle.getMessage("buildAutoBuildMsg", Integer.toString(nE))); 3183 if (nE > Setup.getMaxNumberEngines()) { 3184 addLine(THREE, Bundle.getMessage("buildMaximumNumberEngines", Setup.getMaxNumberEngines())); 3185 nE = Setup.getMaxNumberEngines(); 3186 } 3187 return nE; 3188 } 3189 3190 protected void addLine(String level, String string) { 3191 addLine(getBuildReport(), level, string); 3192 } 3193 3194 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilderBase.class); 3195 3196}