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