001package jmri.jmrit.operations.trains.trainbuilder; 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.jmrit.operations.trains.*; 012import jmri.jmrit.operations.trains.csv.TrainCsvManifest; 013import jmri.util.swing.JmriJOptionPane; 014 015/** 016 * Builds a train and then creates the train's manifest. 017 * 018 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 019 * 2014, 2015, 2021 020 */ 021public class TrainBuilder extends TrainBuilderCars { 022 023 /** 024 * Build rules: 025 * <ol> 026 * <li>Need at least one location in route to build train 027 * <li>Select only locos and cars that the train can service 028 * <li>If required, add caboose or car with FRED to train 029 * <li>When departing staging find a track matching train requirements 030 * <li>All cars and locos on one track must leave staging 031 * <li>Optionally block cars from staging 032 * <li>Route cars with home divisions 033 * <li>Route cars with custom loads or final destinations. 034 * <li>Service locations based on train direction, location car types, roads 035 * and loads. 036 * <li>Ignore track direction when train is a local (serves one location) 037 * </ol> 038 * <p> 039 * History: 040 * <p> 041 * First version of train builder found cars along a train's route and 042 * assigned destinations (tracks) willing to accept the car. This is called 043 * the random method as cars just bounce around the layout without purpose. 044 * Afterwards custom loads and routing was added to the program. Cars with 045 * custom loads or final destinations move with purpose as those cars are 046 * routed. The last major feature added was car divisions. Cars assigned a 047 * division are always routed. 048 * <p> 049 * The program was written around the concept of a build report. The report 050 * provides a description of the train build process and the steps taken to 051 * place rolling stock in a train. The goal was to help users understand why 052 * rolling stock was either assigned to the train or not, and which choices 053 * the program had available when determining an engine's or car's 054 * destination. 055 * 056 * @param train the train that is to be built 057 * @return True if successful. 058 */ 059 public boolean build(Train train) { 060 this._train = train; 061 try { 062 build(); 063 return true; 064 } catch (BuildFailedException e) { 065 buildFailed(e); 066 return false; 067 } 068 } 069 070 private void build() throws BuildFailedException { 071 _startTime = new Date(); 072 073 log.debug("Building train ({})", _train.getName()); 074 075 _train.setStatusCode(Train.CODE_BUILDING); 076 _train.setBuilt(false); 077 _train.setLeadEngine(null); 078 079 createBuildReportFile(); // backup build report and create new 080 showBuildReportInfo(); // add the build report header information 081 setUpRoute(); // load route, departure and terminate locations 082 showTrainBuildOptions(); // show the build options 083 showSpecificTrainBuildOptions(); // show the train build options 084 showAndInitializeTrainRoute(); // show the train's route and initialize 085 showIfLocalSwitcher(); // show if this train a switcher 086 showTrainRequirements(); // show how many engines, caboose, FRED changes 087 showTrainServices(); // engine roads, owners, built dates, and types 088 getAndRemoveEnginesFromList(); // get a list of available engines 089 showEnginesByLocation(); // list available engines by location 090 determineIfTrainTerminatesIntoStaging(); // find staging terminus track 091 determineIfTrainDepartsStagingAndAddEngines(); // add engines if staging 092 addEnginesToTrain(); // 1st, 2nd and 3rd engine swaps in a train's route 093 showTrainCarRoads(); // show car roads that this train will service 094 showTrainCabooseRoads(); // show caboose roads that this train will service 095 showTrainCarTypes(); // show car types that this train will service 096 showTrainLoadNames(); // show load names that this train will service 097 getCarList(); // remove unwanted cars 098 adjustCarsInStaging(); // adjust for cars on one staging track 099 showCarsByLocation(); // list available cars by location 100 sortCarsOnFifoLifoTracks(); // sort cars on FIFO or LIFO tracks 101 saveCarFinalDestinations(); // save car's final dest and schedule id 102 addCabooseOrFredToTrain(); // caboose and FRED changes 103 removeCaboosesAndCarsWithFred(); // done with cabooses and FRED 104 blockCarsFromStaging(); // block cars from staging 105 106 addCarsToTrain(); // finds and adds cars to the train (main routine) 107 108 checkStuckCarsInStaging(); // determine if cars are stuck in staging 109 showTrainBuildStatus(); // show how well the build went 110 checkEngineHP(); // determine if train has appropriate engine HP 111 checkNumnberOfEnginesNeededHPT(); // check train engine requirements 112 showCarsNotRoutable(); // list cars that couldn't be routed 113 114 // done building 115 if (_warnings > 0) { 116 addLine(_buildReport, ONE, Bundle.getMessage("buildWarningMsg", _train.getName(), _warnings)); 117 } 118 addLine(_buildReport, FIVE, 119 Bundle.getMessage("buildTime", _train.getName(), new Date().getTime() - _startTime.getTime())); 120 121 _buildReport.flush(); 122 _buildReport.close(); 123 124 createManifests(); // now make Manifests 125 126 // notify locations have been modified by this train's build 127 for (Location location : _modifiedLocations) { 128 location.setStatus(Location.MODIFIED); 129 } 130 131 // operations automations use wait for train built to create custom 132 // manifests and switch lists 133 _train.setPrinted(false); 134 _train.setSwitchListStatus(Train.UNKNOWN); 135 _train.setCurrentLocation(_train.getTrainDepartsRouteLocation()); 136 _train.setBuilt(true); 137 // create and place train icon 138 _train.moveTrainIcon(_train.getTrainDepartsRouteLocation()); 139 140 log.debug("Done building train ({})", _train.getName()); 141 showWarningMessage(); 142 } 143 144 /** 145 * Figures out if the train terminates into staging, and if true, sets the 146 * termination track. Note if the train is returning back to the same track 147 * in staging _terminateStageTrack is null, and is loaded later when the 148 * departure track is determined. 149 * 150 * @throws BuildFailedException if staging track can't be found 151 */ 152 private void determineIfTrainTerminatesIntoStaging() throws BuildFailedException { 153 // does train terminate into staging? 154 _terminateStageTrack = null; 155 List<Track> stagingTracksTerminate = _terminateLocation.getTracksByMoves(Track.STAGING); 156 if (stagingTracksTerminate.size() > 0) { 157 addLine(_buildReport, THREE, BLANK_LINE); 158 addLine(_buildReport, ONE, Bundle.getMessage("buildTerminateStaging", _terminateLocation.getName(), 159 Integer.toString(stagingTracksTerminate.size()))); 160 if (stagingTracksTerminate.size() > 1 && Setup.isStagingPromptToEnabled()) { 161 _terminateStageTrack = promptToStagingDialog(); 162 _startTime = new Date(); // reset build time since user can take 163 // awhile to pick 164 } else { 165 // is this train returning to the same staging in aggressive 166 // mode? 167 if (_departLocation == _terminateLocation && 168 Setup.isBuildAggressive() && 169 Setup.isStagingTrackImmediatelyAvail()) { 170 addLine(_buildReport, ONE, Bundle.getMessage("buildStagingReturn", _terminateLocation.getName())); 171 } else { 172 for (Track track : stagingTracksTerminate) { 173 if (checkTerminateStagingTrack(track)) { 174 _terminateStageTrack = track; 175 addLine(_buildReport, ONE, Bundle.getMessage("buildStagingAvail", 176 _terminateStageTrack.getName(), _terminateLocation.getName())); 177 break; 178 } 179 } 180 } 181 } 182 if (_terminateStageTrack == null) { 183 // is this train returning to the same staging in aggressive 184 // mode? 185 if (_departLocation == _terminateLocation && 186 Setup.isBuildAggressive() && 187 Setup.isStagingTrackImmediatelyAvail()) { 188 log.debug("Train is returning to same track in staging"); 189 } else { 190 addLine(_buildReport, ONE, Bundle.getMessage("buildErrorStagingFullNote")); 191 throw new BuildFailedException( 192 Bundle.getMessage("buildErrorStagingFull", _terminateLocation.getName())); 193 } 194 } 195 } 196 } 197 198 /** 199 * Figures out if the train is departing staging, and if true, sets the 200 * departure track. Also sets the arrival track if the train is returning to 201 * the same departure track in staging. 202 * 203 * @throws BuildFailedException if staging departure track not found 204 */ 205 private void determineIfTrainDepartsStagingAndAddEngines() throws BuildFailedException { 206 // allow up to two engine and caboose swaps in the train's route 207 RouteLocation engineTerminatesFirstLeg = _train.getTrainTerminatesRouteLocation(); 208 209 // Adjust where the locos will terminate 210 if ((_train.getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 211 _train.getSecondLegStartRouteLocation() != null) { 212 engineTerminatesFirstLeg = _train.getSecondLegStartRouteLocation(); 213 } else if ((_train.getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES && 214 _train.getThirdLegStartRouteLocation() != null) { 215 engineTerminatesFirstLeg = _train.getThirdLegStartRouteLocation(); 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)) { 386 addLine(_buildReport, ONE, 387 Bundle.getMessage("buildLocSkipped", rl.getName(), rl.getId(), _train.getName())); 388 continue; 389 } 390 if (!rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) { 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"), 547 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 548 _train.setStatusCode(Train.CODE_TRAIN_RESET); 549 } 550 } else if ((size = engineManager.getList(_train).size()) > 0) { 551 if (JmriJOptionPane.showConfirmDialog(null, 552 Bundle.getMessage("buildEnginesResetTrain", size, trainName), 553 Bundle.getMessage("buildResetTrain"), 554 JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) { 555 _train.setStatusCode(Train.CODE_TRAIN_RESET); 556 } 557 } 558 } else { 559 // build messages disabled 560 // remove cars and engines from this train via property change 561 _train.setStatusCode(Train.CODE_TRAIN_RESET); 562 } 563 564 _train.setStatusCode(Train.CODE_BUILD_FAILED); 565 566 if (_buildReport != null) { 567 addLine(_buildReport, ONE, msg); 568 // Write to disk and close buildReport 569 addLine(_buildReport, ONE, 570 Bundle.getMessage("buildFailedMsg", _train.getName())); 571 _buildReport.flush(); 572 _buildReport.close(); 573 } 574 } 575 576 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilder.class); 577 578}