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