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