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