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