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