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