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