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