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