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