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