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