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