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}