001package jmri.jmrit.operations.trains.trainbuilder;
002
003import java.io.*;
004import java.nio.charset.StandardCharsets;
005import java.util.*;
006
007import jmri.InstanceManager;
008import jmri.Version;
009import jmri.jmrit.operations.locations.Location;
010import jmri.jmrit.operations.locations.Track;
011import jmri.jmrit.operations.locations.schedules.ScheduleItem;
012import jmri.jmrit.operations.rollingstock.RollingStock;
013import jmri.jmrit.operations.rollingstock.cars.*;
014import jmri.jmrit.operations.rollingstock.engines.Engine;
015import jmri.jmrit.operations.router.Router;
016import jmri.jmrit.operations.routes.RouteLocation;
017import jmri.jmrit.operations.setup.Setup;
018import jmri.jmrit.operations.trains.*;
019import jmri.jmrit.operations.trains.schedules.TrainSchedule;
020import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
021import jmri.util.swing.JmriJOptionPane;
022
023import org.apache.commons.lang3.StringUtils;
024
025/**
026 * Methods to support the TrainBuilder class.
027 *
028 * @author Daniel Boudreau Copyright (C) 2021, 2026
029 */
030public class TrainBuilderBase extends TrainCommon {
031
032    // report levels
033    protected static final String ONE = Setup.BUILD_REPORT_MINIMAL;
034    protected static final String THREE = Setup.BUILD_REPORT_NORMAL;
035    protected static final String FIVE = Setup.BUILD_REPORT_DETAILED;
036    protected static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
037
038    protected static final int DISPLAY_CAR_LIMIT_20 = 20; // build exception out
039                                                          // of staging
040    protected static final int DISPLAY_CAR_LIMIT_50 = 50;
041    protected static final int DISPLAY_CAR_LIMIT_100 = 100;
042
043    protected static final boolean USE_BUNIT = true;
044    protected static final String TIMING = "timing of trains";
045
046    // build variables shared between local routines
047    Date _startTime; // when the build report started
048    Train _train; // the train being built
049    int _numberCars = 0; // number of cars moved by this train
050    List<Engine> _engineList; // engines for this train, modified during build
051    Engine _lastEngine; // last engine found from getEngine
052    Engine _secondLeadEngine; // lead engine 2nd part of train's route
053    Engine _thirdLeadEngine; // lead engine 3rd part of the train's route
054    int _carIndex; // index for carList
055    List<Car> _carList; // cars for this train, modified during the build
056    List<RouteLocation> _routeList; // ordered list of locations
057    Hashtable<String, Integer> _numOfBlocks; // Number of blocks of cars
058                                             // departing staging.
059    int _completedMoves; // the number of pick up car moves for a location
060    int _reqNumOfMoves; // the requested number of car moves for a location
061    Location _departLocation; // train departs this location
062    Track _departStageTrack; // departure staging track (null if not staging)
063    Location _terminateLocation; // train terminates at this location
064    Track _terminateStageTrack; // terminate staging track (null if not staging)
065    PrintWriter _buildReport; // build report for this train
066    List<Car> _notRoutable = new ArrayList<>(); // cars that couldn't be routed
067    List<Location> _modifiedLocations = new ArrayList<>(); // modified locations
068    int _warnings = 0; // the number of warnings in the build report
069
070    // managers
071    TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
072    TrainScheduleManager trainScheduleManager = InstanceManager.getDefault(TrainScheduleManager.class);
073    CarLoads carLoads = InstanceManager.getDefault(CarLoads.class);
074    Router router = InstanceManager.getDefault(Router.class);
075
076    protected Date getStartTime() {
077        return _startTime;
078    }
079
080    protected void setStartTime(Date date) {
081        _startTime = date;
082    }
083
084    protected Train getTrain() {
085        return _train;
086    }
087
088    protected void setTrain(Train train) {
089        _train = train;
090    }
091
092    protected List<Engine> getEngineList() {
093        return _engineList;
094    }
095
096    protected void setEngineList(List<Engine> list) {
097        _engineList = list;
098    }
099
100    protected List<Car> getCarList() {
101        return _carList;
102    }
103
104    protected void setCarList(List<Car> list) {
105        _carList = list;
106    }
107
108    protected List<RouteLocation> getRouteList() {
109        return _routeList;
110    }
111
112    protected void setRouteList(List<RouteLocation> list) {
113        _routeList = list;
114    }
115
116    protected PrintWriter getBuildReport() {
117        return _buildReport;
118    }
119
120    protected void setBuildReport(PrintWriter printWriter) {
121        _buildReport = printWriter;
122    }
123
124    protected void remove(Car car) {
125        // remove this car from the list
126        if (getCarList().remove(car)) {
127            _carIndex--;
128        }
129    }
130
131    /**
132     * Will also set the termination track if returning to staging
133     *
134     * @param track departure track from staging
135     */
136    protected void setDepartureStagingTrack(Track track) {
137        if ((getTerminateStagingTrack() == null || getTerminateStagingTrack() == _departStageTrack) &&
138                getDepartureLocation() == getTerminateLocation() &&
139                Setup.isBuildAggressive() &&
140                Setup.isStagingTrackImmediatelyAvail()) {
141            setTerminateStagingTrack(track); // use the same track
142        }
143        _departStageTrack = track;
144    }
145
146    protected Location getDepartureLocation() {
147        return _departLocation;
148    }
149
150    protected void setDepartureLocation(Location location) {
151        _departLocation = location;
152    }
153
154    protected Track getDepartureStagingTrack() {
155        return _departStageTrack;
156    }
157
158    protected void setTerminateStagingTrack(Track track) {
159        _terminateStageTrack = track;
160    }
161
162    protected Location getTerminateLocation() {
163        return _terminateLocation;
164    }
165
166    protected void setTerminateLocation(Location location) {
167        _terminateLocation = location;
168    }
169
170    protected Track getTerminateStagingTrack() {
171        return _terminateStageTrack;
172    }
173
174    protected void createBuildReportFile() throws BuildFailedException {
175        // backup the train's previous build report file
176        InstanceManager.getDefault(TrainManagerXml.class).savePreviousBuildStatusFile(getTrain().getName());
177
178        // create build report file
179        File file = InstanceManager.getDefault(TrainManagerXml.class).createTrainBuildReportFile(getTrain().getName());
180        try {
181            setBuildReport(new PrintWriter(
182                    new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)),
183                    true));
184        } catch (IOException e) {
185            log.error("Can not open build report file: {}", e.getLocalizedMessage());
186            throw new BuildFailedException(e);
187        }
188    }
189
190    /**
191     * Creates the build report header information lines. Build report date,
192     * JMRI version, train schedule, build report display levels, setup comment.
193     */
194    protected void showBuildReportInfo() {
195        addLine(ONE, Bundle.getMessage("BuildReportMsg", getTrain().getName(), getDate(getStartTime())));
196        addLine(ONE,
197                Bundle.getMessage("BuildReportVersion", Version.name()));
198        if (!trainScheduleManager.getTrainScheduleActiveId().equals(TrainScheduleManager.NONE)) {
199            if (trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY)) {
200                addLine(ONE, Bundle.getMessage("buildActiveSchedule", Bundle.getMessage("Any")));
201            } else {
202                TrainSchedule sch = trainScheduleManager.getActiveSchedule();
203                if (sch != null) {
204                    addLine(ONE, Bundle.getMessage("buildActiveSchedule", sch.getName()));
205                }
206            }
207        }
208        // show the various build detail levels
209        addLine(THREE, Bundle.getMessage("buildReportLevelThree"));
210        addLine(FIVE, Bundle.getMessage("buildReportLevelFive"));
211        addLine(SEVEN, Bundle.getMessage("buildReportLevelSeven"));
212
213        if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED)) {
214            addLine(SEVEN, Bundle.getMessage("buildRouterReportLevelDetailed"));
215        } else if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) {
216            addLine(SEVEN, Bundle.getMessage("buildRouterReportLevelVeryDetailed"));
217        }
218
219        if (!Setup.getComment().trim().isEmpty()) {
220            addLine(ONE, BLANK_LINE);
221            addLine(ONE, Setup.getComment());
222        }
223    }
224
225    protected void setUpRoute() throws BuildFailedException {
226        if (getTrain().getRoute() == null) {
227            throw new BuildFailedException(
228                    Bundle.getMessage("buildErrorRoute", getTrain().getName()));
229        }
230        // get the train's route
231        setRouteList(getTrain().getRoute().getLocationsBySequenceList());
232        if (getRouteList().size() < 1) {
233            throw new BuildFailedException(
234                    Bundle.getMessage("buildErrorNeedRoute", getTrain().getName()));
235        }
236        // train departs
237        setDepartureLocation(locationManager.getLocationByName(getTrain().getTrainDepartsName()));
238        if (getDepartureLocation() == null) {
239            throw new BuildFailedException(
240                    Bundle.getMessage("buildErrorNeedDepLoc", getTrain().getName()));
241        }
242        // train terminates
243        setTerminateLocation(locationManager.getLocationByName(getTrain().getTrainTerminatesName()));
244        if (getTerminateLocation() == null) {
245            throw new BuildFailedException(Bundle.getMessage("buildErrorNeedTermLoc", getTrain().getName()));
246        }
247    }
248
249    /**
250     * show train build options when in detailed mode
251     */
252    protected void showTrainBuildOptions() {
253        ResourceBundle rb = ResourceBundle.getBundle("jmri.jmrit.operations.setup.JmritOperationsSetupBundle");
254        addLine(FIVE, BLANK_LINE);
255        addLine(FIVE, Bundle.getMessage("MenuItemBuildOptions") + ":");
256        if (Setup.isBuildAggressive()) {
257            if (Setup.isBuildOnTime()) {
258                addLine(FIVE, Bundle.getMessage("BuildModeOnTime"));
259            } else {
260                addLine(FIVE, Bundle.getMessage("BuildModeAggressive"));
261            }
262            addLine(FIVE, Bundle.getMessage("BuildNumberPasses", Setup.getNumberPasses()));
263            if (Setup.isStagingTrackImmediatelyAvail() && getDepartureLocation().isStaging()) {
264                addLine(FIVE, Bundle.getMessage("BuildStagingTrackAvail"));
265            }
266        } else {
267            addLine(FIVE, Bundle.getMessage("BuildModeNormal"));
268        }
269        // show switcher options
270        if (getTrain().isLocalSwitcher()) {
271            addLine(FIVE, BLANK_LINE);
272            addLine(FIVE, rb.getString("BorderLayoutSwitcherService") + ":");
273            if (Setup.isLocalInterchangeMovesEnabled()) {
274                addLine(FIVE, rb.getString("AllowLocalInterchange"));
275            } else {
276                addLine(FIVE, rb.getString("NoAllowLocalInterchange"));
277            }
278            if (Setup.isLocalSpurMovesEnabled()) {
279                addLine(FIVE, rb.getString("AllowLocalSpur"));
280            } else {
281                addLine(FIVE, rb.getString("NoAllowLocalSpur"));
282            }
283            if (Setup.isLocalYardMovesEnabled()) {
284                addLine(FIVE, rb.getString("AllowLocalYard"));
285            } else {
286                addLine(FIVE, rb.getString("NoAllowLocalYard"));
287            }
288        }
289        // show staging options
290        if (getDepartureLocation().isStaging() || getTerminateLocation().isStaging()) {
291            addLine(FIVE, BLANK_LINE);
292            addLine(FIVE, Bundle.getMessage("buildStagingOptions"));
293
294            if (Setup.isStagingTrainCheckEnabled() && getTerminateLocation().isStaging()) {
295                addLine(FIVE, Bundle.getMessage("buildOptionRestrictStaging"));
296            }
297            if (Setup.isStagingTrackImmediatelyAvail() && getTerminateLocation().isStaging()) {
298                addLine(FIVE, rb.getString("StagingAvailable"));
299            }
300            if (Setup.isStagingAllowReturnEnabled() &&
301                    getDepartureLocation().isStaging() &&
302                    getTerminateLocation().isStaging() &&
303                    getDepartureLocation() == getTerminateLocation()) {
304                addLine(FIVE, rb.getString("AllowCarsToReturn"));
305            }
306            if (Setup.isStagingPromptFromEnabled() && getDepartureLocation().isStaging()) {
307                addLine(FIVE, rb.getString("PromptFromStaging"));
308            }
309            if (Setup.isStagingPromptToEnabled() && getTerminateLocation().isStaging()) {
310                addLine(FIVE, rb.getString("PromptToStaging"));
311            }
312            if (Setup.isStagingTryNormalBuildEnabled() && getDepartureLocation().isStaging()) {
313                addLine(FIVE, rb.getString("TryNormalStaging"));
314            }
315        }
316
317        // Car routing options
318        addLine(FIVE, BLANK_LINE);
319        addLine(FIVE, Bundle.getMessage("buildCarRoutingOptions"));
320
321        // warn if car routing is disabled
322        if (!Setup.isCarRoutingEnabled()) {
323            addLine(FIVE, Bundle.getMessage("RoutingDisabled"));
324            _warnings++;
325        } else {
326            if (Setup.isCarRoutingViaYardsEnabled()) {
327                addLine(FIVE, Bundle.getMessage("RoutingViaYardsEnabled"));
328            }
329            if (Setup.isCarRoutingViaStagingEnabled()) {
330                addLine(FIVE, Bundle.getMessage("RoutingViaStagingEnabled"));
331            }
332            if (Setup.isOnlyActiveTrainsEnabled()) {
333                addLine(FIVE, Bundle.getMessage("OnlySelectedTrains"));
334                _warnings++;
335                // list the selected trains
336                for (Train train : trainManager.getTrainsByNameList()) {
337                    if (train.isBuildEnabled()) {
338                        addLine(SEVEN,
339                                Bundle.getMessage("buildTrainNameAndDesc", train.getName(), train.getDescription()));
340                    }
341                }
342                if (!getTrain().isBuildEnabled()) {
343                    addLine(FIVE, Bundle.getMessage("buildTrainNotSelected", getTrain().getName()));
344                }
345            } else {
346                addLine(FIVE, rb.getString("AllTrains"));
347            }
348            if (Setup.isCheckCarDestinationEnabled()) {
349                addLine(FIVE, Bundle.getMessage("CheckCarDestination"));
350            }
351        }
352    }
353
354    /*
355     * Show the enabled and disabled build options for this train.
356     */
357    protected void showSpecificTrainBuildOptions() {
358        addLine(FIVE, BLANK_LINE);
359        addLine(FIVE,
360                Bundle.getMessage("buildOptionsForTrain", getTrain().getName()));
361        showSpecificTrainBuildOptions(true);
362        addLine(FIVE, BLANK_LINE);
363        addLine(FIVE, Bundle.getMessage("buildDisabledOptionsForTrain", getTrain().getName()));
364        showSpecificTrainBuildOptions(false);
365    }
366
367    /*
368     * Enabled when true lists selected build options for this train. Enabled
369     * when false list disabled build options for this train.
370     */
371    private void showSpecificTrainBuildOptions(boolean enabled) {
372
373        if (getTrain().isBuildTrainNormalEnabled() ^ !enabled) {
374            addLine(FIVE, Bundle.getMessage("NormalModeWhenBuilding"));
375        }
376        if (getTrain().isSendCarsToTerminalEnabled() ^ !enabled) {
377            addLine(FIVE, Bundle.getMessage("SendToTerminal", getTerminateLocation().getName()));
378        }
379        if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) ^ !enabled &&
380                getDepartureLocation().isStaging() &&
381                getDepartureLocation() == getTerminateLocation()) {
382            addLine(FIVE, Bundle.getMessage("AllowCarsToReturn"));
383        }
384        if (getTrain().isAllowLocalMovesEnabled() ^ !enabled) {
385            addLine(FIVE, Bundle.getMessage("AllowLocalMoves"));
386        }
387        if (getTrain().isAllowThroughCarsEnabled() ^ !enabled && getDepartureLocation() != getTerminateLocation()) {
388            addLine(FIVE, Bundle.getMessage("AllowThroughCars"));
389        }
390        if (getTrain().isServiceAllCarsWithFinalDestinationsEnabled() ^ !enabled) {
391            addLine(FIVE, Bundle.getMessage("ServiceAllCars"));
392        }
393        if (getTrain().isSendCarsWithCustomLoadsToStagingEnabled() ^ !enabled) {
394            addLine(FIVE, Bundle.getMessage("SendCustomToStaging"));
395        }
396        if (getTrain().isBuildConsistEnabled() ^ !enabled) {
397            addLine(FIVE, Bundle.getMessage("BuildConsist"));
398            if (enabled) {
399                addLine(SEVEN, Bundle.getMessage("BuildConsistHPT", Setup.getHorsePowerPerTon()));
400            }
401        }
402    }
403
404    /**
405     * Adds to the build report what the train will service. Road and owner
406     * names, built dates, and engine types.
407     */
408    protected void showTrainServices() {
409        // show road names that this train will service
410        if (!getTrain().getLocoRoadOption().equals(Train.ALL_ROADS)) {
411            addLine(FIVE, Bundle.getMessage("buildTrainLocoRoads", getTrain().getName(),
412                    getTrain().getLocoRoadOption(), formatStringToCommaSeparated(getTrain().getLocoRoadNames())));
413        }
414        // show owner names that this train will service
415        if (!getTrain().getOwnerOption().equals(Train.ALL_OWNERS)) {
416            addLine(FIVE, Bundle.getMessage("buildTrainOwners", getTrain().getName(), getTrain().getOwnerOption(),
417                    formatStringToCommaSeparated(getTrain().getOwnerNames())));
418        }
419        // show built dates serviced
420        if (!getTrain().getBuiltStartYear().equals(Train.NONE)) {
421            addLine(FIVE,
422                    Bundle.getMessage("buildTrainBuiltAfter", getTrain().getName(), getTrain().getBuiltStartYear()));
423        }
424        if (!getTrain().getBuiltEndYear().equals(Train.NONE)) {
425            addLine(FIVE,
426                    Bundle.getMessage("buildTrainBuiltBefore", getTrain().getName(), getTrain().getBuiltEndYear()));
427        }
428
429        // show engine types that this train will service
430        if (!getTrain().getNumberEngines().equals("0")) {
431            addLine(FIVE, Bundle.getMessage("buildTrainServicesEngineTypes", getTrain().getName()));
432            addLine(FIVE, formatStringToCommaSeparated(getTrain().getLocoTypeNames()));
433        }
434    }
435
436    /**
437     * Show and initialize the train's route. Determines the number of car moves
438     * requested for this train. Also adjust the number of car moves if the
439     * random car moves option was selected.
440     *
441     * @throws BuildFailedException if random variable isn't an integer
442     */
443    protected void showAndInitializeTrainRoute() throws BuildFailedException {
444        int requestedCarMoves = 0; // how many cars were asked to be moved
445        // TODO: DAB control minimal build by each train
446        addLine(ONE, BLANK_LINE);
447        addLine(THREE,
448                Bundle.getMessage("buildTrainRoute", getTrain().getName(), getTrain().getRoute().getName()));
449
450        // get the number of requested car moves for this train
451        for (RouteLocation rl : getRouteList()) {
452            // check to see if there's a location for each stop in the route
453            // this checks for a deleted location
454            Location location = locationManager.getLocationByName(rl.getName());
455            if (location == null || rl.getLocation() == null) {
456                throw new BuildFailedException(
457                        Bundle.getMessage("buildErrorLocMissing", getTrain().getRoute().getName()));
458            }
459            // train doesn't drop or pick up cars from staging locations found
460            // in middle of a route
461            if (location.isStaging() &&
462                    rl != getTrain().getTrainDepartsRouteLocation() &&
463                    rl != getTrain().getTrainTerminatesRouteLocation()) {
464                addLine(ONE,
465                        Bundle.getMessage("buildLocStaging", rl.getName()));
466                // don't allow car moves for this location
467                rl.setCarMoves(rl.getMaxCarMoves());
468            } else if (getTrain().isLocationSkipped(rl)) {
469                // if a location is skipped, no car drops or pick ups
470                addLine(THREE,
471                        Bundle.getMessage("buildLocSkippedMaxTrain", rl.getId(), rl.getName(),
472                                rl.getTrainDirectionString(), getTrain().getName(), rl.getMaxTrainLength(),
473                                Setup.getLengthUnit().toLowerCase()));
474                // don't allow car moves for this location
475                rl.setCarMoves(rl.getMaxCarMoves());
476            } else {
477                // we're going to use this location, so initialize
478                rl.setCarMoves(0); // clear the number of moves
479                // add up the total number of car moves requested
480                requestedCarMoves += rl.getMaxCarMoves();
481                // show the type of moves allowed at this location
482                if (!rl.isDropAllowed() && !rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) {
483                    addLine(THREE,
484                            Bundle.getMessage("buildLocNoDropsOrPickups", rl.getId(),
485                                    location.isStaging() ? Bundle.getMessage("Staging") : Bundle.getMessage("Location"),
486                                    rl.getName(),
487                                    rl.getTrainDirectionString(), rl.getMaxTrainLength(),
488                                    Setup.getLengthUnit().toLowerCase()));
489                } else if (rl == getTrain().getTrainTerminatesRouteLocation()) {
490                    addLine(THREE, Bundle.getMessage("buildLocTerminates", rl.getId(),
491                            location.isStaging() ? Bundle.getMessage("Staging") : Bundle.getMessage("Location"),
492                            rl.getName(), rl.getTrainDirectionString(), rl.getMaxCarMoves(),
493                            rl.isPickUpAllowed() ? Bundle.getMessage("Pickups").toLowerCase() + ", " : "",
494                            rl.isDropAllowed() ? Bundle.getMessage("Drop").toLowerCase() + ", " : "",
495                            rl.isLocalMovesAllowed() ? Bundle.getMessage("LocalMoves").toLowerCase() + ", " : ""));
496                } else {
497                    addLine(THREE, Bundle.getMessage("buildLocRequestMoves", rl.getId(),
498                            location.isStaging() ? Bundle.getMessage("Staging") : Bundle.getMessage("Location"),
499                            rl.getName(), rl.getTrainDirectionString(), rl.getMaxCarMoves(),
500                            rl.isPickUpAllowed() ? Bundle.getMessage("Pickups").toLowerCase() + ", " : "",
501                            rl.isDropAllowed() ? Bundle.getMessage("Drop").toLowerCase() + ", " : "",
502                            rl.isLocalMovesAllowed() ? Bundle.getMessage("LocalMoves").toLowerCase() + ", " : "",
503                            rl.getMaxTrainLength(), Setup.getLengthUnit().toLowerCase()));
504                }
505            }
506            rl.setTrainWeight(0); // clear the total train weight
507            rl.setTrainLength(0); // and length
508        }
509
510        // check for random moves in the train's route
511        for (RouteLocation rl : getRouteList()) {
512            if (rl.getRandomControl().equals(RouteLocation.DISABLED)) {
513                continue;
514            }
515            if (rl.getCarMoves() == 0 && rl.getMaxCarMoves() > 0) {
516                log.debug("Location ({}) has random control value {} and maximum moves {}", rl.getName(),
517                        rl.getRandomControl(), rl.getMaxCarMoves());
518                try {
519                    int value = Integer.parseInt(rl.getRandomControl());
520                    // now adjust the number of available moves for this
521                    // location
522                    double random = Math.random();
523                    log.debug("random {}", random);
524                    int moves = (int) (random * ((rl.getMaxCarMoves() * value / 100) + 1));
525                    log.debug("Reducing number of moves for location ({}) by {}", rl.getName(), moves);
526                    rl.setCarMoves(moves);
527                    requestedCarMoves = requestedCarMoves - moves;
528                    addLine(FIVE,
529                            Bundle.getMessage("buildRouteRandomControl", rl.getName(), rl.getId(),
530                                    rl.getRandomControl(), rl.getMaxCarMoves(), rl.getMaxCarMoves() - moves));
531                } catch (NumberFormatException e) {
532                    throw new BuildFailedException(Bundle.getMessage("buildErrorRandomControl",
533                            getTrain().getRoute().getName(), rl.getName(), rl.getRandomControl()));
534                }
535            }
536        }
537
538        int numMoves = requestedCarMoves; // number of car moves
539        if (!getTrain().isLocalSwitcher()) {
540            requestedCarMoves = requestedCarMoves / 2; // only need half as many
541                                                       // cars to meet requests
542        }
543        addLine(ONE, Bundle.getMessage("buildRouteRequest", getTrain().getRoute().getName(),
544                Integer.toString(requestedCarMoves), Integer.toString(numMoves)));
545
546        getTrain().setNumberCarsRequested(requestedCarMoves); // save number of car
547        // moves requested
548    }
549
550    /**
551     * reports if local switcher
552     */
553    protected void showIfLocalSwitcher() {
554        if (getTrain().isLocalSwitcher()) {
555            addLine(THREE, BLANK_LINE);
556            addLine(THREE, Bundle.getMessage("buildTrainIsSwitcher", getTrain().getName(),
557                    TrainCommon.splitString(getTrain().getTrainDepartsName())));
558        }
559    }
560
561    /**
562     * Show how many engines are required for this train, and if a certain road
563     * name for the engine is requested. Show if there are any engine changes in
564     * the route, or if helper engines are needed. There can be up to 2 engine
565     * changes or helper requests. Show if caboose or FRED is needed for train,
566     * and if there's a road name requested. There can be up to 2 caboose
567     * changes in the route.
568     */
569    protected void showTrainRequirements() {
570        addLine(ONE, BLANK_LINE);
571        addLine(ONE, Bundle.getMessage("TrainRequirements"));
572        if (getTrain().isBuildConsistEnabled() && Setup.getHorsePowerPerTon() > 0) {
573            addLine(ONE,
574                    Bundle.getMessage("buildTrainReqConsist", Setup.getHorsePowerPerTon(),
575                            getTrain().getNumberEngines()));
576        } else if (getTrain().getNumberEngines().equals("0")) {
577            addLine(ONE, Bundle.getMessage("buildTrainReq0Engine"));
578        } else if (getTrain().getNumberEngines().equals("1")) {
579            addLine(ONE, Bundle.getMessage("buildTrainReq1Engine", getTrain().getTrainDepartsName(),
580                    getTrain().getEngineModel(), getTrain().getEngineRoad()));
581        } else {
582            addLine(ONE,
583                    Bundle.getMessage("buildTrainReqEngine", getTrain().getTrainDepartsName(),
584                            getTrain().getNumberEngines(),
585                            getTrain().getEngineModel(), getTrain().getEngineRoad()));
586        }
587        // show any required loco changes
588        if ((getTrain().getSecondLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) {
589            addLine(ONE,
590                    Bundle.getMessage("buildTrainEngineChange", getTrain().getSecondLegStartLocationName(),
591                            getTrain().getSecondLegNumberEngines(), getTrain().getSecondLegEngineModel(),
592                            getTrain().getSecondLegEngineRoad()));
593        }
594        if ((getTrain().getSecondLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) {
595            addLine(ONE,
596                    Bundle.getMessage("buildTrainAddEngines", getTrain().getSecondLegNumberEngines(),
597                            getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(),
598                            getTrain().getSecondLegEngineRoad()));
599        }
600        if ((getTrain().getSecondLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES) {
601            addLine(ONE,
602                    Bundle.getMessage("buildTrainRemoveEngines", getTrain().getSecondLegNumberEngines(),
603                            getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEngineModel(),
604                            getTrain().getSecondLegEngineRoad()));
605        }
606        if ((getTrain().getSecondLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) {
607            addLine(ONE,
608                    Bundle.getMessage("buildTrainHelperEngines", getTrain().getSecondLegNumberEngines(),
609                            getTrain().getSecondLegStartLocationName(), getTrain().getSecondLegEndLocationName(),
610                            getTrain().getSecondLegEngineModel(), getTrain().getSecondLegEngineRoad()));
611        }
612
613        if ((getTrain().getThirdLegOptions() & Train.CHANGE_ENGINES) == Train.CHANGE_ENGINES) {
614            addLine(ONE,
615                    Bundle.getMessage("buildTrainEngineChange", getTrain().getThirdLegStartLocationName(),
616                            getTrain().getThirdLegNumberEngines(), getTrain().getThirdLegEngineModel(),
617                            getTrain().getThirdLegEngineRoad()));
618        }
619        if ((getTrain().getThirdLegOptions() & Train.ADD_ENGINES) == Train.ADD_ENGINES) {
620            addLine(ONE,
621                    Bundle.getMessage("buildTrainAddEngines", getTrain().getThirdLegNumberEngines(),
622                            getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(),
623                            getTrain().getThirdLegEngineRoad()));
624        }
625        if ((getTrain().getThirdLegOptions() & Train.REMOVE_ENGINES) == Train.REMOVE_ENGINES) {
626            addLine(ONE,
627                    Bundle.getMessage("buildTrainRemoveEngines", getTrain().getThirdLegNumberEngines(),
628                            getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEngineModel(),
629                            getTrain().getThirdLegEngineRoad()));
630        }
631        if ((getTrain().getThirdLegOptions() & Train.HELPER_ENGINES) == Train.HELPER_ENGINES) {
632            addLine(ONE,
633                    Bundle.getMessage("buildTrainHelperEngines", getTrain().getThirdLegNumberEngines(),
634                            getTrain().getThirdLegStartLocationName(), getTrain().getThirdLegEndLocationName(),
635                            getTrain().getThirdLegEngineModel(), getTrain().getThirdLegEngineRoad()));
636        }
637        // show caboose or FRED requirements
638        if (getTrain().isCabooseNeeded()) {
639            addLine(ONE, Bundle.getMessage("buildTrainRequiresCaboose", getTrain().getTrainDepartsName(),
640                    getTrain().getCabooseRoad()));
641        }
642        // show any caboose changes in the train's route
643        if ((getTrain().getSecondLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
644                (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
645            addLine(ONE,
646                    Bundle.getMessage("buildCabooseChange", getTrain().getSecondLegStartRouteLocation()));
647        }
648        if ((getTrain().getThirdLegOptions() & Train.REMOVE_CABOOSE) == Train.REMOVE_CABOOSE ||
649                (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE) == Train.ADD_CABOOSE) {
650            addLine(ONE, Bundle.getMessage("buildCabooseChange", getTrain().getThirdLegStartRouteLocation()));
651        }
652        if (getTrain().isFredNeeded()) {
653            addLine(ONE,
654                    Bundle.getMessage("buildTrainRequiresFRED", getTrain().getTrainDepartsName(),
655                            getTrain().getCabooseRoad()));
656        }
657    }
658
659    protected void showTrainCarRoads() {
660        if (!getTrain().getCarRoadOption().equals(Train.ALL_ROADS)) {
661            addLine(FIVE, BLANK_LINE);
662            addLine(FIVE, Bundle.getMessage("buildTrainRoads", getTrain().getName(),
663                    getTrain().getCarRoadOption(), formatStringToCommaSeparated(getTrain().getCarRoadNames())));
664        }
665    }
666
667    protected void showTrainCabooseRoads() {
668        if (!getTrain().getCabooseRoadOption().equals(Train.ALL_ROADS)) {
669            addLine(FIVE, BLANK_LINE);
670            addLine(FIVE, Bundle.getMessage("buildTrainCabooseRoads", getTrain().getName(),
671                    getTrain().getCabooseRoadOption(), formatStringToCommaSeparated(getTrain().getCabooseRoadNames())));
672        }
673    }
674
675    protected void showTrainCarTypes() {
676        addLine(FIVE, BLANK_LINE);
677        addLine(FIVE, Bundle.getMessage("buildTrainServicesCarTypes", getTrain().getName()));
678        addLine(FIVE, formatStringToCommaSeparated(getTrain().getCarTypeNames()));
679    }
680
681    protected void showTrainLoadNames() {
682        if (!getTrain().getLoadOption().equals(Train.ALL_LOADS)) {
683            addLine(FIVE, Bundle.getMessage("buildTrainLoads", getTrain().getName(), getTrain().getLoadOption(),
684                    formatStringToCommaSeparated(getTrain().getLoadNames())));
685        }
686    }
687
688    /**
689     * Ask which staging track the train is to depart on.
690     *
691     * @return The departure track the user selected.
692     */
693    protected Track promptFromStagingDialog() {
694        List<Track> tracksIn = getDepartureLocation().getTracksByNameList(null);
695        List<Track> validTracks = new ArrayList<>();
696        // only show valid tracks
697        for (Track track : tracksIn) {
698            if (checkDepartureStagingTrack(track)) {
699                validTracks.add(track);
700            }
701        }
702        if (validTracks.size() > 1) {
703            // need an object array for dialog window
704            Object[] tracks = new Object[validTracks.size()];
705            for (int i = 0; i < validTracks.size(); i++) {
706                tracks[i] = validTracks.get(i);
707            }
708
709            Track selected = (Track) JmriJOptionPane.showInputDialog(null,
710                    Bundle.getMessage("TrainDepartingStaging", getTrain().getName(), getDepartureLocation().getName()),
711                    Bundle.getMessage("SelectDepartureTrack"), JmriJOptionPane.QUESTION_MESSAGE, null, tracks, null);
712            if (selected != null) {
713                addLine(FIVE, Bundle.getMessage("buildUserSelectedDeparture", selected.getName(),
714                        selected.getLocation().getName()));
715            } else {
716                addLine(FIVE, Bundle.getMessage("buildUserCanceledDeparture"));
717            }
718            return selected;
719        } else if (validTracks.size() == 1) {
720            Track track = validTracks.get(0);
721            addLine(FIVE,
722                    Bundle.getMessage("buildOnlyOneDepartureTrack", track.getName(), track.getLocation().getName()));
723            return track;
724        }
725        return null; // no tracks available
726    }
727
728    /**
729     * Ask which staging track the train is to terminate on.
730     *
731     * @return The termination track selected by the user.
732     */
733    protected Track promptToStagingDialog() {
734        List<Track> tracksIn = getTerminateLocation().getTracksByNameList(null);
735        List<Track> validTracks = new ArrayList<>();
736        // only show valid tracks
737        for (Track track : tracksIn) {
738            if (checkTerminateStagingTrack(track)) {
739                validTracks.add(track);
740            }
741        }
742        if (validTracks.size() > 1) {
743            // need an object array for dialog window
744            Object[] tracks = new Object[validTracks.size()];
745            for (int i = 0; i < validTracks.size(); i++) {
746                tracks[i] = validTracks.get(i);
747            }
748
749            Track selected = (Track) JmriJOptionPane.showInputDialog(null,
750                    Bundle.getMessage("TrainTerminatingStaging", getTrain().getName(),
751                            getTerminateLocation().getName()),
752                    Bundle.getMessage("SelectArrivalTrack"), JmriJOptionPane.QUESTION_MESSAGE, null, tracks, null);
753            if (selected != null) {
754                addLine(FIVE, Bundle.getMessage("buildUserSelectedArrival", selected.getName(),
755                        selected.getLocation().getName()));
756            }
757            return selected;
758        } else if (validTracks.size() == 1) {
759            return validTracks.get(0);
760        }
761        return null; // no tracks available
762    }
763
764    /**
765     * Removes the remaining cabooses and cars with FRED from consideration.
766     *
767     * @throws BuildFailedException code check if car being removed is in
768     *                              staging
769     */
770    protected void removeCaboosesAndCarsWithFred() throws BuildFailedException {
771        addLine(SEVEN, BLANK_LINE);
772        addLine(SEVEN, Bundle.getMessage("buildRemoveCarsNotNeeded"));
773        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
774            Car car = getCarList().get(_carIndex);
775            if (car.isCaboose() || car.hasFred()) {
776                addLine(SEVEN,
777                        Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(),
778                                car.getTypeExtensions(), car.getLocationName(), car.getTrackName()));
779                // code check, should never be staging
780                if (car.getTrack() == getDepartureStagingTrack()) {
781                    throw new BuildFailedException("ERROR: Attempt to removed car with FRED or Caboose from staging"); // NOI18N
782                }
783                remove(car); // remove this car from the list
784            }
785        }
786    }
787
788    /**
789     * Save the car's final destination and schedule id in case of train reset
790     */
791    protected void saveCarFinalDestinations() {
792        for (Car car : getCarList()) {
793            car.setPreviousFinalDestination(car.getFinalDestination());
794            car.setPreviousFinalDestinationTrack(car.getFinalDestinationTrack());
795            car.setPreviousScheduleId(car.getScheduleItemId());
796        }
797    }
798
799    /**
800     * Creates the carList. Only cars that can be serviced by this train are in
801     * the list.
802     *
803     * @throws BuildFailedException if car is marked as missing and is in
804     *                              staging
805     */
806    protected void createCarList() throws BuildFailedException {
807        // get list of cars for this route
808        setCarList(carManager.getAvailableTrainList(getTrain()));
809        addLine(SEVEN, BLANK_LINE);
810        addLine(SEVEN, Bundle.getMessage("buildRemoveCars"));
811        boolean showCar = true;
812        int carListSize = getCarList().size();
813        // now remove cars that the train can't service
814        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
815            Car car = getCarList().get(_carIndex);
816            // only show the first 100 cars removed due to wrong car type for
817            // train
818            if (showCar && carListSize - getCarList().size() == DISPLAY_CAR_LIMIT_100) {
819                showCar = false;
820                addLine(FIVE,
821                        Bundle.getMessage("buildOnlyFirstXXXCars", DISPLAY_CAR_LIMIT_100, Bundle.getMessage("Type")));
822            }
823            // remove cars that don't have a track assignment
824            if (car.getTrack() == null) {
825                _warnings++;
826                addLine(ONE,
827                        Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName()));
828                remove(car);
829                continue;
830            }
831            // remove cars that have been reported as missing
832            if (car.isLocationUnknown()) {
833                addLine(SEVEN, Bundle.getMessage("buildExcludeCarLocUnknown", car.toString(),
834                        car.getLocationName(), car.getTrackName()));
835                if (car.getTrack() == getDepartureStagingTrack()) {
836                    throw new BuildFailedException(Bundle.getMessage("buildErrorLocationUnknown", car.getLocationName(),
837                            car.getTrackName(), car.toString()));
838                }
839                remove(car);
840                continue;
841            }
842            // remove cars that are out of service
843            if (car.isOutOfService()) {
844                addLine(SEVEN, Bundle.getMessage("buildExcludeCarOutOfService", car.toString(),
845                        car.getLocationName(), car.getTrackName()));
846                if (car.getTrack() == getDepartureStagingTrack()) {
847                    throw new BuildFailedException(
848                            Bundle.getMessage("buildErrorLocationOutOfService", car.getLocationName(),
849                                    car.getTrackName(), car.toString()));
850                }
851                remove(car);
852                continue;
853            }
854            // does car have a destination that is part of this train's route?
855            if (car.getDestination() != null) {
856                RouteLocation rld = getTrain().getRoute().getLastLocationByName(car.getDestinationName());
857                if (rld == null) {
858                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(),
859                            car.getDestinationName(), car.getDestinationTrackName(), getTrain().getRoute().getName()));
860                    // Code check, programming ERROR if car departing staging
861                    if (car.getLocation() == getDepartureLocation() && getDepartureStagingTrack() != null) {
862                        throw new BuildFailedException(Bundle.getMessage("buildErrorCarNotPartRoute", car.toString()));
863                    }
864                    remove(car); // remove this car from the list
865                    continue;
866                }
867            }
868            // remove cars with FRED that have a destination that isn't the
869            // terminal
870            if (car.hasFred() && car.getDestination() != null && car.getDestination() != getTerminateLocation()) {
871                addLine(FIVE,
872                        Bundle.getMessage("buildExcludeCarWrongDest", car.toString(), car.getTypeName(),
873                                car.getTypeExtensions(), car.getDestinationName()));
874                remove(car);
875                continue;
876            }
877
878            // remove cabooses that have a destination that isn't the terminal,
879            // and no caboose changes in the train's route
880            if (car.isCaboose() &&
881                    car.getDestination() != null &&
882                    car.getDestination() != getTerminateLocation() &&
883                    (getTrain().getSecondLegOptions() & Train.ADD_CABOOSE + Train.REMOVE_CABOOSE) == 0 &&
884                    (getTrain().getThirdLegOptions() & Train.ADD_CABOOSE + Train.REMOVE_CABOOSE) == 0) {
885                addLine(FIVE,
886                        Bundle.getMessage("buildExcludeCarWrongDest", car.toString(), car.getTypeName(),
887                                car.getTypeExtensions(), car.getDestinationName()));
888                remove(car);
889                continue;
890            }
891
892            // is car at interchange or spur and is this train allowed to pull?
893            if (!checkPickupInterchangeOrSpur(car)) {
894                remove(car);
895                continue;
896            }
897
898            // is car at interchange with destination restrictions?
899            if (!checkPickupInterchangeDestinationRestrictions(car)) {
900                remove(car);
901                continue;
902            }
903            // note that for trains departing staging the engine and car roads,
904            // types, owners, and built date were already checked.
905
906            if (!car.isCaboose() && !getTrain().isCarRoadNameAccepted(car.getRoadName()) ||
907                    car.isCaboose() && !getTrain().isCabooseRoadNameAccepted(car.getRoadName())) {
908                addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(),
909                        car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getTypeExtensions(),
910                        car.getRoadName()));
911                remove(car);
912                continue;
913            }
914            if (!getTrain().isTypeNameAccepted(car.getTypeName())) {
915                // only show lead cars when excluding car type
916                if (showCar && (car.getKernel() == null || car.isLead())) {
917                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongType", car.toString(),
918                            car.getLocationName(), car.getTrackName(), car.getTypeName()));
919                }
920                remove(car);
921                continue;
922            }
923            if (!getTrain().isOwnerNameAccepted(car.getOwnerName())) {
924                addLine(SEVEN,
925                        Bundle.getMessage("buildExcludeCarOwnerAtLoc", car.toString(), car.getOwnerName(),
926                                car.getLocationName(), car.getTrackName()));
927                remove(car);
928                continue;
929            }
930            if (!getTrain().isBuiltDateAccepted(car.getBuilt())) {
931                addLine(SEVEN,
932                        Bundle.getMessage("buildExcludeCarBuiltAtLoc", car.toString(), car.getBuilt(),
933                                car.getLocationName(), car.getTrackName()));
934                remove(car);
935                continue;
936            }
937
938            // all cars in staging must be accepted, so don't exclude if in
939            // staging
940            // note that a car's load can change when departing staging
941            // a car's wait value is ignored when departing staging
942            // a car's pick up day is ignored when departing staging
943            if (getDepartureStagingTrack() == null || car.getTrack() != getDepartureStagingTrack()) {
944                if (!car.isCaboose() &&
945                        !car.isPassenger() &&
946                        !getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
947                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarLoadAtLoc", car.toString(),
948                            car.getTypeName(), car.getLoadName()));
949                    remove(car);
950                    continue;
951                }
952                // remove cars with FRED if not needed by train
953                if (car.hasFred() && !getTrain().isFredNeeded()) {
954                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarWithFredAtLoc", car.toString(),
955                            car.getTypeName(), (car.getLocationName() + ", " + car.getTrackName())));
956                    remove(car); // remove this car from the list
957                    continue;
958                }
959                // does the car have a pick up day?
960                if (!car.getPickupScheduleId().equals(Car.NONE)) {
961                    if (trainScheduleManager.getTrainScheduleActiveId().equals(TrainSchedule.ANY) ||
962                            car.getPickupScheduleId().equals(trainScheduleManager.getTrainScheduleActiveId())) {
963                        car.setPickupScheduleId(Car.NONE);
964                    } else {
965                        TrainSchedule sch = trainScheduleManager.getScheduleById(car.getPickupScheduleId());
966                        if (sch != null) {
967                            addLine(SEVEN,
968                                    Bundle.getMessage("buildExcludeCarSchedule", car.toString(), car.getTypeName(),
969                                            car.getLocationName(), car.getTrackName(), sch.getName()));
970                            remove(car);
971                            continue;
972                        }
973                    }
974                }
975                // does car have a wait count?
976                if (car.getWait() > 0) {
977                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarWait", car.toString(),
978                            car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait()));
979                    if (getTrain().isServiceable(car)) {
980                        addLine(SEVEN, Bundle.getMessage("buildTrainCanServiceWait", getTrain().getName(),
981                                car.toString(), car.getWait() - 1));
982                        car.setWait(car.getWait() - 1); // decrement wait count
983                        // a car's load changes when the wait count reaches 0
984                        String oldLoad = car.getLoadName();
985                        if (car.getTrack().isSpur()) {
986                            car.updateLoad(car.getTrack()); // has the wait
987                                                            // count reached 0?
988                        }
989                        if (!oldLoad.equals(car.getLoadName())) {
990                            addLine(SEVEN,
991                                    Bundle.getMessage("buildCarLoadChangedWait", car.toString(), car.getTypeName(),
992                                            oldLoad, car.getLoadName(), car.getFinalDestinationName(),
993                                            car.getFinalDestinationTrackName()));
994                        }
995                    }
996                    remove(car);
997                    continue;
998                }
999            }
1000        }
1001    }
1002
1003    /**
1004     * Adjust car list to only have cars from one staging track
1005     *
1006     * @throws BuildFailedException if all cars departing staging can't be used
1007     */
1008    protected void adjustCarsInStaging() throws BuildFailedException {
1009        if (!getTrain().isDepartingStaging()) {
1010            return; // not departing staging
1011        }
1012        int numCarsFromStaging = 0;
1013        _numOfBlocks = new Hashtable<>();
1014        addLine(SEVEN, BLANK_LINE);
1015        addLine(SEVEN, Bundle.getMessage("buildRemoveCarsStaging"));
1016        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
1017            Car car = getCarList().get(_carIndex);
1018            if (car.getLocation() == getDepartureLocation()) {
1019                if (car.getTrack() == getDepartureStagingTrack()) {
1020                    numCarsFromStaging++;
1021                    // populate car blocking hashtable
1022                    // don't block cabooses, cars with FRED, or passenger. Only
1023                    // block lead cars in
1024                    // kernel
1025                    if (!car.isCaboose() &&
1026                            !car.hasFred() &&
1027                            !car.isPassenger() &&
1028                            (car.getKernel() == null || car.isLead())) {
1029                        log.debug("Car {} last location id: {}", car.toString(), car.getLastLocationId());
1030                        Integer number = 1;
1031                        if (_numOfBlocks.containsKey(car.getLastLocationId())) {
1032                            number = _numOfBlocks.get(car.getLastLocationId()) + 1;
1033                            _numOfBlocks.remove(car.getLastLocationId());
1034                        }
1035                        _numOfBlocks.put(car.getLastLocationId(), number);
1036                    }
1037                } else {
1038                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarAtLoc", car.toString(),
1039                            car.getTypeName(), car.getLocationName(), car.getTrackName()));
1040                    remove(car);
1041                }
1042            }
1043        }
1044        // show how many cars are departing from staging
1045        addLine(FIVE, BLANK_LINE);
1046        addLine(FIVE, Bundle.getMessage("buildDepartingStagingCars",
1047                getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName(),
1048                numCarsFromStaging));
1049        // and list them
1050        for (Car car : getCarList()) {
1051            if (car.getTrack() == getDepartureStagingTrack()) {
1052                addLine(SEVEN, Bundle.getMessage("buildStagingCarAtLoc", car.toString(),
1053                        car.getTypeName(), car.getLoadType().toLowerCase(), car.getLoadName()));
1054            }
1055        }
1056        // error if all of the cars from staging aren't available
1057        if (!Setup.isBuildOnTime() && numCarsFromStaging != getDepartureStagingTrack().getNumberCars()) {
1058            throw new BuildFailedException(
1059                    Bundle.getMessage("buildErrorNotAllCars", getDepartureStagingTrack().getName(),
1060                            Integer.toString(getDepartureStagingTrack().getNumberCars() - numCarsFromStaging)));
1061        }
1062        log.debug("Staging departure track ({}) has {} cars and {} blocks", getDepartureStagingTrack().getName(),
1063                numCarsFromStaging, _numOfBlocks.size()); // NOI18N
1064    }
1065
1066    /**
1067     * List available cars by location. Removes non-lead kernel cars from the
1068     * car list.
1069     *
1070     * @throws BuildFailedException if kernel doesn't have lead or cars aren't
1071     *                              on the same track.
1072     */
1073    protected void showCarsByLocation() throws BuildFailedException {
1074        // show how many cars were found
1075        addLine(FIVE, BLANK_LINE);
1076        addLine(ONE,
1077                Bundle.getMessage("buildFoundCars", Integer.toString(getCarList().size()), getTrain().getName()));
1078        // only show cars once using the train's route
1079        List<String> locationNames = new ArrayList<>();
1080        for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) {
1081            if (locationNames.contains(rl.getName())) {
1082                continue;
1083            }
1084            locationNames.add(rl.getName());
1085            int count = countRollingStockAt(rl, new ArrayList<RollingStock>(getCarList()));
1086            if (rl.getLocation().isStaging()) {
1087                addLine(FIVE,
1088                        Bundle.getMessage("buildCarsInStaging", count, rl.getName()));
1089            } else {
1090                addLine(FIVE,
1091                        Bundle.getMessage("buildCarsAtLocation", count, rl.getName()));
1092            }
1093            // now go through the car list and remove non-lead cars in kernels,
1094            // destinations
1095            // that aren't part of this route
1096            int carCount = 0;
1097            for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
1098                Car car = getCarList().get(_carIndex);
1099                if (!car.getLocationName().equals(rl.getName())) {
1100                    continue;
1101                }
1102                // only print out the first DISPLAY_CAR_LIMIT cars for each
1103                // location
1104                if (carCount < DISPLAY_CAR_LIMIT_50 && (car.getKernel() == null || car.isLead())) {
1105                    if (car.getLoadPriority().equals(CarLoad.PRIORITY_LOW) &&
1106                            car.getTrack().getTrackPriority().equals(Track.PRIORITY_NORMAL)) {
1107                        addLine(SEVEN,
1108                                Bundle.getMessage("buildCarAtLocWithMoves", car.toString(), car.getTypeName(),
1109                                        car.getTypeExtensions(), car.getLocationName(), car.getTrackName(),
1110                                        car.getMoves()));
1111                    } else {
1112                        addLine(SEVEN,
1113                                Bundle.getMessage("buildCarAtLocWithMovesPriority", car.toString(), car.getTypeName(),
1114                                        car.getTypeExtensions(), car.getLocationName(), car.getTrackName(),
1115                                        car.getTrack().getTrackPriority(), car.getMoves(),
1116                                        car.getLoadType().toLowerCase(), car.getLoadName(),
1117                                        car.getLoadPriority()));
1118                    }
1119                    if (car.isLead()) {
1120                        addLine(SEVEN,
1121                                Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1122                                        car.getKernel().getSize(), car.getKernel().getTotalLength(),
1123                                        Setup.getLengthUnit().toLowerCase()));
1124                        // list all of the cars in the kernel now
1125                        for (Car k : car.getKernel().getCars()) {
1126                            if (!k.isLead()) {
1127                                addLine(SEVEN,
1128                                        Bundle.getMessage("buildCarPartOfKernel", k.toString(), k.getKernelName(),
1129                                                k.getKernel().getSize(), k.getKernel().getTotalLength(),
1130                                                Setup.getLengthUnit().toLowerCase()));
1131                            }
1132                        }
1133                    }
1134                    carCount++;
1135                    if (carCount == DISPLAY_CAR_LIMIT_50) {
1136                        addLine(SEVEN,
1137                                Bundle.getMessage("buildOnlyFirstXXXCars", carCount, rl.getName()));
1138                    }
1139                }
1140                // report car in kernel but lead has been removed
1141                if (car.getKernel() != null && !getCarList().contains(car.getKernel().getLead())) {
1142                    addLine(SEVEN,
1143                            Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(),
1144                                    car.getKernel().getSize(), car.getKernel().getTotalLength(),
1145                                    Setup.getLengthUnit().toLowerCase()));
1146                }
1147                // use only the lead car in a kernel for building trains
1148                if (car.getKernel() != null) {
1149                    checkKernel(car); // kernel needs lead car and all cars on
1150                                      // the same track
1151                    if (!car.isLead()) {
1152                        remove(car); // remove this car from the list
1153                        continue;
1154                    }
1155                }
1156                if (getTrain().equals(car.getTrain())) {
1157                    addLine(FIVE, Bundle.getMessage("buildCarAlreadyAssigned", car.toString()));
1158                }
1159            }
1160            addLine(SEVEN, BLANK_LINE);
1161        }
1162    }
1163
1164    protected void sortCarsOnFifoLifoTracks() {
1165        addLine(SEVEN, Bundle.getMessage("buildSortCarsByLastDate"));
1166        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
1167            Car car = getCarList().get(_carIndex);
1168            if (car.getTrack().getServiceOrder().equals(Track.NORMAL) || car.getTrack().isStaging()) {
1169                continue;
1170            }
1171            addLine(SEVEN,
1172                    Bundle.getMessage("buildTrackModePriority", car.toString(), car.getTrack().getTrackTypeName(),
1173                            car.getLocationName(), car.getTrackName(), car.getTrack().getServiceOrder(),
1174                            car.getLastDate()));
1175            Car bestCar = car;
1176            for (int i = _carIndex + 1; i < getCarList().size(); i++) {
1177                Car testCar = getCarList().get(i);
1178                if (testCar.getTrack() == car.getTrack() &&
1179                        bestCar.getLoadPriority().equals(testCar.getLoadPriority())) {
1180                    log.debug("{} car ({}) last moved date: {}", car.getTrack().getTrackTypeName(), testCar.toString(),
1181                            testCar.getLastDate()); // NOI18N
1182                    if (car.getTrack().getServiceOrder().equals(Track.FIFO)) {
1183                        if (bestCar.getLastMoveDate().after(testCar.getLastMoveDate())) {
1184                            bestCar = testCar;
1185                            log.debug("New best car ({})", bestCar.toString());
1186                        }
1187                    } else if (car.getTrack().getServiceOrder().equals(Track.LIFO)) {
1188                        if (bestCar.getLastMoveDate().before(testCar.getLastMoveDate())) {
1189                            bestCar = testCar;
1190                            log.debug("New best car ({})", bestCar.toString());
1191                        }
1192                    }
1193                }
1194            }
1195            if (car != bestCar) {
1196                addLine(SEVEN,
1197                        Bundle.getMessage("buildTrackModeCarPriority", car.getTrack().getTrackTypeName(),
1198                                car.getTrackName(), car.getTrack().getServiceOrder(), bestCar.toString(),
1199                                bestCar.getLastDate(), car.toString(), car.getLastDate()));
1200                getCarList().remove(bestCar); // change sort
1201                getCarList().add(_carIndex, bestCar);
1202            }
1203        }
1204        addLine(SEVEN, BLANK_LINE);
1205    }
1206
1207    /**
1208     * Verifies that all cars in the kernel have the same departure track. Also
1209     * checks to see if the kernel has a lead car and the lead car is in
1210     * service.
1211     *
1212     * @throws BuildFailedException
1213     */
1214    private void checkKernel(Car car) throws BuildFailedException {
1215        boolean foundLeadCar = false;
1216        for (Car c : car.getKernel().getCars()) {
1217            // check that lead car exists
1218            if (c.isLead() && !c.isOutOfService()) {
1219                foundLeadCar = true;
1220            }
1221            // check to see that all cars have the same location and track
1222            if (car.getLocation() != c.getLocation() ||
1223                    c.getTrack() == null ||
1224                    !car.getTrack().getSplitName().equals(c.getTrack().getSplitName())) {
1225                throw new BuildFailedException(Bundle.getMessage("buildErrorCarKernelLocation", c.toString(),
1226                        car.getKernelName(), c.getLocationName(), c.getTrackName(), car.toString(),
1227                        car.getLocationName(), car.getTrackName()));
1228            }
1229        }
1230        // code check, all kernels should have a lead car
1231        if (foundLeadCar == false) {
1232            throw new BuildFailedException(Bundle.getMessage("buildErrorCarKernelNoLead", car.getKernelName()));
1233        }
1234    }
1235
1236    /*
1237     * For blocking cars out of staging
1238     */
1239    protected String getLargestBlock() {
1240        Enumeration<String> en = _numOfBlocks.keys();
1241        String largestBlock = "";
1242        int maxCars = 0;
1243        while (en.hasMoreElements()) {
1244            String locId = en.nextElement();
1245            if (_numOfBlocks.get(locId) > maxCars) {
1246                largestBlock = locId;
1247                maxCars = _numOfBlocks.get(locId);
1248            }
1249        }
1250        return largestBlock;
1251    }
1252
1253    /**
1254     * Returns the routeLocation with the most available moves. Used for
1255     * blocking a train out of staging.
1256     *
1257     * @param blockRouteList The route for this train, modified by deleting
1258     *                       RouteLocations serviced
1259     * @param blockId        Where these cars were originally picked up from.
1260     * @return The location in the route with the most available moves.
1261     */
1262    protected RouteLocation getLocationWithMaximumMoves(List<RouteLocation> blockRouteList, String blockId) {
1263        RouteLocation rlMax = null;
1264        int maxMoves = 0;
1265        for (RouteLocation rl : blockRouteList) {
1266            if (rl == getTrain().getTrainDepartsRouteLocation()) {
1267                continue;
1268            }
1269            if (rl.getMaxCarMoves() - rl.getCarMoves() > maxMoves) {
1270                maxMoves = rl.getMaxCarMoves() - rl.getCarMoves();
1271                rlMax = rl;
1272            }
1273            // if two locations have the same number of moves, return the one
1274            // that doesn't match the block id
1275            if (rl.getMaxCarMoves() - rl.getCarMoves() == maxMoves && !rl.getLocation().getId().equals(blockId)) {
1276                rlMax = rl;
1277            }
1278        }
1279        return rlMax;
1280    }
1281
1282    /**
1283     * Temporally remove cars from staging track if train returning to the same
1284     * staging track to free up track space.
1285     */
1286    protected void makeAdjustmentsIfDepartingStaging() {
1287        if (getTrain().isDepartingStaging()) {
1288            _reqNumOfMoves = 0;
1289            // Move cars out of staging after working other locations
1290            // if leaving and returning to staging on the same track, temporary pull cars off the track
1291            if (getDepartureStagingTrack() == getTerminateStagingTrack()) {
1292                if (!getTrain().isAllowReturnToStagingEnabled() && !Setup.isStagingAllowReturnEnabled()) {
1293                    // takes care of cars in a kernel by getting all cars
1294                    for (Car car : carManager.getList()) {
1295                        // don't remove caboose or car with FRED already
1296                        // assigned to train
1297                        if (car.getTrack() == getDepartureStagingTrack() && car.getRouteDestination() == null) {
1298                            car.setLocation(car.getLocation(), null);
1299                        }
1300                    }
1301                } else {
1302                    // since all cars can return to staging, the track space is
1303                    // consumed for now
1304                    addLine(THREE, BLANK_LINE);
1305                    addLine(THREE, Bundle.getMessage("buildWarnDepartStaging",
1306                            getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName()));
1307                    addLine(THREE, BLANK_LINE);
1308                }
1309            }
1310            addLine(THREE,
1311                    Bundle.getMessage("buildDepartStagingAggressive",
1312                            getDepartureStagingTrack().getLocation().getName()));
1313        }
1314    }
1315
1316    /**
1317     * Restores cars departing staging track assignment.
1318     */
1319    protected void restoreCarsIfDepartingStaging() {
1320        if (getTrain().isDepartingStaging() &&
1321                getDepartureStagingTrack() == getTerminateStagingTrack() &&
1322                !getTrain().isAllowReturnToStagingEnabled() &&
1323                !Setup.isStagingAllowReturnEnabled()) {
1324            // restore departure track for cars departing staging
1325            for (Car car : getCarList()) {
1326                if (car.getLocation() == getDepartureStagingTrack().getLocation() && car.getTrack() == null) {
1327                    car.setLocation(getDepartureStagingTrack().getLocation(), getDepartureStagingTrack(),
1328                            RollingStock.FORCE); // force
1329                    if (car.getKernel() != null) {
1330                        for (Car k : car.getKernel().getCars()) {
1331                            k.setLocation(getDepartureStagingTrack().getLocation(), getDepartureStagingTrack(),
1332                                    RollingStock.FORCE); // force
1333                        }
1334                    }
1335                }
1336            }
1337        }
1338    }
1339
1340    protected void showLoadGenerationOptionsStaging() {
1341        if (getDepartureStagingTrack() != null &&
1342                _reqNumOfMoves > 0 &&
1343                (getDepartureStagingTrack().isAddCustomLoadsEnabled() ||
1344                        getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled() ||
1345                        getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled())) {
1346            addLine(FIVE, Bundle.getMessage("buildCustomLoadOptions", getDepartureStagingTrack().getName()));
1347            if (getDepartureStagingTrack().isAddCustomLoadsEnabled()) {
1348                addLine(FIVE, Bundle.getMessage("buildLoadCarLoads"));
1349            }
1350            if (getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled()) {
1351                addLine(FIVE, Bundle.getMessage("buildLoadAnyCarLoads"));
1352            }
1353            if (getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled()) {
1354                addLine(FIVE, Bundle.getMessage("buildLoadsStaging"));
1355            }
1356            addLine(FIVE, BLANK_LINE);
1357        }
1358    }
1359
1360    /**
1361     * Checks to see if all cars on a staging track have been given a
1362     * destination. Throws exception if there's a car without a destination.
1363     *
1364     * @throws BuildFailedException if car on staging track not assigned to
1365     *                              train
1366     */
1367    protected void checkStuckCarsInStaging() throws BuildFailedException {
1368        if (!getTrain().isDepartingStaging()) {
1369            return;
1370        }
1371        int carCount = 0;
1372        StringBuffer buf = new StringBuffer();
1373        // confirm that all cars in staging are departing
1374        for (Car car : getCarList()) {
1375            // build failure if car departing staging without a destination or
1376            // train
1377            if (car.getTrack() == getDepartureStagingTrack() &&
1378                    (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) {
1379                if (car.getKernel() != null) {
1380                    for (Car c : car.getKernel().getCars()) {
1381                        carCount++;
1382                        addCarToStuckStagingList(c, buf, carCount);
1383                    }
1384                } else {
1385                    carCount++;
1386                    addCarToStuckStagingList(car, buf, carCount);
1387                }
1388            }
1389        }
1390        if (carCount > 0) {
1391            log.debug("{} cars stuck in staging", carCount);
1392            String msg = Bundle.getMessage("buildStagingCouldNotFindDest", carCount,
1393                    getDepartureStagingTrack().getLocation().getName(), getDepartureStagingTrack().getName());
1394            throw new BuildFailedException(msg + buf.toString(), BuildFailedException.STAGING);
1395        }
1396    }
1397
1398    /**
1399     * Creates a list of up to 20 cars stuck in staging.
1400     *
1401     * @param car      The car to add to the list
1402     * @param buf      StringBuffer
1403     * @param carCount how many cars in the list
1404     */
1405    private void addCarToStuckStagingList(Car car, StringBuffer buf, int carCount) {
1406        if (carCount <= DISPLAY_CAR_LIMIT_20) {
1407            buf.append(NEW_LINE + " " + car.toString());
1408        } else if (carCount == DISPLAY_CAR_LIMIT_20 + 1) {
1409            buf.append(NEW_LINE +
1410                    Bundle.getMessage("buildOnlyFirstXXXCars", DISPLAY_CAR_LIMIT_20,
1411                            getDepartureStagingTrack().getName()));
1412        }
1413    }
1414
1415    /**
1416     * Used to determine if a car on a staging track doesn't have a destination
1417     * or train
1418     *
1419     * @return true if at least one car doesn't have a destination or train.
1420     *         false if all cars have a destination.
1421     */
1422    protected boolean isCarStuckStaging() {
1423        if (getTrain().isDepartingStaging()) {
1424            // confirm that all cars in staging are departing
1425            for (Car car : getCarList()) {
1426                if (car.getTrack() == getDepartureStagingTrack() &&
1427                        (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) {
1428                    return true;
1429                }
1430            }
1431        }
1432        return false;
1433    }
1434
1435    protected void finishAddRsToTrain(RollingStock rs, RouteLocation rl, RouteLocation rld, int length,
1436            int weightTons) {
1437        // notify that locations have been modified when build done
1438        // allows automation actions to run properly
1439        if (!_modifiedLocations.contains(rl.getLocation())) {
1440            _modifiedLocations.add(rl.getLocation());
1441        }
1442        if (!_modifiedLocations.contains(rld.getLocation())) {
1443            _modifiedLocations.add(rld.getLocation());
1444        }
1445        rs.setTrain(getTrain());
1446        rs.setRouteLocation(rl);
1447        rs.setRouteDestination(rld);
1448        // now adjust train length and weight for each location that the rolling
1449        // stock is in the train
1450        boolean inTrain = false;
1451        for (RouteLocation routeLocation : getRouteList()) {
1452            if (rl == routeLocation) {
1453                inTrain = true;
1454            }
1455            if (rld == routeLocation) {
1456                break; // done
1457            }
1458            if (inTrain) {
1459                routeLocation.setTrainLength(routeLocation.getTrainLength() + length);
1460                routeLocation.setTrainWeight(routeLocation.getTrainWeight() + weightTons);
1461            }
1462        }
1463    }
1464
1465    /**
1466     * Determine if rolling stock can be picked up based on train direction at
1467     * the route location.
1468     *
1469     * @param rs The rolling stock
1470     * @param rl The rolling stock's route location
1471     * @throws BuildFailedException if coding issue
1472     * @return true if there isn't a problem
1473     */
1474    protected boolean checkPickUpTrainDirection(RollingStock rs, RouteLocation rl) throws BuildFailedException {
1475        // Code Check, car or engine should have a track assignment
1476        if (rs.getTrack() == null) {
1477            throw new BuildFailedException(
1478                    Bundle.getMessage("buildWarningRsNoTrack", rs.toString(), rs.getLocationName()));
1479        }
1480        // ignore local switcher direction
1481        if (getTrain().isLocalSwitcher()) {
1482            return true;
1483        }
1484        if ((rl.getTrainDirection() &
1485                rs.getLocation().getTrainDirections() &
1486                rs.getTrack().getTrainDirections()) != 0) {
1487            return true;
1488        }
1489
1490        // Only track direction can cause the following message. Location
1491        // direction has already been checked
1492        addLine(SEVEN,
1493                Bundle.getMessage("buildRsCanNotPickupUsingTrain", rs.toString(), rl.getTrainDirectionString(),
1494                        rs.getTrackName(), rs.getLocationName(), rl.getId()));
1495        return false;
1496    }
1497
1498    /**
1499     * Used to report a problem picking up the rolling stock due to train
1500     * direction.
1501     *
1502     * @param rl The route location
1503     * @return true if there isn't a problem
1504     */
1505    protected boolean checkPickUpTrainDirection(RouteLocation rl) {
1506        // ignore local switcher direction
1507        if (getTrain().isLocalSwitcher()) {
1508            return true;
1509        }
1510        if ((rl.getTrainDirection() & rl.getLocation().getTrainDirections()) != 0) {
1511            return true;
1512        }
1513
1514        addLine(ONE, Bundle.getMessage("buildLocDirection", rl.getName(), rl.getTrainDirectionString()));
1515        return false;
1516    }
1517
1518    /**
1519     * Determines if car can be pulled from an interchange or spur. Needed for
1520     * quick service tracks.
1521     * 
1522     * @param car the car being pulled
1523     * @return true if car can be pulled, otherwise false.
1524     */
1525    protected boolean checkPickupInterchangeOrSpur(Car car) {
1526        if (car.getTrack().isInterchange()) {
1527            // don't service a car at interchange and has been dropped off
1528            // by this train
1529            if (car.getTrack().getPickupOption().equals(Track.ANY) &&
1530                    car.getLastRouteId().equals(getTrain().getRoute().getId())) {
1531                addLine(SEVEN, Bundle.getMessage("buildExcludeCarDropByTrain", car.toString(),
1532                        car.getTypeName(), getTrain().getRoute().getName(), car.getLocationName(), car.getTrackName()));
1533                return false;
1534            }
1535        }
1536        // is car at interchange or spur and is this train allowed to pull?
1537        if (car.getTrack().isInterchange() || car.getTrack().isSpur()) {
1538            if (car.getTrack().getPickupOption().equals(Track.TRAINS) ||
1539                    car.getTrack().getPickupOption().equals(Track.EXCLUDE_TRAINS)) {
1540                if (car.getTrack().isPickupTrainAccepted(getTrain())) {
1541                    log.debug("Car ({}) can be picked up by this train", car.toString());
1542                } else {
1543                    addLine(SEVEN,
1544                            Bundle.getMessage("buildExcludeCarByTrain", car.toString(), car.getTypeName(),
1545                                    car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName()));
1546                    return false;
1547                }
1548            } else if (car.getTrack().getPickupOption().equals(Track.ROUTES) ||
1549                    car.getTrack().getPickupOption().equals(Track.EXCLUDE_ROUTES)) {
1550                if (car.getTrack().isPickupRouteAccepted(getTrain().getRoute())) {
1551                    log.debug("Car ({}) can be picked up by this route", car.toString());
1552                } else {
1553                    addLine(SEVEN,
1554                            Bundle.getMessage("buildExcludeCarByRoute", car.toString(), car.getTypeName(),
1555                                    car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName()));
1556                    return false;
1557                }
1558            }
1559        }
1560        return true;
1561    }
1562
1563    /**
1564     * Checks to see if an interchange track has destination restrictions.
1565     * Returns true if there's at least one destination in the train's route
1566     * that can service the car departing the interchange.
1567     * 
1568     * @param car the car being evaluated
1569     * @return true if car can be pulled
1570     */
1571    protected boolean checkPickupInterchangeDestinationRestrictions(Car car) {
1572        if (!car.getTrack().isInterchange() ||
1573                car.getTrack().getDestinationOption().equals(Track.ALL_DESTINATIONS) ||
1574                car.getFinalDestination() != null) {
1575            return true;
1576        }
1577        for (RouteLocation rl : getTrain().getRoute().getLocationsBySequenceList()) {
1578            if (car.getTrack().isDestinationAccepted(rl.getLocation())) {
1579                return true;
1580            }
1581        }
1582        addLine(SEVEN, Bundle.getMessage("buildExcludeCarByInterchange", car.toString(),
1583                car.getTypeName(), car.getTrackTypeName(), car.getLocationName(), car.getTrackName()));
1584        return false;
1585    }
1586
1587    /**
1588     * Checks to see if train length would be exceeded if this car was added to
1589     * the train.
1590     *
1591     * @param car the car in question
1592     * @param rl  the departure route location for this car
1593     * @param rld the destination route location for this car
1594     * @return true if car can be added to train
1595     */
1596    protected boolean checkTrainLength(Car car, RouteLocation rl, RouteLocation rld) {
1597        // car can be a kernel so get total length
1598        int length = car.getTotalKernelLength();
1599        boolean carInTrain = false;
1600        for (RouteLocation rlt : getRouteList()) {
1601            if (rl == rlt) {
1602                carInTrain = true;
1603            }
1604            if (rld == rlt) {
1605                break;
1606            }
1607            if (carInTrain && rlt.getTrainLength() + length > rlt.getMaxTrainLength()) {
1608                addLine(FIVE,
1609                        Bundle.getMessage("buildCanNotPickupCarLength", car.toString(), length,
1610                                Setup.getLengthUnit().toLowerCase(), rlt.getMaxTrainLength(),
1611                                Setup.getLengthUnit().toLowerCase(),
1612                                rlt.getTrainLength() + length - rlt.getMaxTrainLength(), rlt.getName(), rlt.getId()));
1613                return false;
1614            }
1615        }
1616        return true;
1617    }
1618
1619    protected boolean checkDropTrainDirection(RollingStock rs, RouteLocation rld, Track track) {
1620        // local?
1621        if (getTrain().isLocalSwitcher()) {
1622            return true;
1623        }
1624        // this location only services trains with these directions
1625        int serviceTrainDir = rld.getLocation().getTrainDirections();
1626        if (track != null) {
1627            serviceTrainDir = serviceTrainDir & track.getTrainDirections();
1628        }
1629
1630        // is this a car going to alternate track? Check to see if direct move
1631        // from alternate to FD track is possible
1632        if ((rld.getTrainDirection() & serviceTrainDir) != 0 &&
1633                rs != null &&
1634                track != null &&
1635                Car.class.isInstance(rs)) {
1636            Car car = (Car) rs;
1637            if (car.getFinalDestinationTrack() != null &&
1638                    track == car.getFinalDestinationTrack().getAlternateTrack() &&
1639                    (track.getTrainDirections() & car.getFinalDestinationTrack().getTrainDirections()) == 0) {
1640                addLine(SEVEN,
1641                        Bundle.getMessage("buildCanNotDropRsUsingTrain4", car.getFinalDestinationTrack().getName(),
1642                                formatStringToCommaSeparated(
1643                                        Setup.getDirectionStrings(car.getFinalDestinationTrack().getTrainDirections())),
1644                                car.getFinalDestinationTrack().getAlternateTrack().getName(),
1645                                formatStringToCommaSeparated(Setup.getDirectionStrings(
1646                                        car.getFinalDestinationTrack().getAlternateTrack().getTrainDirections()))));
1647                return false;
1648            }
1649        }
1650
1651        if ((rld.getTrainDirection() & serviceTrainDir) != 0) {
1652            return true;
1653        }
1654        if (rs == null || track == null) {
1655            addLine(SEVEN,
1656                    Bundle.getMessage("buildDestinationDoesNotService", rld.getName(), rld.getTrainDirectionString()));
1657        } else {
1658            addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain", rs.toString(),
1659                    rld.getTrainDirectionString(), track.getName()));
1660        }
1661        return false;
1662    }
1663
1664    protected boolean checkDropTrainDirection(RouteLocation rld) {
1665        return (checkDropTrainDirection(null, rld, null));
1666    }
1667
1668    /**
1669     * Determinate if rolling stock can be dropped by this train to the track
1670     * specified.
1671     *
1672     * @param rs    the rolling stock to be set out.
1673     * @param track the destination track.
1674     * @return true if able to drop.
1675     */
1676    protected boolean checkTrainCanDrop(RollingStock rs, Track track) {
1677        if (track.isInterchange() || track.isSpur()) {
1678            if (track.getDropOption().equals(Track.TRAINS) || track.getDropOption().equals(Track.EXCLUDE_TRAINS)) {
1679                if (track.isDropTrainAccepted(getTrain())) {
1680                    log.debug("Rolling stock ({}) can be droped by train to track ({})", rs.toString(),
1681                            track.getName());
1682                } else {
1683                    addLine(SEVEN,
1684                            Bundle.getMessage("buildCanNotDropTrain", rs.toString(), getTrain().getName(),
1685                                    track.getTrackTypeName(), track.getLocation().getName(), track.getName()));
1686                    return false;
1687                }
1688            }
1689            if (track.getDropOption().equals(Track.ROUTES) || track.getDropOption().equals(Track.EXCLUDE_ROUTES)) {
1690                if (track.isDropRouteAccepted(getTrain().getRoute())) {
1691                    log.debug("Rolling stock ({}) can be droped by route to track ({})", rs.toString(),
1692                            track.getName());
1693                } else {
1694                    addLine(SEVEN,
1695                            Bundle.getMessage("buildCanNotDropRoute", rs.toString(), getTrain().getRoute().getName(),
1696                                    track.getTrackTypeName(), track.getLocation().getName(), track.getName()));
1697                    return false;
1698                }
1699            }
1700        }
1701        return true;
1702    }
1703
1704    /**
1705     * Check departure staging track to see if engines and cars are available to
1706     * a new train. Also confirms that the engine and car type, load, road, etc.
1707     * are accepted by the train.
1708     *
1709     * @param departStageTrack The staging track
1710     * @return true is there are engines and cars available.
1711     */
1712    protected boolean checkDepartureStagingTrack(Track departStageTrack) {
1713        addLine(THREE,
1714                Bundle.getMessage("buildStagingHas", departStageTrack.getName(),
1715                        Integer.toString(departStageTrack.getNumberEngines()),
1716                        Integer.toString(departStageTrack.getNumberCars())));
1717        // does this staging track service this train?
1718        if (!departStageTrack.isPickupTrainAccepted(getTrain())) {
1719            addLine(THREE, Bundle.getMessage("buildStagingNotTrain", departStageTrack.getName()));
1720            return false;
1721        }
1722        if (departStageTrack.getNumberRS() == 0 && getTrain().getTrainDepartsRouteLocation().getMaxCarMoves() > 0) {
1723            addLine(THREE, Bundle.getMessage("buildStagingEmpty", departStageTrack.getName()));
1724            return false;
1725        }
1726        if (departStageTrack.getUsedLength() > getTrain().getTrainDepartsRouteLocation().getMaxTrainLength()) {
1727            addLine(THREE,
1728                    Bundle.getMessage("buildStagingTrainTooLong", departStageTrack.getName(),
1729                            departStageTrack.getUsedLength(), Setup.getLengthUnit().toLowerCase(),
1730                            getTrain().getTrainDepartsRouteLocation().getMaxTrainLength()));
1731            return false;
1732        }
1733        if (departStageTrack.getNumberCars() > getTrain().getTrainDepartsRouteLocation().getMaxCarMoves()) {
1734            addLine(THREE, Bundle.getMessage("buildStagingTooManyCars", departStageTrack.getName(),
1735                    departStageTrack.getNumberCars(), getTrain().getTrainDepartsRouteLocation().getMaxCarMoves()));
1736            return false;
1737        }
1738        // does the staging track have the right number of locomotives?
1739        if (!getTrain().getNumberEngines().equals("0") &&
1740                getNumberEngines(getTrain().getNumberEngines()) != departStageTrack.getNumberEngines()) {
1741            addLine(THREE, Bundle.getMessage("buildStagingNotEngines", departStageTrack.getName(),
1742                    departStageTrack.getNumberEngines(), getTrain().getNumberEngines()));
1743            return false;
1744        }
1745        // is the staging track direction correct for this train?
1746        if ((departStageTrack.getTrainDirections() &
1747                getTrain().getTrainDepartsRouteLocation().getTrainDirection()) == 0) {
1748            addLine(THREE, Bundle.getMessage("buildStagingNotDirection", departStageTrack.getName()));
1749            return false;
1750        }
1751
1752        // check engines on staging track
1753        if (!checkStagingEngines(departStageTrack)) {
1754            return false;
1755        }
1756
1757        // check for car road, load, owner, built, Caboose or FRED needed
1758        if (!checkStagingCarTypeRoadLoadOwnerBuiltCabooseOrFRED(departStageTrack)) {
1759            return false;
1760        }
1761
1762        // determine if staging track is in a pool (multiple trains on one
1763        // staging track)
1764        if (!checkStagingPool(departStageTrack)) {
1765            return false;
1766        }
1767        addLine(FIVE,
1768                Bundle.getMessage("buildTrainCanDepartTrack", getTrain().getName(), departStageTrack.getName()));
1769        return true;
1770    }
1771
1772    /**
1773     * Used to determine if engines on staging track are acceptable to the train
1774     * being built.
1775     *
1776     * @param departStageTrack Depart staging track
1777     * @return true if engines on staging track meet train requirement
1778     */
1779    private boolean checkStagingEngines(Track departStageTrack) {
1780        if (departStageTrack.getNumberEngines() > 0) {
1781            for (Engine eng : engineManager.getList(departStageTrack)) {
1782                // clones are are already assigned to a train
1783                if (eng.isClone()) {
1784                    continue;
1785                }
1786                // has engine been assigned to another train?
1787                if (eng.getRouteLocation() != null) {
1788                    addLine(THREE, Bundle.getMessage("buildStagingDepart", departStageTrack.getName(),
1789                            eng.getTrainName()));
1790                    return false;
1791                }
1792                if (eng.getTrain() != null && eng.getTrain() != getTrain()) {
1793                    addLine(THREE, Bundle.getMessage("buildStagingDepartEngineTrain",
1794                            departStageTrack.getName(), eng.toString(), eng.getTrainName()));
1795                    return false;
1796                }
1797                // does the train accept the engine type from the staging
1798                // track?
1799                if (!getTrain().isTypeNameAccepted(eng.getTypeName())) {
1800                    addLine(THREE, Bundle.getMessage("buildStagingDepartEngineType",
1801                            departStageTrack.getName(), eng.toString(), eng.getTypeName(), getTrain().getName()));
1802                    return false;
1803                }
1804                // does the train accept the engine model from the staging
1805                // track?
1806                if (!getTrain().getEngineModel().equals(Train.NONE) &&
1807                        !getTrain().getEngineModel().equals(eng.getModel())) {
1808                    addLine(THREE,
1809                            Bundle.getMessage("buildStagingDepartEngineModel", departStageTrack.getName(),
1810                                    eng.toString(), eng.getModel(), getTrain().getName()));
1811                    return false;
1812                }
1813                // does the engine road match the train requirements?
1814                if (!getTrain().getCarRoadOption().equals(Train.ALL_ROADS) &&
1815                        !getTrain().getEngineRoad().equals(Train.NONE) &&
1816                        !getTrain().getEngineRoad().equals(eng.getRoadName())) {
1817                    addLine(THREE, Bundle.getMessage("buildStagingDepartEngineRoad",
1818                            departStageTrack.getName(), eng.toString(), eng.getRoadName(), getTrain().getName()));
1819                    return false;
1820                }
1821                // does the train accept the engine road from the staging
1822                // track?
1823                if (getTrain().getEngineRoad().equals(Train.NONE) &&
1824                        !getTrain().isLocoRoadNameAccepted(eng.getRoadName())) {
1825                    addLine(THREE, Bundle.getMessage("buildStagingDepartEngineRoad",
1826                            departStageTrack.getName(), eng.toString(), eng.getRoadName(), getTrain().getName()));
1827                    return false;
1828                }
1829                // does the train accept the engine owner from the staging
1830                // track?
1831                if (!getTrain().isOwnerNameAccepted(eng.getOwnerName())) {
1832                    addLine(THREE, Bundle.getMessage("buildStagingDepartEngineOwner",
1833                            departStageTrack.getName(), eng.toString(), eng.getOwnerName(), getTrain().getName()));
1834                    return false;
1835                }
1836                // does the train accept the engine built date from the
1837                // staging track?
1838                if (!getTrain().isBuiltDateAccepted(eng.getBuilt())) {
1839                    addLine(THREE,
1840                            Bundle.getMessage("buildStagingDepartEngineBuilt", departStageTrack.getName(),
1841                                    eng.toString(), eng.getBuilt(), getTrain().getName()));
1842                    return false;
1843                }
1844            }
1845        }
1846        return true;
1847    }
1848
1849    /**
1850     * Checks to see if all cars in staging can be serviced by the train being
1851     * built. Also searches for caboose or car with FRED.
1852     *
1853     * @param departStageTrack Departure staging track
1854     * @return True if okay
1855     */
1856    private boolean checkStagingCarTypeRoadLoadOwnerBuiltCabooseOrFRED(Track departStageTrack) {
1857        boolean foundCaboose = false;
1858        boolean foundFRED = false;
1859        if (departStageTrack.getNumberCars() > 0) {
1860            for (Car car : carManager.getList(departStageTrack)) {
1861                // clones are are already assigned to a train
1862                if (car.isClone()) {
1863                    continue;
1864                }
1865                // ignore non-lead cars in kernels
1866                if (car.getKernel() != null && !car.isLead()) {
1867                    continue; // ignore non-lead cars
1868                }
1869                // has car been assigned to another train?
1870                if (car.getRouteLocation() != null) {
1871                    log.debug("Car ({}) has route location ({})", car.toString(), car.getRouteLocation().getName());
1872                    addLine(THREE,
1873                            Bundle.getMessage("buildStagingDepart", departStageTrack.getName(), car.getTrainName()));
1874                    return false;
1875                }
1876                if (car.getTrain() != null && car.getTrain() != getTrain()) {
1877                    addLine(THREE, Bundle.getMessage("buildStagingDepartCarTrain",
1878                            departStageTrack.getName(), car.toString(), car.getTrainName()));
1879                    return false;
1880                }
1881                // does the train accept the car type from the staging track?
1882                if (!getTrain().isTypeNameAccepted(car.getTypeName())) {
1883                    addLine(THREE,
1884                            Bundle.getMessage("buildStagingDepartCarType", departStageTrack.getName(), car.toString(),
1885                                    car.getTypeName(), getTrain().getName()));
1886                    return false;
1887                }
1888                // does the train accept the car road from the staging track?
1889                if (!car.isCaboose() && !getTrain().isCarRoadNameAccepted(car.getRoadName())) {
1890                    addLine(THREE,
1891                            Bundle.getMessage("buildStagingDepartCarRoad", departStageTrack.getName(), car.toString(),
1892                                    car.getRoadName(), getTrain().getName()));
1893                    return false;
1894                }
1895                // does the train accept the car load from the staging track?
1896                if (!car.isCaboose() &&
1897                        !car.isPassenger() &&
1898                        (!car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
1899                                !departStageTrack.isAddCustomLoadsEnabled() &&
1900                                        !departStageTrack.isAddCustomLoadsAnySpurEnabled() &&
1901                                        !departStageTrack.isAddCustomLoadsAnyStagingTrackEnabled()) &&
1902                        !getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
1903                    addLine(THREE,
1904                            Bundle.getMessage("buildStagingDepartCarLoad", departStageTrack.getName(), car.toString(),
1905                                    car.getLoadName(), getTrain().getName()));
1906                    return false;
1907                }
1908                // does the train accept the car owner from the staging track?
1909                if (!getTrain().isOwnerNameAccepted(car.getOwnerName())) {
1910                    addLine(THREE, Bundle.getMessage("buildStagingDepartCarOwner",
1911                            departStageTrack.getName(), car.toString(), car.getOwnerName(), getTrain().getName()));
1912                    return false;
1913                }
1914                // does the train accept the car built date from the staging
1915                // track?
1916                if (!getTrain().isBuiltDateAccepted(car.getBuilt())) {
1917                    addLine(THREE, Bundle.getMessage("buildStagingDepartCarBuilt",
1918                            departStageTrack.getName(), car.toString(), car.getBuilt(), getTrain().getName()));
1919                    return false;
1920                }
1921                // does the car have a destination serviced by this train?
1922                if (car.getDestination() != null) {
1923                    log.debug("Car ({}) has a destination ({}, {})", car.toString(), car.getDestinationName(),
1924                            car.getDestinationTrackName());
1925                    if (!getTrain().isServiceable(car)) {
1926                        addLine(THREE,
1927                                Bundle.getMessage("buildStagingDepartCarDestination", departStageTrack.getName(),
1928                                        car.toString(), car.getDestinationName(), getTrain().getName()));
1929                        return false;
1930                    }
1931                }
1932                // is this car a caboose with the correct road for this train?
1933                if (car.isCaboose() &&
1934                        (getTrain().getCabooseRoad().equals(Train.NONE) ||
1935                                getTrain().getCabooseRoad().equals(car.getRoadName()))) {
1936                    foundCaboose = true;
1937                }
1938                // is this car have a FRED with the correct road for this train?
1939                if (car.hasFred() &&
1940                        (getTrain().getCabooseRoad().equals(Train.NONE) ||
1941                                getTrain().getCabooseRoad().equals(car.getRoadName()))) {
1942                    foundFRED = true;
1943                }
1944            }
1945        }
1946        // does the train require a caboose and did we find one from staging?
1947        if (getTrain().isCabooseNeeded() && !foundCaboose) {
1948            addLine(THREE,
1949                    Bundle.getMessage("buildStagingNoCaboose", departStageTrack.getName(),
1950                            getTrain().getCabooseRoad()));
1951            return false;
1952        }
1953        // does the train require a car with FRED and did we find one from
1954        // staging?
1955        if (getTrain().isFredNeeded() && !foundFRED) {
1956            addLine(THREE,
1957                    Bundle.getMessage("buildStagingNoCarFRED", departStageTrack.getName(),
1958                            getTrain().getCabooseRoad()));
1959            return false;
1960        }
1961        return true;
1962    }
1963
1964    /**
1965     * Used to determine if staging track in a pool is the appropriated one for
1966     * departure. Staging tracks in a pool can operate in one of two ways FIFO
1967     * or LIFO. In FIFO mode (First in First out), the program selects a staging
1968     * track from the pool that has cars with the earliest arrival date. In LIFO
1969     * mode (Last in First out), the program selects a staging track from the
1970     * pool that has cars with the latest arrival date.
1971     *
1972     * @param departStageTrack the track being tested
1973     * @return true if departure on this staging track is possible
1974     */
1975    private boolean checkStagingPool(Track departStageTrack) {
1976        if (departStageTrack.getPool() == null ||
1977                departStageTrack.getServiceOrder().equals(Track.NORMAL) ||
1978                departStageTrack.getNumberCars() == 0) {
1979            return true;
1980        }
1981
1982        addLine(SEVEN, Bundle.getMessage("buildStagingTrackPool", departStageTrack.getName(),
1983                departStageTrack.getPool().getName(), departStageTrack.getPool().getSize(),
1984                departStageTrack.getServiceOrder()));
1985
1986        List<Car> carList = carManager.getAvailableTrainList(getTrain());
1987        Date carDepartStageTrackDate = null;
1988        for (Car car : carList) {
1989            if (car.getTrack() == departStageTrack) {
1990                carDepartStageTrackDate = car.getLastMoveDate();
1991                break; // use 1st car found
1992            }
1993        }
1994        // next check isn't really necessary, null is never returned
1995        if (carDepartStageTrackDate == null) {
1996            return true; // no cars with found date
1997        }
1998
1999        for (Track track : departStageTrack.getPool().getTracks()) {
2000            if (track == departStageTrack || track.getNumberCars() == 0) {
2001                continue;
2002            }
2003            // determine dates cars arrived into staging
2004            Date carOtherStageTrackDate = null;
2005
2006            for (Car car : carList) {
2007                if (car.getTrack() == track) {
2008                    carOtherStageTrackDate = car.getLastMoveDate();
2009                    break; // use 1st car found
2010                }
2011            }
2012            if (carOtherStageTrackDate != null) {
2013                if (departStageTrack.getServiceOrder().equals(Track.LIFO)) {
2014                    if (carDepartStageTrackDate.before(carOtherStageTrackDate)) {
2015                        addLine(SEVEN,
2016                                Bundle.getMessage("buildStagingCarsBefore", departStageTrack.getName(),
2017                                        track.getName()));
2018                        return false;
2019                    }
2020                } else {
2021                    if (carOtherStageTrackDate.before(carDepartStageTrackDate)) {
2022                        addLine(SEVEN, Bundle.getMessage("buildStagingCarsBefore", track.getName(),
2023                                departStageTrack.getName()));
2024                        return false;
2025                    }
2026                }
2027            }
2028        }
2029        return true;
2030    }
2031
2032    /**
2033     * Checks to see if staging track can accept train.
2034     *
2035     * @param terminateStageTrack the staging track
2036     * @return true if staging track is empty, not reserved, and accepts car and
2037     *         engine types, roads, and loads.
2038     */
2039    protected boolean checkTerminateStagingTrack(Track terminateStageTrack) {
2040        if (!terminateStageTrack.isDropTrainAccepted(getTrain())) {
2041            addLine(FIVE, Bundle.getMessage("buildStagingNotTrain", terminateStageTrack.getName()));
2042            return false;
2043        }
2044        // In normal mode, find a completely empty track. In aggressive mode, a
2045        // track that scheduled to depart is okay
2046        if (((!Setup.isBuildAggressive() ||
2047                !Setup.isStagingTrackImmediatelyAvail() ||
2048                terminateStageTrack.isQuickServiceEnabled()) &&
2049                terminateStageTrack.getNumberRS() != 0) ||
2050                (terminateStageTrack.getNumberRS() != terminateStageTrack.getPickupRS()) &&
2051                        terminateStageTrack.getNumberRS() != 0) {
2052            addLine(FIVE,
2053                    Bundle.getMessage("buildStagingTrackOccupied", terminateStageTrack.getName(),
2054                            terminateStageTrack.getNumberEngines(), terminateStageTrack.getNumberCars()));
2055            if (terminateStageTrack.getIgnoreUsedLengthPercentage() == Track.IGNORE_0) {
2056                return false;
2057            } else {
2058                addLine(FIVE,
2059                        Bundle.getMessage("buildTrackHasPlannedPickups", terminateStageTrack.getName(),
2060                                terminateStageTrack.getIgnoreUsedLengthPercentage(), terminateStageTrack.getLength(),
2061                                Setup.getLengthUnit().toLowerCase(), terminateStageTrack.getUsedLength(),
2062                                terminateStageTrack.getReserved(),
2063                                terminateStageTrack.getReservedLengthSetouts(),
2064                                terminateStageTrack.getReservedLengthSetouts() - terminateStageTrack.getReserved(),
2065                                terminateStageTrack.getAvailableTrackSpace()));
2066            }
2067        }
2068        if ((!Setup.isBuildOnTime() || !terminateStageTrack.isQuickServiceEnabled()) &&
2069                terminateStageTrack.getDropRS() != 0) {
2070            addLine(FIVE, Bundle.getMessage("buildStagingTrackReserved", terminateStageTrack.getName(),
2071                    terminateStageTrack.getDropRS()));
2072            return false;
2073        }
2074        if (terminateStageTrack.getPickupRS() > 0) {
2075            addLine(FIVE, Bundle.getMessage("buildStagingTrackDepart", terminateStageTrack.getName()));
2076        }
2077        // if track is setup to accept a specific train or route, then ignore
2078        // other track restrictions
2079        if (terminateStageTrack.getDropOption().equals(Track.TRAINS) ||
2080                terminateStageTrack.getDropOption().equals(Track.ROUTES)) {
2081            addLine(SEVEN,
2082                    Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(),
2083                            terminateStageTrack.getName()));
2084            return true; // train can drop to this track, ignore other track
2085                         // restrictions
2086        }
2087        if (!Setup.isStagingTrainCheckEnabled()) {
2088            addLine(SEVEN,
2089                    Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(),
2090                            terminateStageTrack.getName()));
2091            return true;
2092        } else if (!checkTerminateStagingTrackRestrictions(terminateStageTrack)) {
2093            addLine(SEVEN,
2094                    Bundle.getMessage("buildStagingTrackRestriction", terminateStageTrack.getName(),
2095                            getTrain().getName()));
2096            addLine(SEVEN, Bundle.getMessage("buildOptionRestrictStaging"));
2097            return false;
2098        }
2099        return true;
2100    }
2101
2102    private boolean checkTerminateStagingTrackRestrictions(Track terminateStageTrack) {
2103        // check go see if location/track will accept the train's car and engine
2104        // types
2105        for (String name : getTrain().getTypeNames()) {
2106            if (!getTerminateLocation().acceptsTypeName(name)) {
2107                addLine(FIVE,
2108                        Bundle.getMessage("buildDestinationType", getTerminateLocation().getName(), name));
2109                return false;
2110            }
2111            if (!terminateStageTrack.isTypeNameAccepted(name)) {
2112                addLine(FIVE,
2113                        Bundle.getMessage("buildStagingTrackType", terminateStageTrack.getLocation().getName(),
2114                                terminateStageTrack.getName(), name));
2115                return false;
2116            }
2117        }
2118        // check go see if track will accept the train's car roads
2119        if (getTrain().getCarRoadOption().equals(Train.ALL_ROADS) &&
2120                !terminateStageTrack.getRoadOption().equals(Track.ALL_ROADS)) {
2121            addLine(FIVE, Bundle.getMessage("buildStagingTrackAllRoads", terminateStageTrack.getName()));
2122            return false;
2123        }
2124        // now determine if roads accepted by train are also accepted by staging
2125        // track
2126        // TODO should we be checking caboose and loco road names?
2127        for (String road : InstanceManager.getDefault(CarRoads.class).getNames()) {
2128            if (getTrain().isCarRoadNameAccepted(road)) {
2129                if (!terminateStageTrack.isRoadNameAccepted(road)) {
2130                    addLine(FIVE,
2131                            Bundle.getMessage("buildStagingTrackRoad", terminateStageTrack.getLocation().getName(),
2132                                    terminateStageTrack.getName(), road, ""));
2133                    return false;
2134                }
2135            }
2136        }
2137
2138        // determine if staging will accept loads carried by train
2139        if (getTrain().getLoadOption().equals(Train.ALL_LOADS) &&
2140                !terminateStageTrack.getLoadOption().equals(Track.ALL_LOADS)) {
2141            addLine(FIVE, Bundle.getMessage("buildStagingTrackAllLoads", terminateStageTrack.getName()));
2142            return false;
2143        }
2144        // get all of the types and loads that a train can carry, and determine
2145        // if staging will accept
2146        for (String type : getTrain().getTypeNames()) {
2147            for (String load : carLoads.getNames(type)) {
2148                if (getTrain().isLoadNameAccepted(load, type)) {
2149                    if (!terminateStageTrack.isLoadNameAndCarTypeAccepted(load, type)) {
2150                        addLine(FIVE,
2151                                Bundle.getMessage("buildStagingTrackLoad", terminateStageTrack.getLocation().getName(),
2152                                        terminateStageTrack.getName(), type + CarLoad.SPLIT_CHAR + load));
2153                        return false;
2154                    }
2155                }
2156            }
2157        }
2158        addLine(SEVEN,
2159                Bundle.getMessage("buildTrainCanTerminateTrack", getTrain().getName(), terminateStageTrack.getName()));
2160        return true;
2161    }
2162
2163    boolean routeToTrackFound;
2164
2165    protected boolean checkBasicMoves(Car car, Track track) {
2166        if (car.getTrack() == track) {
2167            return false;
2168        }
2169        // don't allow local move to track with a "similar" name
2170        if (car.getSplitLocationName().equals(track.getLocation().getSplitName()) &&
2171                car.getSplitTrackName().equals(track.getSplitName())) {
2172            return false;
2173        }
2174        if (track.isStaging() && car.getLocation() == track.getLocation()) {
2175            return false; // don't use same staging location
2176        }
2177        // is the car's destination the terminal and is that allowed?
2178        if (!checkThroughCarsAllowed(car, track.getLocation().getName())) {
2179            return false;
2180        }
2181        if (!checkLocalMovesAllowed(car, track)) {
2182            return false;
2183        }
2184        return true;
2185    }
2186
2187    /**
2188     * Used when generating a car load from staging.
2189     *
2190     * @param car   the car.
2191     * @param track the car's destination track that has the schedule.
2192     * @return ScheduleItem si if match found, null otherwise.
2193     * @throws BuildFailedException if schedule doesn't have any line items
2194     */
2195    protected ScheduleItem getScheduleItem(Car car, Track track) throws BuildFailedException {
2196        if (track.getSchedule() == null) {
2197            return null;
2198        }
2199        if (!track.isTypeNameAccepted(car.getTypeName())) {
2200            log.debug("Track ({}) doesn't service car type ({})", track.getName(), car.getTypeName());
2201            if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) {
2202                addLine(SEVEN,
2203                        Bundle.getMessage("buildSpurNotThisType", track.getLocation().getName(), track.getName(),
2204                                track.getScheduleName(), car.getTypeName()));
2205            }
2206            return null;
2207        }
2208        ScheduleItem si = null;
2209        if (track.getScheduleMode() == Track.SEQUENTIAL) {
2210            si = track.getCurrentScheduleItem();
2211            // code check
2212            if (si == null) {
2213                throw new BuildFailedException(Bundle.getMessage("buildErrorNoScheduleItem", track.getScheduleItemId(),
2214                        track.getScheduleName(), track.getName(), track.getLocation().getName()));
2215            }
2216            return checkScheduleItem(si, car, track);
2217        }
2218        log.debug("Track ({}) in match mode", track.getName());
2219        // go through entire schedule looking for a match
2220        for (int i = 0; i < track.getSchedule().getSize(); i++) {
2221            si = track.getNextScheduleItem();
2222            // code check
2223            if (si == null) {
2224                throw new BuildFailedException(Bundle.getMessage("buildErrorNoScheduleItem", track.getScheduleItemId(),
2225                        track.getScheduleName(), track.getName(), track.getLocation().getName()));
2226            }
2227            si = checkScheduleItem(si, car, track);
2228            if (si != null) {
2229                break;
2230            }
2231        }
2232        return si;
2233    }
2234
2235    /**
2236     * Used when generating a car load from staging. Checks a schedule item to
2237     * see if the car type matches, and the train and track can service the
2238     * schedule item's load. This code doesn't check to see if the car's load
2239     * can be serviced by the schedule. Instead a schedule item is returned that
2240     * allows the program to assign a custom load to the car that matches a
2241     * schedule item. Therefore, schedule items that don't request a custom load
2242     * are ignored.
2243     *
2244     * @param si    the schedule item
2245     * @param car   the car to check
2246     * @param track the destination track
2247     * @return Schedule item si if okay, null otherwise.
2248     */
2249    private ScheduleItem checkScheduleItem(ScheduleItem si, Car car, Track track) {
2250        if (!car.getTypeName().equals(si.getTypeName()) ||
2251                si.getReceiveLoadName().equals(ScheduleItem.NONE) ||
2252                si.getReceiveLoadName().equals(carLoads.getDefaultEmptyName()) ||
2253                si.getReceiveLoadName().equals(carLoads.getDefaultLoadName())) {
2254            log.debug("Not using track ({}) schedule request type ({}) road ({}) load ({})", track.getName(),
2255                    si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()); // NOI18N
2256            if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) {
2257                addLine(SEVEN,
2258                        Bundle.getMessage("buildSpurScheduleNotUsed", track.getLocation().getName(), track.getName(),
2259                                track.getScheduleName(), si.getId(), track.getScheduleModeName().toLowerCase(),
2260                                si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()));
2261            }
2262            return null;
2263        }
2264        if (!si.getRoadName().equals(ScheduleItem.NONE) && !car.getRoadName().equals(si.getRoadName())) {
2265            log.debug("Not using track ({}) schedule request type ({}) road ({}) load ({})", track.getName(),
2266                    si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()); // NOI18N
2267            if (!Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) {
2268                addLine(SEVEN,
2269                        Bundle.getMessage("buildSpurScheduleNotUsed", track.getLocation().getName(), track.getName(),
2270                                track.getScheduleName(), si.getId(), track.getScheduleModeName().toLowerCase(),
2271                                si.getTypeName(), si.getRoadName(), si.getReceiveLoadName()));
2272            }
2273            return null;
2274        }
2275        if (!getTrain().isLoadNameAccepted(si.getReceiveLoadName(), si.getTypeName())) {
2276            addLine(SEVEN, Bundle.getMessage("buildTrainNotNewLoad", getTrain().getName(),
2277                    si.getReceiveLoadName(), track.getLocation().getName(), track.getName()));
2278            return null;
2279        }
2280        // does the departure track allow this load?
2281        if (!car.getTrack().isLoadNameAndCarTypeShipped(si.getReceiveLoadName(), car.getTypeName())) {
2282            addLine(SEVEN,
2283                    Bundle.getMessage("buildTrackNotLoadSchedule", car.getTrackName(), si.getReceiveLoadName(),
2284                            track.getLocation().getName(), track.getName(), si.getId()));
2285            return null;
2286        }
2287        if (!si.getSetoutTrainScheduleId().equals(ScheduleItem.NONE) &&
2288                !trainScheduleManager.getTrainScheduleActiveId().equals(si.getSetoutTrainScheduleId())) {
2289            log.debug("Schedule item isn't active");
2290            // build the status message
2291            TrainSchedule aSch = trainScheduleManager.getScheduleById(trainScheduleManager.getTrainScheduleActiveId());
2292            TrainSchedule tSch = trainScheduleManager.getScheduleById(si.getSetoutTrainScheduleId());
2293            String aName = "";
2294            String tName = "";
2295            if (aSch != null) {
2296                aName = aSch.getName();
2297            }
2298            if (tSch != null) {
2299                tName = tSch.getName();
2300            }
2301            addLine(SEVEN,
2302                    Bundle.getMessage("buildScheduleNotActive", track.getName(), si.getId(), tName, aName));
2303
2304            return null;
2305        }
2306        if (!si.getRandom().equals(ScheduleItem.NONE)) {
2307            if (!si.doRandom()) {
2308                addLine(SEVEN,
2309                        Bundle.getMessage("buildScheduleRandom", track.getLocation().getName(), track.getName(),
2310                                track.getScheduleName(), si.getId(), si.getReceiveLoadName(), si.getRandom(),
2311                                si.getCalculatedRandom()));
2312                return null;
2313            }
2314        }
2315        log.debug("Found track ({}) schedule item id ({}) for car ({})", track.getName(), si.getId(), car.toString());
2316        return si;
2317    }
2318
2319    protected void showCarServiceOrder(Car car) {
2320        if (!car.getTrack().getServiceOrder().equals(Track.NORMAL) && !car.getTrack().isStaging()) {
2321            addLine(SEVEN,
2322                    Bundle.getMessage("buildTrackModePriority", car.toString(), car.getTrack().getTrackTypeName(),
2323                            car.getLocationName(), car.getTrackName(), car.getTrack().getServiceOrder(),
2324                            car.getLastDate()));
2325        }
2326    }
2327
2328    /**
2329     * Returns a list containing two tracks. The 1st track found for the car,
2330     * the 2nd track is the car's final destination if an alternate track was
2331     * used for the car. 2nd track can be null.
2332     *
2333     * @param car The car needing a destination track
2334     * @param rld the RouteLocation destination
2335     * @return List containing up to two tracks. No tracks if none found.
2336     */
2337    protected List<Track> getTracksAtDestination(Car car, RouteLocation rld) {
2338        List<Track> tracks = new ArrayList<>();
2339        Location testDestination = rld.getLocation();
2340        // first report if there are any alternate tracks
2341        for (Track track : testDestination.getTracksByNameList(null)) {
2342            if (track.isAlternate()) {
2343                addLine(SEVEN, Bundle.getMessage("buildTrackIsAlternate", car.toString(),
2344                        track.getTrackTypeName(), track.getLocation().getName(), track.getName()));
2345            }
2346        }
2347        // now find a track for this car
2348        for (Track testTrack : testDestination.getTracksByMoves(null)) {
2349            // normally don't move car to a track with the same name at the same
2350            // location
2351            if (car.getSplitLocationName().equals(testTrack.getLocation().getSplitName()) &&
2352                    car.getSplitTrackName().equals(testTrack.getSplitName()) &&
2353                    !car.isPassenger() &&
2354                    !car.isCaboose() &&
2355                    !car.hasFred()) {
2356                addLine(SEVEN,
2357                        Bundle.getMessage("buildCanNotDropCarSameTrack", car.toString(), testTrack.getName()));
2358                continue;
2359            }
2360            // Can the train service this track?
2361            if (!checkDropTrainDirection(car, rld, testTrack)) {
2362                continue;
2363            }
2364            // drop to interchange or spur?
2365            if (!checkTrainCanDrop(car, testTrack)) {
2366                continue;
2367            }
2368            // report if track has planned pickups
2369            if (testTrack.getIgnoreUsedLengthPercentage() > Track.IGNORE_0) {
2370                addLine(SEVEN,
2371                        Bundle.getMessage("buildTrackHasPlannedPickups", testTrack.getName(),
2372                                testTrack.getIgnoreUsedLengthPercentage(), testTrack.getLength(),
2373                                Setup.getLengthUnit().toLowerCase(), testTrack.getUsedLength(), testTrack.getReserved(),
2374                                testTrack.getReservedLengthSetouts(),
2375                                testTrack.getReservedLengthPickups(),
2376                                testTrack.getAvailableTrackSpace()));
2377            }
2378            String status = car.checkDestination(testDestination, testTrack);
2379            // Can be a caboose or car with FRED with a custom load
2380            // is the destination a spur with a schedule demanding this car's
2381            // custom load?
2382            if (status.equals(Track.OKAY) &&
2383                    !testTrack.getScheduleId().equals(Track.NONE) &&
2384                    !car.getLoadName().equals(carLoads.getDefaultEmptyName()) &&
2385                    !car.getLoadName().equals(carLoads.getDefaultLoadName())) {
2386                addLine(FIVE,
2387                        Bundle.getMessage("buildSpurScheduleLoad", testTrack.getName(), car.getLoadName()));
2388            }
2389            // check to see if alternate track is available if track full
2390            if (status.startsWith(Track.LENGTH)) {
2391                addLine(SEVEN,
2392                        Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), testTrack.getTrackTypeName(),
2393                                testTrack.getLocation().getName(), testTrack.getName(), status));
2394                if (checkForAlternate(car, testTrack)) {
2395                    // send car to alternate track
2396                    tracks.add(testTrack.getAlternateTrack());
2397                    tracks.add(testTrack); // car's final destination
2398                    break; // done with this destination
2399                }
2400                continue;
2401            }
2402            // check for train timing
2403            if (status.equals(Track.OKAY)) {
2404                status = checkReserved(getTrain(), rld, car, testTrack, true);
2405                if (status.equals(TIMING) && checkForAlternate(car, testTrack)) {
2406                    // send car to alternate track
2407                    tracks.add(testTrack.getAlternateTrack());
2408                    tracks.add(testTrack); // car's final destination
2409                    break; // done with this destination
2410                }
2411            }
2412            // okay to drop car?
2413            if (!status.equals(Track.OKAY)) {
2414                addLine(SEVEN,
2415                        Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), testTrack.getTrackTypeName(),
2416                                testTrack.getLocation().getName(), testTrack.getName(), status));
2417                continue;
2418            }
2419            if (!checkForLocalMove(car, testTrack)) {
2420                continue;
2421            }
2422            tracks.add(testTrack);
2423            tracks.add(null); // no final destination for this car
2424            break; // done with this destination
2425        }
2426        return tracks;
2427    }
2428
2429    /**
2430     * Checks to see if track has an alternate and can be used
2431     * 
2432     * @param car   the car being dropped
2433     * @param track the destination track
2434     * @return true if track has an alternate and can be used
2435     */
2436    protected boolean checkForAlternate(Car car, Track track) {
2437        if (track.getAlternateTrack() != null &&
2438                car.getTrack() != track.getAlternateTrack() &&
2439                checkTrainCanDrop(car, track.getAlternateTrack())) {
2440            addLine(SEVEN,
2441                    Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(),
2442                            track.getName(), track.getAlternateTrack().getName()));
2443            String status = car.checkDestination(track.getLocation(), track.getAlternateTrack());
2444            if (status.equals(Track.OKAY)) {
2445                return true;
2446            }
2447            addLine(SEVEN,
2448                    Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
2449                            track.getAlternateTrack().getTrackTypeName(),
2450                            track.getLocation().getName(), track.getAlternateTrack().getName(),
2451                            status));
2452        }
2453        return false;
2454    }
2455
2456    /**
2457     * Used to determine if car could be set out at earlier location in the
2458     * train's route.
2459     *
2460     * @param car       The car
2461     * @param trackTemp The destination track for this car
2462     * @param rld       Where in the route the destination track was found
2463     * @param start     Where to begin the check
2464     * @param routeEnd  Where to stop the check
2465     * @return The best RouteLocation to drop off the car
2466     */
2467    protected RouteLocation checkForEarlierDrop(Car car, Track trackTemp, RouteLocation rld, int start, int routeEnd) {
2468        for (int m = start; m < routeEnd; m++) {
2469            RouteLocation rle = getRouteList().get(m);
2470            if (rle == rld) {
2471                break;
2472            }
2473            car.setRouteDestinationTiming(rle); // for timing
2474            if (rle.getName().equals(rld.getName()) &&
2475                    (rle.getCarMoves() < rle.getMaxCarMoves()) &&
2476                    rle.isDropAllowed() &&
2477                    checkDropTrainDirection(car, rle, trackTemp) &&
2478                    trackTemp.isRollingStockAccepted(car).equals(Track.OKAY)) {
2479                log.debug("Found an earlier drop for car ({}) destination ({})", car.toString(), rle.getName()); // NOI18N
2480                return rle; // earlier drop in train's route
2481            }
2482        }
2483        return rld;
2484    }
2485
2486    /*
2487     * Determines if rolling stock can be delivered to track when considering
2488     * timing of car pulls by other trains.
2489     */
2490    protected String checkReserved(Train train, RouteLocation rld, Car car, Track destTrack, boolean printMsg) {
2491        // car returning to same track?
2492        if (car.getTrack() != destTrack) {
2493            // car can be a kernel so get total length
2494            int length = car.getTotalKernelLength();
2495            log.debug("Car length: {}, available track space: {}, reserved: {}", length,
2496                    destTrack.getAvailableTrackSpace(), destTrack.getReserved());
2497            if (length > destTrack.getAvailableTrackSpace() +
2498                    destTrack.getReserved()) {
2499                boolean returned = false;
2500                String trainExpectedArrival = train.getExpectedArrivalTime(rld, true);
2501                int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival);
2502                int reservedReturned = 0;
2503                // does this car already have this destination?
2504                if (car.getDestinationTrack() == destTrack) {
2505                    reservedReturned = -car.getTotalKernelLength();
2506                }
2507                // get a list of cars on this track
2508                List<Car> cars = carManager.getList(destTrack);
2509                for (Car kar : cars) {
2510                    if (kar.getTrain() != null && kar.getTrain() != train) {
2511                        int carPullTime = convertStringTime(kar.getPickupTime());
2512                        if (trainArrivalTimeMinutes < carPullTime) {
2513                            // don't print if checking redirect to alternate
2514                            if (printMsg) {
2515                                addLine(SEVEN,
2516                                        Bundle.getMessage("buildCarTrainTiming", kar.toString(),
2517                                                kar.getTrack().getTrackTypeName(), kar.getLocationName(),
2518                                                kar.getTrackName(), kar.getTrainName(), kar.getPickupTime(),
2519                                                getTrain().getName(), trainExpectedArrival));
2520                            }
2521                            reservedReturned += kar.getTotalLength();
2522                            returned = true;
2523                        }
2524                    }
2525                }
2526                if (returned && length > destTrack.getAvailableTrackSpace() - reservedReturned) {
2527                    if (printMsg) {
2528                        addLine(SEVEN,
2529                                Bundle.getMessage("buildWarnTrainTiming", car.toString(), destTrack.getTrackTypeName(),
2530                                        destTrack.getLocation().getName(), destTrack.getName(), getTrain().getName(),
2531                                        destTrack.getAvailableTrackSpace() - reservedReturned,
2532                                        Setup.getLengthUnit().toLowerCase()));
2533                    }
2534                    return TIMING;
2535                }
2536            }
2537        }
2538        return Track.OKAY;
2539    }
2540
2541    /**
2542     * Checks to see if local move is allowed for this car
2543     *
2544     * @param car       the car being moved
2545     * @param testTrack the destination track for this car
2546     * @return false if local move not allowed
2547     */
2548    private boolean checkForLocalMove(Car car, Track testTrack) {
2549        if (getTrain().isLocalSwitcher()) {
2550            // No local moves from spur to spur
2551            if (!Setup.isLocalSpurMovesEnabled() && testTrack.isSpur() && car.getTrack().isSpur()) {
2552                addLine(SEVEN,
2553                        Bundle.getMessage("buildNoSpurToSpurMove", car.getTrackName(), testTrack.getName()));
2554                return false;
2555            }
2556            // No local moves from yard to yard, except for cabooses and cars
2557            // with FRED
2558            if (!Setup.isLocalYardMovesEnabled() &&
2559                    testTrack.isYard() &&
2560                    car.getTrack().isYard() &&
2561                    !car.isCaboose() &&
2562                    !car.hasFred()) {
2563                addLine(SEVEN,
2564                        Bundle.getMessage("buildNoYardToYardMove", car.getTrackName(), testTrack.getName()));
2565                return false;
2566            }
2567            // No local moves from interchange to interchange
2568            if (!Setup.isLocalInterchangeMovesEnabled() &&
2569                    testTrack.isInterchange() &&
2570                    car.getTrack().isInterchange()) {
2571                addLine(SEVEN,
2572                        Bundle.getMessage("buildNoInterchangeToInterchangeMove", car.getTrackName(),
2573                                testTrack.getName()));
2574                return false;
2575            }
2576        }
2577        return true;
2578    }
2579
2580    protected Track tryStaging(Car car, RouteLocation rldSave) throws BuildFailedException {
2581        // local switcher working staging?
2582        if (getTrain().isLocalSwitcher() &&
2583                !car.isPassenger() &&
2584                !car.isCaboose() &&
2585                !car.hasFred() &&
2586                car.getTrack() == getTerminateStagingTrack()) {
2587            addLine(SEVEN,
2588                    Bundle.getMessage("buildCanNotDropCarSameTrack", car.toString(), car.getTrack().getName()));
2589            return null;
2590        }
2591        // no need to check train and track direction into staging, already done
2592        String status = car.checkDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack());
2593        if (status.equals(Track.OKAY)) {
2594            return getTerminateStagingTrack();
2595            // only generate a new load if there aren't any other tracks
2596            // available for this car
2597        } else if (status.startsWith(Track.LOAD) &&
2598                car.getTrack() == getDepartureStagingTrack() &&
2599                car.getLoadName().equals(carLoads.getDefaultEmptyName()) &&
2600                rldSave == null &&
2601                (getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled() ||
2602                        getDepartureStagingTrack().isAddCustomLoadsEnabled() ||
2603                        getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled())) {
2604            // try and generate a load for this car into staging
2605            if (generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack())) {
2606                return getTerminateStagingTrack();
2607            }
2608        }
2609        addLine(SEVEN,
2610                Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
2611                        getTerminateStagingTrack().getTrackTypeName(),
2612                        getTerminateStagingTrack().getLocation().getName(), getTerminateStagingTrack().getName(),
2613                        status));
2614        return null;
2615    }
2616
2617    /**
2618     * Returns true if car can be picked up later in a train's route
2619     *
2620     * @param car the car
2621     * @param rl  car's route location
2622     * @param rld car's route location destination
2623     * @return true if car can be picked up later in a train's route
2624     * @throws BuildFailedException if coding issue
2625     */
2626    protected boolean checkForLaterPickUp(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException {
2627        // is there another pick up location in the route?
2628        if (rl == rld || !rld.getName().equals(car.getLocationName())) {
2629            return false;
2630        }
2631        // last route location in the route?
2632        if (rld == getTrain().getTrainTerminatesRouteLocation() && !car.isLocalMove()) {
2633            return false;
2634        }
2635        // don't delay adding a caboose, passenger car, or car with FRED
2636        if (car.isCaboose() || car.isPassenger() || car.hasFred()) {
2637            return false;
2638        }
2639        // no later pick up if car is departing staging
2640        if (car.getLocation().isStaging()) {
2641            return false;
2642        }
2643        if (!checkPickUpTrainDirection(car, rld)) {
2644            addLine(SEVEN,
2645                    Bundle.getMessage("buildNoPickupLaterDirection", car.toString(), rld.getName(), rld.getId()));
2646            return false;
2647        }
2648        if (!rld.isPickUpAllowed() && !rld.isLocalMovesAllowed() ||
2649                !rld.isPickUpAllowed() && rld.isLocalMovesAllowed() && !car.isLocalMove()) {
2650            addLine(SEVEN,
2651                    Bundle.getMessage("buildNoPickupLater", car.toString(), rld.getName(), rld.getId()));
2652            return false;
2653        }
2654        if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
2655            addLine(SEVEN,
2656                    Bundle.getMessage("buildNoPickupLaterMoves", car.toString(), rld.getName(), rld.getId()));
2657            return false;
2658        }
2659        // is the track full? If so, pull immediately, prevents overloading
2660        if (checkForPickUps(car, rl, false)) {
2661            addLine(SEVEN, Bundle.getMessage("buildNoPickupLaterTrack", car.toString(), rld.getName(),
2662                    car.getTrackName(), rld.getId(), car.getTrack().getLength() - car.getTrack().getUsedLength(),
2663                    Setup.getLengthUnit().toLowerCase(), car.getTrackName()));
2664            return false;
2665        }
2666        // are there any other cars being pull from the same track, route location, and train?
2667        if (checkForPickUps(car, rl, true)) {
2668            addLine(SEVEN, Bundle.getMessage("buildAlreadyPickups", car.toString(), rld.getName(),
2669                    car.getTrackName(), rld.getId(), car.getTrack().getTrackTypeName(), rl.getName(),
2670                    car.getTrack().getName(), getTrain().getName()));
2671            return false;
2672        }
2673        addLine(SEVEN,
2674                Bundle.getMessage("buildPickupLaterOkay", car.toString(), rld.getName(), rld.getId()));
2675        return true;
2676    }
2677
2678    /*
2679     * checks to see if the train being built already has car pick ups at the
2680     * same track, route location rl, and train, and there's a track space
2681     * issue.
2682     * 
2683     * return true if there are already pick ups from the car's track
2684     */
2685    private boolean checkForPickUps(Car car, RouteLocation rl, boolean isCheckForCars) {
2686        if (!car.isLocalMove() && rl.isDropAllowed()) {
2687            int length = 0;
2688            if (isCheckForCars) {
2689                for (Car c : carManager.getByTrainList(getTrain())) {
2690                    if (car.getTrack() == c.getTrack() && rl == c.getRouteLocation()) {
2691                        length += c.getTotalKernelLength();
2692                    }
2693                }
2694            }
2695            if (car.getTrack().getLength() - car.getTrack().getUsedLength() < car.getTotalKernelLength() + length) {
2696                return true;
2697            }
2698        }
2699        return false;
2700    }
2701
2702    /**
2703     * Returns true is cars are allowed to travel from origin to terminal
2704     *
2705     * @param car             The car
2706     * @param destinationName Destination name for this car
2707     * @return true if through cars are allowed. false if not.
2708     */
2709    protected boolean checkThroughCarsAllowed(Car car, String destinationName) {
2710        if (!getTrain().isAllowThroughCarsEnabled() &&
2711                !getTrain().isLocalSwitcher() &&
2712                !car.isCaboose() &&
2713                !car.hasFred() &&
2714                !car.isPassenger() &&
2715                car.getSplitLocationName().equals(getDepartureLocation().getSplitName()) &&
2716                splitString(destinationName).equals(getTerminateLocation().getSplitName()) &&
2717                !getDepartureLocation().getSplitName().equals(getTerminateLocation().getSplitName())) {
2718            addLine(FIVE, Bundle.getMessage("buildThroughTrafficNotAllow", getDepartureLocation().getName(),
2719                    getTerminateLocation().getName()));
2720            return false; // through cars not allowed
2721        }
2722        return true; // through cars allowed
2723    }
2724
2725    private boolean checkLocalMovesAllowed(Car car, Track track) {
2726        if (!getTrain().isLocalSwitcher() &&
2727                !getTrain().isAllowLocalMovesEnabled() &&
2728                car.getSplitLocationName().equals(track.getLocation().getSplitName())) {
2729            addLine(SEVEN,
2730                    Bundle.getMessage("buildNoLocalMoveToTrack", car.getLocationName(), car.getTrackName(),
2731                            track.getLocation().getName(), track.getName(), getTrain().getName()));
2732            return false;
2733        }
2734        return true;
2735    }
2736
2737    /**
2738     * Creates a car load for a car departing staging and eventually terminating
2739     * into staging.
2740     *
2741     * @param car        the car!
2742     * @param stageTrack the staging track the car will terminate to
2743     * @return true if a load was generated this this car.
2744     * @throws BuildFailedException if coding check fails
2745     */
2746    protected boolean generateLoadCarDepartingAndTerminatingIntoStaging(Car car, Track stageTrack)
2747            throws BuildFailedException {
2748        addLine(SEVEN, BLANK_LINE);
2749        // code check
2750        if (stageTrack == null || !stageTrack.isStaging()) {
2751            throw new BuildFailedException("ERROR coding issue, staging track null or not staging");
2752        }
2753        if (!stageTrack.isTypeNameAccepted(car.getTypeName())) {
2754            addLine(SEVEN,
2755                    Bundle.getMessage("buildStagingTrackType", stageTrack.getLocation().getName(), stageTrack.getName(),
2756                            car.getTypeName()));
2757            return false;
2758        }
2759        if (!stageTrack.isRoadNameAndLoadTypeAccepted(car.getRoadName(), car.getLoadType())) {
2760            addLine(SEVEN,
2761                    Bundle.getMessage("buildStagingTrackRoad", stageTrack.getLocation().getName(), stageTrack.getName(),
2762                            car.getRoadName(), car.getLoadType()));
2763            return false;
2764        }
2765        // Departing and returning to same location in staging?
2766        if (!getTrain().isAllowReturnToStagingEnabled() &&
2767                !Setup.isStagingAllowReturnEnabled() &&
2768                !car.isCaboose() &&
2769                !car.hasFred() &&
2770                !car.isPassenger() &&
2771                car.getSplitLocationName().equals(stageTrack.getLocation().getSplitName())) {
2772            addLine(SEVEN,
2773                    Bundle.getMessage("buildNoReturnStaging", car.toString(), stageTrack.getLocation().getName()));
2774            return false;
2775        }
2776        // figure out which loads the car can use
2777        List<String> loads = carLoads.getNames(car.getTypeName());
2778        // remove the default names
2779        loads.remove(carLoads.getDefaultEmptyName());
2780        loads.remove(carLoads.getDefaultLoadName());
2781        if (loads.size() == 0) {
2782            log.debug("No custom loads for car type ({}) ignoring staging track ({})", car.getTypeName(),
2783                    stageTrack.getName());
2784            return false;
2785        }
2786        addLine(SEVEN,
2787                Bundle.getMessage("buildSearchTrackLoadStaging", car.toString(), car.getTypeName(),
2788                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(),
2789                        stageTrack.getLocation().getName(), stageTrack.getName()));
2790        String oldLoad = car.getLoadName(); // save car's "E" load
2791        for (int i = loads.size() - 1; i >= 0; i--) {
2792            String load = loads.get(i);
2793            log.debug("Try custom load ({}) for car ({})", load, car.toString());
2794            if (!car.getTrack().isLoadNameAndCarTypeShipped(load, car.getTypeName()) ||
2795                    !stageTrack.isLoadNameAndCarTypeAccepted(load, car.getTypeName()) ||
2796                    !getTrain().isLoadNameAccepted(load, car.getTypeName())) {
2797                // report why the load was rejected and remove it from consideration
2798                if (!car.getTrack().isLoadNameAndCarTypeShipped(load, car.getTypeName())) {
2799                    addLine(SEVEN,
2800                            Bundle.getMessage("buildTrackNotNewLoad", car.getTrackName(), load,
2801                                    stageTrack.getLocation().getName(), stageTrack.getName()));
2802                }
2803                if (!stageTrack.isLoadNameAndCarTypeAccepted(load, car.getTypeName())) {
2804                    addLine(SEVEN,
2805                            Bundle.getMessage("buildDestTrackNoLoad", stageTrack.getLocation().getName(),
2806                                    stageTrack.getName(), car.toString(), load));
2807                }
2808                if (!getTrain().isLoadNameAccepted(load, car.getTypeName())) {
2809                    addLine(SEVEN,
2810                            Bundle.getMessage("buildTrainNotNewLoad", getTrain().getName(), load,
2811                                    stageTrack.getLocation().getName(), stageTrack.getName()));
2812                }
2813                loads.remove(i);
2814                continue;
2815            }
2816            car.setLoadName(load);
2817            // does the car have a home division?
2818            if (car.getDivision() != null) {
2819                addLine(SEVEN,
2820                        Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(),
2821                                car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(),
2822                                car.getLocationName(),
2823                                car.getTrackName(), car.getTrack().getDivisionName()));
2824                // load type empty must return to car's home division
2825                // or load type load from foreign division must return to car's
2826                // home division
2827                if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) &&
2828                        car.getDivision() != stageTrack.getDivision() ||
2829                        car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) &&
2830                                car.getTrack().getDivision() != car.getDivision() &&
2831                                car.getDivision() != stageTrack.getDivision()) {
2832                    addLine(SEVEN,
2833                            Bundle.getMessage("buildNoDivisionTrack", stageTrack.getTrackTypeName(),
2834                                    stageTrack.getLocation().getName(), stageTrack.getName(),
2835                                    stageTrack.getDivisionName(), car.toString(),
2836                                    car.getLoadType().toLowerCase(), car.getLoadName()));
2837                    loads.remove(i);
2838                    continue;
2839                }
2840            }
2841        }
2842        // do we need to test all car loads?
2843        boolean loadRestrictions = isLoadRestrictions();
2844        // now determine if the loads can be routed to the staging track
2845        for (int i = loads.size() - 1; i >= 0; i--) {
2846            String load = loads.get(i);
2847            car.setLoadName(load);
2848            if (!router.isCarRouteable(car, getTrain(), stageTrack, getBuildReport())) {
2849                loads.remove(i); // no remove this load
2850                addLine(SEVEN, Bundle.getMessage("buildStagingTrackNotReachable",
2851                        stageTrack.getLocation().getName(), stageTrack.getName(), load));
2852                if (!loadRestrictions) {
2853                    loads.clear(); // no loads can be routed
2854                    break;
2855                }
2856            } else if (!loadRestrictions) {
2857                break; // done all loads can be routed
2858            }
2859        }
2860        // Use random loads rather that the first one that works to create
2861        // interesting loads
2862        if (loads.size() > 0) {
2863            int rnd = (int) (Math.random() * loads.size());
2864            car.setLoadName(loads.get(rnd));
2865            // check to see if car is now accepted by staging
2866            String status = car.checkDestination(stageTrack.getLocation(), stageTrack);
2867            if (status.equals(Track.OKAY) ||
2868                    (status.startsWith(Track.LENGTH) && stageTrack != getTerminateStagingTrack())) {
2869                car.setLoadGeneratedFromStaging(true);
2870                car.setFinalDestination(stageTrack.getLocation());
2871                // don't set track assignment unless the car is going to this
2872                // train's staging
2873                if (stageTrack == getTerminateStagingTrack()) {
2874                    car.setFinalDestinationTrack(stageTrack);
2875                } else {
2876                    // don't assign the track, that will be done later
2877                    car.setFinalDestinationTrack(null);
2878                }
2879                car.updateKernel(); // is car part of kernel?
2880                addLine(SEVEN,
2881                        Bundle.getMessage("buildAddingScheduleLoad", loads.size(), car.getLoadName(), car.toString()));
2882                return true;
2883            }
2884            addLine(SEVEN,
2885                    Bundle.getMessage("buildCanNotDropCarBecause", car.toString(), stageTrack.getTrackTypeName(),
2886                            stageTrack.getLocation().getName(), stageTrack.getName(), status));
2887        }
2888        car.setLoadName(oldLoad); // restore load and report failure
2889        addLine(SEVEN, Bundle.getMessage("buildUnableNewLoadStaging", car.toString(), car.getTrackName(),
2890                stageTrack.getLocation().getName(), stageTrack.getName()));
2891        return false;
2892    }
2893
2894    /**
2895     * Checks to see if there are any load restrictions for trains,
2896     * interchanges, and yards if routing through yards is enabled.
2897     *
2898     * @return true if there are load restrictions.
2899     */
2900    private boolean isLoadRestrictions() {
2901        boolean restrictions = isLoadRestrictionsTrain() || isLoadRestrictions(Track.INTERCHANGE);
2902        if (Setup.isCarRoutingViaYardsEnabled()) {
2903            restrictions = restrictions || isLoadRestrictions(Track.YARD);
2904        }
2905        return restrictions;
2906    }
2907
2908    private boolean isLoadRestrictions(String type) {
2909        for (Track track : locationManager.getTracks(type)) {
2910            if (!track.getLoadOption().equals(Track.ALL_LOADS)) {
2911                return true;
2912            }
2913        }
2914        return false;
2915    }
2916
2917    private boolean isLoadRestrictionsTrain() {
2918        for (Train train : trainManager.getList()) {
2919            if (!train.getLoadOption().equals(Train.ALL_LOADS)) {
2920                return true;
2921            }
2922        }
2923        return false;
2924    }
2925
2926    /**
2927     * report any cars left at route location
2928     *
2929     * @param rl route location
2930     */
2931    protected void showCarsNotMoved(RouteLocation rl) {
2932        if (_carIndex < 0) {
2933            _carIndex = 0;
2934        }
2935        // cars up this point have build report messages, only show the cars
2936        // that aren't
2937        // in the build report
2938        int numberCars = 0;
2939        for (int i = _carIndex; i < getCarList().size(); i++) {
2940            if (numberCars == DISPLAY_CAR_LIMIT_100) {
2941                addLine(FIVE, Bundle.getMessage("buildOnlyFirstXXXCars", numberCars, rl.getName()));
2942                break;
2943            }
2944            Car car = getCarList().get(i);
2945            // find a car at this location that hasn't been given a destination
2946            if (!car.getLocationName().equals(rl.getName()) || car.getRouteDestination() != null) {
2947                continue;
2948            }
2949            if (numberCars == 0) {
2950                addLine(SEVEN,
2951                        Bundle.getMessage("buildMovesCompleted", rl.getMaxCarMoves(), rl.getName()));
2952            }
2953            addLine(SEVEN, Bundle.getMessage("buildCarIgnored", car.toString(), car.getTypeName(),
2954                    car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName()));
2955            numberCars++;
2956        }
2957        addLine(SEVEN, BLANK_LINE);
2958    }
2959
2960    /**
2961     * Remove rolling stock from train
2962     *
2963     * @param rs the rolling stock to be removed
2964     */
2965    protected void removeRollingStockFromTrain(RollingStock rs) {
2966        // adjust train length and weight for each location that the rolling
2967        // stock is in the train
2968        boolean inTrain = false;
2969        for (RouteLocation routeLocation : getRouteList()) {
2970            if (rs.getRouteLocation() == routeLocation) {
2971                inTrain = true;
2972            }
2973            if (rs.getRouteDestination() == routeLocation) {
2974                break;
2975            }
2976            if (inTrain) {
2977                routeLocation.setTrainLength(routeLocation.getTrainLength() - rs.getTotalLength()); // includes
2978                                                                                                    // couplers
2979                routeLocation.setTrainWeight(routeLocation.getTrainWeight() - rs.getAdjustedWeightTons());
2980            }
2981        }
2982        rs.reset(); // remove this rolling stock from the train
2983    }
2984
2985    /**
2986     * Lists cars that couldn't be routed.
2987     */
2988    protected void showCarsNotRoutable() {
2989        // any cars unable to route?
2990        if (_notRoutable.size() > 0) {
2991            addLine(ONE, BLANK_LINE);
2992            addLine(ONE, Bundle.getMessage("buildCarsNotRoutable"));
2993            for (Car car : _notRoutable) {
2994                _warnings++;
2995                addLine(ONE,
2996                        Bundle.getMessage("buildCarNotRoutable", car.toString(), car.getLocationName(),
2997                                car.getTrackName(), car.getPreviousFinalDestinationName(),
2998                                car.getPreviousFinalDestinationTrackName()));
2999            }
3000            addLine(ONE, BLANK_LINE);
3001        }
3002    }
3003
3004    protected void finshBuildReport() {
3005        // done building
3006        if (_warnings > 0) {
3007            addLine(ONE, Bundle.getMessage("buildWarningMsg", getTrain().getName(), _warnings));
3008        }
3009        addLine(FIVE,
3010                Bundle.getMessage("buildTime", getTrain().getName(), new Date().getTime() - getStartTime().getTime()));
3011    }
3012
3013    /**
3014     * build has failed due to cars in staging not having destinations this
3015     * routine removes those cars from the staging track by user request.
3016     */
3017    protected void removeCarsFromStaging() {
3018        // Code check, only called if train was departing staging
3019        if (getDepartureStagingTrack() == null) {
3020            log.error("Error, called when cars in staging not assigned to train");
3021            return;
3022        }
3023        for (Car car : getCarList()) {
3024            // remove cars from departure staging track that haven't been
3025            // assigned to this train
3026            if (car.getTrack() == getDepartureStagingTrack() && car.getTrain() == null) {
3027                // remove track from kernel
3028                if (car.getKernel() != null) {
3029                    for (Car c : car.getKernel().getCars())
3030                        c.setLocation(car.getLocation(), null);
3031                } else {
3032                    car.setLocation(car.getLocation(), null);
3033                }
3034            }
3035        }
3036    }
3037
3038    protected int countRollingStockAt(RouteLocation rl, List<RollingStock> list) {
3039        int count = 0;
3040        for (RollingStock rs : list) {
3041            if (rs.getLocationName().equals(rl.getName())) {
3042                count++;
3043            }
3044        }
3045        return count;
3046    }
3047
3048    /*
3049     * lists the tracks that aren't in quick service mode
3050     */
3051    protected void showTracksNotQuickService() {
3052        if (Setup.isBuildOnTime()) {
3053            addLine(FIVE, BLANK_LINE);
3054            addLine(FIVE, Bundle.getMessage("buildTracksNotQuickService"));
3055            for (Track track : locationManager.getTracks(null)) {
3056                if (!track.isQuickServiceEnabled()) {
3057                    addLine(SEVEN, Bundle.getMessage("buildTrackNotQuick",
3058                            StringUtils.capitalize(track.getTrackTypeName()), track.getLocation().getName(),
3059                            track.getName()));
3060                }
3061            }
3062        }
3063    }
3064
3065    protected boolean checkRouteLocation(RouteLocation rl) {
3066        if (getTrain().isLocationSkipped(rl)) {
3067            addLine(ONE,
3068                    Bundle.getMessage("buildLocSkipped", rl.getName(), rl.getId(), getTrain().getName()));
3069            return false;
3070        }
3071        if (!rl.isPickUpAllowed() && !rl.isLocalMovesAllowed()) {
3072            addLine(ONE,
3073                    Bundle.getMessage("buildLocNoPickups", getTrain().getRoute().getName(), rl.getId(), rl.getName()));
3074            return false;
3075        }
3076        // no pick ups from staging unless at the start of the train's route
3077        if (rl != getTrain().getTrainDepartsRouteLocation() && rl.getLocation().isStaging()) {
3078            addLine(ONE, Bundle.getMessage("buildNoPickupsFromStaging", rl.getName()));
3079            return false;
3080        }
3081        // the next check provides a build report message if there's an
3082        // issue with the train direction
3083        if (!checkPickUpTrainDirection(rl)) {
3084            return false;
3085        }
3086        return true;
3087    }
3088
3089    /**
3090     * Checks to see if rolling stock is departing a quick service track and is
3091     * allowed to be pulled by this train. To pull, the route location must be
3092     * different than the one used to deliver the rolling stock. To service the
3093     * rolling stock, the train must arrive after the rolling stock's clone is
3094     * set out by this train or by another train.
3095     * 
3096     * @param rs the rolling stock
3097     * @param rl the route location pulling the rolling stock
3098     * @return true if rolling stock can be pulled
3099     */
3100    protected boolean checkQuickServiceDeparting(RollingStock rs, RouteLocation rl) {
3101        if (rs.getTrack().isQuickServiceEnabled()) {
3102            RollingStock clone = null;
3103            if (Car.class.isInstance(rs)) {
3104                clone = carManager.getClone(rs);
3105            }
3106            if (Engine.class.isInstance(rs)) {
3107                clone = engineManager.getClone(rs);
3108            }
3109            if (clone != null) {
3110                // was the rolling stock delivered using this route location?
3111                if (rs.getRouteDestination() == rl) {
3112                    addLine(FIVE,
3113                            Bundle.getMessage("buildRouteLocation", rs.toString(), rs.getTrack().getTrackTypeName(),
3114                                    rs.getLocationName(), rs.getTrackName(), getTrain().getName(), rl.getName(),
3115                                    rl.getId()));
3116                    addLine(FIVE, BLANK_LINE);
3117                    return false;
3118                }
3119
3120                // determine when the train arrives
3121                String trainExpectedArrival = getTrain().getExpectedArrivalTime(rl, true);
3122                int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival);
3123                // determine when the clone is going to be delivered
3124                int cloneSetoutTimeMinutes = convertStringTime(clone.getSetoutTime());
3125                // in aggressive mode the dwell time is 0
3126                int dwellTime = Setup.getDwellTime();
3127                if (cloneSetoutTimeMinutes + dwellTime > trainArrivalTimeMinutes) {
3128                    String earliest = convertMinutesTime(cloneSetoutTimeMinutes + dwellTime);
3129                    addLine(FIVE, Bundle.getMessage("buildDeliveryTiming", rs.toString(),
3130                            clone.getSetoutTime(), rs.getTrack().getTrackTypeName(), rs.getLocationName(),
3131                            rs.getTrackName(), clone.getTrainName(), clone.getRouteDestination().getId(),
3132                            getTrain().getName(), trainExpectedArrival, dwellTime, earliest));
3133                    addLine(FIVE, BLANK_LINE);
3134                    return false;
3135                } else {
3136                    addLine(SEVEN, Bundle.getMessage("buildCloneDeliveryTiming", clone.toString(),
3137                            clone.getSetoutTime(), rs.getTrack().getTrackTypeName(), rs.getLocationName(),
3138                            rs.getTrackName(), clone.getTrainName(), clone.getRouteDestination().getId(),
3139                            getTrain().getName(), trainExpectedArrival, dwellTime, rs.toString()));
3140                }
3141            }
3142        }
3143        return true;
3144    }
3145
3146    /*
3147     * Engine methods start here
3148     */
3149
3150    /**
3151     * Used to determine the number of engines requested by the user.
3152     *
3153     * @param requestEngines Can be a number, AUTO or AUTO HPT.
3154     * @return the number of engines requested by user.
3155     */
3156    protected int getNumberEngines(String requestEngines) {
3157        int numberEngines = 0;
3158        if (requestEngines.equals(Train.AUTO)) {
3159            numberEngines = getAutoEngines();
3160        } else if (requestEngines.equals(Train.AUTO_HPT)) {
3161            numberEngines = 1; // get one loco for now, check HP requirements
3162                               // after train is built
3163        } else {
3164            numberEngines = Integer.parseInt(requestEngines);
3165        }
3166        return numberEngines;
3167    }
3168
3169    /**
3170     * Returns the number of engines needed for this train, minimum 1, maximum
3171     * user specified in setup. Based on maximum allowable train length and
3172     * grade between locations, and the maximum cars that the train can have at
3173     * the maximum train length. One engine per sixteen 40' cars for 1% grade.
3174     *
3175     * @return The number of engines needed
3176     */
3177    private int getAutoEngines() {
3178        double numberEngines = 1;
3179        int moves = 0;
3180        int carLength = 40 + Car.COUPLERS; // typical 40' car
3181
3182        // adjust if length in meters
3183        if (!Setup.getLengthUnit().equals(Setup.FEET)) {
3184            carLength = 12 + Car.COUPLERS; // typical car in meters
3185        }
3186
3187        for (RouteLocation rl : getRouteList()) {
3188            if (rl.isPickUpAllowed() && rl != getTrain().getTrainTerminatesRouteLocation()) {
3189                moves += rl.getMaxCarMoves(); // assume all moves are pick ups
3190                double carDivisor = 16; // number of 40' cars per engine 1% grade
3191                // change engine requirements based on grade
3192                if (rl.getGrade() > 1) {
3193                    carDivisor = carDivisor / rl.getGrade();
3194                }
3195                log.debug("Maximum train length {} for location ({})", rl.getMaxTrainLength(), rl.getName());
3196                if (rl.getMaxTrainLength() / (carDivisor * carLength) > numberEngines) {
3197                    numberEngines = rl.getMaxTrainLength() / (carDivisor * carLength);
3198                    // round up to next whole integer
3199                    numberEngines = Math.ceil(numberEngines);
3200                    // determine if there's enough car pick ups at this point to
3201                    // reach the max train length
3202                    if (numberEngines > moves / carDivisor) {
3203                        // no reduce based on moves
3204                        numberEngines = Math.ceil(moves / carDivisor);
3205                    }
3206                }
3207            }
3208        }
3209        int nE = (int) numberEngines;
3210        if (getTrain().isLocalSwitcher()) {
3211            nE = 1; // only one engine if switcher
3212        }
3213        addLine(ONE,
3214                Bundle.getMessage("buildAutoBuildMsg", Integer.toString(nE)));
3215        if (nE > Setup.getMaxNumberEngines()) {
3216            addLine(THREE, Bundle.getMessage("buildMaximumNumberEngines", Setup.getMaxNumberEngines()));
3217            nE = Setup.getMaxNumberEngines();
3218        }
3219        return nE;
3220    }
3221
3222    protected void addLine(String level, String string) {
3223        addLine(getBuildReport(), level, string);
3224    }
3225
3226    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TrainBuilderBase.class);
3227
3228}