001package jmri.jmrit.operations.trains.trainbuilder; 002 003import java.io.IOException; 004import java.util.Date; 005import java.util.List; 006 007import jmri.InstanceManager; 008import jmri.jmrit.operations.locations.Location; 009import jmri.jmrit.operations.locations.Track; 010import jmri.jmrit.operations.rollingstock.cars.Car; 011import jmri.jmrit.operations.routes.RouteLocation; 012import jmri.jmrit.operations.setup.Setup; 013import jmri.jmrit.operations.trains.*; 014import jmri.jmrit.operations.trains.csv.TrainCsvManifest; 015import jmri.jmrit.operations.trains.manualtrainbuilder.*; 016import jmri.util.swing.JmriJOptionPane; 017 018/** 019 * Builds a train and then creates the train's manifest. 020 * 021 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 022 * 2014, 2015, 2021, 2026 023 */ 024public class TrainBuilder extends TrainBuilderCars { 025 026 /** 027 * Build rules: 028 * <ol> 029 * <li>Need at least one location in route to build train 030 * <li>Select only locos and cars that the train can service 031 * <li>If required, add caboose or car with FRED to train 032 * <li>When departing staging find a track matching train requirements 033 * <li>All cars and locos on one track must leave staging 034 * <li>Optionally block cars from staging 035 * <li>Route cars with home divisions 036 * <li>Route cars with custom loads or final destinations. 037 * <li>Service locations based on train direction, location car types, roads 038 * and loads. 039 * <li>Ignore track direction when train is a local (serves one location) 040 * </ol> 041 * <p> 042 * History: 043 * <p> 044 * First version of train builder found cars along a train's route and 045 * assigned destinations (tracks) willing to accept the car. This is called 046 * the random method as cars just bounce around the layout without purpose. 047 * Afterwards custom loads and routing was added to the program. Cars with 048 * custom loads or final destinations move with purpose as those cars are 049 * routed. The last major feature added was car divisions. Cars assigned a 050 * division are always routed. 051 * <p> 052 * The program was written around the concept of a build report. The report 053 * provides a description of the train build process and the steps taken to 054 * place rolling stock in a train. The goal was to help users understand why 055 * rolling stock was either assigned to the train or not, and which choices 056 * the program had available when determining an engine's or car's 057 * destination. 058 * 059 * @param train the train that is to be built 060 * @return True if successful. 061 */ 062 public boolean build(Train train) { 063 setTrain(train); 064 try { 065 build(); 066 return true; 067 } catch (BuildFailedException e) { 068 buildFailed(e); 069 return false; 070 } 071 } 072 073 private void build() throws BuildFailedException { 074 setStartTime(new Date()); 075 log.debug("Building train ({})", getTrain().getName()); 076 077 getTrain().setStatusCode(Train.CODE_BUILDING); 078 getTrain().setBuilt(false); 079 getTrain().setLeadEngine(null); 080 081 createBuildReportFile(); // backup build report and create new 082 showBuildReportInfo(); // add the build report header information 083 setUpRoute(); // load route, departure and terminate locations 084 showTrainBuildOptions(); // show the build options 085 showSpecificTrainBuildOptions(); // show the train build options 086 showAndInitializeTrainRoute(); // show the train's route and initialize 087 showIfLocalSwitcher(); // show if this train a switcher 088 showTrainRequirements(); // show how many engines, caboose, FRED changes 089 showTrainServices(); // engine roads, owners, built dates, and types 090 getAndRemoveEnginesFromList(); // get a list of available engines 091 showEnginesByLocation(); // list available engines by location 092 determineIfTrainTerminatesIntoStaging(); // find staging terminus track 093 determineIfTrainDepartsStagingAndAddEngines(); // add engines if staging 094 addEnginesToTrain(); // 1st, 2nd and 3rd engine swaps in a train's route 095 showTrainCarRoads(); // show car roads that this train will service 096 showTrainCabooseRoads(); // show caboose roads that this train will service 097 showTrainCarTypes(); // show car types that this train will service 098 showTrainLoadNames(); // show load names that this train will service 099 createCarList(); // remove unwanted cars 100 adjustCarsInStaging(); // adjust for cars on one staging track 101 showCarsByLocation(); // list available cars by location 102 sortCarsOnFifoLifoTracks(); // sort cars on FIFO or LIFO tracks 103 saveCarFinalDestinations(); // save car's final dest and schedule id 104 addCabooseOrFredToTrain(); // caboose and FRED changes 105 removeCaboosesAndCarsWithFred(); // done with cabooses and FRED 106 blockCarsFromStaging(); // block cars from staging 107 showTracksNotQuickService(); // list tracks that aren't using quick service 108 109 manualBuild(); // adds cars to train based on user's requests 110 addCarsToTrain(); // finds and adds cars to the train (main routine) 111 112 checkStuckCarsInStaging(); // determine if cars are stuck in staging 113 setTrainBuildStatus(); // show how well the build went 114 checkEngineHP(); // determine if train has appropriate engine HP 115 checkNumnberOfEnginesNeededHPT(); // check train engine requirements 116 showCarsNotRoutable(); // list cars that couldn't be routed 117 finshBuildReport(); // number of warnings, build time 118 119 getBuildReport().flush(); 120 getBuildReport().close(); 121 122 createManifests(); // now make Manifests 123 124 // notify locations have been modified by this train's build 125 for (Location location : _modifiedLocations) { 126 location.setStatus(Location.MODIFIED); 127 } 128 129 // operations automations use wait for train built to create custom 130 // manifests and switch lists 131 getTrain().setPrinted(false); 132 getTrain().setSwitchListStatus(Train.UNKNOWN); 133 getTrain().setCurrentLocation(getTrain().getTrainDepartsRouteLocation()); 134 getTrain().setBuilt(true); 135 // create and place train icon 136 getTrain().moveTrainIcon(getTrain().getTrainDepartsRouteLocation()); 137 138 log.debug("Done building train ({})", getTrain().getName()); 139 showWarningMessage(); 140 } 141 142 /** 143 * Figures out if the train terminates into staging, and if true, sets the 144 * termination track. Note if the train is returning back to the same track 145 * in staging getTerminateStagingTrack() is null, and is loaded later when 146 * the departure track is determined. 147 * 148 * @throws BuildFailedException if staging track can't be found 149 */ 150 private void determineIfTrainTerminatesIntoStaging() throws BuildFailedException { 151 // does train terminate into staging? 152 setTerminateStagingTrack(null); 153 List<Track> stagingTracksTerminate = getTerminateLocation().getTracksByMoves(Track.STAGING); 154 if (stagingTracksTerminate.size() > 0) { 155 addLine(THREE, BLANK_LINE); 156 addLine(ONE, Bundle.getMessage("buildTerminateStaging", getTerminateLocation().getName(), 157 Integer.toString(stagingTracksTerminate.size()))); 158 if (stagingTracksTerminate.size() > 1 && Setup.isStagingPromptToEnabled()) { 159 setTerminateStagingTrack(promptToStagingDialog()); 160 setStartTime(new Date()); // reset build time since user can take 161 // awhile to pick 162 } else { 163 // is this train returning to the same staging in aggressive 164 // mode? 165 if (getDepartureLocation() == getTerminateLocation() && 166 Setup.isBuildAggressive() && 167 Setup.isStagingTrackImmediatelyAvail()) { 168 addLine(ONE, Bundle.getMessage("buildStagingReturn", getTerminateLocation().getName())); 169 } else { 170 for (Track track : stagingTracksTerminate) { 171 if (checkTerminateStagingTrack(track)) { 172 setTerminateStagingTrack(track); 173 addLine(ONE, Bundle.getMessage("buildStagingAvail", 174 getTerminateStagingTrack().getName(), getTerminateLocation().getName())); 175 break; 176 } 177 } 178 } 179 } 180 if (getTerminateStagingTrack() == null) { 181 // is this train returning to the same staging in aggressive 182 // mode? 183 if (getDepartureLocation() == getTerminateLocation() && 184 Setup.isBuildAggressive() && 185 Setup.isStagingTrackImmediatelyAvail()) { 186 log.debug("Train is returning to same track in staging"); 187 } else { 188 addLine(ONE, Bundle.getMessage("buildErrorStagingFullNote")); 189 throw new BuildFailedException( 190 Bundle.getMessage("buildErrorStagingFull", getTerminateLocation().getName())); 191 } 192 } 193 } 194 } 195 196 /** 197 * Figures out if the train is departing staging, and if true, sets the 198 * departure track. Also sets the arrival track if the train is returning to 199 * the same departure track in staging. 200 * 201 * @throws BuildFailedException if staging departure track not found 202 */ 203 private void determineIfTrainDepartsStagingAndAddEngines() throws BuildFailedException { 204 // allow up to two engine and caboose swaps in the train's route 205 RouteLocation engineTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation(); 206 207 // Adjust where the locos will terminate 208 if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 209 getTrain().getSecondLegStartRouteLocation() != null) { 210 engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation(); 211 } else if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 212 getTrain().getThirdLegStartRouteLocation() != null) { 213 engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation(); 214 } 215 216 // determine if train is departing staging 217 List<Track> stagingTracks = getDepartureLocation().getTracksByMoves(Track.STAGING); 218 if (stagingTracks.size() > 0) { 219 addLine(THREE, BLANK_LINE); 220 addLine(ONE, Bundle.getMessage("buildDepartStaging", getDepartureLocation().getName(), 221 Integer.toString(stagingTracks.size()))); 222 if (stagingTracks.size() > 1 && Setup.isStagingPromptFromEnabled()) { 223 setDepartureStagingTrack(promptFromStagingDialog()); 224 setStartTime(new Date()); // restart build timer 225 if (getDepartureStagingTrack() == null) { 226 showTrainRequirements(); 227 throw new BuildFailedException( 228 Bundle.getMessage("buildErrorStagingEmpty", getDepartureLocation().getName())); 229 } 230 } else { 231 for (Track track : stagingTracks) { 232 // is the departure track available? 233 if (!checkDepartureStagingTrack(track)) { 234 addLine(SEVEN, 235 Bundle.getMessage("buildStagingTrackRestriction", track.getName(), 236 getTrain().getName())); 237 continue; 238 } 239 setDepartureStagingTrack(track); 240 // try each departure track for the required engines 241 if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), 242 getTrain().getEngineRoad(), 243 getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 244 addLine(SEVEN, Bundle.getMessage("buildDoneAssignEnginesStaging")); 245 break; // done! 246 } 247 setDepartureStagingTrack(null); 248 } 249 } 250 if (getDepartureStagingTrack() == null) { 251 showTrainRequirements(); 252 throw new BuildFailedException( 253 Bundle.getMessage("buildErrorStagingEmpty", getDepartureLocation().getName())); 254 } 255 } 256 getTrain().setTerminationTrack(getTerminateStagingTrack()); 257 getTrain().setDepartureTrack(getDepartureStagingTrack()); 258 } 259 260 /** 261 * Adds and removes cabooses or car with FRED in the train's route. Up to 2 262 * caboose changes. 263 * 264 * @throws BuildFailedException 265 */ 266 private void addCabooseOrFredToTrain() throws BuildFailedException { 267 // allow up to two caboose swaps in the train's route 268 RouteLocation cabooseOrFredTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation(); 269 RouteLocation cabooseOrFredTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation(); 270 271 // determine if there are any caboose changes 272 if ((getTrain().getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 273 (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 274 cabooseOrFredTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation(); 275 } else if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 276 (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 277 cabooseOrFredTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation(); 278 } 279 if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 280 (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 281 cabooseOrFredTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation(); 282 } 283 284 // Do caboose changes in reverse order in case there isn't enough track 285 // space second caboose change? 286 if ((getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE && 287 getTrain().getThirdLegStartRouteLocation() != null && 288 getTrain().getTrainTerminatesRouteLocation() != null) { 289 getCaboose(getTrain().getThirdLegCabooseRoad(), _thirdLeadEngine, 290 getTrain().getThirdLegStartRouteLocation(), 291 getTrain().getTrainTerminatesRouteLocation(), true); 292 } 293 294 // first caboose change? 295 if ((getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE && 296 getTrain().getSecondLegStartRouteLocation() != null && 297 cabooseOrFredTerminatesSecondLeg != null) { 298 getCaboose(getTrain().getSecondLegCabooseRoad(), _secondLeadEngine, 299 getTrain().getSecondLegStartRouteLocation(), 300 cabooseOrFredTerminatesSecondLeg, true); 301 } 302 303 // departure caboose or car with FRED 304 getCaboose(getTrain().getCabooseRoad(), getTrain().getLeadEngine(), getTrain().getTrainDepartsRouteLocation(), 305 cabooseOrFredTerminatesFirstLeg, getTrain().isCabooseNeeded()); 306 getCarWithFred(getTrain().getCabooseRoad(), getTrain().getTrainDepartsRouteLocation(), 307 cabooseOrFredTerminatesFirstLeg); 308 } 309 310 /* 311 * User can manually assigns cars that don't have a train, destination or 312 * final destination. Build items can request car type, road, load, route 313 * location, track, and pick up day. Build items can also provide a final 314 * destination, which allows for routing if needed. 315 */ 316 private void manualBuild() throws BuildFailedException { 317 // determine if there's a manual build available 318 TrainManualBuildManager manualBuildManager = InstanceManager.getDefault(TrainManualBuildManager.class); 319 TrainManualBuild manualBuild = manualBuildManager.getManualBuildByTrainId(getTrain().getId()); 320 if (manualBuild == null) { 321 return; 322 } 323 addLine(ONE, Bundle.getMessage("mbuildFound", getTrain().getName())); 324 for (TrainManualBuildItem mbi : manualBuild.getItemsBySequenceList()) { 325 addLine(THREE, 326 Bundle.getMessage("mbuildRequest", mbi.getId(), mbi.getTypeName(), mbi.getRoadName(), 327 mbi.getLoadName(), mbi.getLocationName(), mbi.getLocationTrackName(), 328 mbi.getDestinationName(), mbi.getDestinationTrackName(), mbi.getTrainScheduleName(), 329 mbi.getCount())); 330 // the number of cars requested by this line item 331 int count = mbi.getCount(); 332 if (count > 0) { 333 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 334 Car car = getCarList().get(_carIndex); 335 // find a car that meets the user's requests 336 if (!checkItem(mbi, car)) { 337 continue; 338 } 339 addLine(FIVE, 340 Bundle.getMessage("mbuildFoundCar", car.getTypeName(), car.getTypeExtensions(), 341 car.getRoadName(), car.getLoadName(), car.getLocationName(), car.getTrackName())); 342 // there could be a an optional destination 343 car.setFinalDestination(mbi.getDestination()); 344 car.setFinalDestinationTrack(mbi.getDestinationTrack()); 345 // case where the user specified a route location 346 if (mbi.getRouteLocation() != null) { 347 findDestinationForCar(mbi.getRouteLocation(), car); 348 } else { 349 for (RouteLocation rl : getRouteList()) { 350 // start looking at car's location 351 if (rl.getLocation() == car.getLocation()) { 352 findDestinationForCar(rl, car); 353 } 354 if (car.getTrain() != null) { 355 break; // done 356 } 357 } 358 } 359 // if not assigned to train, clear final destination 360 if (car.getTrain() == null) { 361 car.setFinalDestination(null); 362 car.setFinalDestinationTrack(null); 363 } else { 364 if (--count <= 0) { 365 break; // done 366 } 367 } 368 } 369 } 370 371 addLine(THREE, Bundle.getMessage("mbuildCompleted", mbi.getId(), manualBuild.getTrainName(), 372 mbi.getCount() - count, mbi.getCount())); 373 addLine(THREE, BLANK_LINE); 374 375 if (count > 0 && mbi.isFailEnabled()) { 376 throw new BuildFailedException( 377 Bundle.getMessage("mbuildFail", manualBuild.getTrainName(), mbi.getId())); 378 } else if (count > 0 && mbi.isWarnEnabled()) { 379 _warnings++; 380 addLine(ONE, Bundle.getMessage("mbuildWarn", manualBuild.getTrainName(), mbi.getId())); 381 addLine(ONE, BLANK_LINE); 382 } 383 384 if (mbi.isRemoveEnabled()) { 385 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 386 Car car = getCarList().get(_carIndex); 387 // find a car that meets the user's requests 388 if (checkItem(mbi, car)) { 389 addLine(FIVE, Bundle.getMessage("mbuildRemove", mbi.getId(), car.toString(), car.getTrackType(), 390 car.getLocationName(), car.getTrackName())); 391 remove(car); 392 } 393 } 394 addLine(FIVE, BLANK_LINE); 395 } 396 } 397 addLine(ONE, Bundle.getMessage("mbuildDone")); 398 addLine(ONE, BLANK_LINE); // end of manual build 399 } 400 401 /* 402 * Returns true if the car matches the user's request. Car can not be 403 * assigned to this train, have a destination, or a final destination. 404 */ 405 private boolean checkItem(TrainManualBuildItem mbi, Car car) { 406 if (car.getTrain() != null) { 407 log.debug("Car ({}) is assigned to train ({})", car.toString(), car.getTrain().getName()); 408 return false; 409 } 410 if (car.getDestination() != null) { 411 log.debug("Car ({}) has destination ({})", car.toString(), car.getDestination().getName()); 412 return false; 413 } 414 if (car.getFinalDestination() != null) { 415 log.debug("Car ({}) has final destination ({})", car.toString(), 416 car.getFinalDestination().getName()); 417 return false; 418 } 419 if (!mbi.getTypeName().equals(Car.NONE) && !car.getTypeName().equals(mbi.getTypeName())) { 420 return false; 421 } 422 if (!mbi.getRoadName().equals(Car.NONE) && !car.getRoadName().equals(mbi.getRoadName())) { 423 return false; 424 } 425 if (!mbi.getLoadName().equals(Car.NONE) && !car.getLoadName().equals(mbi.getLoadName())) { 426 return false; 427 } 428 if (mbi.getRouteLocation() != null && car.getLocation() != mbi.getRouteLocation().getLocation()) { 429 return false; 430 } 431 if (mbi.getLocationTrack() != null && car.getTrack() != mbi.getLocationTrack()) { 432 return false; 433 } 434 // pick up day 435 if (trainScheduleManager.getActiveSchedule() != null && 436 !mbi.getTrainScheduleName().equals(TrainManualBuildItem.NONE) && 437 !trainScheduleManager.getActiveSchedule().getId().equals(mbi.getTrainScheduleId())) { 438 return false; 439 } 440 return true; 441 } 442 443 private boolean findDestinationForCar(RouteLocation rl, Car car) throws BuildFailedException { 444 if (!checkRouteLocationMoves(rl, car)) { 445 return false; 446 } 447 if (!checkRouteLocation(rl)) { 448 addLine(ONE, BLANK_LINE); 449 return false; 450 } 451 findDestinationsFromLocation(rl, car, false); 452 return true; 453 } 454 455 /* 456 * returns true if there are moves available at the car's departure route 457 * location 458 */ 459 private boolean checkRouteLocationMoves(RouteLocation rl, Car car) { 460 if (rl.getCarMoves() < rl.getMaxCarMoves()) { 461 return true; 462 } 463 addLine(FIVE, Bundle.getMessage("mbuildRouteLocationMoves", rl.getName(), rl.getId(), rl.getMaxCarMoves(), 464 rl.getMaxCarMoves() - rl.getCarMoves())); 465 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 466 addLine(FIVE, BLANK_LINE); 467 return false; 468 } 469 470 /** 471 * Routine to find and add available cars to the train. In normal mode 472 * performs a single pass. In aggressive mode, will perform multiple passes. 473 * If train is departing staging and in aggressive mode, will try again 474 * using normal mode if there's a train build issue. 475 * 476 * @throws BuildFailedException 477 */ 478 private void addCarsToTrain() throws BuildFailedException { 479 addLine(THREE, 480 Bundle.getMessage("buildTrain", getTrain().getNumberCarsRequested(), getTrain().getName(), 481 getCarList().size())); 482 483 if (Setup.isBuildAggressive() && !getTrain().isBuildTrainNormalEnabled()) { 484 // perform a multiple pass build for this train, default is two 485 // passes 486 int pass = 0; 487 while (pass++ < Setup.getNumberPasses()) { 488 addCarsToTrain(pass, false); 489 } 490 // are cars stuck in staging? 491 secondAttemptNormalBuild(); 492 } else { 493 addCarsToTrain(Setup.getNumberPasses(), true); // normal build one 494 // pass 495 } 496 } 497 498 /** 499 * If cars stuck in staging, try building again in normal mode. 500 * 501 * @throws BuildFailedException 502 */ 503 private void secondAttemptNormalBuild() throws BuildFailedException { 504 if (Setup.isStagingTryNormalBuildEnabled() && isCarStuckStaging()) { 505 addLine(ONE, Bundle.getMessage("buildFailedTryNormalMode")); 506 addLine(ONE, BLANK_LINE); 507 getTrain().reset(); 508 getTrain().setStatusCode(Train.CODE_BUILDING); 509 getTrain().setLeadEngine(null); 510 // using the same departure and termination tracks 511 getTrain().setDepartureTrack(getDepartureStagingTrack()); 512 getTrain().setTerminationTrack(getTerminateStagingTrack()); 513 showAndInitializeTrainRoute(); 514 getAndRemoveEnginesFromList(); 515 addEnginesToTrain(); 516 createCarList(); 517 adjustCarsInStaging(); 518 showCarsByLocation(); 519 addCabooseOrFredToTrain(); 520 removeCaboosesAndCarsWithFred(); 521 saveCarFinalDestinations(); // save final destination and schedule 522 // id 523 blockCarsFromStaging(); // block cars from staging 524 addCarsToTrain(Setup.getNumberPasses(), true); // try normal build 525 // one pass 526 } 527 } 528 529 /** 530 * Main routine to place cars into the train. Can be called multiple times. 531 * When departing staging, ignore staged cars on the first pass unless the 532 * option to build normal was selected by user. 533 * 534 * @param pass Which pass when there are multiple passes requested by 535 * user. 536 * @param normal True if single pass or normal mode is requested by user. 537 * @throws BuildFailedException 538 */ 539 private void addCarsToTrain(int pass, boolean normal) throws BuildFailedException { 540 addLine(THREE, BLANK_LINE); 541 if (normal) { 542 addLine(THREE, Bundle.getMessage("NormalModeWhenBuilding")); 543 } else { 544 addLine(THREE, Bundle.getMessage("buildMultiplePass", pass, Setup.getNumberPasses())); 545 } 546 // now go through each location starting at departure and place cars as 547 // requested 548 for (RouteLocation rl : getRouteList()) { 549 if (!checkRouteLocation(rl)) { 550 continue; 551 } 552 553 _completedMoves = 0; // moves completed for this location 554 _reqNumOfMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 555 556 if (!normal) { 557 if (rl == getTrain().getTrainDepartsRouteLocation()) { 558 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) * pass / Setup.getNumberPasses(); 559 } else if (pass == 1) { 560 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2; 561 // round up requested moves 562 int remainder = (rl.getMaxCarMoves() - rl.getCarMoves()) % 2; 563 if (remainder > 0) { 564 _reqNumOfMoves++; 565 } 566 } 567 } 568 569 // if departing staging make adjustments 570 if (rl == getTrain().getTrainDepartsRouteLocation()) { 571 if (pass == 1) { 572 makeAdjustmentsIfDepartingStaging(); 573 } else { 574 restoreCarsIfDepartingStaging(); 575 } 576 } 577 578 int saveReqMoves = _reqNumOfMoves; // save a copy for status message 579 addLine(ONE, 580 Bundle.getMessage("buildLocReqMoves", rl.getName(), rl.getId(), _reqNumOfMoves, 581 rl.getMaxCarMoves() - rl.getCarMoves(), rl.getMaxCarMoves())); 582 addLine(FIVE, BLANK_LINE); 583 584 // show the car load generation options for staging 585 if (rl == getTrain().getTrainDepartsRouteLocation()) { 586 showLoadGenerationOptionsStaging(); 587 } 588 589 _carIndex = 0; // see reportCarsNotMoved(rl) below 590 591 findDestinationsForCarsFromLocation(rl, false); // first pass 592 593 // perform 2nd pass if aggressive mode and there are requested 594 // moves. This will perform local moves at this location, services 595 // off spot tracks, only in aggressive mode and at least one car 596 // has a new destination 597 if (Setup.isBuildAggressive() && saveReqMoves != _reqNumOfMoves) { 598 log.debug("Perform extra pass at location ({})", rl.getName()); 599 // use up to half of the available moves left for this location 600 if (_reqNumOfMoves < (rl.getMaxCarMoves() - rl.getCarMoves()) / 2) { 601 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2; 602 } 603 findDestinationsForCarsFromLocation(rl, true); // second pass 604 605 // we might have freed up space at a spur that has an alternate 606 // track 607 if (redirectCarsFromAlternateTrack()) { 608 addLine(SEVEN, BLANK_LINE); 609 } 610 } 611 if (rl == getTrain().getTrainDepartsRouteLocation() && 612 pass == Setup.getNumberPasses() && 613 isCarStuckStaging()) { 614 return; // report ASAP that there are stuck cars 615 } 616 addLine(ONE, 617 Bundle.getMessage("buildStatusMsg", 618 (saveReqMoves <= _completedMoves ? Bundle.getMessage("Success") 619 : Bundle.getMessage("Partial")), 620 Integer.toString(_completedMoves), Integer.toString(saveReqMoves), rl.getName(), 621 getTrain().getName())); 622 623 if (_reqNumOfMoves <= 0 && pass == Setup.getNumberPasses()) { 624 showCarsNotMoved(rl); 625 } 626 } 627 } 628 629 private void setTrainBuildStatus() { 630 if (_numberCars < getTrain().getNumberCarsRequested()) { 631 getTrain().setStatusCode(Train.CODE_PARTIAL_BUILT); 632 addLine(ONE, 633 Train.PARTIAL_BUILT + 634 " " + 635 getTrain().getNumberCarsWorked() + 636 "/" + 637 getTrain().getNumberCarsRequested() + 638 " " + 639 Bundle.getMessage("cars")); 640 } else { 641 getTrain().setStatusCode(Train.CODE_BUILT); 642 addLine(ONE, 643 Train.BUILT + " " + getTrain().getNumberCarsWorked() + " " + Bundle.getMessage("cars")); 644 } 645 } 646 647 private void createManifests() throws BuildFailedException { 648 new TrainManifest(getTrain()); 649 try { 650 new JsonManifest(getTrain()).build(); 651 } catch ( 652 IOException | 653 RuntimeException ex) { 654 log.error("Unable to create JSON manifest: {}", ex.getLocalizedMessage()); 655 log.error("JSON manifest stack trace:", ex); 656 throw new BuildFailedException(ex); 657 } 658 new TrainCsvManifest(getTrain()); 659 } 660 661 private void showWarningMessage() { 662 if (trainManager.isBuildMessagesEnabled() && _warnings > 0) { 663 JmriJOptionPane.showMessageDialog(null, 664 Bundle.getMessage("buildCheckReport", getTrain().getName(), getTrain().getDescription()), 665 Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings), 666 JmriJOptionPane.WARNING_MESSAGE); 667 } 668 } 669 670 private void buildFailed(BuildFailedException e) { 671 String msg = e.getMessage(); 672 getTrain().setBuildFailedMessage(msg); 673 getTrain().setBuildFailed(true); 674 log.debug(msg); 675 676 if (trainManager.isBuildMessagesEnabled()) { 677 // don't pass the object getTrain() to the GUI, can cause thread lock 678 String trainName = getTrain().getName(); 679 String trainDescription = getTrain().getDescription(); 680 if (e.getExceptionType().equals(BuildFailedException.NORMAL)) { 681 JmriJOptionPane.showMessageDialog(null, msg, 682 Bundle.getMessage("buildErrorMsg", trainName, trainDescription), JmriJOptionPane.ERROR_MESSAGE); 683 } else { 684 // build error, could not find destinations for cars departing 685 // staging 686 Object[] options = {Bundle.getMessage("buttonRemoveCars"), Bundle.getMessage("ButtonOK")}; 687 int results = JmriJOptionPane.showOptionDialog(null, msg, 688 Bundle.getMessage("buildErrorMsg", trainName, trainDescription), 689 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.ERROR_MESSAGE, null, options, options[1]); 690 if (results == 0) { 691 log.debug("User requested that cars be removed from staging track"); 692 removeCarsFromStaging(); 693 } 694 } 695 int size = carManager.getList(getTrain()).size(); 696 if (size > 0) { 697 if (JmriJOptionPane.showConfirmDialog(null, 698 Bundle.getMessage("buildCarsResetTrain", size, trainName), 699 Bundle.getMessage("buildResetTrain"), 700 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 701 getTrain().setStatusCode(Train.CODE_TRAIN_RESET); 702 } 703 } else if ((size = engineManager.getList(getTrain()).size()) > 0) { 704 if (JmriJOptionPane.showConfirmDialog(null, 705 Bundle.getMessage("buildEnginesResetTrain", size, trainName), 706 Bundle.getMessage("buildResetTrain"), 707 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 708 getTrain().setStatusCode(Train.CODE_TRAIN_RESET); 709 } 710 } 711 } else { 712 // build messages disabled 713 // remove cars and engines from this train via property change 714 getTrain().setStatusCode(Train.CODE_TRAIN_RESET); 715 } 716 717 getTrain().setStatusCode(Train.CODE_BUILD_FAILED); 718 719 if (getBuildReport() != null) { 720 addLine(ONE, msg); 721 // Write to disk and close buildReport 722 addLine(ONE, 723 Bundle.getMessage("buildFailedMsg", getTrain().getName())); 724 getBuildReport().flush(); 725 getBuildReport().close(); 726 } 727 } 728 729 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilder.class); 730 731}