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