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