001package jmri.jmrit.operations.trains.trainbuilder;
002
003import java.util.*;
004
005import jmri.jmrit.operations.locations.Location;
006import jmri.jmrit.operations.locations.Track;
007import jmri.jmrit.operations.locations.schedules.ScheduleItem;
008import jmri.jmrit.operations.rollingstock.cars.Car;
009import jmri.jmrit.operations.rollingstock.cars.CarLoad;
010import jmri.jmrit.operations.rollingstock.engines.Engine;
011import jmri.jmrit.operations.router.Router;
012import jmri.jmrit.operations.routes.RouteLocation;
013import jmri.jmrit.operations.setup.Setup;
014import jmri.jmrit.operations.trains.BuildFailedException;
015import jmri.jmrit.operations.trains.Train;
016
017import org.apache.commons.lang3.StringUtils;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020
021/**
022 * Contains methods for cars when building a train.
023 * 
024 * @author Daniel Boudreau Copyright (C) 2022, 2025, 2026
025 */
026public class TrainBuilderCars extends TrainBuilderEngines {
027
028    /**
029     * Find a caboose if needed at the correct location and add it to the train.
030     * If departing staging, all cabooses are added to the train. If there isn't
031     * a road name required for the caboose, tries to find a caboose with the
032     * same road name as the lead engine.
033     *
034     * @param roadCaboose     Optional road name for this car.
035     * @param leadEngine      The lead engine for this train. Used to find a
036     *                        caboose with the same road name as the engine.
037     * @param rl              Where in the route to pick up this car.
038     * @param rld             Where in the route to set out this car.
039     * @param requiresCaboose When true, the train requires a caboose.
040     * @throws BuildFailedException If car not found.
041     */
042    protected void getCaboose(String roadCaboose, Engine leadEngine, RouteLocation rl, RouteLocation rld,
043            boolean requiresCaboose) throws BuildFailedException {
044        // code check
045        if (rl == null) {
046            throw new BuildFailedException(Bundle.getMessage("buildErrorCabooseNoLocation", getTrain().getName()));
047        }
048        // code check
049        if (rld == null) {
050            throw new BuildFailedException(
051                    Bundle.getMessage("buildErrorCabooseNoDestination", getTrain().getName(), rl.getName()));
052        }
053        // load departure track if staging
054        Track departStagingTrack = null;
055        if (rl == getTrain().getTrainDepartsRouteLocation()) {
056            departStagingTrack = getDepartureStagingTrack(); // can be null
057        }
058        if (!requiresCaboose) {
059            addLine(FIVE,
060                    Bundle.getMessage("buildTrainNoCaboose", rl.getName()));
061            if (departStagingTrack == null) {
062                return;
063            }
064        } else {
065            addLine(ONE, Bundle.getMessage("buildTrainReqCaboose", getTrain().getName(), roadCaboose,
066                    rl.getName(), rld.getName()));
067        }
068
069        // Now go through the car list looking for cabooses
070        boolean cabooseTip = true; // add a user tip to the build report about
071                                   // cabooses if none found
072        boolean cabooseAtDeparture = false; // set to true if caboose at
073                                            // departure location is found
074        boolean foundCaboose = false;
075        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
076            Car car = getCarList().get(_carIndex);
077            if (!car.isCaboose()) {
078                continue;
079            }
080            showCarServiceOrder(car);
081
082            cabooseTip = false; // found at least one caboose, so they exist!
083            addLine(FIVE, Bundle.getMessage("buildCarIsCaboose", car.toString(), car.getRoadName(),
084                    car.getLocationName(), car.getTrackName()));
085            // car departing staging must leave with train
086            if (car.getTrack() == departStagingTrack) {
087                foundCaboose = false;
088                if (!generateCarLoadFromStaging(car, rld)) {
089                    // departing and terminating into staging?
090                    if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() &&
091                            rld.getLocation() == getTerminateLocation() &&
092                            getTerminateStagingTrack() != null) {
093                        // try and generate a custom load for this caboose
094                        generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack());
095                    }
096                }
097                if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
098                    // if destination track in quick service, car moved to location
099                    if (car.getTrain() == getTrain() || car.getLocation() == rld.getLocation()) {
100                        foundCaboose = true;
101                    }
102                } else if (findDestinationAndTrack(car, rl, rld)) {
103                    foundCaboose = true;
104                }
105                if (!foundCaboose) {
106                    throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString()));
107                }
108                // is there a specific road requirement for the caboose?
109            } else if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) {
110                addLine(SEVEN, Bundle.getMessage("buildCabooseWrongRoad", car.toString(),
111                        car.getRoadName(), roadCaboose, rl.getName()));
112                continue;
113            } else if (!foundCaboose && car.getLocationName().equals(rl.getName())) {
114                // remove cars that can't be picked up due to train and track
115                // directions
116                if (!checkPickUpTrainDirection(car, rl)) {
117                    addLine(SEVEN,
118                            Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(),
119                                    car.getTypeExtensions(), car.getLocationName(), car.getTrackName()));
120                    remove(car); // remove this car from the list
121                    continue;
122                }
123                // first pass, find a caboose that matches the engine road
124                if (leadEngine != null && car.getRoadName().equals(leadEngine.getRoadName())) {
125                    addLine(SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(),
126                            car.getRoadName(), leadEngine.toString()));
127                    if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
128                        if (car.getTrain() == getTrain()) {
129                            foundCaboose = true;
130                        }
131                    } else if (findDestinationAndTrack(car, rl, rld)) {
132                        foundCaboose = true;
133                    }
134                    if (!foundCaboose) {
135                        remove(car); // remove this car from the list
136                        continue;
137                    }
138                }
139                // done if we found a caboose and not departing staging
140                if (foundCaboose && departStagingTrack == null) {
141                    break;
142                }
143            }
144        }
145        // second pass, take a caboose with a road name that is "similar"
146        // (hyphen feature) to the engine road name
147        if (requiresCaboose && !foundCaboose && roadCaboose.equals(Train.NONE)) {
148            log.debug("Second pass looking for caboose");
149            for (Car car : getCarList()) {
150                if (car.isCaboose() && car.getLocationName().equals(rl.getName())) {
151                    if (leadEngine != null &&
152                            TrainCommon.splitString(car.getRoadName())
153                                    .equals(TrainCommon.splitString(leadEngine.getRoadName()))) {
154                        addLine(SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(),
155                                car.getRoadName(), leadEngine.toString()));
156                        if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
157                            if (car.getTrain() == getTrain()) {
158                                foundCaboose = true;
159                                break;
160                            }
161                        } else if (findDestinationAndTrack(car, rl, rld)) {
162                            foundCaboose = true;
163                            break;
164                        }
165                    }
166                }
167            }
168        }
169        // third pass, take any caboose unless a caboose road name is specified
170        if (requiresCaboose && !foundCaboose) {
171            log.debug("Third pass looking for caboose");
172            for (Car car : getCarList()) {
173                if (!car.isCaboose()) {
174                    continue;
175                }
176                if (car.getLocationName().equals(rl.getName())) {
177                    // is there a specific road requirement for the caboose?
178                    if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) {
179                        continue; // yes
180                    }
181                    // okay, we found a caboose at the departure location
182                    cabooseAtDeparture = true;
183                    if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
184                        if (car.getTrain() == getTrain()) {
185                            foundCaboose = true;
186                            break;
187                        }
188                    } else if (findDestinationAndTrack(car, rl, rld)) {
189                        foundCaboose = true;
190                        break;
191                    }
192                }
193            }
194        }
195        if (requiresCaboose && !foundCaboose) {
196            if (cabooseTip) {
197                addLine(ONE, Bundle.getMessage("buildNoteCaboose"));
198                addLine(ONE, Bundle.getMessage("buildNoteCaboose2"));
199            }
200            if (!cabooseAtDeparture) {
201                throw new BuildFailedException(Bundle.getMessage("buildErrorReqDepature", getTrain().getName(),
202                        Bundle.getMessage("Caboose").toLowerCase(), rl.getName()));
203            }
204            // we did find a caboose at departure that meet requirements, but
205            // couldn't place it at destination.
206            throw new BuildFailedException(Bundle.getMessage("buildErrorReqDest", getTrain().getName(),
207                    Bundle.getMessage("Caboose"), rld.getName()));
208        }
209    }
210
211    /**
212     * Find a car with FRED if needed at the correct location and adds the car
213     * to the train. If departing staging, will make sure all cars with FRED are
214     * added to the train.
215     *
216     * @param road Optional road name for this car.
217     * @param rl   Where in the route to pick up this car.
218     * @param rld  Where in the route to set out this car.
219     * @throws BuildFailedException If car not found.
220     */
221    protected void getCarWithFred(String road, RouteLocation rl, RouteLocation rld) throws BuildFailedException {
222        // load departure track if staging
223        Track departStagingTrack = null;
224        if (rl == getTrain().getTrainDepartsRouteLocation()) {
225            departStagingTrack = getDepartureStagingTrack();
226        }
227        boolean foundCarWithFred = false;
228        if (getTrain().isFredNeeded()) {
229            addLine(ONE,
230                    Bundle.getMessage("buildTrainReqFred", getTrain().getName(), road, rl.getName(), rld.getName()));
231        } else {
232            addLine(FIVE, Bundle.getMessage("buildTrainNoFred"));
233            // if not departing staging we're done
234            if (departStagingTrack == null) {
235                return;
236            }
237        }
238        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
239            Car car = getCarList().get(_carIndex);
240            if (!car.hasFred()) {
241                continue;
242            }
243            showCarServiceOrder(car);
244            addLine(FIVE,
245                    Bundle.getMessage("buildCarHasFRED", car.toString(), car.getRoadName(), car.getLocationName(),
246                            car.getTrackName()));
247            // all cars with FRED departing staging must leave with train
248            if (car.getTrack() == departStagingTrack) {
249                foundCarWithFred = false;
250                if (!generateCarLoadFromStaging(car, rld)) {
251                    // departing and terminating into staging?
252                    if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() &&
253                            rld.getLocation() == getTerminateLocation() &&
254                            getTerminateStagingTrack() != null) {
255                        // try and generate a custom load for this car with FRED
256                        generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack());
257                    }
258                }
259                if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
260                    // if destination track in quick service, car moved to location
261                    if (car.getTrain() == getTrain() || car.getLocation() == rld.getLocation()) {
262                        foundCarWithFred = true;
263                    }
264                } else if (findDestinationAndTrack(car, rl, rld)) {
265                    foundCarWithFred = true;
266                }
267                if (!foundCarWithFred) {
268                    throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString()));
269                }
270            } // is there a specific road requirement for the car with FRED?
271            else if (!road.equals(Train.NONE) && !road.equals(car.getRoadName())) {
272                addLine(SEVEN, Bundle.getMessage("buildExcludeCarWrongRoad", car.toString(),
273                        car.getLocationName(), car.getTrackName(), car.getTypeName(), car.getTypeExtensions(),
274                        car.getRoadName()));
275                remove(car); // remove this car from the list
276                continue;
277            } else if (!foundCarWithFred && car.getLocationName().equals(rl.getName())) {
278                // remove cars that can't be picked up due to train and track
279                // directions
280                if (!checkPickUpTrainDirection(car, rl)) {
281                    addLine(SEVEN, Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(),
282                            car.getTypeName(), car.getTypeExtensions(), car.getLocationName(), car.getTrackName()));
283                    remove(car); // remove this car from the list
284                    continue;
285                }
286                if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
287                    if (car.getTrain() == getTrain()) {
288                        foundCarWithFred = true;
289                    }
290                } else if (findDestinationAndTrack(car, rl, rld)) {
291                    foundCarWithFred = true;
292                }
293                if (foundCarWithFred && departStagingTrack == null) {
294                    break;
295                }
296            }
297        }
298        if (getTrain().isFredNeeded() && !foundCarWithFred) {
299            throw new BuildFailedException(Bundle.getMessage("buildErrorRequirements", getTrain().getName(),
300                    Bundle.getMessage("FRED"), rl.getName(), rld.getName()));
301        }
302    }
303
304    /**
305     * Determine if caboose or car with FRED was given a destination and track.
306     * Need to check if there's been a train assignment.
307     * 
308     * @param car the car in question
309     * @param rl  car's route location
310     * @param rld car's route location destination
311     * @return true if car has a destination. Need to check if there's been a
312     *         train assignment.
313     * @throws BuildFailedException if destination was staging and can't place
314     *                              car there
315     */
316    private boolean checkAndAddCarForDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld)
317            throws BuildFailedException {
318        return checkCarForDestination(car, rl, getRouteList().indexOf(rld));
319    }
320
321    /**
322     * Optionally block cars departing staging. No guarantee that cars departing
323     * staging can be blocked by destination. By using the pick up location id,
324     * this routine tries to find destinations that are willing to accepts all
325     * of the cars that were "blocked" together when they were picked up. Rules:
326     * The route must allow set outs at the destination. The route must allow
327     * the correct number of set outs. The destination must accept all cars in
328     * the pick up block.
329     *
330     * @throws BuildFailedException if blocking fails
331     */
332    protected void blockCarsFromStaging() throws BuildFailedException {
333        if (getDepartureStagingTrack() == null || !getDepartureStagingTrack().isBlockCarsEnabled()) {
334            return;
335        }
336
337        addLine(THREE, BLANK_LINE);
338        addLine(THREE,
339                Bundle.getMessage("blockDepartureHasBlocks", getDepartureStagingTrack().getName(),
340                        _numOfBlocks.size()));
341
342        Enumeration<String> en = _numOfBlocks.keys();
343        while (en.hasMoreElements()) {
344            String locId = en.nextElement();
345            int numCars = _numOfBlocks.get(locId);
346            String locName = "";
347            Location l = locationManager.getLocationById(locId);
348            if (l != null) {
349                locName = l.getName();
350            }
351            addLine(SEVEN, Bundle.getMessage("blockFromHasCars", locId, locName, numCars));
352            if (_numOfBlocks.size() < 2) {
353                addLine(SEVEN, Bundle.getMessage("blockUnable"));
354                return;
355            }
356        }
357        blockCarsByLocationMoves();
358        addLine(SEVEN, Bundle.getMessage("blockDone", getDepartureStagingTrack().getName()));
359    }
360
361    /**
362     * Blocks cars out of staging by assigning the largest blocks of cars to
363     * locations requesting the most moves.
364     * 
365     * @throws BuildFailedException
366     */
367    private void blockCarsByLocationMoves() throws BuildFailedException {
368        List<RouteLocation> blockRouteList = getTrain().getRoute().getLocationsBySequenceList();
369        for (RouteLocation rl : blockRouteList) {
370            // start at the second location in the route to begin blocking
371            if (rl == getTrain().getTrainDepartsRouteLocation()) {
372                continue;
373            }
374            int possibleMoves = rl.getMaxCarMoves() - rl.getCarMoves();
375            if (rl.isDropAllowed() && possibleMoves > 0) {
376                addLine(SEVEN, Bundle.getMessage("blockLocationHasMoves", rl.getName(), possibleMoves));
377            }
378        }
379        // now block out cars, send the largest block of cars to the locations
380        // requesting the greatest number of moves
381        while (true) {
382            String blockId = getLargestBlock(); // get the id of the largest
383                                                // block of cars
384            if (blockId.isEmpty() || _numOfBlocks.get(blockId) == 1) {
385                break; // done
386            }
387            // get the remaining location with the greatest number of moves
388            RouteLocation rld = getLocationWithMaximumMoves(blockRouteList, blockId);
389            if (rld == null) {
390                break; // done
391            }
392            // check to see if there are enough moves for all of the cars
393            // departing staging
394            if (rld.getMaxCarMoves() > _numOfBlocks.get(blockId)) {
395                // remove the largest block and maximum moves RouteLocation from
396                // the lists
397                _numOfBlocks.remove(blockId);
398                // block 0 cars have never left staging.
399                if (blockId.equals(Car.LOCATION_UNKNOWN)) {
400                    continue;
401                }
402                blockRouteList.remove(rld);
403                Location loc = locationManager.getLocationById(blockId);
404                Location setOutLoc = rld.getLocation();
405                if (loc != null && setOutLoc != null && checkDropTrainDirection(rld)) {
406                    for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
407                        Car car = getCarList().get(_carIndex);
408                        if (car.getTrack() == getDepartureStagingTrack() && car.getLastLocationId().equals(blockId)) {
409                            if (car.getDestination() != null) {
410                                addLine(SEVEN, Bundle.getMessage("blockNotAbleDest", car.toString(),
411                                        car.getDestinationName()));
412                                continue; // can't block this car
413                            }
414                            if (car.getFinalDestination() != null) {
415                                addLine(SEVEN,
416                                        Bundle.getMessage("blockNotAbleFinalDest", car.toString(),
417                                                car.getFinalDestination().getName()));
418                                continue; // can't block this car
419                            }
420                            if (!car.getLoadName().equals(carLoads.getDefaultEmptyName()) &&
421                                    !car.getLoadName().equals(carLoads.getDefaultLoadName())) {
422                                addLine(SEVEN,
423                                        Bundle.getMessage("blockNotAbleCustomLoad", car.toString(), car.getLoadName()));
424                                continue; // can't block this car
425                            }
426                            if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) &&
427                                    (getDepartureStagingTrack().isAddCustomLoadsEnabled() ||
428                                            getDepartureStagingTrack().isAddCustomLoadsAnySpurEnabled() ||
429                                            getDepartureStagingTrack().isAddCustomLoadsAnyStagingTrackEnabled())) {
430                                addLine(SEVEN,
431                                        Bundle.getMessage("blockNotAbleCarTypeGenerate", car.toString(),
432                                                car.getLoadName()));
433                                continue; // can't block this car
434                            }
435                            addLine(SEVEN,
436                                    Bundle.getMessage("blockingCar", car.toString(), loc.getName(), rld.getName()));
437                            if (!findDestinationAndTrack(car, getTrain().getTrainDepartsRouteLocation(), rld)) {
438                                addLine(SEVEN,
439                                        Bundle.getMessage("blockNotAbleCarType", car.toString(), rld.getName(),
440                                                car.getTypeName()));
441                            }
442                        }
443                    }
444                }
445            } else {
446                addLine(SEVEN, Bundle.getMessage("blockDestNotEnoughMoves", rld.getName(), blockId));
447                // block is too large for any stop along this train's route
448                _numOfBlocks.remove(blockId);
449            }
450        }
451    }
452
453    /**
454     * Attempts to find a destinations for cars departing a specific route
455     * location.
456     *
457     * @param rl           The route location where cars need destinations.
458     * @param isSecondPass When true this is the second time we've looked at
459     *                     these cars. Used to perform local moves.
460     * @throws BuildFailedException if failure
461     */
462    protected void findDestinationsForCarsFromLocation(RouteLocation rl, boolean isSecondPass)
463            throws BuildFailedException {
464        if (_reqNumOfMoves <= 0) {
465            return;
466        }
467        if (!rl.isLocalMovesAllowed() && isSecondPass) {
468            addLine(FIVE,
469                    Bundle.getMessage("buildRouteNoLocalLocation", getTrain().getRoute().getName(),
470                            rl.getId(), rl.getName()));
471            addLine(FIVE, BLANK_LINE);
472            return;
473        }
474        boolean messageFlag = true;
475        boolean foundCar = false;
476        for (_carIndex = 0; _carIndex < getCarList().size(); _carIndex++) {
477            if (_reqNumOfMoves <= 0) {
478                break; // done
479            }
480            Car car = getCarList().get(_carIndex);
481            // second pass deals with cars that have a final destination equal
482            // to this location.
483            // therefore a local move can be made. This causes "off spots" to be
484            // serviced.
485            if (isSecondPass && !car.getFinalDestinationName().equals(rl.getName())) {
486                continue;
487            }
488            // find a car at this location
489            if (!car.getLocationName().equals(rl.getName())) {
490                continue;
491            }
492            foundCar = true;
493            // add message that we're on the second pass for this location
494            if (isSecondPass && messageFlag) {
495                messageFlag = false;
496                addLine(FIVE, Bundle.getMessage("buildExtraPassForLocation", rl.getName()));
497                addLine(SEVEN, BLANK_LINE);
498            }
499            // are pick ups allowed?
500            if (!rl.isPickUpAllowed() &&
501                    !car.isLocalMove() &&
502                    !car.getSplitFinalDestinationName().equals(rl.getSplitName())) {
503                addLine(FIVE,
504                        Bundle.getMessage("buildNoPickUpCar", car.toString(), rl.getLocation().getName(), rl.getId()));
505                addLine(FIVE, BLANK_LINE);
506                continue;
507            }
508            findDestinationsFromLocation(rl, car, isSecondPass);
509        }
510        if (!foundCar && !isSecondPass) {
511            addLine(FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName()));
512            addLine(FIVE, BLANK_LINE);
513        }
514    }
515
516    protected void findDestinationsFromLocation(RouteLocation rl, Car car, boolean isSecondPass)
517            throws BuildFailedException {
518        if (!rl.isLocalMovesAllowed() && car.getSplitFinalDestinationName().equals(rl.getSplitName())) {
519            addLine(FIVE,
520                    Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(),
521                            rl.getId(), rl.getName(), car.toString()));
522        }
523        // can this car be pulled from an interchange or spur?
524        if (!checkPickupInterchangeOrSpur(car)) {
525            log.debug("Removing car ({}) from list", car.toString());
526            remove(car);
527            addLine(FIVE, BLANK_LINE);
528            return; // no
529        }
530        // can this car be picked up?
531        if (!checkPickUpTrainDirection(car, rl)) {
532            addLine(FIVE, BLANK_LINE);
533            return; // no
534        }
535        // do alternate track moves on the second pass (makes FIFO / LIFO work correctly)
536        if (car.getTrack().isAlternate()) {
537            addLine(SEVEN, Bundle.getMessage("buildCarOnAlternateTrack", car.toString(),
538                    car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName(),
539                    car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
540            if (Setup.isBuildAggressive() && !isSecondPass && _completedMoves != 0) {
541                addLine(SEVEN, BLANK_LINE);
542                return;
543            }
544        }
545
546        showCarServiceOrder(car); // car on FIFO or LIFO track?
547
548        car.setRouteDestinationTiming(rl); // for timing
549
550        // is car departing staging and generate custom load?
551        if (!generateCarLoadFromStaging(car)) {
552            if (!generateCarLoadStagingToStaging(car) &&
553                    car.getTrack() == getDepartureStagingTrack() &&
554                    !getDepartureStagingTrack().isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) {
555                // report build failure car departing staging with a
556                // restricted load
557                addLine(ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(),
558                        car.getLoadName(), getDepartureStagingTrack().getName()));
559                addLine(FIVE, BLANK_LINE);
560                return; // keep going and see if there are other cars with
561                        // issues outs of staging
562            }
563        }
564        // check for quick service track timing
565        if (!checkQuickServiceDeparting(car, rl)) {
566            return;
567        }
568        // If car been given a home division follow division rules for car
569        // movement.
570        if (!findDestinationsForCarsWithHomeDivision(car)) {
571            addLine(FIVE,
572                    Bundle.getMessage("buildNoDestForCar", car.toString()));
573            addLine(FIVE, BLANK_LINE);
574            return; // hold car at current location
575        }
576        // does car have a custom load without a destination?
577        // if departing staging, a destination for this car is needed, so
578        // keep going
579        if (findFinalDestinationForCarLoad(car) &&
580                car.getDestination() == null &&
581                car.getTrack() != getDepartureStagingTrack()) {
582            // done with this car, it has a custom load, and there are
583            // spurs/schedules, but no destination found
584            addLine(FIVE,
585                    Bundle.getMessage("buildNoDestForCar", car.toString()));
586            addLine(FIVE, BLANK_LINE);
587            return;
588        }
589        // Check car for final destination, then an assigned destination, if
590        // neither, find a destination for the car
591        if (checkCarForFinalDestination(car)) {
592            log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString());
593        } else if (checkCarForDestination(car, rl, getRouteList().indexOf(rl))) {
594            // car had a destination, could have been added to the train.
595            log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(),
596                    car.getTrainName());
597        } else {
598            findDestinationAndTrack(car, rl, getRouteList().indexOf(rl), getRouteList().size());
599        }
600        // build failure if car departing staging without a destination and
601        // a train we'll just put out a warning message here so we can find
602        // out how many cars have issues
603        if (car.getTrack() == getDepartureStagingTrack() &&
604                (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) {
605            addLine(ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString()));
606            // does the car have a final destination to staging? If so we
607            // need to reset this car
608            if (car.getFinalDestinationTrack() != null &&
609                    car.getFinalDestinationTrack() == getTerminateStagingTrack()) {
610                addLine(THREE,
611                        Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(),
612                                car.getFinalDestinationTrackName()));
613                car.reset();
614            }
615            addLine(SEVEN, BLANK_LINE);
616        }
617    }
618
619    private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException {
620        return generateCarLoadFromStaging(car, null);
621    }
622
623    /**
624     * Used to generate a car's load from staging. Search for a spur with a
625     * schedule and load car if possible.
626     *
627     * @param car the car
628     * @param rld The route location destination for this car. Can be null.
629     * @return true if car given a custom load
630     * @throws BuildFailedException If code check fails
631     */
632    private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException {
633        // Code Check, car should have a track assignment
634        if (car.getTrack() == null) {
635            throw new BuildFailedException(
636                    Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName()));
637        }
638        if (!car.getTrack().isStaging() ||
639                (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) ||
640                !car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
641                car.getDestination() != null ||
642                car.getFinalDestination() != null) {
643            log.debug(
644                    "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})",
645                    car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false",
646                    car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName());
647            // if car has a destination or final destination add "no load
648            // generated" message to report
649            if (car.getTrack().isStaging() &&
650                    car.getTrack().isAddCustomLoadsAnySpurEnabled() &&
651                    car.getLoadName().equals(carLoads.getDefaultEmptyName())) {
652                addLine(FIVE,
653                        Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(),
654                                car.getDestinationName(), car.getFinalDestinationName()));
655            }
656            return false; // no load generated for this car
657        }
658        addLine(FIVE,
659                Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(),
660                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(),
661                        rld != null ? rld.getLocation().getName() : ""));
662        // check to see if car type has custom loads
663        if (carLoads.getNames(car.getTypeName()).size() == 2) {
664            addLine(SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName()));
665            return false;
666        }
667        if (car.getKernel() != null) {
668            addLine(SEVEN,
669                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
670                            car.getKernel().getSize(), car.getKernel().getTotalLength(),
671                            Setup.getLengthUnit().toLowerCase()));
672        }
673        // save the car's load, should be the default empty
674        String oldCarLoad = car.getLoadName();
675        List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR);
676        log.debug("Found {} spurs", tracks.size());
677        // show locations not serviced by departure track once
678        List<Location> locationsNotServiced = new ArrayList<>();
679        for (Track track : tracks) {
680            if (locationsNotServiced.contains(track.getLocation())) {
681                continue;
682            }
683            if (rld != null && track.getLocation() != rld.getLocation()) {
684                locationsNotServiced.add(track.getLocation());
685                continue;
686            }
687            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
688                addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced",
689                        track.getLocation().getName(), car.getTrackName()));
690                locationsNotServiced.add(track.getLocation());
691                continue;
692            }
693            // only use tracks serviced by this train?
694            if (car.getTrack().isAddCustomLoadsEnabled() &&
695                    !getTrain().getRoute().isLocationNameInRoute(track.getLocation().getName())) {
696                continue;
697            }
698            // only the first match in a schedule is used for a spur
699            ScheduleItem si = getScheduleItem(car, track);
700            if (si == null) {
701                continue; // no match
702            }
703            // need to set car load so testDestination will work properly
704            car.setLoadName(si.getReceiveLoadName());
705            car.setScheduleItemId(si.getId());
706            String status = car.checkDestination(track.getLocation(), track);
707            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
708                addLine(SEVEN,
709                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
710                                track.getLocation().getName(), track.getName(), car.toString(),
711                                Track.LOAD, si.getReceiveLoadName(),
712                                status));
713                continue;
714            }
715            addLine(SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(),
716                    track.getName(), car.getLoadName()));
717            // does the car have a home division?
718            if (car.getDivision() != null) {
719                addLine(SEVEN,
720                        Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(),
721                                car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(),
722                                car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName()));
723                // load type empty must return to car's home division
724                // or load type load from foreign division must return to car's
725                // home division
726                if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() ||
727                        car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) &&
728                                car.getTrack().getDivision() != car.getDivision() &&
729                                car.getDivision() != track.getDivision()) {
730                    addLine(SEVEN,
731                            Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(),
732                                    track.getLocation().getName(), track.getName(), track.getDivisionName(),
733                                    car.toString(), car.getLoadType().toLowerCase(), car.getLoadName()));
734                    continue;
735                }
736            }
737            if (!track.isSpaceAvailable(car)) {
738                addLine(SEVEN,
739                        Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(),
740                                track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(),
741                                Setup.getLengthUnit().toLowerCase(), track.getReservationFactor()));
742                continue;
743            }
744            // try routing car
745            car.setFinalDestination(track.getLocation());
746            car.setFinalDestinationTrack(track);
747            if (router.setDestination(car, getTrain(), getBuildReport()) && car.getDestination() != null) {
748                // return car with this custom load and destination
749                addLine(FIVE,
750                        Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(),
751                                track.getLocation().getName(), track.getName()));
752                car.setLoadGeneratedFromStaging(true);
753                // is car part of kernel?
754                car.updateKernel();
755                track.bumpMoves();
756                track.bumpSchedule();
757                return true; // done, car now has a custom load
758            }
759            addLine(SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(),
760                    si.getReceiveLoadName(), track.getLocation().getName(), track.getName()));
761            addLine(SEVEN, BLANK_LINE);
762            car.setDestination(null, null);
763            car.setFinalDestination(null);
764            car.setFinalDestinationTrack(null);
765        }
766        // restore car's load
767        car.setLoadName(oldCarLoad);
768        car.setScheduleItemId(Car.NONE);
769        addLine(FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString()));
770        return false; // done, no load generated for this car
771    }
772
773    /**
774     * Tries to place a custom load in the car that is departing staging and
775     * attempts to find a destination for the car that is also staging.
776     *
777     * @param car the car
778     * @return True if custom load added to car
779     * @throws BuildFailedException If code check fails
780     */
781    private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException {
782        // Code Check, car should have a track assignment
783        if (car.getTrack() == null) {
784            throw new BuildFailedException(
785                    Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName()));
786        }
787        if (!car.getTrack().isStaging() ||
788                !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ||
789                !car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
790                car.getDestination() != null ||
791                car.getFinalDestination() != null) {
792            log.debug(
793                    "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})",
794                    car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false",
795                    car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName());
796            return false;
797        }
798        // check to see if car type has custom loads
799        if (carLoads.getNames(car.getTypeName()).size() == 2) {
800            return false;
801        }
802        List<Track> tracks = locationManager.getTracks(Track.STAGING);
803        addLine(FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size()));
804        if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) {
805            for (Track track : tracks) {
806                addLine(SEVEN,
807                        Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName()));
808            }
809        }
810        // list of locations that can't be reached by the router
811        List<Location> locationsNotServiced = new ArrayList<>();
812        if (getTerminateStagingTrack() != null) {
813            addLine(SEVEN,
814                    Bundle.getMessage("buildIgnoreStagingFirstPass",
815                            getTerminateStagingTrack().getLocation().getName()));
816            locationsNotServiced.add(getTerminateStagingTrack().getLocation());
817        }
818        while (tracks.size() > 0) {
819            // pick a track randomly
820            int rnd = (int) (Math.random() * tracks.size());
821            Track track = tracks.get(rnd);
822            tracks.remove(track);
823            log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName());
824            // find a staging track that isn't at the departure
825            if (track.getLocation() == getDepartureLocation()) {
826                log.debug("Can't use departure location ({})", track.getLocation().getName());
827                continue;
828            }
829            if (!getTrain().isAllowThroughCarsEnabled() && track.getLocation() == getTerminateLocation()) {
830                log.debug("Through cars to location ({}) not allowed", track.getLocation().getName());
831                continue;
832            }
833            if (locationsNotServiced.contains(track.getLocation())) {
834                log.debug("Location ({}) not reachable", track.getLocation().getName());
835                continue;
836            }
837            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
838                addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced",
839                        track.getLocation().getName(), car.getTrackName()));
840                locationsNotServiced.add(track.getLocation());
841                continue;
842            }
843            // the following method sets the Car load generated from staging
844            // boolean
845            if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) {
846                // test to see if destination is reachable by this train
847                if (router.setDestination(car, getTrain(), getBuildReport()) && car.getDestination() != null) {
848                    return true; // done, car has a custom load and a final
849                                 // destination
850                }
851                addLine(SEVEN, Bundle.getMessage("buildStagingTrackNotReachable",
852                        track.getLocation().getName(), track.getName(), car.getLoadName()));
853                // return car to original state
854                car.setLoadName(carLoads.getDefaultEmptyName());
855                car.setLoadGeneratedFromStaging(false);
856                car.setFinalDestination(null);
857                car.updateKernel();
858                // couldn't route to this staging location
859                locationsNotServiced.add(track.getLocation());
860            }
861        }
862        // No staging tracks reachable, try the track the train is terminating
863        // to
864        if (getTrain().isAllowThroughCarsEnabled() &&
865                getTerminateStagingTrack() != null &&
866                car.getTrack().isDestinationAccepted(getTerminateStagingTrack().getLocation()) &&
867                generateLoadCarDepartingAndTerminatingIntoStaging(car, getTerminateStagingTrack())) {
868            return true;
869        }
870
871        addLine(SEVEN,
872                Bundle.getMessage("buildNoStagingForCarCustom", car.toString()));
873        addLine(SEVEN, BLANK_LINE);
874        return false;
875    }
876
877    /**
878     * Check to see if car has been assigned a home division. If car has a home
879     * division the following rules are applied when assigning the car a
880     * destination:
881     * <p>
882     * If car load is type empty not at car's home division yard: Car is sent to
883     * a home division yard. If home division yard not available, then car is
884     * sent to home division staging, then spur (industry).
885     * <p>
886     * If car load is type empty at a yard at the car's home division: Car is
887     * sent to a home division spur, then home division staging.
888     * <p>
889     * If car load is type load not at car's home division: Car is sent to home
890     * division spur, and if spur not available then home division staging.
891     * <p>
892     * If car load is type load at car's home division: Car is sent to any
893     * division spur or staging.
894     * 
895     * @param car the car being checked for a home division
896     * @return false if destination track not found for this car
897     * @throws BuildFailedException
898     */
899    private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException {
900        if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) {
901            return true;
902        }
903        if (car.getDivision() == car.getTrack().getDivision()) {
904            addLine(FIVE,
905                    Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(),
906                            car.getLoadType().toLowerCase(),
907                            car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(),
908                            car.getLocationName(), car.getTrackName(),
909                            car.getTrack().getDivisionName()));
910        } else {
911            addLine(FIVE,
912                    Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(),
913                            car.getLoadType().toLowerCase(),
914                            car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(),
915                            car.getLocationName(), car.getTrackName(),
916                            car.getTrack().getDivisionName()));
917        }
918        if (car.getKernel() != null) {
919            addLine(SEVEN,
920                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
921                            car.getKernel().getSize(),
922                            car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
923        }
924        // does train terminate into staging? and car not departing staging?
925        if (getTerminateStagingTrack() != null && !car.getTrack().getTrackType().equals(Track.STAGING)) {
926            log.debug("Train terminates into staging track ({})", getTerminateStagingTrack().getName());
927            // bias cars to staging
928            if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
929                log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName());
930                if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) {
931                    log.debug("Car ({}) at it's home division yard", car.toString());
932                    if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
933                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
934                    }
935                }
936                // try to send to home division staging, then home division yard,
937                // then home division spur
938                else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
939                    if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) {
940                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
941                    }
942                }
943            } else {
944                log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName());
945                // 1st send car to staging dependent of shipping track division, then
946                // try spur
947                if (!sendCarToHomeDivisionTrack(car, Track.STAGING,
948                        car.getTrack().getDivision() != car.getDivision())) {
949                    return sendCarToHomeDivisionTrack(car, Track.SPUR,
950                            car.getTrack().getDivision() != car.getDivision());
951                }
952            }
953        } else {
954            // train doesn't terminate into staging or the car is departing staging
955            if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
956                log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName());
957                if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) {
958                    log.debug("Car ({}) at it's home division yard", car.toString());
959                    if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) {
960                        return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION);
961                    }
962                }
963                // try to send to home division yard, then home division staging,
964                // then home division spur
965                else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) {
966                    if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
967                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
968                    }
969                }
970            } else {
971                log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName());
972                // 1st send car to spur dependent of shipping track division, then
973                // try staging
974                if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) {
975                    return sendCarToHomeDivisionTrack(car, Track.STAGING,
976                            car.getTrack().getDivision() != car.getDivision());
977                }
978            }
979        }
980        return true;
981    }
982
983    private static final boolean HOME_DIVISION = true;
984
985    /**
986     * Tries to set a final destination for the car with a home division.
987     * 
988     * @param car           the car
989     * @param trackType     One of three track types: Track.SPUR Track.YARD or
990     *                      Track.STAGING
991     * @param home_division If true track's division must match the car's
992     * @return true if car was given a final destination
993     */
994    private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) {
995        // locations not reachable
996        List<Location> locationsNotServiced = new ArrayList<>();
997        List<Track> tracks = locationManager.getTracksByMoves(trackType);
998        log.debug("Found {} {} tracks", tracks.size(), trackType);
999        for (Track track : tracks) {
1000            if (home_division && car.getDivision() != track.getDivision()) {
1001                addLine(SEVEN,
1002                        Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(),
1003                                track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(),
1004                                car.getLoadType().toLowerCase(),
1005                                car.getLoadName()));
1006                continue;
1007            }
1008            if (locationsNotServiced.contains(track.getLocation())) {
1009                continue;
1010            }
1011            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1012                addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1013                        track.getLocation().getName(), car.getTrackName()));
1014                // location not reachable
1015                locationsNotServiced.add(track.getLocation());
1016                continue;
1017            }
1018            // only use the termination staging track for this train
1019            if (trackType.equals(Track.STAGING) &&
1020                    getTerminateStagingTrack() != null &&
1021                    track.getLocation() == getTerminateLocation() &&
1022                    track != getTerminateStagingTrack()) {
1023                continue;
1024            }
1025            if (trackType.equals(Track.SPUR)) {
1026                if (sendCarToDestinationSpur(car, track)) {
1027                    return true;
1028                }
1029            } else {
1030                if (sendCarToDestinationTrack(car, track)) {
1031                    return true;
1032                }
1033            }
1034        }
1035        addLine(FIVE,
1036                Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(),
1037                        car.getLoadType().toLowerCase(), car.getLoadName()));
1038        addLine(SEVEN, BLANK_LINE);
1039        return false;
1040    }
1041
1042    /**
1043     * Set the final destination and track for a car with a custom load. Car
1044     * must not have a destination or final destination. There's a check to see
1045     * if there's a spur/schedule for this car. Returns true if a schedule was
1046     * found. Will hold car at current location if any of the spurs checked has
1047     * the the option to "Hold cars with custom loads" enabled and the spur has
1048     * an alternate track assigned. Tries to sent the car to staging if there
1049     * aren't any spurs with schedules available.
1050     *
1051     * @param car the car with the load
1052     * @return true if there's a schedule that can be routed to for this car and
1053     *         load
1054     * @throws BuildFailedException
1055     */
1056    private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException {
1057        if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
1058                car.getLoadName().equals(carLoads.getDefaultLoadName()) ||
1059                car.getDestination() != null ||
1060                car.getFinalDestination() != null) {
1061            return false; // car doesn't have a custom load, or already has a
1062                          // destination set
1063        }
1064        addLine(FIVE,
1065                Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(), car.getTypeExtensions(),
1066                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackTypeName(),
1067                        car.getLocationName(), car.getTrackName()));
1068        if (car.getKernel() != null) {
1069            addLine(SEVEN,
1070                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1071                            car.getKernel().getSize(), car.getKernel().getTotalLength(),
1072                            Setup.getLengthUnit().toLowerCase()));
1073        }
1074        _routeToTrackFound = false;
1075        List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR);
1076        log.debug("Found {} spurs", tracks.size());
1077        // locations not reachable
1078        List<Location> locationsNotServiced = new ArrayList<>();
1079        for (Track track : tracks) {
1080            if (car.getTrack() == track) {
1081                continue;
1082            }
1083            if (track.getSchedule() == null) {
1084                addLine(SEVEN, Bundle.getMessage("buildSpurNoSchedule",
1085                        track.getLocation().getName(), track.getName()));
1086                continue;
1087            }
1088            if (locationsNotServiced.contains(track.getLocation())) {
1089                continue;
1090            }
1091            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1092                addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1093                        track.getLocation().getName(), car.getTrackName()));
1094                // location not reachable
1095                locationsNotServiced.add(track.getLocation());
1096                continue;
1097            }
1098            if (sendCarToDestinationSpur(car, track)) {
1099                return true;
1100            }
1101        }
1102        addLine(SEVEN,
1103                Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(),
1104                        car.toString(), car.getLoadType().toLowerCase(), car.getLoadName()));
1105        if (_routeToTrackFound &&
1106                !getTrain().isSendCarsWithCustomLoadsToStagingEnabled() &&
1107                !car.getLocation().isStaging()) {
1108            addLine(SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(),
1109                    car.getLocationName(), car.getTrackName()));
1110        } else {
1111            // try and send car to staging
1112            addLine(SEVEN, BLANK_LINE);
1113            addLine(FIVE,
1114                    Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName()));
1115            tracks = locationManager.getTracks(Track.STAGING);
1116            log.debug("Found {} staging tracks", tracks.size());
1117            while (tracks.size() > 0) {
1118                // pick a track randomly
1119                int rnd = (int) (Math.random() * tracks.size());
1120                Track track = tracks.get(rnd);
1121                tracks.remove(track);
1122                log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName());
1123                if (track.getLocation() == car.getLocation()) {
1124                    continue;
1125                }
1126                if (locationsNotServiced.contains(track.getLocation())) {
1127                    continue;
1128                }
1129                if (getTerminateStagingTrack() != null &&
1130                        track.getLocation() == getTerminateLocation() &&
1131                        track != getTerminateStagingTrack()) {
1132                    continue; // ignore other staging tracks at terminus
1133                }
1134                if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1135                    addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1136                            track.getLocation().getName(), car.getTrackName()));
1137                    locationsNotServiced.add(track.getLocation());
1138                    continue;
1139                }
1140                String status = track.isRollingStockAccepted(car);
1141                if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1142                    log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString());
1143                    continue;
1144                }
1145                addLine(SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(),
1146                        track.getName(), car.getLoadName()));
1147                // try to send car to staging
1148                car.setFinalDestination(track.getLocation());
1149                // test to see if destination is reachable by this train
1150                if (router.setDestination(car, getTrain(), getBuildReport())) {
1151                    _routeToTrackFound = true; // found a route to staging
1152                }
1153                if (car.getDestination() != null) {
1154                    car.updateKernel(); // car part of kernel?
1155                    return true;
1156                }
1157                // couldn't route to this staging location
1158                locationsNotServiced.add(track.getLocation());
1159                car.setFinalDestination(null);
1160            }
1161            addLine(SEVEN,
1162                    Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName()));
1163            if (!_routeToTrackFound) {
1164                addLine(SEVEN, BLANK_LINE);
1165            }
1166        }
1167        log.debug("routeToSpurFound is {}", _routeToTrackFound);
1168        return _routeToTrackFound; // done
1169    }
1170
1171    boolean _routeToTrackFound;
1172
1173    /**
1174     * Used to determine if spur can accept car. Also will set routeToTrackFound
1175     * to true if there's a valid route available to the spur being tested. Sets
1176     * car's final destination to track if okay.
1177     * 
1178     * @param car   the car
1179     * @param track the spur
1180     * @return false if there's an issue with using the spur
1181     */
1182    private boolean sendCarToDestinationSpur(Car car, Track track) {
1183        addLine(SEVEN, BLANK_LINE);
1184        if (!checkBasicMoves(car, track)) {
1185            addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(),
1186                    car.toString(), track.getLocation().getName(), track.getName()));
1187            return false;
1188        }
1189        String status = car.checkDestination(track.getLocation(), track);
1190        if (!status.equals(Track.OKAY)) {
1191            if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) {
1192                addLine(SEVEN, Bundle.getMessage("buildTrackSequentialMode",
1193                        track.getLocation().getName(), track.getName(), status));
1194            }
1195            // if the track has an alternate track don't abort if the issue was
1196            // space
1197            if (!status.startsWith(Track.LENGTH)) {
1198                addLine(SEVEN,
1199                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1200                                track.getLocation().getName(), track.getName(), car.toString(),
1201                                car.getLoadType().toLowerCase(), car.getLoadName(), status));
1202                return false;
1203            }
1204            if (track.getAlternateTrack() == null) {
1205                // report that the spur is full and no alternate
1206                addLine(SEVEN,
1207                        Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName()));
1208                return false;
1209            } else {
1210                addLine(SEVEN,
1211                        Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(),
1212                                track.getAlternateTrack().getName()));
1213                // check to see if alternate and track are configured properly
1214                if (!getTrain().isLocalSwitcher() &&
1215                        (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) {
1216                    addLine(SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(),
1217                            formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())),
1218                            track.getAlternateTrack().getName(), formatStringToCommaSeparated(
1219                                    Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections()))));
1220                    return false;
1221                }
1222            }
1223        }
1224        addLine(SEVEN,
1225                Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(),
1226                        track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(),
1227                        car.getLoadName()));
1228
1229        // show if track is requesting cars with custom loads to only go to
1230        // spurs
1231        if (track.isHoldCarsWithCustomLoadsEnabled()) {
1232            addLine(SEVEN,
1233                    Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName()));
1234        }
1235        // check the number of in bound cars to this track
1236        if (!track.isSpaceAvailable(car)) {
1237            // Now determine if we should move the car or just leave it
1238            if (track.isHoldCarsWithCustomLoadsEnabled()) {
1239                // determine if this car can be routed to the spur
1240                String id = track.getScheduleItemId();
1241                if (router.isCarRouteable(car, getTrain(), track, getBuildReport())) {
1242                    // hold car if able to route to track
1243                    _routeToTrackFound = true;
1244                } else {
1245                    addLine(SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(),
1246                            car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1247                }
1248                track.setScheduleItemId(id); // restore id
1249            }
1250            if (car.getTrack().isStaging()) {
1251                addLine(SEVEN,
1252                        Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(),
1253                                track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(),
1254                                Setup.getLengthUnit().toLowerCase(), track.getReservationFactor()));
1255            } else {
1256                addLine(SEVEN,
1257                        Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(),
1258                                track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(),
1259                                track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase()));
1260            }
1261            return false;
1262        }
1263        // try to send car to this spur
1264        car.setFinalDestination(track.getLocation());
1265        car.setFinalDestinationTrack(track);
1266        // test to see if destination is reachable by this train
1267        if (router.setDestination(car, getTrain(), getBuildReport()) && track.isHoldCarsWithCustomLoadsEnabled()) {
1268            _routeToTrackFound = true; // if we don't find another spur, don't
1269                                       // move car
1270        }
1271        if (car.getDestination() == null) {
1272            if (!router.getStatus().equals(Track.OKAY)) {
1273                addLine(SEVEN,
1274                        Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1275            }
1276            car.setFinalDestination(null);
1277            car.setFinalDestinationTrack(null);
1278            car.setScheduleItemId(Car.NONE);
1279            // don't move car if another train can
1280            if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) {
1281                _routeToTrackFound = true;
1282            }
1283            return false;
1284        }
1285        if (car.getDestinationTrack() != track) {
1286            track.bumpMoves();
1287            // car is being routed to this track
1288            if (track.getSchedule() != null) {
1289                car.setScheduleItemId(track.getScheduleItemId());
1290                track.bumpSchedule();
1291            }
1292        }
1293        car.updateKernel();
1294        return true; // done, car has a new destination
1295    }
1296
1297    /**
1298     * Destination track can be division yard or staging, NOT a spur.
1299     * 
1300     * @param car   the car
1301     * @param track the car's destination track
1302     * @return true if car given a new final destination
1303     */
1304    private boolean sendCarToDestinationTrack(Car car, Track track) {
1305        if (!checkBasicMoves(car, track)) {
1306            addLine(SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", getTrain().getName(),
1307                    car.toString(), track.getLocation().getName(), track.getName()));
1308            return false;
1309        }
1310        String status = car.checkDestination(track.getLocation(), track);
1311
1312        if (!status.equals(Track.OKAY)) {
1313            addLine(SEVEN,
1314                    Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1315                            track.getLocation().getName(), track.getName(), car.toString(),
1316                            car.getLoadType().toLowerCase(), car.getLoadName(), status));
1317            return false;
1318        }
1319        if (!track.isSpaceAvailable(car)) {
1320            addLine(SEVEN,
1321                    Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(),
1322                            track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(),
1323                            track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase()));
1324            return false;
1325        }
1326        // try to send car to this division track
1327        addLine(SEVEN,
1328                Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(),
1329                        track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(),
1330                        car.getLoadName()));
1331        car.setFinalDestination(track.getLocation());
1332        car.setFinalDestinationTrack(track);
1333        // test to see if destination is reachable by this train
1334        if (router.setDestination(car, getTrain(), getBuildReport())) {
1335            log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName());
1336        }
1337        if (car.getDestination() == null) {
1338            addLine(SEVEN,
1339                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1340            car.setFinalDestination(null);
1341            car.setFinalDestinationTrack(null);
1342            return false;
1343        }
1344        car.updateKernel();
1345        return true; // done, car has a new final destination
1346    }
1347
1348    /**
1349     * Checks for a car's final destination, and then after checking, tries to
1350     * route the car to that destination. Normal return from this routine is
1351     * false, with the car returning with a set destination. Returns true if car
1352     * has a final destination, but can't be used for this train.
1353     *
1354     * @param car
1355     * @return false if car needs destination processing (normal).
1356     */
1357    private boolean checkCarForFinalDestination(Car car) {
1358        if (car.getFinalDestination() == null || car.getDestination() != null) {
1359            return false;
1360        }
1361
1362        addLine(FIVE,
1363                Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(),
1364                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackTypeName(),
1365                        car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
1366                        car.getFinalDestinationTrackName()));
1367
1368        // no local moves for this train?
1369        if (!getTrain().isLocalSwitcher() &&
1370                !getTrain().isAllowLocalMovesEnabled() &&
1371                car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) &&
1372                car.getTrack() != getDepartureStagingTrack()) {
1373            addLine(FIVE,
1374                    Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(),
1375                            car.getFinalDestinationName(), getTrain().getName()));
1376            addLine(FIVE, BLANK_LINE);
1377            return true; // car has a final destination, but no local moves by
1378                         // this train
1379        }
1380        // is the car's destination the terminal and is that allowed?
1381        if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) {
1382            if (car.getTrack() == getDepartureStagingTrack()) {
1383                addLine(ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString()));
1384            }
1385            return true; // car has a final destination, but through traffic not
1386                         // allowed by this train
1387        }
1388        // does the car have a final destination track that is willing to
1389        // service the car?
1390        // note the default mode for all track types is MATCH
1391        if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) {
1392            String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack());
1393            // keep going if the only issue was track length and the track
1394            // accepts the car's load
1395            if (!status.equals(Track.OKAY) &&
1396                    !status.startsWith(Track.LENGTH) &&
1397                    !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) {
1398                addLine(SEVEN,
1399                        Bundle.getMessage("buildNoDestTrackNewLoad",
1400                                StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()),
1401                                car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(),
1402                                car.toString(), car.getLoadType().toLowerCase(), car.getLoadName(), status));
1403                // is this car or kernel being sent to a track that is too
1404                // short?
1405                if (status.startsWith(Track.CAPACITY)) {
1406                    // track is too short for this car or kernel
1407                    addLine(SEVEN,
1408                            Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(),
1409                                    car.getFinalDestinationTrack().getName(), car.toString()));
1410                }
1411                _warnings++;
1412                addLine(SEVEN,
1413                        Bundle.getMessage("buildWarningRemovingFinalDest", car.getFinalDestination().getName(),
1414                                car.getFinalDestinationTrack().getName(), car.toString()));
1415                car.setFinalDestination(null);
1416                car.setFinalDestinationTrack(null);
1417                return false; // car no longer has a final destination
1418            }
1419        }
1420
1421        // now try and route the car
1422        if (!router.setDestination(car, getTrain(), getBuildReport())) {
1423            addLine(SEVEN,
1424                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1425            // don't move car if routing issue was track space but not departing
1426            // staging
1427            if ((!router.getStatus().startsWith(Track.LENGTH) &&
1428                    !getTrain().isServiceAllCarsWithFinalDestinationsEnabled()) ||
1429                    (car.getTrack() == getDepartureStagingTrack())) {
1430                // add car to unable to route list
1431                if (!_notRoutable.contains(car)) {
1432                    _notRoutable.add(car);
1433                }
1434                addLine(FIVE, BLANK_LINE);
1435                addLine(FIVE,
1436                        Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(),
1437                                car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1438                addLine(FIVE, BLANK_LINE);
1439                return false; // move this car, routing failed!
1440            }
1441        } else {
1442            if (car.getDestination() != null) {
1443                return false; // routing successful process this car, normal
1444                              // exit from this routine
1445            }
1446            if (car.getTrack() == getDepartureStagingTrack()) {
1447                log.debug("Car ({}) departing staging with final destination ({}) and no destination",
1448                        car.toString(), car.getFinalDestinationName());
1449                return false; // try and move this car out of staging
1450            }
1451        }
1452        addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1453        addLine(FIVE, BLANK_LINE);
1454        return true;
1455    }
1456
1457    /**
1458     * Checks to see if car has a destination and tries to add car to train.
1459     * Will find a track for the car if needed. Returns false if car doesn't
1460     * have a destination.
1461     *
1462     * @param rl         the car's route location
1463     * @param routeIndex where in the route to start search
1464     * @return true if car has a destination. Need to check if car given a train
1465     *         assignment.
1466     * @throws BuildFailedException if destination was staging and can't place
1467     *                              car there
1468     */
1469    private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException {
1470        if (car.getDestination() == null) {
1471            return false; // the only false return
1472        }
1473        addLine(SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(),
1474                car.getDestinationName(), car.getDestinationTrackName(), car.getFinalDestinationName(),
1475                car.getFinalDestinationTrackName()));
1476        RouteLocation rld = getTrain().getRoute().getLastLocationByName(car.getDestinationName());
1477        if (rld == null) {
1478            // code check, router doesn't set a car's destination if not carried
1479            // by train being built. Car has a destination that isn't serviced
1480            // by this train. Find buildExcludeCarDestNotPartRoute in
1481            // loadRemoveAndListCars()
1482            throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(),
1483                    car.getDestinationName(), car.getDestinationTrackName(), getTrain().getRoute().getName()));
1484        }
1485        // now go through the route and try and find a location with
1486        // the correct destination name
1487        for (int k = routeIndex; k < getRouteList().size(); k++) {
1488            rld = getRouteList().get(k);
1489            car.setRouteDestinationTiming(rld); // for timing
1490            // if car can be picked up later at same location, skip
1491            if (checkForLaterPickUp(car, rl, rld)) {
1492                addLine(SEVEN, BLANK_LINE);
1493                return true;
1494            }
1495            if (!rld.getName().equals(car.getDestinationName())) {
1496                continue;
1497            }
1498            // is the car's destination the terminal and is that allowed?
1499            if (!checkThroughCarsAllowed(car, car.getDestinationName())) {
1500                return true;
1501            }
1502            log.debug("Car ({}) found a destination in train's route", car.toString());
1503            // are drops allows at this location?
1504            if (!rld.isDropAllowed() && !car.isLocalMove()) {
1505                addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(),
1506                        rld.getId(), rld.getName()));
1507                continue;
1508            }
1509            // are local moves allows at this location?
1510            if (!rld.isLocalMovesAllowed() && car.isLocalMove()) {
1511                addLine(FIVE, Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(),
1512                        rld.getId(), rld.getName(), car.toString()));
1513                continue;
1514            }
1515            if (getTrain().isLocationSkipped(rld)) {
1516                addLine(FIVE,
1517                        Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName()));
1518                continue;
1519            }
1520            // any moves left at this location?
1521            if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
1522                addLine(FIVE,
1523                        Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(),
1524                                getTrain().getRoute().getName(), rld.getId(), rld.getName()));
1525                continue;
1526            }
1527            // is the train length okay?
1528            if (!checkTrainLength(car, rl, rld)) {
1529                continue;
1530            }
1531            // check for valid destination track
1532            if (car.getDestinationTrack() == null) {
1533                addLine(FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString()));
1534                // is car going into staging?
1535                if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) {
1536                    String status = car.checkDestination(car.getDestination(), getTerminateStagingTrack());
1537                    if (status.equals(Track.OKAY)) {
1538                        addLine(FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(),
1539                                getTerminateStagingTrack().getName()));
1540                        addCarToTrain(car, rl, rld, getTerminateStagingTrack());
1541                        return true;
1542                    } else {
1543                        addLine(SEVEN,
1544                                Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
1545                                        getTerminateStagingTrack().getTrackTypeName(),
1546                                        getTerminateStagingTrack().getLocation().getName(),
1547                                        getTerminateStagingTrack().getName(),
1548                                        status));
1549                        continue;
1550                    }
1551                } else {
1552                    // no staging at this location, now find a destination track
1553                    // for this car
1554                    List<Track> tracks = getTracksAtDestination(car, rld);
1555                    if (tracks.size() > 0) {
1556                        if (tracks.get(1) != null) {
1557                            car.setFinalDestination(car.getDestination());
1558                            car.setFinalDestinationTrack(tracks.get(1));
1559                            tracks.get(1).bumpMoves();
1560                        }
1561                        addLine(FIVE,
1562                                Bundle.getMessage("buildCarCanDropMoves", car.toString(),
1563                                        tracks.get(0).getTrackTypeName(),
1564                                        tracks.get(0).getLocation().getName(), tracks.get(0).getName(),
1565                                        rld.getCarMoves(), rld.getMaxCarMoves()));
1566                        addCarToTrain(car, rl, rld, tracks.get(0));
1567                        return true;
1568                    }
1569                }
1570            } else {
1571                log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName());
1572                // going into the correct staging track?
1573                if (rld.equals(getTrain().getTrainTerminatesRouteLocation()) &&
1574                        getTerminateStagingTrack() != null &&
1575                        getTerminateStagingTrack() != car.getDestinationTrack()) {
1576                    // car going to wrong track in staging, change track
1577                    addLine(SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(),
1578                            car.getDestinationName(), car.getDestinationTrackName()));
1579                    car.setDestination(getTerminateStagingTrack().getLocation(), getTerminateStagingTrack());
1580                }
1581                if (!rld.equals(getTrain().getTrainTerminatesRouteLocation()) ||
1582                        getTerminateStagingTrack() == null ||
1583                        getTerminateStagingTrack() == car.getDestinationTrack()) {
1584                    // is train direction correct? and drop to interchange or
1585                    // spur?
1586                    if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) &&
1587                            checkTrainCanDrop(car, car.getDestinationTrack())) {
1588                        String status = car.checkDestination(car.getDestination(), car.getDestinationTrack());
1589                        if (status.equals(Track.OKAY) &&
1590                                (status = checkReserved(getTrain(), rld, car, car.getDestinationTrack(), true))
1591                                        .equals(Track.OKAY)) {
1592                            Track destTrack = car.getDestinationTrack();
1593                            addCarToTrain(car, rl, rld, destTrack);
1594                            return true;
1595                        }
1596                        if (status.equals(TIMING) && checkForAlternate(car, car.getDestinationTrack())) {
1597                            // send car to alternate track) {
1598                            car.setFinalDestination(car.getDestination());
1599                            car.setFinalDestinationTrack(car.getDestinationTrack());
1600                            addCarToTrain(car, rl, rld, car.getDestinationTrack().getAlternateTrack());
1601                            return true;
1602                        }
1603                        addLine(SEVEN,
1604                                Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
1605                                        car.getDestinationTrack().getTrackTypeName(),
1606                                        car.getDestinationTrack().getLocation().getName(),
1607                                        car.getDestinationTrackName(), status));
1608
1609                    }
1610                } else {
1611                    // code check
1612                    throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(),
1613                            car.getDestinationName(), car.getDestinationTrackName()));
1614                }
1615            }
1616            addLine(FIVE,
1617                    Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId()));
1618            if (car.getDestinationTrack() == null) {
1619                log.debug("Could not find a destination track for location ({})", car.getDestinationName());
1620            }
1621        }
1622        log.debug("car ({}) not added to train", car.toString());
1623        addLine(FIVE,
1624                Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId()));
1625        // remove destination and revert to final destination
1626        if (car.getDestinationTrack() != null) {
1627            // going to remove this destination from car
1628            car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1);
1629            Track destTrack = car.getDestinationTrack();
1630            // TODO should we leave the car's destination? The spur expects this
1631            // car!
1632            if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) {
1633                addLine(SEVEN, Bundle.getMessage("buildPickupCanceled",
1634                        destTrack.getLocation().getName(), destTrack.getName()));
1635            }
1636        }
1637        car.setFinalDestination(car.getPreviousFinalDestination());
1638        car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
1639        car.setDestination(null, null);
1640        car.updateKernel();
1641
1642        addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1643        addLine(FIVE, BLANK_LINE);
1644        return true; // car no longer has a destination, but it had one.
1645    }
1646
1647    /**
1648     * Find a destination and track for a car at a route location.
1649     *
1650     * @param car the car!
1651     * @param rl  The car's route location
1652     * @param rld The car's route destination
1653     * @return true if successful.
1654     * @throws BuildFailedException if code check fails
1655     */
1656    private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException {
1657        int index = getRouteList().indexOf(rld);
1658        if (getTrain().isLocalSwitcher()) {
1659            return findDestinationAndTrack(car, rl, index, index + 1);
1660        }
1661        return findDestinationAndTrack(car, rl, index - 1, index + 1);
1662    }
1663
1664    /**
1665     * Find a destination and track for a car, and add the car to the train.
1666     *
1667     * @param car        The car that is looking for a destination and
1668     *                   destination track.
1669     * @param rl         The route location for this car.
1670     * @param routeIndex Where in the train's route to begin a search for a
1671     *                   destination for this car.
1672     * @param routeEnd   Where to stop looking for a destination.
1673     * @return true if successful, car has destination, track and a train.
1674     * @throws BuildFailedException if code check fails
1675     */
1676    private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd)
1677            throws BuildFailedException {
1678        if (routeIndex + 1 == routeEnd) {
1679            log.debug("Car ({}) is at the last location in the train's route", car.toString());
1680        }
1681        addLine(FIVE,
1682                Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(),
1683                        car.getTypeExtensions(), car.getLoadType().toLowerCase(), car.getLoadName(),
1684                        car.getTrackTypeName(), car.getLocationName(), car.getTrackName()));
1685        if (car.getKernel() != null) {
1686            addLine(SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1687                    car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
1688        }
1689
1690        // normally start looking after car's route location
1691        int start = routeIndex;
1692        // the route location destination being checked for the car
1693        RouteLocation rld = null;
1694        // holds the best route location destination for the car
1695        RouteLocation rldSave = null;
1696        // holds the best track at destination for the car
1697        Track trackSave = null;
1698        // used when a spur has an alternate track and no schedule
1699        Track finalDestinationTrackSave = null;
1700        // true when car can be picked up from two or more locations in the
1701        // route
1702        boolean multiplePickup = false;
1703
1704        if (!getTrain().isLocalSwitcher()) {
1705            start++; // begin looking for tracks at the next location
1706        }
1707        // all pick ups to terminal?
1708        if (getTrain().isSendCarsToTerminalEnabled() &&
1709                !rl.getSplitName().equals(getDepartureLocation().getSplitName()) &&
1710                routeEnd == getRouteList().size()) {
1711            addLine(FIVE, Bundle.getMessage("buildSendToTerminal", getTerminateLocation().getName()));
1712            // user could have specified several terminal locations with the
1713            // "same" name
1714            start = routeEnd - 1;
1715            while (start > routeIndex) {
1716                if (!getRouteList().get(start - 1).getSplitName()
1717                        .equals(getTerminateLocation().getSplitName())) {
1718                    break;
1719                }
1720                start--;
1721            }
1722        }
1723        // now search for a destination for this car
1724        for (int k = start; k < routeEnd; k++) {
1725            rld = getRouteList().get(k);
1726            car.setRouteDestinationTiming(rld); // for timing
1727            // if car can be picked up later at same location, set flag
1728            if (checkForLaterPickUp(car, rl, rld)) {
1729                multiplePickup = true;
1730            }
1731            if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) {
1732                addLine(FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId()));
1733            } else {
1734                addLine(FIVE, Bundle.getMessage("buildRouteNoDropLocation", getTrain().getRoute().getName(),
1735                        rld.getId(), rld.getName()));
1736                continue;
1737            }
1738            if (getTrain().isLocationSkipped(rld)) {
1739                addLine(FIVE,
1740                        Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), getTrain().getName()));
1741                continue;
1742            }
1743            // any moves left at this location?
1744            if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
1745                addLine(FIVE,
1746                        Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(),
1747                                getTrain().getRoute().getName(), rld.getId(), rld.getName()));
1748                continue;
1749            }
1750            // get the destination
1751            Location testDestination = rld.getLocation();
1752            // code check, all locations in the route have been already checked
1753            if (testDestination == null) {
1754                throw new BuildFailedException(
1755                        Bundle.getMessage("buildErrorRouteLoc", getTrain().getRoute().getName(), rld.getName()));
1756            }
1757            // don't move car to same location unless the train is a switcher
1758            // (local moves) or is passenger, caboose or car with FRED
1759            if (rl.getSplitName().equals(rld.getSplitName()) &&
1760                    !getTrain().isLocalSwitcher() &&
1761                    !car.isPassenger() &&
1762                    !car.isCaboose() &&
1763                    !car.hasFred()) {
1764                // allow cars to return to the same staging location if no other
1765                // options (tracks) are available
1766                if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) &&
1767                        testDestination.isStaging() &&
1768                        trackSave == null) {
1769                    addLine(SEVEN,
1770                            Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName()));
1771                } else {
1772                    addLine(SEVEN,
1773                            Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName()));
1774                    continue;
1775                }
1776            }
1777            // don't allow local moves for a car with a final destination
1778            if (rl.getSplitName().equals(rld.getSplitName()) &&
1779                    car.getFinalDestination() != null &&
1780                    !car.isPassenger() &&
1781                    !car.isCaboose() &&
1782                    !car.hasFred()) {
1783                if (!rld.isLocalMovesAllowed()) {
1784                    addLine(FIVE,
1785                            Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(),
1786                                    rld.getId(), rld.getName(), car.toString()));
1787                    continue;
1788                }
1789                if (!rl.isLocalMovesAllowed()) {
1790                    addLine(FIVE,
1791                            Bundle.getMessage("buildRouteNoLocalLocCar", getTrain().getRoute().getName(),
1792                                    rl.getId(), rl.getName(), car.toString()));
1793                    continue;
1794                }
1795            }
1796
1797            // check to see if departure track has any restrictions
1798            if (!car.getTrack().isDestinationAccepted(testDestination)) {
1799                addLine(SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(),
1800                        car.getTrackName()));
1801                continue;
1802            }
1803
1804            if (!testDestination.acceptsTypeName(car.getTypeName())) {
1805                addLine(SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(),
1806                        car.getTypeName(), testDestination.getName()));
1807                continue;
1808            }
1809            // can this location service this train's direction
1810            if (!checkDropTrainDirection(rld)) {
1811                continue;
1812            }
1813            // is the train length okay?
1814            if (!checkTrainLength(car, rl, rld)) {
1815                break; // no, done with this car
1816            }
1817            // is the car's destination the terminal and is that allowed?
1818            if (!checkThroughCarsAllowed(car, rld.getName())) {
1819                continue; // not allowed
1820            }
1821
1822            Track trackTemp = null;
1823            // used when alternate track selected
1824            Track finalDestinationTrackTemp = null;
1825
1826            // is there a track assigned for staging cars?
1827            if (rld == getTrain().getTrainTerminatesRouteLocation() && getTerminateStagingTrack() != null) {
1828                trackTemp = tryStaging(car, rldSave);
1829                if (trackTemp == null) {
1830                    continue; // no
1831                }
1832            } else {
1833                // not staging, start track search
1834                List<Track> tracks = getTracksAtDestination(car, rld);
1835                if (tracks.size() > 0) {
1836                    trackTemp = tracks.get(0);
1837                    finalDestinationTrackTemp = tracks.get(1);
1838                }
1839            }
1840            // did we find a new destination?
1841            if (trackTemp == null) {
1842                addLine(FIVE,
1843                        Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName()));
1844            } else {
1845                addLine(FIVE,
1846                        Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(),
1847                                trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(),
1848                                rld.getMaxCarMoves()));
1849                if (multiplePickup) {
1850                    if (rldSave != null) {
1851                        addLine(FIVE,
1852                                Bundle.getMessage("buildTrackServicedLater", car.getLocationName(),
1853                                        trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(),
1854                                        trackTemp.getName(), car.getLocationName()));
1855                    } else {
1856                        addLine(FIVE,
1857                                Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName()));
1858                        trackSave = null;
1859                    }
1860                    break; // done
1861                }
1862                // if there's more than one available destination use the lowest
1863                // ratio
1864                if (rldSave != null) {
1865                    // check for an earlier drop in the route
1866                    rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd);
1867                    double saveCarMoves = rldSave.getCarMoves();
1868                    double saveRatio = saveCarMoves / rldSave.getMaxCarMoves();
1869                    double nextCarMoves = rld.getCarMoves();
1870                    double nextRatio = nextCarMoves / rld.getMaxCarMoves();
1871
1872                    // bias cars to the terminal
1873                    if (rld == getTrain().getTrainTerminatesRouteLocation()) {
1874                        nextRatio = nextRatio * nextRatio;
1875                        log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(),
1876                                Double.toString(nextRatio));
1877
1878                        // bias cars with default loads to a track with a
1879                        // schedule
1880                    } else if (!trackTemp.getScheduleId().equals(Track.NONE)) {
1881                        nextRatio = nextRatio * nextRatio;
1882                        log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(),
1883                                trackTemp.getScheduleName(), Double.toString(nextRatio));
1884                    }
1885                    // bias cars with default loads to saved track with a
1886                    // schedule
1887                    if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) {
1888                        saveRatio = saveRatio * saveRatio;
1889                        log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(),
1890                                trackSave.getScheduleName(), Double.toString(saveRatio));
1891                    }
1892                    log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(),
1893                            Double.toString(nextRatio));
1894                    if (saveRatio < nextRatio) {
1895                        // the saved is better than the last found
1896                        rld = rldSave;
1897                        trackTemp = trackSave;
1898                        finalDestinationTrackTemp = finalDestinationTrackSave;
1899                    }
1900                }
1901                // every time through, save the best route destination, and
1902                // track
1903                rldSave = rld;
1904                trackSave = trackTemp;
1905                finalDestinationTrackSave = finalDestinationTrackTemp;
1906            }
1907        }
1908        // did we find a destination?
1909        if (trackSave != null && rldSave != null) {
1910            // determine if local staging move is allowed (leaves car in staging)
1911            if ((getTrain().isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) &&
1912                    rl.isDropAllowed() &&
1913                    rl.getLocation().isStaging() &&
1914                    trackSave.isStaging() &&
1915                    rl.getLocation() == rldSave.getLocation() &&
1916                    !getTrain().isLocalSwitcher() &&
1917                    !car.isPassenger() &&
1918                    !car.isCaboose() &&
1919                    !car.hasFred()) {
1920                addLine(SEVEN,
1921                        Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(),
1922                                car.getTrackName()));
1923                rldSave = rl; // make local move
1924            } else if (trackSave.isSpur()) {
1925                car.setScheduleItemId(trackSave.getScheduleItemId());
1926                trackSave.bumpSchedule();
1927                log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(),
1928                        trackSave.getName(), car.getScheduleItemId());
1929            } else {
1930                car.setScheduleItemId(Car.NONE);
1931            }
1932            if (finalDestinationTrackSave != null) {
1933                car.setFinalDestination(finalDestinationTrackSave.getLocation());
1934                car.setFinalDestinationTrack(finalDestinationTrackSave);
1935                if (trackSave.isAlternate()) {
1936                    finalDestinationTrackSave.bumpMoves(); // bump move count
1937                }
1938            }
1939            addCarToTrain(car, rl, rldSave, trackSave);
1940            return true;
1941        }
1942        addLine(FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1943        addLine(FIVE, BLANK_LINE);
1944        return false; // no build errors, but car not given destination
1945    }
1946
1947    /**
1948     * Add car to train, and adjust train length and weight
1949     *
1950     * @param car   the car being added to the train
1951     * @param rl    the departure route location for this car
1952     * @param rld   the destination route location for this car
1953     * @param track the destination track for this car
1954     */
1955    protected void addCarToTrain(Car car, RouteLocation rl, RouteLocation rld, Track track) {
1956        car = checkQuickServiceArrival(car, rld, track);
1957        addLine(THREE,
1958                Bundle.getMessage("buildCarAssignedDest", car.toString(), rld.getName(), track.getName(), rld.getId()));
1959        car.setDestination(track.getLocation(), track, Car.FORCE);
1960        int length = car.getTotalLength();
1961        int weightTons = car.getAdjustedWeightTons();
1962        // car could be part of a kernel
1963        if (car.getKernel() != null) {
1964            length = car.getKernel().getTotalLength(); // includes couplers
1965            weightTons = car.getKernel().getAdjustedWeightTons();
1966            List<Car> kCars = car.getKernel().getCars();
1967            addLine(THREE,
1968                    Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), kCars.size(),
1969                            car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
1970            for (Car kCar : kCars) {
1971                if (kCar != car) {
1972                    addLine(THREE, Bundle.getMessage("buildCarKernelAssignedDest", kCar.toString(),
1973                            kCar.getKernelName(), rld.getName(), track.getName()));
1974                    kCar.setTrain(getTrain());
1975                    kCar.setRouteLocation(rl);
1976                    kCar.setRouteDestination(rld);
1977                    kCar.setDestination(track.getLocation(), track, Car.FORCE); // force destination
1978                    // save final destination and track values in case of train reset
1979                    kCar.setPreviousFinalDestination(car.getPreviousFinalDestination());
1980                    kCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
1981                }
1982            }
1983            car.updateKernel();
1984        }
1985        // warn if car's load wasn't generated out of staging
1986        if (!getTrain().isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
1987            _warnings++;
1988            addLine(SEVEN,
1989                    Bundle.getMessage("buildWarnCarDepartStaging", car.toString(), car.getLoadName()));
1990        }
1991        addLine(THREE, BLANK_LINE);
1992        _numberCars++; // bump number of cars moved by this train
1993        _completedMoves++; // bump number of car pick up moves for the location
1994        _reqNumOfMoves--; // decrement number of moves left for the location
1995
1996        remove(car); // remove car from list
1997
1998        rl.setCarMoves(rl.getCarMoves() + 1);
1999        if (rl != rld) {
2000            rld.setCarMoves(rld.getCarMoves() + 1);
2001        }
2002        // now adjust train length and weight for each location that car is in
2003        // the train
2004        finishAddRsToTrain(car, rl, rld, length, weightTons);
2005    }
2006
2007    /**
2008     * Checks to see if cars that are already in the train can be redirected
2009     * from the alternate track to the spur that really wants the car. Fixes the
2010     * issue of having cars placed at the alternate when the spur's cars get
2011     * pulled by this train, but cars were sent to the alternate because the
2012     * spur was full at the time it was tested.
2013     * 
2014     * @param rl route location were the cars are to be redirected if possible.
2015     * @return true if one or more cars were redirected
2016     * @throws BuildFailedException if coding issue
2017     */
2018    protected boolean redirectCarsFromAlternateTrack(RouteLocation rl) throws BuildFailedException {
2019        // code check, should be aggressive
2020        if (!Setup.isBuildAggressive()) {
2021            throw new BuildFailedException("ERROR coding issue, should be using aggressive mode");
2022        }
2023        boolean redirected = false;
2024        List<Car> cars = carManager.getByTrainList(getTrain());
2025        for (Car car : cars) {
2026            // does the car have a final destination and the destination is this
2027            // one?
2028            if (car.getRouteDestination() != rl ||
2029                    car.getFinalDestination() == null ||
2030                    car.getFinalDestinationTrack() == null ||
2031                    !car.getFinalDestinationName().equals(car.getDestinationName())) {
2032                continue;
2033            }
2034            Track alternate = car.getFinalDestinationTrack().getAlternateTrack();
2035            if (alternate == null || car.getDestinationTrack() != alternate) {
2036                continue;
2037            }
2038            // is the car in a kernel?
2039            if (car.getKernel() != null && !car.isLead()) {
2040                continue;
2041            }
2042            log.debug("Car ({}) alternate track ({}) has final destination track ({}) location ({})", car.toString(),
2043                    car.getDestinationTrackName(), car.getFinalDestinationTrackName(), car.getDestinationName()); // NOI18N
2044            car.setRouteDestinationTiming(rl); // for timing
2045            if ((alternate.isYard() || alternate.isInterchange()) &&
2046                    car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack())
2047                            .equals(Track.OKAY) &&
2048                    checkReserved(getTrain(), car.getRouteDestination(), car, car.getFinalDestinationTrack(), false)
2049                            .equals(Track.OKAY) &&
2050                    checkDropTrainDirection(car, car.getRouteDestination(), car.getFinalDestinationTrack()) &&
2051                    checkTrainCanDrop(car, car.getFinalDestinationTrack())) {
2052                log.debug("Car ({}) alternate track ({}) can be redirected to final destination track ({})",
2053                        car.toString(), car.getDestinationTrackName(), car.getFinalDestinationTrackName());
2054                if (car.getKernel() != null) {
2055                    for (Car k : car.getKernel().getCars()) {
2056                        if (k.isLead()) {
2057                            continue;
2058                        }
2059                        addLine(FIVE,
2060                                Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(),
2061                                        car.getFinalDestinationTrackName(), k.toString(),
2062                                        car.getDestinationTrackName()));
2063                        // force car to track
2064                        k.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE);
2065                        checkClone(k);
2066                    }
2067                }
2068                addLine(FIVE,
2069                        Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(),
2070                                car.getFinalDestinationTrackName(),
2071                                car.toString(), car.getDestinationTrackName()));
2072                car.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE);
2073                checkClone(car);
2074                redirected = true;
2075            }
2076        }
2077        return redirected;
2078    }
2079
2080    private void checkClone(Car car) {
2081        if (car.isClone()) {
2082            // move original car to spur
2083            Car carOriginal = car.getOriginal(car);
2084            addLine(SEVEN, Bundle.getMessage("buildRedirectYardToSpur", carOriginal.toString(),
2085                    carOriginal.getTrack().getTrackTypeName(), carOriginal.getLocationName(),
2086                    carOriginal.getTrackName(), car.getDestinationTrack().getTrackTypeName(), car.getDestinationName(),
2087                    car.getDestinationTrackName()));
2088            carOriginal.setFinalDestination(null);
2089            carOriginal.setFinalDestinationTrack(null);
2090            carOriginal.setLocation(car.getDestination(), car.getDestinationTrack(), Car.FORCE);
2091        } else {
2092            checkQuickServiceRedirected(car);
2093        }
2094    }
2095
2096    /*
2097     * Checks to see if the redirected car is going to a track with quick
2098     * service. The car in this case has already been assigned to the train.
2099     * This routine will create clones if needed, and allow the car to be
2100     * reassigned to the same train. Only lead car in a kernel is allowed.
2101     */
2102    private void checkQuickServiceRedirected(Car car) {
2103        if (car.getKernel() != null && !car.isLead()) {
2104            return;
2105        }
2106        if (car.getDestinationTrack().isQuickServiceEnabled()) {
2107            RouteLocation rl = car.getRouteLocation();
2108            RouteLocation rld = car.getRouteDestination();
2109            Track track = car.getDestinationTrack();
2110            // remove cars from train
2111            if (car.getKernel() != null) {
2112                for (Car kar : car.getKernel().getCars())
2113                    kar.reset();
2114            } else {
2115                car.reset();
2116            }
2117            // restore moves counts location
2118            rl.setCarMoves(rl.getCarMoves() - 1);
2119            if (rl != rld) {
2120                rld.setCarMoves(rld.getCarMoves() - 1);
2121            }
2122            getCarList().add(0, car);
2123            addCarToTrain(car, rl, rld, track);
2124        }
2125    }
2126
2127    /**
2128     * Checks to see if track is requesting a quick service. Since it isn't
2129     * possible for a car to be pulled and set out twice, this code creates a
2130     * "clone" car to create the requested Manifest. A car could have multiple
2131     * clones, therefore each clone has a creation order number appended to its
2132     * road number. Clones are used to restore a car's location and load in the
2133     * case of reset.
2134     * 
2135     * @param car   the car possibly needing quick service
2136     * @param track the destination track
2137     * @return the car if not quick service, or a clone if quick service
2138     */
2139    private Car checkQuickServiceArrival(Car car, RouteLocation rld, Track track) {
2140        if (!track.isQuickServiceEnabled()) {
2141            if (Setup.isBuildOnTime()) {
2142                addLine(THREE,
2143                        Bundle.getMessage("buildTrackNotQuickService", StringUtils.capitalize(track.getTrackTypeName()),
2144                                track.getLocation().getName(), track.getName(), car.toString()));
2145                // warn if departing staging that is quick serviced enabled
2146                if (car.getTrack().isStaging() && car.getTrack().isQuickServiceEnabled()) {
2147                    _warnings++;
2148                    addLine(THREE,
2149                            Bundle.getMessage("buildWarningQuickService", car.toString(),
2150                                    car.getTrack().getTrackTypeName(),
2151                                    car.getTrack().getLocation().getName(), car.getTrack().getName(),
2152                                    getTrain().getName(), StringUtils.capitalize(car.getTrack().getTrackTypeName())));
2153                }
2154            }
2155            return car;
2156        }
2157        // quick service enabled, create clones
2158        Car cloneCar = carManager.createClone(car, track, getTrain(), getStartTime());
2159        addLine(FIVE,
2160                Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()),
2161                        track.getLocation().getName(), track.getName(), cloneCar.toString(), car.toString()));
2162        track.scheduleNext(car); // apply schedule to car
2163        car.loadNext(track); // update load, wait count
2164        if (car.getWait() > 0) {
2165            remove(car); // available for next train
2166            addLine(FIVE, Bundle.getMessage("buildExcludeCarWait", car.toString(),
2167                    car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait()));
2168            car.setWait(car.getWait() - 1);
2169            car.updateLoad(track);
2170        }
2171        // remember where in the route the car was delivered
2172        car.setRouteDestination(rld);
2173        car.updateKernel();
2174        return cloneCar; // return clone
2175    }
2176
2177    private static final Logger log = LoggerFactory.getLogger(TrainBuilderCars.class);
2178}