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 manualBuild(); // adds cars to train based on user's requests 106 removeCaboosesAndCarsWithFred(); // done with cabooses and FRED 107 blockCarsFromStaging(); // block cars from staging 108 showTracksNotQuickService(); // list tracks that aren't using quick service 109 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, BLANK_LINE); 324 addLine(ONE, Bundle.getMessage("mbuildFound", getTrain().getName())); 325 for (TrainManualBuildItem mbi : manualBuild.getItemsBySequenceList()) { 326 addLine(THREE, 327 Bundle.getMessage("mbuildRequest", mbi.getId(), mbi.getTypeName(), mbi.getRoadName(), 328 mbi.getLoadName(), mbi.getLocationName(), mbi.getLocationTrackName(), 329 mbi.getDestinationName(), mbi.getDestinationTrackName(), mbi.getTrainScheduleName(), 330 mbi.getCount())); 331 // the number of cars requested by this line item 332 int count = mbi.getCount(); 333 if (count > 0) { 334 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 335 Car car = getCarList().get(_carIndex); 336 // find a car that meets the user's requests 337 if (!checkItem(mbi, car)) { 338 continue; 339 } 340 addLine(FIVE, 341 Bundle.getMessage("mbuildFoundCar", car.getTypeName(), car.getTypeExtensions(), 342 car.getRoadName(), car.getLoadName(), car.getLocationName(), car.getTrackName())); 343 // there could be a an optional destination 344 car.setFinalDestination(mbi.getDestination()); 345 car.setFinalDestinationTrack(mbi.getDestinationTrack()); 346 boolean carAdded = false; 347 // case where the user specified a route location 348 if (mbi.getRouteLocation() != null) { 349 carAdded = findDestinationForCar(mbi.getRouteLocation(), car); 350 } else { 351 for (RouteLocation rl : getRouteList()) { 352 // start looking at car's location 353 if (rl.getLocation() == car.getLocation()) { 354 carAdded = findDestinationForCar(rl, car); 355 } 356 if (carAdded) { 357 break; // done 358 } 359 } 360 } 361 // if not assigned to train, clear final destination 362 if (!carAdded) { 363 car.setFinalDestination(null); 364 car.setFinalDestinationTrack(null); 365 } else { 366 if (--count <= 0) { 367 break; // done 368 } 369 } 370 } 371 } 372 373 addLine(THREE, Bundle.getMessage("mbuildCompleted", mbi.getId(), manualBuild.getTrainName(), 374 mbi.getCount() - count, mbi.getCount())); 375 addLine(THREE, BLANK_LINE); 376 377 if (count > 0 && mbi.isFailEnabled()) { 378 throw new BuildFailedException( 379 Bundle.getMessage("mbuildFail", manualBuild.getTrainName(), mbi.getId())); 380 } else if (count > 0 && mbi.isWarnEnabled()) { 381 _warnings++; 382 addLine(ONE, Bundle.getMessage("mbuildWarn", manualBuild.getTrainName(), mbi.getId())); 383 addLine(ONE, BLANK_LINE); 384 } 385 386 if (mbi.isRemoveEnabled()) { 387 for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) { 388 Car car = getCarList().get(_carIndex); 389 // find a car that meets the user's requests 390 if (checkItem(mbi, car)) { 391 addLine(FIVE, Bundle.getMessage("mbuildRemove", mbi.getId(), car.toString(), car.getTrackTypeName(), 392 car.getLocationName(), car.getTrackName())); 393 remove(car); 394 } 395 } 396 addLine(FIVE, BLANK_LINE); 397 } 398 } 399 addLine(ONE, Bundle.getMessage("mbuildDone")); 400 } 401 402 /* 403 * Returns true if the car matches the user's request. Car can not be 404 * assigned to this train, have a destination, or a final destination. 405 */ 406 private boolean checkItem(TrainManualBuildItem mbi, Car car) { 407 if (car.getTrain() != null) { 408 log.debug("Car ({}) is assigned to train ({})", car.toString(), car.getTrain().getName()); 409 return false; 410 } 411 if (car.getDestination() != null) { 412 log.debug("Car ({}) has destination ({})", car.toString(), car.getDestination().getName()); 413 return false; 414 } 415 if (car.getFinalDestination() != null) { 416 log.debug("Car ({}) has final destination ({})", car.toString(), 417 car.getFinalDestination().getName()); 418 return false; 419 } 420 if (!mbi.getTypeName().equals(Car.NONE) && !car.getTypeName().equals(mbi.getTypeName())) { 421 return false; 422 } 423 if (!mbi.getRoadName().equals(Car.NONE) && !car.getRoadName().equals(mbi.getRoadName())) { 424 return false; 425 } 426 if (!mbi.getLoadName().equals(Car.NONE) && !car.getLoadName().equals(mbi.getLoadName())) { 427 return false; 428 } 429 if (mbi.getRouteLocation() != null && car.getLocation() != mbi.getRouteLocation().getLocation()) { 430 return false; 431 } 432 if (mbi.getLocationTrack() != null && car.getTrack() != mbi.getLocationTrack()) { 433 return false; 434 } 435 // pick up day 436 if (trainScheduleManager.getActiveSchedule() != null && 437 !mbi.getTrainScheduleName().equals(TrainManualBuildItem.NONE) && 438 !trainScheduleManager.getActiveSchedule().getId().equals(mbi.getTrainScheduleId())) { 439 return false; 440 } 441 return true; 442 } 443 444 private boolean findDestinationForCar(RouteLocation rl, Car car) throws BuildFailedException { 445 if (!checkRouteLocationMoves(rl, car)) { 446 return false; 447 } 448 if (!checkRouteLocation(rl)) { 449 addLine(ONE, BLANK_LINE); 450 return false; 451 } 452 // if car is sent to quick service track, moves for car is bumped 453 int moves = car.getMoves(); 454 findDestinationsFromLocation(rl, car, false); 455 if (car.getTrain() == null && moves == car.getMoves()) { 456 return false; 457 } 458 return true; 459 } 460 461 /* 462 * returns true if there are moves available at the car's departure route 463 * location 464 */ 465 private boolean checkRouteLocationMoves(RouteLocation rl, Car car) { 466 if (rl.getCarMoves() < rl.getMaxCarMoves()) { 467 return true; 468 } 469 addLine(FIVE, Bundle.getMessage("mbuildRouteLocationMoves", rl.getName(), rl.getId(), rl.getMaxCarMoves(), 470 rl.getMaxCarMoves() - rl.getCarMoves())); 471 addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString())); 472 addLine(FIVE, BLANK_LINE); 473 return false; 474 } 475 476 /** 477 * Routine to find and add available cars to the train. In normal mode 478 * performs a single pass. In aggressive mode, will perform multiple passes. 479 * If train is departing staging and in aggressive mode, will try again 480 * using normal mode if there's a train build issue. 481 * 482 * @throws BuildFailedException 483 */ 484 private void addCarsToTrain() throws BuildFailedException { 485 addLine(THREE, BLANK_LINE); 486 addLine(THREE, 487 Bundle.getMessage("buildTrain", getTrain().getNumberCarsRequested(), getTrain().getName(), 488 getCarList().size())); 489 490 if (Setup.isBuildAggressive() && !getTrain().isBuildTrainNormalEnabled()) { 491 // perform a multiple pass build for this train, default is two 492 // passes 493 int pass = 0; 494 while (pass++ < Setup.getNumberPasses()) { 495 addCarsToTrain(pass, false); 496 } 497 // are cars stuck in staging? 498 secondAttemptNormalBuild(); 499 } else { 500 addCarsToTrain(Setup.getNumberPasses(), true); // normal build one 501 // pass 502 } 503 } 504 505 /** 506 * If cars stuck in staging, try building again in normal mode. 507 * 508 * @throws BuildFailedException 509 */ 510 private void secondAttemptNormalBuild() throws BuildFailedException { 511 if (Setup.isStagingTryNormalBuildEnabled() && isCarStuckStaging()) { 512 addLine(ONE, Bundle.getMessage("buildFailedTryNormalMode")); 513 addLine(ONE, BLANK_LINE); 514 getTrain().reset(); 515 getTrain().setStatusCode(Train.CODE_BUILDING); 516 getTrain().setLeadEngine(null); 517 // using the same departure and termination tracks 518 getTrain().setDepartureTrack(getDepartureStagingTrack()); 519 getTrain().setTerminationTrack(getTerminateStagingTrack()); 520 showAndInitializeTrainRoute(); 521 getAndRemoveEnginesFromList(); 522 addEnginesToTrain(); 523 createCarList(); 524 adjustCarsInStaging(); 525 showCarsByLocation(); 526 addCabooseOrFredToTrain(); 527 removeCaboosesAndCarsWithFred(); 528 saveCarFinalDestinations(); // save final destination and schedule 529 // id 530 blockCarsFromStaging(); // block cars from staging 531 addCarsToTrain(Setup.getNumberPasses(), true); // try normal build 532 // one pass 533 } 534 } 535 536 /** 537 * Main routine to place cars into the train. Can be called multiple times. 538 * When departing staging, ignore staged cars on the first pass unless the 539 * option to build normal was selected by user. 540 * 541 * @param pass Which pass when there are multiple passes requested by 542 * user. 543 * @param normal True if single pass or normal mode is requested by user. 544 * @throws BuildFailedException 545 */ 546 private void addCarsToTrain(int pass, boolean normal) throws BuildFailedException { 547 addLine(THREE, BLANK_LINE); 548 if (normal) { 549 addLine(THREE, Bundle.getMessage("NormalModeWhenBuilding")); 550 } else { 551 addLine(THREE, Bundle.getMessage("buildMultiplePass", pass, Setup.getNumberPasses())); 552 } 553 // now go through each location starting at departure and place cars as 554 // requested 555 for (RouteLocation rl : getRouteList()) { 556 if (!checkRouteLocation(rl)) { 557 continue; 558 } 559 560 _completedMoves = 0; // moves completed for this location 561 _reqNumOfMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 562 563 if (!normal) { 564 if (rl == getTrain().getTrainDepartsRouteLocation()) { 565 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) * pass / Setup.getNumberPasses(); 566 } else if (pass == 1) { 567 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2; 568 // round up requested moves 569 int remainder = (rl.getMaxCarMoves() - rl.getCarMoves()) % 2; 570 if (remainder > 0) { 571 _reqNumOfMoves++; 572 } 573 } 574 } 575 576 // if departing staging make adjustments 577 if (rl == getTrain().getTrainDepartsRouteLocation()) { 578 if (pass == 1) { 579 makeAdjustmentsIfDepartingStaging(); 580 } else { 581 restoreCarsIfDepartingStaging(); 582 } 583 } 584 585 int saveReqMoves = _reqNumOfMoves; // save a copy for status message 586 addLine(ONE, 587 Bundle.getMessage("buildLocReqMoves", rl.getName(), rl.getId(), _reqNumOfMoves, 588 rl.getMaxCarMoves() - rl.getCarMoves(), rl.getMaxCarMoves())); 589 addLine(FIVE, BLANK_LINE); 590 591 // show the car load generation options for staging 592 if (rl == getTrain().getTrainDepartsRouteLocation()) { 593 showLoadGenerationOptionsStaging(); 594 } 595 596 _carIndex = 0; // see reportCarsNotMoved(rl) below 597 598 findDestinationsForCarsFromLocation(rl, false); // first pass 599 600 // perform 2nd pass if aggressive mode and there are requested 601 // moves. This will perform local moves at this location, services 602 // off spot tracks, only in aggressive mode and at least one car 603 // has a new destination 604 if (Setup.isBuildAggressive() && saveReqMoves != _reqNumOfMoves) { 605 log.debug("Perform extra pass at location ({})", rl.getName()); 606 // use up to half of the available moves left for this location 607 if (_reqNumOfMoves < (rl.getMaxCarMoves() - rl.getCarMoves()) / 2) { 608 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2; 609 } 610 findDestinationsForCarsFromLocation(rl, true); // second pass 611 612 // we might have freed up space at a spur that has an alternate 613 // track 614 if (redirectCarsFromAlternateTrack()) { 615 addLine(SEVEN, BLANK_LINE); 616 } 617 } 618 if (rl == getTrain().getTrainDepartsRouteLocation() && 619 pass == Setup.getNumberPasses() && 620 isCarStuckStaging()) { 621 return; // report ASAP that there are stuck cars 622 } 623 addLine(ONE, 624 Bundle.getMessage("buildStatusMsg", 625 (saveReqMoves <= _completedMoves ? Bundle.getMessage("Success") 626 : Bundle.getMessage("Partial")), 627 Integer.toString(_completedMoves), Integer.toString(saveReqMoves), rl.getName(), 628 getTrain().getName())); 629 630 if (_reqNumOfMoves <= 0 && pass == Setup.getNumberPasses()) { 631 showCarsNotMoved(rl); 632 } 633 } 634 } 635 636 private void setTrainBuildStatus() { 637 if (_numberCars < getTrain().getNumberCarsRequested()) { 638 getTrain().setStatusCode(Train.CODE_PARTIAL_BUILT); 639 addLine(ONE, 640 Train.PARTIAL_BUILT + 641 " " + 642 getTrain().getNumberCarsWorked() + 643 "/" + 644 getTrain().getNumberCarsRequested() + 645 " " + 646 Bundle.getMessage("cars")); 647 } else { 648 getTrain().setStatusCode(Train.CODE_BUILT); 649 addLine(ONE, 650 Train.BUILT + " " + getTrain().getNumberCarsWorked() + " " + Bundle.getMessage("cars")); 651 } 652 } 653 654 private void createManifests() throws BuildFailedException { 655 new TrainManifest(getTrain()); 656 try { 657 new JsonManifest(getTrain()).build(); 658 } catch ( 659 IOException | 660 RuntimeException ex) { 661 log.error("Unable to create JSON manifest: {}", ex.getLocalizedMessage()); 662 log.error("JSON manifest stack trace:", ex); 663 throw new BuildFailedException(ex); 664 } 665 new TrainCsvManifest(getTrain()); 666 } 667 668 private void showWarningMessage() { 669 if (trainManager.isBuildMessagesEnabled() && _warnings > 0) { 670 JmriJOptionPane.showMessageDialog(null, 671 Bundle.getMessage("buildCheckReport", getTrain().getName(), getTrain().getDescription()), 672 Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings), 673 JmriJOptionPane.WARNING_MESSAGE); 674 } 675 } 676 677 private void buildFailed(BuildFailedException e) { 678 String msg = e.getMessage(); 679 getTrain().setBuildFailedMessage(msg); 680 getTrain().setBuildFailed(true); 681 log.debug(msg); 682 683 if (trainManager.isBuildMessagesEnabled()) { 684 // don't pass the object getTrain() to the GUI, can cause thread lock 685 String trainName = getTrain().getName(); 686 String trainDescription = getTrain().getDescription(); 687 if (e.getExceptionType().equals(BuildFailedException.NORMAL)) { 688 JmriJOptionPane.showMessageDialog(null, msg, 689 Bundle.getMessage("buildErrorMsg", trainName, trainDescription), JmriJOptionPane.ERROR_MESSAGE); 690 } else { 691 // build error, could not find destinations for cars departing 692 // staging 693 Object[] options = {Bundle.getMessage("buttonRemoveCars"), Bundle.getMessage("ButtonOK")}; 694 int results = JmriJOptionPane.showOptionDialog(null, msg, 695 Bundle.getMessage("buildErrorMsg", trainName, trainDescription), 696 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.ERROR_MESSAGE, null, options, options[1]); 697 if (results == 0) { 698 log.debug("User requested that cars be removed from staging track"); 699 removeCarsFromStaging(); 700 } 701 } 702 int size = carManager.getList(getTrain()).size(); 703 if (size > 0) { 704 if (JmriJOptionPane.showConfirmDialog(null, 705 Bundle.getMessage("buildCarsResetTrain", size, trainName), 706 Bundle.getMessage("buildResetTrain"), 707 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 708 getTrain().setStatusCode(Train.CODE_TRAIN_RESET); 709 } 710 } else if ((size = engineManager.getList(getTrain()).size()) > 0) { 711 if (JmriJOptionPane.showConfirmDialog(null, 712 Bundle.getMessage("buildEnginesResetTrain", size, trainName), 713 Bundle.getMessage("buildResetTrain"), 714 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 715 getTrain().setStatusCode(Train.CODE_TRAIN_RESET); 716 } 717 } 718 } else { 719 // build messages disabled 720 // remove cars and engines from this train via property change 721 getTrain().setStatusCode(Train.CODE_TRAIN_RESET); 722 } 723 724 getTrain().setStatusCode(Train.CODE_BUILD_FAILED); 725 726 if (getBuildReport() != null) { 727 addLine(ONE, msg); 728 // Write to disk and close buildReport 729 addLine(ONE, 730 Bundle.getMessage("buildFailedMsg", getTrain().getName())); 731 getBuildReport().flush(); 732 getBuildReport().close(); 733 } 734 } 735 736 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilder.class); 737 738}