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