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