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