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