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