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
073        log.debug("Building train ({})", getTrain().getName());
074
075        getTrain().setStatusCode(Train.CODE_BUILDING);
076        getTrain().setBuilt(false);
077        getTrain().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        createCarList(); // 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        showTracksNotQuickService(); // list tracks that aren't using quick service
106
107        addCarsToTrain(); // finds and adds cars to the train (main routine)
108
109        checkStuckCarsInStaging(); // determine if cars are stuck in staging
110        setTrainBuildStatus(); // show how well the build went
111        checkEngineHP(); // determine if train has appropriate engine HP 
112        checkNumnberOfEnginesNeededHPT(); // check train engine requirements
113        showCarsNotRoutable(); // list cars that couldn't be routed
114
115        // done building
116        if (_warnings > 0) {
117            addLine(ONE, Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings));
118        }
119        addLine(FIVE,
120                Bundle.getMessage("buildTime", getTrain().getName(), new Date().getTime() - getStartTime().getTime()));
121
122        getBuildReport().flush();
123        getBuildReport().close();
124
125        createManifests(); // now make Manifests
126
127        // notify locations have been modified by this train's build
128        for (Location location : _modifiedLocations) {
129            location.setStatus(Location.MODIFIED);
130        }
131
132        // operations automations use wait for train built to create custom
133        // manifests and switch lists
134        getTrain().setPrinted(false);
135        getTrain().setSwitchListStatus(Train.UNKNOWN);
136        getTrain().setCurrentLocation(getTrain().getTrainDepartsRouteLocation());
137        getTrain().setBuilt(true);
138        // create and place train icon
139        getTrain().moveTrainIcon(getTrain().getTrainDepartsRouteLocation());
140
141        log.debug("Done building train ({})", getTrain().getName());
142        showWarningMessage();
143    }
144
145    /**
146     * Figures out if the train terminates into staging, and if true, sets the
147     * termination track. Note if the train is returning back to the same track
148     * in staging getTerminateStagingTrack() is null, and is loaded later when the
149     * departure track is determined.
150     * 
151     * @throws BuildFailedException if staging track can't be found
152     */
153    private void determineIfTrainTerminatesIntoStaging() throws BuildFailedException {
154        // does train terminate into staging?
155        setTerminateStagingTrack(null);
156        List<Track> stagingTracksTerminate = getTerminateLocation().getTracksByMoves(Track.STAGING);
157        if (stagingTracksTerminate.size() > 0) {
158            addLine(THREE, BLANK_LINE);
159            addLine(ONE, Bundle.getMessage("buildTerminateStaging", getTerminateLocation().getName(),
160                    Integer.toString(stagingTracksTerminate.size())));
161            if (stagingTracksTerminate.size() > 1 && Setup.isStagingPromptToEnabled()) {
162                setTerminateStagingTrack(promptToStagingDialog());
163                setStartTime(new Date()); // reset build time since user can take
164                                         // awhile to pick
165            } else {
166                // is this train returning to the same staging in aggressive
167                // mode?
168                if (getDepartureLocation() == getTerminateLocation() &&
169                        Setup.isBuildAggressive() &&
170                        Setup.isStagingTrackImmediatelyAvail()) {
171                    addLine(ONE, Bundle.getMessage("buildStagingReturn", getTerminateLocation().getName()));
172                } else {
173                    for (Track track : stagingTracksTerminate) {
174                        if (checkTerminateStagingTrack(track)) {
175                            setTerminateStagingTrack(track);
176                            addLine(ONE, Bundle.getMessage("buildStagingAvail",
177                                    getTerminateStagingTrack().getName(), getTerminateLocation().getName()));
178                            break;
179                        }
180                    }
181                }
182            }
183            if (getTerminateStagingTrack() == null) {
184                // is this train returning to the same staging in aggressive
185                // mode?
186                if (getDepartureLocation() == getTerminateLocation() &&
187                        Setup.isBuildAggressive() &&
188                        Setup.isStagingTrackImmediatelyAvail()) {
189                    log.debug("Train is returning to same track in staging");
190                } else {
191                    addLine(ONE, Bundle.getMessage("buildErrorStagingFullNote"));
192                    throw new BuildFailedException(
193                            Bundle.getMessage("buildErrorStagingFull", getTerminateLocation().getName()));
194                }
195            }
196        }
197    }
198
199    /**
200     * Figures out if the train is departing staging, and if true, sets the
201     * departure track. Also sets the arrival track if the train is returning to
202     * the same departure track in staging.
203     * 
204     * @throws BuildFailedException if staging departure track not found
205     */
206    private void determineIfTrainDepartsStagingAndAddEngines() throws BuildFailedException {
207        // allow up to two engine and caboose swaps in the train's route
208        RouteLocation engineTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation();
209
210        // Adjust where the locos will terminate
211        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
212                getTrain().getSecondLegStartRouteLocation() != null) {
213            engineTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation();
214        } else if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES &&
215                getTrain().getThirdLegStartRouteLocation() != null) {
216            engineTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation();
217        }
218
219        // determine if train is departing staging
220        List<Track> stagingTracks = getDepartureLocation().getTracksByMoves(Track.STAGING);
221        if (stagingTracks.size() > 0) {
222            addLine(THREE, BLANK_LINE);
223            addLine(ONE, Bundle.getMessage("buildDepartStaging", getDepartureLocation().getName(),
224                    Integer.toString(stagingTracks.size())));
225            if (stagingTracks.size() > 1 && Setup.isStagingPromptFromEnabled()) {
226                setDepartureStagingTrack(promptFromStagingDialog());
227                setStartTime(new Date()); // restart build timer
228                if (getDepartureStagingTrack() == null) {
229                    showTrainRequirements();
230                    throw new BuildFailedException(
231                            Bundle.getMessage("buildErrorStagingEmpty", getDepartureLocation().getName()));
232                }
233            } else {
234                for (Track track : stagingTracks) {
235                    // is the departure track available?
236                    if (!checkDepartureStagingTrack(track)) {
237                        addLine(SEVEN,
238                                Bundle.getMessage("buildStagingTrackRestriction", track.getName(), getTrain().getName()));
239                        continue;
240                    }
241                    setDepartureStagingTrack(track);
242                    // try each departure track for the required engines
243                    if (getEngines(getTrain().getNumberEngines(), getTrain().getEngineModel(), getTrain().getEngineRoad(),
244                            getTrain().getTrainDepartsRouteLocation(), engineTerminatesFirstLeg)) {
245                        addLine(SEVEN, Bundle.getMessage("buildDoneAssignEnginesStaging"));
246                        break; // done!
247                    }
248                    setDepartureStagingTrack(null);
249                }
250            }
251            if (getDepartureStagingTrack() == null) {
252                showTrainRequirements();
253                throw new BuildFailedException(Bundle.getMessage("buildErrorStagingEmpty", getDepartureLocation().getName()));
254            }
255        }
256        getTrain().setTerminationTrack(getTerminateStagingTrack());
257        getTrain().setDepartureTrack(getDepartureStagingTrack());
258    }
259
260    /**
261     * Adds and removes cabooses or car with FRED in the train's route. Up to 2
262     * caboose changes.
263     * 
264     * @throws BuildFailedException
265     */
266    private void addCabooseOrFredToTrain() throws BuildFailedException {
267        // allow up to two caboose swaps in the train's route
268        RouteLocation cabooseOrFredTerminatesFirstLeg = getTrain().getTrainTerminatesRouteLocation();
269        RouteLocation cabooseOrFredTerminatesSecondLeg = getTrain().getTrainTerminatesRouteLocation();
270
271        // determine if there are any caboose changes
272        if ((getTrain().getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
273                (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
274            cabooseOrFredTerminatesFirstLeg = getTrain().getSecondLegStartRouteLocation();
275        } else if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
276                (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
277            cabooseOrFredTerminatesFirstLeg = getTrain().getThirdLegStartRouteLocation();
278        }
279        if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
280                (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
281            cabooseOrFredTerminatesSecondLeg = getTrain().getThirdLegStartRouteLocation();
282        }
283
284        // Do caboose changes in reverse order in case there isn't enough track
285        // space second caboose change?
286        if ((getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE &&
287                getTrain().getThirdLegStartRouteLocation() != null &&
288                getTrain().getTrainTerminatesRouteLocation() != null) {
289            getCaboose(getTrain().getThirdLegCabooseRoad(), _thirdLeadEngine, getTrain().getThirdLegStartRouteLocation(),
290                    getTrain().getTrainTerminatesRouteLocation(), true);
291        }
292
293        // first caboose change?
294        if ((getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE &&
295                getTrain().getSecondLegStartRouteLocation() != null &&
296                cabooseOrFredTerminatesSecondLeg != null) {
297            getCaboose(getTrain().getSecondLegCabooseRoad(), _secondLeadEngine, getTrain().getSecondLegStartRouteLocation(),
298                    cabooseOrFredTerminatesSecondLeg, true);
299        }
300
301        // departure caboose or car with FRED
302        getCaboose(getTrain().getCabooseRoad(), getTrain().getLeadEngine(), getTrain().getTrainDepartsRouteLocation(),
303                cabooseOrFredTerminatesFirstLeg, getTrain().isCabooseNeeded());
304        getCarWithFred(getTrain().getCabooseRoad(), getTrain().getTrainDepartsRouteLocation(), cabooseOrFredTerminatesFirstLeg);
305    }
306
307    /**
308     * Routine to find and add available cars to the train. In normal mode
309     * performs a single pass. In aggressive mode, will perform multiple passes.
310     * If train is departing staging and in aggressive mode, will try again
311     * using normal mode if there's a train build issue.
312     * 
313     * @throws BuildFailedException
314     */
315    private void addCarsToTrain() throws BuildFailedException {
316        addLine(THREE,
317                Bundle.getMessage("buildTrain", getTrain().getNumberCarsRequested(), getTrain().getName(), getCarList().size()));
318
319        if (Setup.isBuildAggressive() && !getTrain().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(ONE, Bundle.getMessage("buildFailedTryNormalMode"));
342            addLine(ONE, BLANK_LINE);
343            getTrain().reset();
344            getTrain().setStatusCode(Train.CODE_BUILDING);
345            getTrain().setLeadEngine(null);
346            // using the same departure and termination tracks
347            getTrain().setDepartureTrack(getDepartureStagingTrack());
348            getTrain().setTerminationTrack(getTerminateStagingTrack());
349            showAndInitializeTrainRoute();
350            getAndRemoveEnginesFromList();
351            addEnginesToTrain();
352            createCarList();
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(THREE, BLANK_LINE);
377        if (normal) {
378            addLine(THREE, Bundle.getMessage("NormalModeWhenBuilding"));
379        } else {
380            addLine(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 : getRouteList()) {
385            if (getTrain().isLocationSkipped(rl)) {
386                addLine(ONE,
387                        Bundle.getMessage("buildLocSkipped", rl.getName(), rl.getId(), getTrain().getName()));
388                continue;
389            }
390            if (!rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) {
391                addLine(ONE,
392                        Bundle.getMessage("buildLocNoPickups", getTrain().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 != getTrain().getTrainDepartsRouteLocation() && rl.getLocation().isStaging()) {
397                addLine(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 == getTrain().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 == getTrain().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(ONE,
432                    Bundle.getMessage("buildLocReqMoves", rl.getName(), rl.getId(), _reqNumOfMoves,
433                            rl.getMaxCarMoves() - rl.getCarMoves(), rl.getMaxCarMoves()));
434            addLine(FIVE, BLANK_LINE);
435
436            // show the car load generation options for staging
437            if (rl == getTrain().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(SEVEN, BLANK_LINE);
461                }
462            }
463            if (rl == getTrain().getTrainDepartsRouteLocation() && pass == Setup.getNumberPasses() && isCarStuckStaging()) {
464                return; // report ASAP that there are stuck cars
465            }
466            addLine(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                            getTrain().getName()));
472
473            if (_reqNumOfMoves <= 0 && pass == Setup.getNumberPasses()) {
474                showCarsNotMoved(rl);
475            }
476        }
477    }
478
479    private void setTrainBuildStatus() {
480        if (_numberCars < getTrain().getNumberCarsRequested()) {
481            getTrain().setStatusCode(Train.CODE_PARTIAL_BUILT);
482            addLine(ONE,
483                    Train.PARTIAL_BUILT +
484                            " " +
485                            getTrain().getNumberCarsWorked() +
486                            "/" +
487                            getTrain().getNumberCarsRequested() +
488                            " " +
489                            Bundle.getMessage("cars"));
490        } else {
491            getTrain().setStatusCode(Train.CODE_BUILT);
492            addLine(ONE,
493                    Train.BUILT + " " + getTrain().getNumberCarsWorked() + " " + Bundle.getMessage("cars"));
494        }
495    }
496
497    private void createManifests() throws BuildFailedException {
498        new TrainManifest(getTrain());
499        try {
500            new JsonManifest(getTrain()).build();
501        } catch (IOException ex) {
502            log.error("Unable to create JSON manifest: {}", ex.getLocalizedMessage());
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}