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