001package jmri.jmrit.operations.trains.trainbuilder;
002
003import java.util.Date;
004import java.util.List;
005
006import jmri.jmrit.operations.locations.Location;
007import jmri.jmrit.operations.locations.Track;
008import jmri.jmrit.operations.routes.RouteLocation;
009import jmri.jmrit.operations.setup.Setup;
010import jmri.jmrit.operations.trains.*;
011import jmri.jmrit.operations.trains.csv.TrainCsvManifest;
012import jmri.util.swing.JmriJOptionPane;
013
014/**
015 * Builds a train and then creates the train's manifest.
016 *
017 * @author Daniel Boudreau Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013,
018 *         2014, 2015, 2021, 2026
019 */
020public class TrainBuilder extends TrainBuilderCars {
021
022    /**
023     * Build rules:
024     * <ol>
025     * <li>Need at least one location in route to build train
026     * <li>Select only locos and cars that the train can service
027     * <li>If required, add caboose or car with FRED to train
028     * <li>When departing staging find a track matching train requirements
029     * <li>All cars and locos on one track must leave staging
030     * <li>Optionally block cars from staging
031     * <li>Route cars with home divisions
032     * <li>Route cars with custom loads or final destinations.
033     * <li>Service locations based on train direction, location car types, roads
034     * and loads.
035     * <li>Ignore track direction when train is a local (serves one location)
036     * </ol>
037     * <p>
038     * History:
039     * <p>
040     * First version of train builder found cars along a train's route and
041     * assigned destinations (tracks) willing to accept the car. This is called
042     * the random method as cars just bounce around the layout without purpose.
043     * Afterwards custom loads and routing was added to the program. Cars with
044     * custom loads or final destinations move with purpose as those cars are
045     * routed. The last major feature added was car divisions. Cars assigned a
046     * division are always routed.
047     * <p>
048     * The program was written around the concept of a build report. The report
049     * provides a description of the train build process and the steps taken to
050     * place rolling stock in a train. The goal was to help users understand why
051     * rolling stock was either assigned to the train or not, and which choices
052     * the program had available when determining an engine's or car's
053     * destination.
054     *
055     * @param train the train that is to be built
056     * @return True if successful.
057     */
058    public boolean build(Train train) {
059        setTrain(train);
060        try {
061            build();
062            return true;
063        } catch (BuildFailedException e) {
064            buildFailed(e);
065            return false;
066        }
067    }
068
069    private void build() throws BuildFailedException {
070        setStartTime(new Date());
071
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
114        // done building
115        if (_warnings > 0) {
116            addLine(ONE, Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings));
117        }
118        addLine(FIVE,
119                Bundle.getMessage("buildTime", getTrain().getName(), new Date().getTime() - getStartTime().getTime()));
120
121        getBuildReport().flush();
122        getBuildReport().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        getTrain().setPrinted(false);
134        getTrain().setSwitchListStatus(Train.UNKNOWN);
135        getTrain().setCurrentLocation(getTrain().getTrainDepartsRouteLocation());
136        getTrain().setBuilt(true);
137        // create and place train icon
138        getTrain().moveTrainIcon(getTrain().getTrainDepartsRouteLocation());
139
140        log.debug("Done building train ({})", getTrain().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 getTerminateStagingTrack() 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        setTerminateStagingTrack(null);
155        List<Track> stagingTracksTerminate = getTerminateLocation().getTracksByMoves(Track.STAGING);
156        if (stagingTracksTerminate.size() > 0) {
157            addLine(THREE, BLANK_LINE);
158            addLine(ONE, Bundle.getMessage("buildTerminateStaging", getTerminateLocation().getName(),
159                    Integer.toString(stagingTracksTerminate.size())));
160            if (stagingTracksTerminate.size() > 1 && Setup.isStagingPromptToEnabled()) {
161                setTerminateStagingTrack(promptToStagingDialog());
162                setStartTime(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 (getDepartureLocation() == getTerminateLocation() &&
168                        Setup.isBuildAggressive() &&
169                        Setup.isStagingTrackImmediatelyAvail()) {
170                    addLine(ONE, Bundle.getMessage("buildStagingReturn", getTerminateLocation().getName()));
171                } else {
172                    for (Track track : stagingTracksTerminate) {
173                        if (checkTerminateStagingTrack(track)) {
174                            setTerminateStagingTrack(track);
175                            addLine(ONE, Bundle.getMessage("buildStagingAvail",
176                                    getTerminateStagingTrack().getName(), getTerminateLocation().getName()));
177                            break;
178                        }
179                    }
180                }
181            }
182            if (getTerminateStagingTrack() == null) {
183                // is this train returning to the same staging in aggressive
184                // mode?
185                if (getDepartureLocation() == getTerminateLocation() &&
186                        Setup.isBuildAggressive() &&
187                        Setup.isStagingTrackImmediatelyAvail()) {
188                    log.debug("Train is returning to same track in staging");
189                } else {
190                    addLine(ONE, Bundle.getMessage("buildErrorStagingFullNote"));
191                    throw new BuildFailedException(
192                            Bundle.getMessage("buildErrorStagingFull", getTerminateLocation().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 = getTrain().getTrainTerminatesRouteLocation();
208
209        // Adjust where the locos will terminate
210        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
211                getTrain().getSecondLegStartRouteLocation() != null) {
212            engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation();
213        } else if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
214                getTrain().getThirdLegStartRouteLocation() != null) {
215            engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation();
216        }
217
218        // determine if train is departing staging
219        List<Track> stagingTracks = getDepartureLocation().getTracksByMoves(Track.STAGING);
220        if (stagingTracks.size() > 0) {
221            addLine(THREE, BLANK_LINE);
222            addLine(ONE, Bundle.getMessage("buildDepartStaging", getDepartureLocation().getName(),
223                    Integer.toString(stagingTracks.size())));
224            if (stagingTracks.size() > 1 && Setup.isStagingPromptFromEnabled()) {
225                setDepartureStagingTrack(promptFromStagingDialog());
226                setStartTime(new Date()); // restart build timer
227                if (getDepartureStagingTrack() == null) {
228                    showTrainRequirements();
229                    throw new BuildFailedException(
230                            Bundle.getMessage("buildErrorStagingEmpty", getDepartureLocation().getName()));
231                }
232            } else {
233                for (Track track : stagingTracks) {
234                    // is the departure track available?
235                    if (!checkDepartureStagingTrack(track)) {
236                        addLine(SEVEN,
237                                Bundle.getMessage("buildStagingTrackRestriction", track.getName(), getTrain().getName()));
238                        continue;
239                    }
240                    setDepartureStagingTrack(track);
241                    // try each departure track for the required engines
242                    if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(),
243                            getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) {
244                        addLine(SEVEN, Bundle.getMessage("buildDoneAssignEnginesStaging"));
245                        break; // done!
246                    }
247                    setDepartureStagingTrack(null);
248                }
249            }
250            if (getDepartureStagingTrack() == null) {
251                showTrainRequirements();
252                throw new BuildFailedException(Bundle.getMessage("buildErrorStagingEmpty", getDepartureLocation().getName()));
253            }
254        }
255        getTrain().setTerminationTrack(getTerminateStagingTrack());
256        getTrain().setDepartureTrack(getDepartureStagingTrack());
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 = getTrain().getTrainTerminatesRouteLocation();
268        RouteLocation cabooseOrFredTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation();
269
270        // determine if there are any caboose changes
271        if ((getTrain().getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
272                (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
273            cabooseOrFredTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation();
274        } else if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
275                (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
276            cabooseOrFredTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation();
277        }
278        if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
279                (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
280            cabooseOrFredTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation();
281        }
282
283        // Do caboose changes in reverse order in case there isn't enough track
284        // space second caboose change?
285        if ((getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE &&
286                getTrain().getThirdLegStartRouteLocation() != null &&
287                getTrain().getTrainTerminatesRouteLocation() != null) {
288            getCaboose(getTrain().getThirdLegCabooseRoad(), _thirdLeadEngine, getTrain().getThirdLegStartRouteLocation(),
289                    getTrain().getTrainTerminatesRouteLocation(), true);
290        }
291
292        // first caboose change?
293        if ((getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE &&
294                getTrain().getSecondLegStartRouteLocation() != null &&
295                cabooseOrFredTerminatesSecondLeg != null) {
296            getCaboose(getTrain().getSecondLegCabooseRoad(), _secondLeadEngine, getTrain().getSecondLegStartRouteLocation(),
297                    cabooseOrFredTerminatesSecondLeg, true);
298        }
299
300        // departure caboose or car with FRED
301        getCaboose(getTrain().getCabooseRoad(), getTrain().getLeadEngine(), getTrain().getTrainDepartsRouteLocation(),
302                cabooseOrFredTerminatesFirstLeg, getTrain().isCabooseNeeded());
303        getCarWithFred(getTrain().getCabooseRoad(), getTrain().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(THREE,
316                Bundle.getMessage("buildTrain", getTrain().getNumberCarsRequested(), getTrain().getName(), getCarList().size()));
317
318        if (Setup.isBuildAggressive() && !getTrain().isBuildTrainNormalEnabled()) {
319            // perform a multiple pass build for this train, default is two
320            // passes
321            int pass = 0;
322            while (pass++ < Setup.getNumberPasses()) {
323                addCarsToTrain(pass, false);
324            }
325            // are cars stuck in staging?
326            secondAttemptNormalBuild();
327        } else {
328            addCarsToTrain(Setup.getNumberPasses(), true); // normal build one
329                                                           // pass
330        }
331    }
332
333    /**
334     * If cars stuck in staging, try building again in normal mode.
335     * 
336     * @throws BuildFailedException
337     */
338    private void secondAttemptNormalBuild() throws BuildFailedException {
339        if (Setup.isStagingTryNormalBuildEnabled() && isCarStuckStaging()) {
340            addLine(ONE, Bundle.getMessage("buildFailedTryNormalMode"));
341            addLine(ONE, BLANK_LINE);
342            getTrain().reset();
343            getTrain().setStatusCode(Train.CODE_BUILDING);
344            getTrain().setLeadEngine(null);
345            // using the same departure and termination tracks
346            getTrain().setDepartureTrack(getDepartureStagingTrack());
347            getTrain().setTerminationTrack(getTerminateStagingTrack());
348            showAndInitializeTrainRoute();
349            getAndRemoveEnginesFromList();
350            addEnginesToTrain();
351            createCarList();
352            adjustCarsInStaging();
353            showCarsByLocation();
354            addCabooseOrFredToTrain();
355            removeCaboosesAndCarsWithFred();
356            saveCarFinalDestinations(); // save final destination and schedule
357                                        // id
358            blockCarsFromStaging(); // block cars from staging
359            addCarsToTrain(Setup.getNumberPasses(), true); // try normal build
360                                                           // one pass
361        }
362    }
363
364    /**
365     * Main routine to place cars into the train. Can be called multiple times.
366     * When departing staging, ignore staged cars on the first pass unless the
367     * option to build normal was selected by user.
368     *
369     * @param pass   Which pass when there are multiple passes requested by
370     *               user.
371     * @param normal True if single pass or normal mode is requested by user.
372     * @throws BuildFailedException
373     */
374    private void addCarsToTrain(int pass, boolean normal) throws BuildFailedException {
375        addLine(THREE, BLANK_LINE);
376        if (normal) {
377            addLine(THREE, Bundle.getMessage("NormalModeWhenBuilding"));
378        } else {
379            addLine(THREE, Bundle.getMessage("buildMultiplePass", pass, Setup.getNumberPasses()));
380        }
381        // now go through each location starting at departure and place cars as
382        // requested
383        for (RouteLocation rl : getRouteList()) {
384            if (getTrain().isLocationSkipped(rl)) {
385                addLine(ONE,
386                        Bundle.getMessage("buildLocSkipped", rl.getName(), rl.getId(), getTrain().getName()));
387                continue;
388            }
389            if (!rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) {
390                addLine(ONE,
391                        Bundle.getMessage("buildLocNoPickups", getTrain().getRoute().getName(), rl.getId(), rl.getName()));
392                continue;
393            }
394            // no pick ups from staging unless at the start of the train's route
395            if (rl != getTrain().getTrainDepartsRouteLocation() && rl.getLocation().isStaging()) {
396                addLine(ONE, Bundle.getMessage("buildNoPickupsFromStaging", rl.getName()));
397                continue;
398            }
399            // the next check provides a build report message if there's an
400            // issue with the train direction
401            if (!checkPickUpTrainDirection(rl)) {
402                continue;
403            }
404            _completedMoves = 0; // moves completed for this location
405            _reqNumOfMoves = rl.getMaxCarMoves() - rl.getCarMoves();
406
407            if (!normal) {
408                if (rl == getTrain().getTrainDepartsRouteLocation()) {
409                    _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) * pass / Setup.getNumberPasses();
410                } else if (pass == 1) {
411                    _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2;
412                    // round up requested moves
413                    int remainder = (rl.getMaxCarMoves() - rl.getCarMoves()) % 2;
414                    if (remainder > 0) {
415                        _reqNumOfMoves++;
416                    }
417                }
418            }
419
420            // if departing staging make adjustments
421            if (rl == getTrain().getTrainDepartsRouteLocation()) {
422                if (pass == 1) {
423                    makeAdjustmentsIfDepartingStaging();
424                } else {
425                    restoreCarsIfDepartingStaging();
426                }
427            }
428
429            int saveReqMoves = _reqNumOfMoves; // save a copy for status message
430            addLine(ONE,
431                    Bundle.getMessage("buildLocReqMoves", rl.getName(), rl.getId(), _reqNumOfMoves,
432                            rl.getMaxCarMoves() - rl.getCarMoves(), rl.getMaxCarMoves()));
433            addLine(FIVE, BLANK_LINE);
434
435            // show the car load generation options for staging
436            if (rl == getTrain().getTrainDepartsRouteLocation()) {
437                showLoadGenerationOptionsStaging();
438            }
439
440            _carIndex = 0; // see reportCarsNotMoved(rl) below
441
442            findDestinationsForCarsFromLocation(rl, false); // first pass
443
444            // perform 2nd pass if aggressive mode and there are requested
445            // moves. This will perform local moves at this location, services
446            // off spot tracks, only in aggressive mode and at least one car
447            // has a new destination
448            if (Setup.isBuildAggressive() && saveReqMoves != _reqNumOfMoves) {
449                log.debug("Perform extra pass at location ({})", rl.getName());
450                // use up to half of the available moves left for this location
451                if (_reqNumOfMoves < (rl.getMaxCarMoves() - rl.getCarMoves()) / 2) {
452                    _reqNumOfMoves = (rl.getMaxCarMoves() - rl.getCarMoves()) / 2;
453                }
454                findDestinationsForCarsFromLocation(rl, true); // second pass
455
456                // we might have freed up space at a spur that has an alternate
457                // track
458                if (redirectCarsFromAlternateTrack()) {
459                    addLine(SEVEN, BLANK_LINE);
460                }
461            }
462            if (rl == getTrain().getTrainDepartsRouteLocation() && pass == Setup.getNumberPasses() && isCarStuckStaging()) {
463                return; // report ASAP that there are stuck cars
464            }
465            addLine(ONE,
466                    Bundle.getMessage("buildStatusMsg",
467                            (saveReqMoves <= _completedMoves ? Bundle.getMessage("Success")
468                                    : Bundle.getMessage("Partial")),
469                            Integer.toString(_completedMoves), Integer.toString(saveReqMoves), rl.getName(),
470                            getTrain().getName()));
471
472            if (_reqNumOfMoves <= 0 && pass == Setup.getNumberPasses()) {
473                showCarsNotMoved(rl);
474            }
475        }
476    }
477
478    private void setTrainBuildStatus() {
479        if (_numberCars < getTrain().getNumberCarsRequested()) {
480            getTrain().setStatusCode(Train.CODE_PARTIAL_BUILT);
481            addLine(ONE,
482                    Train.PARTIAL_BUILT +
483                            " " +
484                            getTrain().getNumberCarsWorked() +
485                            "/" +
486                            getTrain().getNumberCarsRequested() +
487                            " " +
488                            Bundle.getMessage("cars"));
489        } else {
490            getTrain().setStatusCode(Train.CODE_BUILT);
491            addLine(ONE,
492                    Train.BUILT + " " + getTrain().getNumberCarsWorked() + " " + Bundle.getMessage("cars"));
493        }
494    }
495
496    private void createManifests() throws BuildFailedException {
497        new TrainManifest(getTrain());
498        try {
499            new JsonManifest(getTrain()).build();
500        } catch (Exception ex) {
501            log.error("Unable to create JSON manifest: {}", ex.getLocalizedMessage());
502            log.error("JSON manifest stack trace:", ex);
503            throw new BuildFailedException(ex);
504        }
505        new TrainCsvManifest(getTrain());
506    }
507
508    private void showWarningMessage() {
509        if (trainManager.isBuildMessagesEnabled() && _warnings > 0) {
510            JmriJOptionPane.showMessageDialog(null,
511                    Bundle.getMessage("buildCheckReport", getTrain().getName(), getTrain().getDescription()),
512                    Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings),
513                    JmriJOptionPane.WARNING_MESSAGE);
514        }
515    }
516
517    private void buildFailed(BuildFailedException e) {
518        String msg = e.getMessage();
519        getTrain().setBuildFailedMessage(msg);
520        getTrain().setBuildFailed(true);
521        log.debug(msg);
522
523        if (trainManager.isBuildMessagesEnabled()) {
524            // don't pass the object getTrain() to the GUI, can cause thread lock
525            String trainName = getTrain().getName();
526            String trainDescription = getTrain().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(getTrain()).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                    getTrain().setStatusCode(Train.CODE_TRAIN_RESET);
549                }
550            } else if ((size = engineManager.getList(getTrain()).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                    getTrain().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            getTrain().setStatusCode(Train.CODE_TRAIN_RESET);
562        }
563
564        getTrain().setStatusCode(Train.CODE_BUILD_FAILED);
565
566        if (getBuildReport() != null) {
567            addLine(ONE, msg);
568            // Write to disk and close buildReport
569            addLine(ONE,
570                    Bundle.getMessage("buildFailedMsg", getTrain().getName()));
571            getBuildReport().flush();
572            getBuildReport().close();
573        }
574    }
575
576    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilder.class);
577
578}