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