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