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