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