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