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 showTrainCabooseRoads(); // show caboose roads that this train will service 093 showTrainCarTypes(); // show car types that this train will service 094 showTrainLoadNames(); // show load names that this train will service 095 getCarList(); // remove unwanted cars 096 adjustCarsInStaging(); // adjust for cars on one staging track 097 showCarsByLocation(); // list available cars by location 098 sortCarsOnFifoLifoTracks(); // sort cars on FIFO or LIFO tracks 099 saveCarFinalDestinations(); // save car's final dest and schedule id 100 addCabooseOrFredToTrain(); // caboose and FRED changes 101 removeCaboosesAndCarsWithFred(); // done with cabooses and FRED 102 blockCarsFromStaging(); // block cars from staging 103 104 addCarsToTrain(); // finds and adds cars to the train (main routine) 105 106 checkStuckCarsInStaging(); // determine if cars are stuck in staging 107 showTrainBuildStatus(); // show how well the build went 108 checkEngineHP(); // determine if train has appropriate engine HP 109 checkNumnberOfEnginesNeededHPT(); // check train engine requirements 110 showCarsNotRoutable(); // list cars that couldn't be routed 111 112 // done building 113 if (_warnings > 0) { 114 addLine(_buildReport, ONE, Bundle.getMessage("buildWarningMsg", _train.getName(), _warnings)); 115 } 116 addLine(_buildReport, FIVE, 117 Bundle.getMessage("buildTime", _train.getName(), new Date().getTime() - _startTime.getTime())); 118 119 _buildReport.flush(); 120 _buildReport.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 _train.setPrinted(false); 132 _train.setSwitchListStatus(Train.UNKNOWN); 133 _train.setCurrentLocation(_train.getTrainDepartsRouteLocation()); 134 _train.setBuilt(true); 135 // create and place train icon 136 _train.moveTrainIcon(_train.getTrainDepartsRouteLocation()); 137 138 log.debug("Done building train ({})", _train.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 _terminateStageTrack is null, and is loaded later when the 146 * 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 _terminateStageTrack = null; 153 List<Track> stagingTracksTerminate = _terminateLocation.getTracksByMoves(Track.STAGING); 154 if (stagingTracksTerminate.size() > 0) { 155 addLine(_buildReport, THREE, BLANK_LINE); 156 addLine(_buildReport, ONE, Bundle.getMessage("buildTerminateStaging", _terminateLocation.getName(), 157 Integer.toString(stagingTracksTerminate.size()))); 158 if (stagingTracksTerminate.size() > 1 && Setup.isStagingPromptToEnabled()) { 159 _terminateStageTrack = promptToStagingDialog(); 160 _startTime = 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 (_departLocation == _terminateLocation && 166 Setup.isBuildAggressive() && 167 Setup.isStagingTrackImmediatelyAvail()) { 168 addLine(_buildReport, ONE, Bundle.getMessage("buildStagingReturn", _terminateLocation.getName())); 169 } else { 170 for (Track track : stagingTracksTerminate) { 171 if (checkTerminateStagingTrack(track)) { 172 _terminateStageTrack = track; 173 addLine(_buildReport, ONE, Bundle.getMessage("buildStagingAvail", 174 _terminateStageTrack.getName(), _terminateLocation.getName())); 175 break; 176 } 177 } 178 } 179 } 180 if (_terminateStageTrack == null) { 181 // is this train returning to the same staging in aggressive 182 // mode? 183 if (_departLocation == _terminateLocation && 184 Setup.isBuildAggressive() && 185 Setup.isStagingTrackImmediatelyAvail()) { 186 log.debug("Train is returning to same track in staging"); 187 } else { 188 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorStagingFullNote")); 189 throw new BuildFailedException( 190 Bundle.getMessage("buildErrorStagingFull", _terminateLocation.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 = _train.getTrainTerminatesRouteLocation(); 206 207 // Adjust where the locos will terminate 208 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 209 _train.getSecondLegStartRouteLocation() != null) { 210 engineTerminatesFirstLeg = _train.getSecondLegStartRouteLocation(); 211 } else if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 212 _train.getThirdLegStartRouteLocation() != null) { 213 engineTerminatesFirstLeg = _train.getThirdLegStartRouteLocation(); 214 } 215 216 // determine if train is departing staging 217 List<Track> stagingTracks = _departLocation.getTracksByMoves(Track.STAGING); 218 if (stagingTracks.size() > 0) { 219 addLine(_buildReport, THREE, BLANK_LINE); 220 addLine(_buildReport, ONE, Bundle.getMessage("buildDepartStaging", _departLocation.getName(), 221 Integer.toString(stagingTracks.size()))); 222 if (stagingTracks.size() > 1 && Setup.isStagingPromptFromEnabled()) { 223 setDepartureTrack(promptFromStagingDialog()); 224 _startTime = new Date(); // restart build timer 225 if (_departStageTrack == null) { 226 showTrainRequirements(); 227 throw new BuildFailedException( 228 Bundle.getMessage("buildErrorStagingEmpty", _departLocation.getName())); 229 } 230 } else { 231 for (Track track : stagingTracks) { 232 // is the departure track available? 233 if (!checkDepartureStagingTrack(track)) { 234 addLine(_buildReport, SEVEN, 235 Bundle.getMessage("buildStagingTrackRestriction", track.getName(), _train.getName())); 236 continue; 237 } 238 setDepartureTrack(track); 239 // try each departure track for the required engines 240 if (getEngines(_train.getNumberEngines(), _train.getEngineModel(), _train.getEngineRoad(), 241 _train.getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) { 242 addLine(_buildReport, SEVEN, Bundle.getMessage("buildDoneAssignEnginesStaging")); 243 break; // done! 244 } 245 setDepartureTrack(null); 246 } 247 } 248 if (_departStageTrack == null) { 249 showTrainRequirements(); 250 throw new BuildFailedException(Bundle.getMessage("buildErrorStagingEmpty", _departLocation.getName())); 251 } 252 } 253 _train.setTerminationTrack(_terminateStageTrack); 254 _train.setDepartureTrack(_departStageTrack); 255 } 256 257 /** 258 * Adds and removes cabooses or car with FRED in the train's route. Up to 2 259 * caboose changes. 260 * 261 * @throws BuildFailedException 262 */ 263 private void addCabooseOrFredToTrain() throws BuildFailedException { 264 // allow up to two caboose swaps in the train's route 265 RouteLocation cabooseOrFredTerminatesFirstLeg = _train.getTrainTerminatesRouteLocation(); 266 RouteLocation cabooseOrFredTerminatesSecondLeg = _train.getTrainTerminatesRouteLocation(); 267 268 // determine if there are any caboose changes 269 if ((_train.getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 270 (_train.getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 271 cabooseOrFredTerminatesFirstLeg = _train.getSecondLegStartRouteLocation(); 272 } else if ((_train.getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 273 (_train.getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 274 cabooseOrFredTerminatesFirstLeg = _train.getThirdLegStartRouteLocation(); 275 } 276 if ((_train.getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE || 277 (_train.getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) { 278 cabooseOrFredTerminatesSecondLeg = _train.getThirdLegStartRouteLocation(); 279 } 280 281 // Do caboose changes in reverse order in case there isn't enough track 282 // space second caboose change? 283 if ((_train.getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE && 284 _train.getThirdLegStartRouteLocation() != null && 285 _train.getTrainTerminatesRouteLocation() != null) { 286 getCaboose(_train.getThirdLegCabooseRoad(), _thirdLeadEngine, _train.getThirdLegStartRouteLocation(), 287 _train.getTrainTerminatesRouteLocation(), true); 288 } 289 290 // first caboose change? 291 if ((_train.getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE && 292 _train.getSecondLegStartRouteLocation() != null && 293 cabooseOrFredTerminatesSecondLeg != null) { 294 getCaboose(_train.getSecondLegCabooseRoad(), _secondLeadEngine, _train.getSecondLegStartRouteLocation(), 295 cabooseOrFredTerminatesSecondLeg, true); 296 } 297 298 // departure caboose or car with FRED 299 getCaboose(_train.getCabooseRoad(), _train.getLeadEngine(), _train.getTrainDepartsRouteLocation(), 300 cabooseOrFredTerminatesFirstLeg, _train.isCabooseNeeded()); 301 getCarWithFred(_train.getCabooseRoad(), _train.getTrainDepartsRouteLocation(), cabooseOrFredTerminatesFirstLeg); 302 } 303 304 /** 305 * Routine to find and add available cars to the train. In normal mode 306 * performs a single pass. In aggressive mode, will perform multiple passes. 307 * If train is departing staging and in aggressive mode, will try again 308 * using normal mode if there's a train build issue. 309 * 310 * @throws BuildFailedException 311 */ 312 private void addCarsToTrain() throws BuildFailedException { 313 addLine(_buildReport, THREE, BLANK_LINE); 314 addLine(_buildReport, THREE, 315 Bundle.getMessage("buildTrain", _train.getNumberCarsRequested(), _train.getName(), _carList.size())); 316 317 if (Setup.isBuildAggressive() && !_train.isBuildTrainNormalEnabled()) { 318 // perform a multiple pass build for this train, default is two 319 // passes 320 int pass = 0; 321 while (pass++ < Setup.getNumberPasses()) { 322 addCarsToTrain(pass, false); 323 } 324 // are cars stuck in staging? 325 secondAttemptNormalBuild(); 326 } else { 327 addCarsToTrain(Setup.getNumberPasses(), true); // normal build one 328 // pass 329 } 330 } 331 332 /** 333 * If cars stuck in staging, try building again in normal mode. 334 * 335 * @throws BuildFailedException 336 */ 337 private void secondAttemptNormalBuild() throws BuildFailedException { 338 if (Setup.isStagingTryNormalBuildEnabled() && isCarStuckStaging()) { 339 addLine(_buildReport, ONE, Bundle.getMessage("buildFailedTryNormalMode")); 340 addLine(_buildReport, ONE, BLANK_LINE); 341 _train.reset(); 342 _train.setStatusCode(Train.CODE_BUILDING); 343 _train.setLeadEngine(null); 344 // using the same departure and termination tracks 345 _train.setDepartureTrack(_departStageTrack); 346 _train.setTerminationTrack(_terminateStageTrack); 347 showAndInitializeTrainRoute(); 348 getAndRemoveEnginesFromList(); 349 addEnginesToTrain(); 350 getCarList(); 351 adjustCarsInStaging(); 352 showCarsByLocation(); 353 addCabooseOrFredToTrain(); 354 removeCaboosesAndCarsWithFred(); 355 saveCarFinalDestinations(); // save final destination and schedule 356 // id 357 blockCarsFromStaging(); // block cars from staging 358 addCarsToTrain(Setup.getNumberPasses(), true); // try normal build 359 // one pass 360 } 361 } 362 363 /** 364 * Main routine to place cars into the train. Can be called multiple times. 365 * When departing staging, ignore staged cars on the first pass unless the 366 * option to build normal was selected by user. 367 * 368 * @param pass Which pass when there are multiple passes requested by 369 * user. 370 * @param normal True if single pass or normal mode is requested by user. 371 * @throws BuildFailedException 372 */ 373 private void addCarsToTrain(int pass, boolean normal) throws BuildFailedException { 374 addLine(_buildReport, THREE, BLANK_LINE); 375 if (normal) { 376 addLine(_buildReport, THREE, Bundle.getMessage("NormalModeWhenBuilding")); 377 } else { 378 addLine(_buildReport, THREE, Bundle.getMessage("buildMultiplePass", pass, Setup.getNumberPasses())); 379 } 380 // now go through each location starting at departure and place cars as 381 // requested 382 for (RouteLocation rl : _routeList) { 383 if (_train.isLocationSkipped(rl.getId())) { 384 addLine(_buildReport, ONE, 385 Bundle.getMessage("buildLocSkipped", rl.getName(), rl.getId(), _train.getName())); 386 continue; 387 } 388 if (!rl.isPickUpAllowed()) { 389 addLine(_buildReport, ONE, 390 Bundle.getMessage("buildLocNoPickups", _train.getRoute().getName(), rl.getId(), rl.getName())); 391 continue; 392 } 393 // no pick ups from staging unless at the start of the train's route 394 if (rl != _train.getTrainDepartsRouteLocation() && rl.getLocation().isStaging()) { 395 addLine(_buildReport, ONE, Bundle.getMessage("buildNoPickupsFromStaging", rl.getName())); 396 continue; 397 } 398 // the next check provides a build report message if there's an 399 // issue with the train direction 400 if (!checkPickUpTrainDirection(rl)) { 401 continue; 402 } 403 _completedMoves = 0; // moves completed for this location 404 _reqNumOfMoves = rl.getMaxCarMoves() - rl.getCarMoves(); 405 406 if (!normal) { 407 if (rl == _train.getTrainDepartsRouteLocation()) { 408 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) * pass / Setup.getNumberPasses(); 409 } else if (pass == 1) { 410 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2; 411 // round up requested moves 412 int remainder = (rl.getMaxCarMoves() - rl.getCarMoves()) % 2; 413 if (remainder > 0) { 414 _reqNumOfMoves++; 415 } 416 } 417 } 418 419 // if departing staging make adjustments 420 if (rl == _train.getTrainDepartsRouteLocation()) { 421 if (pass == 1) { 422 makeAdjustmentsIfDepartingStaging(); 423 } else { 424 restoreCarsIfDepartingStaging(); 425 } 426 } 427 428 int saveReqMoves = _reqNumOfMoves; // save a copy for status message 429 addLine(_buildReport, ONE, 430 Bundle.getMessage("buildLocReqMoves", rl.getName(), rl.getId(), _reqNumOfMoves, 431 rl.getMaxCarMoves() - rl.getCarMoves(), rl.getMaxCarMoves())); 432 addLine(_buildReport, FIVE, BLANK_LINE); 433 434 // show the car load generation options for staging 435 if (rl == _train.getTrainDepartsRouteLocation()) { 436 showLoadGenerationOptionsStaging(); 437 } 438 439 _carIndex = 0; // see reportCarsNotMoved(rl) below 440 441 findDestinationsForCarsFromLocation(rl, false); // first pass 442 443 // perform 2nd pass if aggressive mode and there are requested 444 // moves. This will perform local moves at this location, services 445 // off spot tracks, only in aggressive mode and at least one car 446 // has a new destination 447 if (Setup.isBuildAggressive() && saveReqMoves != _reqNumOfMoves) { 448 log.debug("Perform extra pass at location ({})", rl.getName()); 449 // use up to half of the available moves left for this location 450 if (_reqNumOfMoves < (rl.getMaxCarMoves() - rl.getCarMoves()) / 2) { 451 _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2; 452 } 453 findDestinationsForCarsFromLocation(rl, true); // second pass 454 455 // we might have freed up space at a spur that has an alternate 456 // track 457 if (redirectCarsFromAlternateTrack()) { 458 addLine(_buildReport, SEVEN, BLANK_LINE); 459 } 460 } 461 if (rl == _train.getTrainDepartsRouteLocation() && pass == Setup.getNumberPasses() && isCarStuckStaging()) { 462 return; // report ASAP that there are stuck cars 463 } 464 addLine(_buildReport, ONE, 465 Bundle.getMessage("buildStatusMsg", 466 (saveReqMoves <= _completedMoves ? Bundle.getMessage("Success") 467 : Bundle.getMessage("Partial")), 468 Integer.toString(_completedMoves), Integer.toString(saveReqMoves), rl.getName(), 469 _train.getName())); 470 471 if (_reqNumOfMoves <= 0 && pass == Setup.getNumberPasses()) { 472 showCarsNotMoved(rl); 473 } 474 } 475 } 476 477 private void showTrainBuildStatus() { 478 if (_numberCars < _train.getNumberCarsRequested()) { 479 _train.setStatusCode(Train.CODE_PARTIAL_BUILT); 480 addLine(_buildReport, ONE, 481 Train.PARTIAL_BUILT + 482 " " + 483 _train.getNumberCarsWorked() + 484 "/" + 485 _train.getNumberCarsRequested() + 486 " " + 487 Bundle.getMessage("cars")); 488 } else { 489 _train.setStatusCode(Train.CODE_BUILT); 490 addLine(_buildReport, ONE, 491 Train.BUILT + " " + _train.getNumberCarsWorked() + " " + Bundle.getMessage("cars")); 492 } 493 } 494 495 private void createManifests() throws BuildFailedException { 496 new TrainManifest(_train); 497 try { 498 new JsonManifest(_train).build(); 499 } catch (IOException ex) { 500 log.error("Unable to create JSON manifest: {}", ex.getLocalizedMessage()); 501 throw new BuildFailedException(ex); 502 } 503 new TrainCsvManifest(_train); 504 } 505 506 private void showWarningMessage() { 507 if (trainManager.isBuildMessagesEnabled() && _warnings > 0) { 508 JmriJOptionPane.showMessageDialog(null, 509 Bundle.getMessage("buildCheckReport", _train.getName(), _train.getDescription()), 510 Bundle.getMessage("buildWarningMsg", _train.getName(), _warnings), 511 JmriJOptionPane.WARNING_MESSAGE); 512 } 513 } 514 515 private void buildFailed(BuildFailedException e) { 516 String msg = e.getMessage(); 517 _train.setBuildFailedMessage(msg); 518 _train.setBuildFailed(true); 519 log.debug(msg); 520 521 if (trainManager.isBuildMessagesEnabled()) { 522 // don't pass the object _train to the GUI, can cause thread lock 523 String trainName = _train.getName(); 524 String trainDescription = _train.getDescription(); 525 if (e.getExceptionType().equals(BuildFailedException.NORMAL)) { 526 JmriJOptionPane.showMessageDialog(null, msg, 527 Bundle.getMessage("buildErrorMsg", trainName, trainDescription), JmriJOptionPane.ERROR_MESSAGE); 528 } else { 529 // build error, could not find destinations for cars departing 530 // staging 531 Object[] options = {Bundle.getMessage("buttonRemoveCars"), Bundle.getMessage("ButtonOK")}; 532 int results = JmriJOptionPane.showOptionDialog(null, msg, 533 Bundle.getMessage("buildErrorMsg", trainName, trainDescription), 534 JmriJOptionPane.DEFAULT_OPTION, JmriJOptionPane.ERROR_MESSAGE, null, options, options[1]); 535 if (results == 0) { 536 log.debug("User requested that cars be removed from staging track"); 537 removeCarsFromStaging(); 538 } 539 } 540 int size = carManager.getList(_train).size(); 541 if (size > 0) { 542 if (JmriJOptionPane.showConfirmDialog(null, 543 Bundle.getMessage("buildCarsResetTrain", size, trainName), 544 Bundle.getMessage("buildResetTrain"), 545 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 546 _train.setStatusCode(Train.CODE_TRAIN_RESET); 547 } 548 } else if ((size = engineManager.getList(_train).size()) > 0) { 549 if (JmriJOptionPane.showConfirmDialog(null, 550 Bundle.getMessage("buildEnginesResetTrain", size, trainName), 551 Bundle.getMessage("buildResetTrain"), 552 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}