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