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