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