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