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