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