001package jmri.jmrit.operations.trains;
002
003import java.util.*;
004
005import org.apache.commons.lang3.StringUtils;
006import org.slf4j.Logger;
007import org.slf4j.LoggerFactory;
008
009import jmri.jmrit.operations.locations.Location;
010import jmri.jmrit.operations.locations.Track;
011import jmri.jmrit.operations.locations.schedules.ScheduleItem;
012import jmri.jmrit.operations.rollingstock.cars.Car;
013import jmri.jmrit.operations.rollingstock.cars.CarLoad;
014import jmri.jmrit.operations.rollingstock.engines.Engine;
015import jmri.jmrit.operations.router.Router;
016import jmri.jmrit.operations.routes.RouteLocation;
017import jmri.jmrit.operations.setup.Setup;
018
019/**
020 * Contains methods for cars when building a train.
021 * 
022 * @author Daniel Boudreau Copyright (C) 2022
023 */
024public class TrainBuilderCars extends TrainBuilderEngines {
025
026    /**
027     * Find a caboose if needed at the correct location and add it to the train.
028     * If departing staging, all cabooses are added to the train. If there isn't
029     * a road name required for the caboose, tries to find a caboose with the
030     * same road name as the lead engine.
031     *
032     * @param roadCaboose     Optional road name for this car.
033     * @param leadEngine      The lead engine for this train. Used to find a
034     *                        caboose with the same road name as the engine.
035     * @param rl              Where in the route to pick up this car.
036     * @param rld             Where in the route to set out this car.
037     * @param requiresCaboose When true, the train requires a caboose.
038     * @throws BuildFailedException If car not found.
039     */
040    protected void getCaboose(String roadCaboose, Engine leadEngine, RouteLocation rl, RouteLocation rld,
041            boolean requiresCaboose) throws BuildFailedException {
042        // code check
043        if (rl == null) {
044            throw new BuildFailedException(Bundle.getMessage("buildErrorCabooseNoLocation", _train.getName()));
045        }
046        // code check
047        if (rld == null) {
048            throw new BuildFailedException(
049                    Bundle.getMessage("buildErrorCabooseNoDestination", _train.getName(), rl.getName()));
050        }
051        // load departure track if staging
052        Track departTrack = null;
053        if (rl == _train.getTrainDepartsRouteLocation()) {
054            departTrack = _departStageTrack; // can be null
055        }
056        if (!requiresCaboose) {
057            addLine(_buildReport, FIVE,
058                    Bundle.getMessage("buildTrainNoCaboose", rl.getName()));
059            if (departTrack == null) {
060                return;
061            }
062        } else {
063            addLine(_buildReport, ONE, Bundle.getMessage("buildTrainReqCaboose", _train.getName(), roadCaboose,
064                    rl.getName(), rld.getName()));
065        }
066
067        // Now go through the car list looking for cabooses
068        boolean cabooseTip = true; // add a user tip to the build report about
069                                   // cabooses if none found
070        boolean cabooseAtDeparture = false; // set to true if caboose at
071                                            // departure location is found
072        boolean foundCaboose = false;
073        for (_carIndex = 0; _carIndex < _carList.size(); _carIndex++) {
074            Car car = _carList.get(_carIndex);
075            if (!car.isCaboose()) {
076                continue;
077            }
078            showCarServiceOrder(car);
079
080            cabooseTip = false; // found at least one caboose, so they exist!
081            addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarIsCaboose", car.toString(), car.getRoadName(),
082                    car.getLocationName(), car.getTrackName()));
083            // car departing staging must leave with train
084            if (car.getTrack() == departTrack) {
085                foundCaboose = false;
086                if (!generateCarLoadFromStaging(car, rld)) {
087                    // departing and terminating into staging?
088                    if (car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() &&
089                            rld.getLocation() == _terminateLocation &&
090                            _terminateStageTrack != null) {
091                        // try and generate a custom load for this caboose
092                        generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack);
093                    }
094                }
095                if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
096                    if (car.getTrain() == _train) {
097                        foundCaboose = true;
098                    }
099                } else if (findDestinationAndTrack(car, rl, rld)) {
100                    foundCaboose = true;
101                }
102                if (!foundCaboose) {
103                    throw new BuildFailedException(Bundle.getMessage("buildErrorCarStageDest", car.toString()));
104                }
105                // is there a specific road requirement for the caboose?
106            } else if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) {
107                continue;
108            } else if (!foundCaboose && car.getLocationName().equals(rl.getName())) {
109                // remove cars that can't be picked up due to train and track
110                // directions
111                if (!checkPickUpTrainDirection(car, rl)) {
112                    addLine(_buildReport, SEVEN,
113                            Bundle.getMessage("buildExcludeCarTypeAtLoc", car.toString(), car.getTypeName(),
114                                    car.getTypeExtensions(), car.getLocationName(), car.getTrackName()));
115                    _carList.remove(car); // remove this car from the list
116                    _carIndex--;
117                    continue;
118                }
119                // first pass, find a caboose that matches the engine road
120                if (leadEngine != null && car.getRoadName().equals(leadEngine.getRoadName())) {
121                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(),
122                            car.getRoadName(), leadEngine.toString()));
123                    if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
124                        if (car.getTrain() == _train) {
125                            foundCaboose = true;
126                        }
127                    } else if (findDestinationAndTrack(car, rl, rld)) {
128                        foundCaboose = true;
129                    }
130                    if (!foundCaboose) {
131                        _carList.remove(car); // remove this car from the list
132                        _carIndex--;
133                        continue;
134                    }
135                }
136                // done if we found a caboose and not departing staging
137                if (foundCaboose && departTrack == null) {
138                    break;
139                }
140            }
141        }
142        // second pass, take a caboose with a road name that is "similar"
143        // (hyphen feature) to the engine road name
144        if (requiresCaboose && !foundCaboose && roadCaboose.equals(Train.NONE)) {
145            log.debug("Second pass looking for caboose");
146            for (Car car : _carList) {
147                if (car.isCaboose() && car.getLocationName().equals(rl.getName())) {
148                    if (leadEngine != null &&
149                            TrainCommon.splitString(car.getRoadName())
150                                    .equals(TrainCommon.splitString(leadEngine.getRoadName()))) {
151                        addLine(_buildReport, SEVEN, Bundle.getMessage("buildCabooseRoadMatches", car.toString(),
152                                car.getRoadName(), leadEngine.toString()));
153                        if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
154                            if (car.getTrain() == _train) {
155                                foundCaboose = true;
156                                break;
157                            }
158                        } else if (findDestinationAndTrack(car, rl, rld)) {
159                            foundCaboose = true;
160                            break;
161                        }
162                    }
163                }
164            }
165        }
166        // third pass, take any caboose unless a caboose road name is specified
167        if (requiresCaboose && !foundCaboose) {
168            log.debug("Third pass looking for caboose");
169            for (Car car : _carList) {
170                if (!car.isCaboose()) {
171                    continue;
172                }
173                if (car.getLocationName().equals(rl.getName())) {
174                    // is there a specific road requirement for the caboose?
175                    if (!roadCaboose.equals(Train.NONE) && !roadCaboose.equals(car.getRoadName())) {
176                        continue; // yes
177                    }
178                    // okay, we found a caboose at the departure location
179                    cabooseAtDeparture = true;
180                    if (checkAndAddCarForDestinationAndTrack(car, rl, rld)) {
181                        if (car.getTrain() == _train) {
182                            foundCaboose = true;
183                            break;
184                        }
185                    } else if (findDestinationAndTrack(car, rl, rld)) {
186                        foundCaboose = true;
187                        break;
188                    }
189                }
190            }
191        }
192        if (requiresCaboose && !foundCaboose) {
193            if (cabooseTip) {
194                addLine(_buildReport, ONE, Bundle.getMessage("buildNoteCaboose"));
195                addLine(_buildReport, ONE, Bundle.getMessage("buildNoteCaboose2"));
196            }
197            if (!cabooseAtDeparture) {
198                throw new BuildFailedException(Bundle.getMessage("buildErrorReqDepature", _train.getName(),
199                        Bundle.getMessage("Caboose").toLowerCase(), rl.getName()));
200            }
201            // we did find a caboose at departure that meet requirements, but
202            // couldn't place it at destination.
203            throw new BuildFailedException(Bundle.getMessage("buildErrorReqDest", _train.getName(),
204                    Bundle.getMessage("Caboose"), rld.getName()));
205        }
206    }
207
208    /**
209     * Find a car with FRED if needed at the correct location and adds the car
210     * to the train. If departing staging, will make sure all cars with FRED are
211     * added to the train.
212     *
213     * @param road Optional road name for this car.
214     * @param rl   Where in the route to pick up this car.
215     * @param rld  Where in the route to set out this car.
216     * @throws BuildFailedException If car not found.
217     */
218    protected void getCarWithFred(String road, RouteLocation rl, RouteLocation rld) throws BuildFailedException {
219        // load departure track if staging
220        Track departTrack = null;
221        if (rl == _train.getTrainDepartsRouteLocation()) {
222            departTrack = _departStageTrack;
223        }
224        boolean foundCarWithFred = false;
225        if (_train.isFredNeeded()) {
226            addLine(_buildReport, ONE,
227                    Bundle.getMessage("buildTrainReqFred", _train.getName(), road, rl.getName(), rld.getName()));
228        } else {
229            addLine(_buildReport, FIVE, Bundle.getMessage("buildTrainNoFred"));
230            // if not departing staging we're done
231            if (departTrack == null) {
232                return;
233            }
234        }
235        for (_carIndex = 0; _carIndex < _carList.size(); _carIndex++) {
236            Car car = _carList.get(_carIndex);
237            if (!car.hasFred()) {
238                continue;
239            }
240            showCarServiceOrder(car);
241            addLine(_buildReport, SEVEN,
242                    Bundle.getMessage("buildCarHasFRED", car.toString(), car.getRoadName(), car.getLocationName()));
243            // 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.getTypeExtensions(), 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().isLocationNameInRoute(track.getLocation().getName())) {
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            addLine(_buildReport, SEVEN, BLANK_LINE);
706            car.setDestination(null, null);
707            car.setFinalDestination(null);
708            car.setFinalDestinationTrack(null);
709        }
710        // restore car's load
711        car.setLoadName(oldCarLoad);
712        car.setScheduleItemId(Car.NONE);
713        addLine(_buildReport, FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString()));
714        return false; // done, no load generated for this car
715    }
716
717    /**
718     * Tries to place a custom load in the car that is departing staging and
719     * attempts to find a destination for the car that is also staging.
720     *
721     * @param car the car
722     * @return True if custom load added to car
723     * @throws BuildFailedException If code check fails
724     */
725    private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException {
726        // Code Check, car should have a track assignment
727        if (car.getTrack() == null) {
728            throw new BuildFailedException(
729                    Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName()));
730        }
731        if (!car.getTrack().isStaging() ||
732                !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ||
733                !car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
734                car.getDestination() != null ||
735                car.getFinalDestination() != null) {
736            log.debug(
737                    "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})",
738                    car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false",
739                    car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName());
740            return false;
741        }
742        // check to see if car type has custom loads
743        if (carLoads.getNames(car.getTypeName()).size() == 2) {
744            return false;
745        }
746        List<Track> tracks = locationManager.getTracks(Track.STAGING);
747        addLine(_buildReport, FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size()));
748        if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) {
749            for (Track track : tracks) {
750                addLine(_buildReport, SEVEN,
751                        Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName()));
752            }
753        }
754        // list of locations that can't be reached by the router
755        List<Location> locationsNotServiced = new ArrayList<>();
756        if (_terminateStageTrack != null) {
757            addLine(_buildReport, SEVEN,
758                    Bundle.getMessage("buildIgnoreStagingFirstPass", _terminateStageTrack.getLocation().getName()));
759            locationsNotServiced.add(_terminateStageTrack.getLocation());
760        }
761        while (tracks.size() > 0) {
762            // pick a track randomly
763            int rnd = (int) (Math.random() * tracks.size());
764            Track track = tracks.get(rnd);
765            tracks.remove(track);
766            log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName());
767            // find a staging track that isn't at the departure
768            if (track.getLocation() == _departLocation) {
769                log.debug("Can't use departure location ({})", track.getLocation().getName());
770                continue;
771            }
772            if (!_train.isAllowThroughCarsEnabled() && track.getLocation() == _terminateLocation) {
773                log.debug("Through cars to location ({}) not allowed", track.getLocation().getName());
774                continue;
775            }
776            if (locationsNotServiced.contains(track.getLocation())) {
777                log.debug("Location ({}) not reachable", track.getLocation().getName());
778                continue;
779            }
780            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
781                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
782                        track.getLocation().getName(), car.getTrackName()));
783                locationsNotServiced.add(track.getLocation());
784                continue;
785            }
786            // the following method sets the Car load generated from staging
787            // boolean
788            if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) {
789                // test to see if destination is reachable by this train
790                if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) {
791                    return true; // done, car has a custom load and a final
792                                 // destination
793                }
794                addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingTrackNotReachable",
795                        track.getLocation().getName(), track.getName(), car.getLoadName()));
796                // return car to original state
797                car.setLoadName(carLoads.getDefaultEmptyName());
798                car.setLoadGeneratedFromStaging(false);
799                car.setFinalDestination(null);
800                car.updateKernel();
801                // couldn't route to this staging location
802                locationsNotServiced.add(track.getLocation());
803            }
804        }
805        // No staging tracks reachable, try the track the train is terminating
806        // to
807        if (_train.isAllowThroughCarsEnabled() &&
808                _terminateStageTrack != null &&
809                car.getTrack().isDestinationAccepted(_terminateStageTrack.getLocation()) &&
810                generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack)) {
811            return true;
812        }
813
814        addLine(_buildReport, SEVEN,
815                Bundle.getMessage("buildNoStagingForCarCustom", car.toString()));
816        addLine(_buildReport, SEVEN, BLANK_LINE);
817        return false;
818    }
819
820    /**
821     * Check to see if car has been assigned a home division. If car has a home
822     * division the following rules are applied when assigning the car a
823     * destination:
824     * <p>
825     * If car load is type empty not at car's home division yard: Car is sent to
826     * a home division yard. If home division yard not available, then car is
827     * sent to home division staging, then spur (industry).
828     * <p>
829     * If car load is type empty at a yard at the car's home division: Car is
830     * sent to a home division spur, then home division staging.
831     * <p>
832     * If car load is type load not at car's home division: Car is sent to home
833     * division spur, and if spur not available then home division staging.
834     * <p>
835     * If car load is type load at car's home division: Car is sent to any
836     * division spur or staging.
837     * 
838     * @param car the car being checked for a home division
839     * @return false if destination track not found for this car
840     * @throws BuildFailedException
841     */
842    private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException {
843        if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) {
844            return true;
845        }
846        if (car.getDivision() == car.getTrack().getDivision()) {
847            addLine(_buildReport, FIVE,
848                    Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(),
849                            car.getLoadType().toLowerCase(),
850                            car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(),
851                            car.getLocationName(), car.getTrackName(),
852                            car.getTrack().getDivisionName()));
853        } else {
854            addLine(_buildReport, FIVE,
855                    Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(),
856                            car.getLoadType().toLowerCase(),
857                            car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(),
858                            car.getLocationName(), car.getTrackName(),
859                            car.getTrack().getDivisionName()));
860        }
861        if (car.getKernel() != null) {
862            addLine(_buildReport, SEVEN,
863                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
864                            car.getKernel().getSize(),
865                            car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
866        }
867        // does train terminate into staging?
868        if (_terminateStageTrack != null) {
869            log.debug("Train terminates into staging track ({})", _terminateStageTrack.getName());
870            // bias cars to staging
871            if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
872                log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName());
873                if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) {
874                    log.debug("Car ({}) at it's home division yard", car.toString());
875                    if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
876                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
877                    }
878                }
879                // try to send to home division staging, then home division yard,
880                // then home division spur
881                else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
882                    if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) {
883                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
884                    }
885                }
886            } else {
887                log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName());
888                // 1st send car to staging dependent of shipping track division, then
889                // try spur
890                if (!sendCarToHomeDivisionTrack(car, Track.STAGING, car.getTrack().getDivision() != car.getDivision())) {
891                    return sendCarToHomeDivisionTrack(car, Track.SPUR,
892                            car.getTrack().getDivision() != car.getDivision());
893                }
894            }
895        } else {
896            // train doesn't terminate into staging
897            if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
898                log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName());
899                if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) {
900                    log.debug("Car ({}) at it's home division yard", car.toString());
901                    if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) {
902                        return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION);
903                    }
904                }
905                // try to send to home division yard, then home division staging,
906                // then home division spur
907                else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) {
908                    if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
909                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
910                    }
911                }
912            } else {
913                log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName());
914                // 1st send car to spur dependent of shipping track division, then
915                // try staging
916                if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) {
917                    return sendCarToHomeDivisionTrack(car, Track.STAGING,
918                            car.getTrack().getDivision() != car.getDivision());
919                }
920            }
921        }
922        return true;
923    }
924
925    private static final boolean HOME_DIVISION = true;
926
927    /**
928     * Tries to set a final destination for the car with a home division.
929     * 
930     * @param car           the car
931     * @param trackType     One of three track types: Track.SPUR Track.YARD or
932     *                      Track.STAGING
933     * @param home_division If true track's division must match the car's
934     * @return true if car was given a final destination
935     */
936    private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) {
937        // locations not reachable
938        List<Location> locationsNotServiced = new ArrayList<>();
939        List<Track> tracks = locationManager.getTracksByMoves(trackType);
940        log.debug("Found {} {} tracks", tracks.size(), trackType);
941        for (Track track : tracks) {
942            if (home_division && car.getDivision() != track.getDivision()) {
943                addLine(_buildReport, SEVEN,
944                        Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(),
945                                track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(),
946                                car.getLoadType().toLowerCase(),
947                                car.getLoadName()));
948                continue;
949            }
950            if (locationsNotServiced.contains(track.getLocation())) {
951                continue;
952            }
953            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
954                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
955                        track.getLocation().getName(), car.getTrackName()));
956                // location not reachable
957                locationsNotServiced.add(track.getLocation());
958                continue;
959            }
960            // only use the termination staging track for this train
961            if (trackType.equals(Track.STAGING) &&
962                    _terminateStageTrack != null &&
963                    track.getLocation() == _terminateLocation &&
964                    track != _terminateStageTrack) {
965                continue;
966            }
967            if (trackType.equals(Track.SPUR)) {
968                if (sendCarToDestinationSpur(car, track)) {
969                    return true;
970                }
971            } else {
972                if (sendCarToDestinationTrack(car, track)) {
973                    return true;
974                }
975            }
976        }
977        addLine(_buildReport, FIVE,
978                Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(),
979                        car.getLoadType().toLowerCase(), car.getLoadName()));
980        addLine(_buildReport, SEVEN, BLANK_LINE);
981        return false;
982    }
983
984    /**
985     * Set the final destination and track for a car with a custom load. Car
986     * must not have a destination or final destination. There's a check to see
987     * if there's a spur/schedule for this car. Returns true if a schedule was
988     * found. Will hold car at current location if any of the spurs checked has
989     * the the option to "Hold cars with custom loads" enabled and the spur has
990     * an alternate track assigned. Tries to sent the car to staging if there
991     * aren't any spurs with schedules available.
992     *
993     * @param car the car with the load
994     * @return true if there's a schedule that can be routed to for this car and
995     *         load
996     * @throws BuildFailedException
997     */
998    private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException {
999        if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
1000                car.getLoadName().equals(carLoads.getDefaultLoadName()) ||
1001                car.getDestination() != null ||
1002                car.getFinalDestination() != null) {
1003            return false; // car doesn't have a custom load, or already has a
1004                          // destination set
1005        }
1006        addLine(_buildReport, FIVE,
1007                Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(),
1008                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(),
1009                        car.getTrackName()));
1010        if (car.getKernel() != null) {
1011            addLine(_buildReport, SEVEN,
1012                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1013                            car.getKernel().getSize(), car.getKernel().getTotalLength(),
1014                            Setup.getLengthUnit().toLowerCase()));
1015        }
1016        _routeToTrackFound = false;
1017        List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR);
1018        log.debug("Found {} spurs", tracks.size());
1019        // locations not reachable
1020        List<Location> locationsNotServiced = new ArrayList<>();
1021        for (Track track : tracks) {
1022            if (car.getTrack() == track) {
1023                continue;
1024            }
1025            if (track.getSchedule() == null) {
1026                addLine(_buildReport, SEVEN, Bundle.getMessage("buildSpurNoSchedule",
1027                        track.getLocation().getName(), track.getName()));
1028                continue;
1029            }
1030            if (locationsNotServiced.contains(track.getLocation())) {
1031                continue;
1032            }
1033            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1034                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1035                        track.getLocation().getName(), car.getTrackName()));
1036                // location not reachable
1037                locationsNotServiced.add(track.getLocation());
1038                continue;
1039            }
1040            if (sendCarToDestinationSpur(car, track)) {
1041                return true;
1042            }
1043        }
1044        addLine(_buildReport, SEVEN, Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(),
1045                car.toString(), car.getLoadType().toLowerCase(), car.getLoadName()));
1046        if (_routeToTrackFound &&
1047                !_train.isSendCarsWithCustomLoadsToStagingEnabled() &&
1048                !car.getLocation().isStaging()) {
1049            addLine(_buildReport, SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(),
1050                    car.getLocationName(), car.getTrackName()));
1051        } else {
1052            // try and send car to staging
1053            addLine(_buildReport, FIVE,
1054                    Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName()));
1055            tracks = locationManager.getTracks(Track.STAGING);
1056            log.debug("Found {} staging tracks", tracks.size());
1057            while (tracks.size() > 0) {
1058                // pick a track randomly
1059                int rnd = (int) (Math.random() * tracks.size());
1060                Track track = tracks.get(rnd);
1061                tracks.remove(track);
1062                log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName());
1063                if (track.getLocation() == car.getLocation()) {
1064                    continue;
1065                }
1066                if (locationsNotServiced.contains(track.getLocation())) {
1067                    continue;
1068                }
1069                if (_terminateStageTrack != null &&
1070                        track.getLocation() == _terminateLocation &&
1071                        track != _terminateStageTrack) {
1072                    continue; // ignore other staging tracks at terminus
1073                }
1074                if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1075                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1076                            track.getLocation().getName(), car.getTrackName()));
1077                    locationsNotServiced.add(track.getLocation());
1078                    continue;
1079                }
1080                String status = track.isRollingStockAccepted(car);
1081                if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1082                    log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString());
1083                    continue;
1084                }
1085                addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(),
1086                        track.getName(), car.getLoadName()));
1087                // try to send car to staging
1088                car.setFinalDestination(track.getLocation());
1089                // test to see if destination is reachable by this train
1090                if (router.setDestination(car, _train, _buildReport)) {
1091                    _routeToTrackFound = true; // found a route to staging
1092                }
1093                if (car.getDestination() != null) {
1094                    car.updateKernel(); // car part of kernel?
1095                    return true;
1096                }
1097                // couldn't route to this staging location
1098                locationsNotServiced.add(track.getLocation());
1099                car.setFinalDestination(null);
1100            }
1101            addLine(_buildReport, SEVEN,
1102                    Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName()));
1103        }
1104        log.debug("routeToSpurFound is {}", _routeToTrackFound);
1105        return _routeToTrackFound; // done
1106    }
1107
1108    boolean _routeToTrackFound;
1109
1110    /**
1111     * Used to determine if spur can accept car. Also will set routeToTrackFound
1112     * to true if there's a valid route available to the spur being tested. Sets
1113     * car's final destination to track if okay.
1114     * 
1115     * @param car   the car
1116     * @param track the spur
1117     * @return false if there's an issue with using the spur
1118     */
1119    private boolean sendCarToDestinationSpur(Car car, Track track) {
1120        if (!checkBasicMoves(car, track)) {
1121            addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(),
1122                    car.toString(), track.getLocation().getName(), track.getName()));
1123            return false;
1124        }
1125        String status = car.checkDestination(track.getLocation(), track);
1126        if (!status.equals(Track.OKAY)) {
1127            if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) {
1128                addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrackSequentialMode", track.getName(),
1129                        track.getLocation().getName(), status));
1130            }
1131            // if the track has an alternate track don't abort if the issue was
1132            // space
1133            if (!status.startsWith(Track.LENGTH)) {
1134                addLine(_buildReport, SEVEN,
1135                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1136                                track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(),
1137                                status));
1138                return false;
1139            }
1140            String scheduleStatus = track.checkSchedule(car);
1141            if (!scheduleStatus.equals(Track.OKAY)) {
1142                addLine(_buildReport, SEVEN,
1143                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1144                                track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(),
1145                                scheduleStatus));
1146                return false;
1147            }
1148            if (track.getAlternateTrack() == null) {
1149                // report that the spur is full and no alternate
1150                addLine(_buildReport, SEVEN,
1151                        Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName()));
1152                return false;
1153            } else {
1154                addLine(_buildReport, SEVEN,
1155                        Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(),
1156                                track.getAlternateTrack().getName()));
1157                // check to see if alternate and track are configured properly
1158                if (!_train.isLocalSwitcher() &&
1159                        (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) {
1160                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(),
1161                            formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())),
1162                            track.getAlternateTrack().getName(), formatStringToCommaSeparated(
1163                                    Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections()))));
1164                    return false;
1165                }
1166            }
1167        }
1168        addLine(_buildReport, SEVEN, BLANK_LINE);
1169        addLine(_buildReport, SEVEN,
1170                Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(),
1171                        track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(),
1172                        car.getLoadName()));
1173
1174        // show if track is requesting cars with custom loads to only go to
1175        // spurs
1176        if (track.isHoldCarsWithCustomLoadsEnabled()) {
1177            addLine(_buildReport, SEVEN,
1178                    Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName()));
1179        }
1180        // check the number of in bound cars to this track
1181        if (!track.isSpaceAvailable(car)) {
1182            // Now determine if we should move the car or just leave it
1183            if (track.isHoldCarsWithCustomLoadsEnabled()) {
1184                // determine if this car can be routed to the spur
1185                String id = track.getScheduleItemId();
1186                if (router.isCarRouteable(car, _train, track, _buildReport)) {
1187                    // hold car if able to route to track
1188                    _routeToTrackFound = true;
1189                } else {
1190                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(),
1191                            car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1192                }
1193                track.setScheduleItemId(id); // restore id
1194            }
1195            if (car.getTrack().isStaging()) {
1196                addLine(_buildReport, SEVEN,
1197                        Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(),
1198                                track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(),
1199                                Setup.getLengthUnit().toLowerCase(), track.getReservationFactor()));
1200            } else {
1201                addLine(_buildReport, SEVEN,
1202                        Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(),
1203                                track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(),
1204                                track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase()));
1205            }
1206            return false;
1207        }
1208        // try to send car to this spur
1209        car.setFinalDestination(track.getLocation());
1210        car.setFinalDestinationTrack(track);
1211        // test to see if destination is reachable by this train
1212        if (router.setDestination(car, _train, _buildReport) && track.isHoldCarsWithCustomLoadsEnabled()) {
1213            _routeToTrackFound = true; // if we don't find another spur, don't
1214                                       // move car
1215        }
1216        if (car.getDestination() == null) {
1217            addLine(_buildReport, SEVEN,
1218                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1219            car.setFinalDestination(null);
1220            car.setFinalDestinationTrack(null);
1221            // don't move car if another train can
1222            if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) {
1223                _routeToTrackFound = true;
1224            }
1225            return false;
1226        }
1227        if (car.getDestinationTrack() != track) {
1228            track.bumpMoves();
1229            // car is being routed to this track
1230            if (track.getSchedule() != null) {
1231                car.setScheduleItemId(track.getCurrentScheduleItem().getId());
1232                track.bumpSchedule();
1233            }
1234        }
1235        car.updateKernel();
1236        return true; // done, car has a new destination
1237    }
1238
1239    /**
1240     * Destination track can be division yard or staging, NOT a spur.
1241     * 
1242     * @param car   the car
1243     * @param track the car's destination track
1244     * @return true if car given a new final destination
1245     */
1246    private boolean sendCarToDestinationTrack(Car car, Track track) {
1247        if (!checkBasicMoves(car, track)) {
1248            addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(),
1249                    car.toString(), track.getLocation().getName(), track.getName()));
1250            return false;
1251        }
1252        String status = car.checkDestination(track.getLocation(), track);
1253        if (!status.equals(Track.OKAY)) {
1254            addLine(_buildReport, SEVEN,
1255                    Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1256                            track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), status));
1257            return false;
1258        }
1259        if (!track.isSpaceAvailable(car)) {
1260            addLine(_buildReport, SEVEN,
1261                    Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(),
1262                            track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(),
1263                            track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase()));
1264            return false;
1265        }
1266        // try to send car to this division track
1267        addLine(_buildReport, SEVEN,
1268                Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(),
1269                        track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(),
1270                        car.getLoadName()));
1271        car.setFinalDestination(track.getLocation());
1272        car.setFinalDestinationTrack(track);
1273        // test to see if destination is reachable by this train
1274        if (router.setDestination(car, _train, _buildReport)) {
1275            log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName());
1276        }
1277        if (car.getDestination() == null) {
1278            addLine(_buildReport, SEVEN,
1279                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1280            car.setFinalDestination(null);
1281            car.setFinalDestinationTrack(null);
1282            return false;
1283        }
1284        car.updateKernel();
1285        return true; // done, car has a new final destination
1286    }
1287
1288    /**
1289     * Checks for a car's final destination, and then after checking, tries to
1290     * route the car to that destination. Normal return from this routine is
1291     * false, with the car returning with a set destination. Returns true if car
1292     * has a final destination, but can't be used for this train.
1293     *
1294     * @param car
1295     * @return false if car needs destination processing (normal).
1296     */
1297    private boolean checkCarForFinalDestination(Car car) {
1298        if (car.getFinalDestination() == null || car.getDestination() != null) {
1299            return false;
1300        }
1301
1302        addLine(_buildReport, FIVE,
1303                Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(),
1304                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(),
1305                        car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1306
1307        // no local moves for this train?
1308        if (!_train.isLocalSwitcher() && !_train.isAllowLocalMovesEnabled() &&
1309                car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) &&
1310                car.getTrack() != _departStageTrack) {
1311            addLine(_buildReport, FIVE,
1312                    Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(),
1313                            car.getFinalDestinationName(), _train.getName()));
1314            addLine(_buildReport, FIVE, BLANK_LINE);
1315            log.debug("Removing car ({}) from list", car.toString());
1316            _carList.remove(car);
1317            _carIndex--;
1318            return true; // car has a final destination, but no local moves by
1319                         // this train
1320        }
1321        // is the car's destination the terminal and is that allowed?
1322        if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) {
1323            // don't remove car from list if departing staging
1324            if (car.getTrack() == _departStageTrack) {
1325                addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString()));
1326            } else {
1327                log.debug("Removing car ({}) from list", car.toString());
1328                _carList.remove(car);
1329                _carIndex--;
1330            }
1331            return true; // car has a final destination, but through traffic not
1332                         // allowed by this train
1333        }
1334        // does the car have a final destination track that is willing to
1335        // service the car?
1336        // note the default mode for all track types is MATCH
1337        if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) {
1338            String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack());
1339            // keep going if the only issue was track length and the track
1340            // accepts the car's load
1341            if (!status.equals(Track.OKAY) &&
1342                    !status.startsWith(Track.LENGTH) &&
1343                    !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) {
1344                addLine(_buildReport, SEVEN,
1345                        Bundle.getMessage("buildNoDestTrackNewLoad",
1346                                StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()),
1347                                car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(),
1348                                car.toString(), car.getLoadName(), status));
1349                // is this car or kernel being sent to a track that is too
1350                // short?
1351                if (status.startsWith(Track.CAPACITY)) {
1352                    // track is too short for this car or kernel
1353                    addLine(_buildReport, SEVEN,
1354                            Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(),
1355                                    car.getFinalDestinationTrack().getName(), car.toString()));
1356                }
1357                addLine(_buildReport, SEVEN,
1358                        Bundle.getMessage("buildRemovingFinalDestinaton", car.getFinalDestination().getName(),
1359                                car.getFinalDestinationTrack().getName(), car.toString()));
1360                car.setFinalDestination(null);
1361                car.setFinalDestinationTrack(null);
1362                return false; // car no longer has a final destination
1363            }
1364        }
1365
1366        // now try and route the car
1367        if (!router.setDestination(car, _train, _buildReport)) {
1368            addLine(_buildReport, SEVEN,
1369                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1370            // don't move car if routing issue was track space but not departing
1371            // staging
1372            if ((!router.getStatus().startsWith(Track.LENGTH) &&
1373                    !_train.isServiceAllCarsWithFinalDestinationsEnabled()) || (car.getTrack() == _departStageTrack)) {
1374                // add car to unable to route list
1375                if (!_notRoutable.contains(car)) {
1376                    _notRoutable.add(car);
1377                }
1378                addLine(_buildReport, FIVE, BLANK_LINE);
1379                addLine(_buildReport, FIVE,
1380                        Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(),
1381                                car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1382                addLine(_buildReport, FIVE, BLANK_LINE);
1383                return false; // move this car, routing failed!
1384            }
1385        } else {
1386            if (car.getDestination() != null) {
1387                return false; // routing successful process this car, normal
1388                              // exit from this routine
1389            }
1390            if (car.getTrack() == _departStageTrack) {
1391                log.debug("Car ({}) departing staging with final destination ({}) and no destination",
1392                        car.toString(), car.getFinalDestinationName());
1393                return false; // try and move this car out of staging
1394            }
1395        }
1396        addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1397        addLine(_buildReport, FIVE, BLANK_LINE);
1398        return true;
1399    }
1400
1401    /**
1402     * Checks to see if car has a destination and tries to add car to train.
1403     * Will find a track for the car if needed. Returns false if car doesn't
1404     * have a destination.
1405     *
1406     * @param rl         the car's route location
1407     * @param routeIndex where in the route to start search
1408     * @return true if car has a destination. Need to check if car given a train
1409     *         assignment.
1410     * @throws BuildFailedException if destination was staging and can't place
1411     *                              car there
1412     */
1413    private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException {
1414        if (car.getDestination() == null) {
1415            return false; // the only false return
1416        }
1417        addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(),
1418                car.getDestinationName(), car.getDestinationTrackName()));
1419        RouteLocation rld = _train.getRoute().getLastLocationByName(car.getDestinationName());
1420        if (rld == null) {
1421            // code check, router doesn't set a car's destination if not carried
1422            // by train being built. Car has a destination that isn't serviced
1423            // by this train. Find buildExcludeCarDestNotPartRoute in
1424            // loadRemoveAndListCars()
1425            throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(),
1426                    car.getDestinationName(), car.getDestinationTrackName(), _train.getRoute().getName()));
1427        }
1428        // now go through the route and try and find a location with
1429        // the correct destination name
1430        for (int k = routeIndex; k < _routeList.size(); k++) {
1431            rld = _routeList.get(k);
1432            // if car can be picked up later at same location, skip
1433            if (checkForLaterPickUp(car, rl, rld)) {
1434                addLine(_buildReport, SEVEN, BLANK_LINE);
1435                return true;
1436            }
1437            if (!rld.getName().equals(car.getDestinationName())) {
1438                continue;
1439            }
1440            // is the car's destination the terminal and is that allowed?
1441            if (!checkThroughCarsAllowed(car, car.getDestinationName())) {
1442                return true;
1443            }
1444            log.debug("Car ({}) found a destination in train's route", car.toString());
1445            // are drops allows at this location?
1446            if (!rld.isDropAllowed()) {
1447                addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(),
1448                        rld.getId(), rld.getName()));
1449                continue;
1450            }
1451            if (_train.isLocationSkipped(rld.getId())) {
1452                addLine(_buildReport, FIVE,
1453                        Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName()));
1454                continue;
1455            }
1456            // any moves left at this location?
1457            if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
1458                addLine(_buildReport, FIVE,
1459                        Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(),
1460                                _train.getRoute().getName(), rld.getId(), rld.getName()));
1461                continue;
1462            }
1463            // is the train length okay?
1464            if (!checkTrainLength(car, rl, rld)) {
1465                continue;
1466            }
1467            // check for valid destination track
1468            if (car.getDestinationTrack() == null) {
1469                addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString()));
1470                // is car going into staging?
1471                if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) {
1472                    String status = car.checkDestination(car.getDestination(), _terminateStageTrack);
1473                    if (status.equals(Track.OKAY)) {
1474                        addLine(_buildReport, FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(),
1475                                _terminateStageTrack.getName()));
1476                        addCarToTrain(car, rl, rld, _terminateStageTrack);
1477                        return true;
1478                    } else {
1479                        addLine(_buildReport, SEVEN,
1480                                Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
1481                                        _terminateStageTrack.getTrackTypeName(),
1482                                        _terminateStageTrack.getLocation().getName(), _terminateStageTrack.getName(),
1483                                        status));
1484                        continue;
1485                    }
1486                } else {
1487                    // no staging at this location, now find a destination track
1488                    // for this car
1489                    List<Track> tracks = getTracksAtDestination(car, rld);
1490                    if (tracks.size() > 0) {
1491                        if (tracks.get(1) != null) {
1492                            car.setFinalDestination(car.getDestination());
1493                            car.setFinalDestinationTrack(tracks.get(1));
1494                            tracks.get(1).bumpMoves();
1495                        }
1496                        addCarToTrain(car, rl, rld, tracks.get(0));
1497                        return true;
1498                    }
1499                }
1500            } else {
1501                log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName());
1502                // going into the correct staging track?
1503                if (rld.equals(_train.getTrainTerminatesRouteLocation()) &&
1504                        _terminateStageTrack != null &&
1505                        _terminateStageTrack != car.getDestinationTrack()) {
1506                    // car going to wrong track in staging, change track
1507                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(),
1508                            car.getDestinationName(), car.getDestinationTrackName()));
1509                    car.setDestination(_terminateStageTrack.getLocation(), _terminateStageTrack);
1510                }
1511                if (!rld.equals(_train.getTrainTerminatesRouteLocation()) ||
1512                        _terminateStageTrack == null ||
1513                        _terminateStageTrack == car.getDestinationTrack()) {
1514                    // is train direction correct? and drop to interchange or
1515                    // spur?
1516                    if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) &&
1517                            checkTrainCanDrop(car, car.getDestinationTrack())) {
1518                        String status = car.checkDestination(car.getDestination(), car.getDestinationTrack());
1519                        if (status.equals(Track.OKAY)) {
1520                            addCarToTrain(car, rl, rld, car.getDestinationTrack());
1521                            return true;
1522                        } else {
1523                            addLine(_buildReport, SEVEN,
1524                                    Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
1525                                            car.getDestinationTrack().getTrackTypeName(),
1526                                            car.getDestinationTrack().getLocation().getName(),
1527                                            car.getDestinationTrackName(), status));
1528                        }
1529                    }
1530                } else {
1531                    // code check
1532                    throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(),
1533                            car.getDestinationName(), car.getDestinationTrackName()));
1534                }
1535            }
1536            addLine(_buildReport, FIVE,
1537                    Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId()));
1538            if (car.getDestinationTrack() == null) {
1539                log.debug("Could not find a destination track for location ({})", car.getDestinationName());
1540            }
1541        }
1542        log.debug("car ({}) not added to train", car.toString());
1543        addLine(_buildReport, FIVE,
1544                Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId()));
1545        // remove destination and revert to final destination
1546        if (car.getDestinationTrack() != null) {
1547            // going to remove this destination from car
1548            car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1);
1549            Track destTrack = car.getDestinationTrack();
1550            // TODO should we leave the car's destination? The spur expects this
1551            // car!
1552            if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) {
1553                addLine(_buildReport, SEVEN, Bundle.getMessage("buildPickupCancelled",
1554                        destTrack.getLocation().getName(), destTrack.getName()));
1555            }
1556        }
1557        car.setFinalDestination(car.getPreviousFinalDestination());
1558        car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
1559        car.setDestination(null, null);
1560        car.updateKernel();
1561
1562        addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1563        addLine(_buildReport, FIVE, BLANK_LINE);
1564        return true; // car no longer has a destination, but it had one.
1565    }
1566
1567    /**
1568     * Find a destination and track for a car at a route location.
1569     *
1570     * @param car the car!
1571     * @param rl  The car's route location
1572     * @param rld The car's route destination
1573     * @return true if successful.
1574     * @throws BuildFailedException if code check fails
1575     */
1576    private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException {
1577        int index = _routeList.indexOf(rld);
1578        if (_train.isLocalSwitcher()) {
1579            return findDestinationAndTrack(car, rl, index, index + 1);
1580        }
1581        return findDestinationAndTrack(car, rl, index - 1, index + 1);
1582    }
1583
1584    /**
1585     * Find a destination and track for a car, and add the car to the train.
1586     *
1587     * @param car        The car that is looking for a destination and
1588     *                   destination track.
1589     * @param rl         The route location for this car.
1590     * @param routeIndex Where in the train's route to begin a search for a
1591     *                   destination for this car.
1592     * @param routeEnd   Where to stop looking for a destination.
1593     * @return true if successful, car has destination, track and a train.
1594     * @throws BuildFailedException if code check fails
1595     */
1596    private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd)
1597            throws BuildFailedException {
1598        if (routeIndex + 1 == routeEnd) {
1599            log.debug("Car ({}) is at the last location in the train's route", car.toString());
1600        }
1601        addLine(_buildReport, FIVE, Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(),
1602                car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(),
1603                car.getTrackName()));
1604        if (car.getKernel() != null) {
1605            addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1606                    car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
1607        }
1608
1609        // normally start looking after car's route location
1610        int start = routeIndex;
1611        // the route location destination being checked for the car
1612        RouteLocation rld = null;
1613        // holds the best route location destination for the car
1614        RouteLocation rldSave = null;
1615        // holds the best track at destination for the car
1616        Track trackSave = null;
1617        // used when a spur has an alternate track and no schedule
1618        Track finalDestinationTrackSave = null;
1619        // true when car can be picked up from two or more locations in the
1620        // route
1621        boolean multiplePickup = false;
1622
1623        // more than one location in this route?
1624        if (!_train.isLocalSwitcher()) {
1625            start++; // begin looking for tracks at the next location
1626        }
1627        // all pick ups to terminal?
1628        if (_train.isSendCarsToTerminalEnabled() &&
1629                !rl.getSplitName().equals(_departLocation.getSplitName()) &&
1630                routeEnd == _routeList.size()) {
1631            addLine(_buildReport, FIVE, Bundle.getMessage("buildSendToTerminal", _terminateLocation.getName()));
1632            // user could have specified several terminal locations with the
1633            // "same" name
1634            start = routeEnd - 1;
1635            while (start > routeIndex) {
1636                if (!_routeList.get(start - 1).getSplitName()
1637                        .equals(_terminateLocation.getSplitName())) {
1638                    break;
1639                }
1640                start--;
1641            }
1642        }
1643        // now search for a destination for this car
1644        for (int k = start; k < routeEnd; k++) {
1645            rld = _routeList.get(k);
1646            // if car can be picked up later at same location, set flag
1647            if (checkForLaterPickUp(car, rl, rld)) {
1648                multiplePickup = true;
1649            }
1650            if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) {
1651                addLine(_buildReport, FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId()));
1652            } else {
1653                addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(),
1654                        rld.getId(), rld.getName()));
1655                continue;
1656            }
1657            if (_train.isLocationSkipped(rld.getId())) {
1658                addLine(_buildReport, FIVE,
1659                        Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName()));
1660                continue;
1661            }
1662            // any moves left at this location?
1663            if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
1664                addLine(_buildReport, FIVE,
1665                        Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(),
1666                                _train.getRoute().getName(), rld.getId(), rld.getName()));
1667                continue;
1668            }
1669            // get the destination
1670            Location testDestination = rld.getLocation();
1671            // code check, all locations in the route have been already checked
1672            if (testDestination == null) {
1673                throw new BuildFailedException(
1674                        Bundle.getMessage("buildErrorRouteLoc", _train.getRoute().getName(), rld.getName()));
1675            }
1676            // don't move car to same location unless the train is a switcher
1677            // (local moves) or is passenger, caboose or car with FRED
1678            if (rl.getSplitName().equals(rld.getSplitName()) &&
1679                    !_train.isLocalSwitcher() &&
1680                    !car.isPassenger() &&
1681                    !car.isCaboose() &&
1682                    !car.hasFred()) {
1683                // allow cars to return to the same staging location if no other
1684                // options (tracks) are available
1685                if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) &&
1686                        testDestination.isStaging() &&
1687                        trackSave == null) {
1688                    addLine(_buildReport, SEVEN,
1689                            Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName()));
1690                } else {
1691                    addLine(_buildReport, SEVEN,
1692                            Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName()));
1693                    continue;
1694                }
1695            }
1696
1697            // check to see if departure track has any restrictions
1698            if (!car.getTrack().isDestinationAccepted(testDestination)) {
1699                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(),
1700                        car.getTrackName()));
1701                continue;
1702            }
1703
1704            if (!testDestination.acceptsTypeName(car.getTypeName())) {
1705                addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(),
1706                        car.getTypeName(), testDestination.getName()));
1707                continue;
1708            }
1709            // can this location service this train's direction
1710            if (!checkDropTrainDirection(rld)) {
1711                continue;
1712            }
1713            // is the train length okay?
1714            if (!checkTrainLength(car, rl, rld)) {
1715                break; // no, done with this car
1716            }
1717            // is the car's destination the terminal and is that allowed?
1718            if (!checkThroughCarsAllowed(car, rld.getName())) {
1719                continue; // not allowed
1720            }
1721
1722            Track trackTemp = null;
1723            // used when alternate track selected
1724            Track finalDestinationTrackTemp = null;
1725
1726            // is there a track assigned for staging cars?
1727            if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) {
1728                trackTemp = tryStaging(car, rldSave);
1729                if (trackTemp == null) {
1730                    continue; // no
1731                }
1732            } else {
1733                // no staging, start track search
1734                List<Track> tracks = getTracksAtDestination(car, rld);
1735                if (tracks.size() > 0) {
1736                    trackTemp = tracks.get(0);
1737                    finalDestinationTrackTemp = tracks.get(1);
1738                }
1739            }
1740            // did we find a new destination?
1741            if (trackTemp == null) {
1742                addLine(_buildReport, FIVE,
1743                        Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName()));
1744            } else {
1745                addLine(_buildReport, FIVE,
1746                        Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(),
1747                                trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(),
1748                                rld.getMaxCarMoves()));
1749                if (multiplePickup) {
1750                    if (rldSave != null) {
1751                        addLine(_buildReport, FIVE,
1752                                Bundle.getMessage("buildTrackServicedLater", car.getLocationName(),
1753                                        trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(),
1754                                        trackTemp.getName(), car.getLocationName()));
1755                    } else {
1756                        addLine(_buildReport, FIVE,
1757                                Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName()));
1758                        trackSave = null;
1759                    }
1760                    break; // done
1761                }
1762                // if there's more than one available destination use the lowest
1763                // ratio
1764                if (rldSave != null) {
1765                    // check for an earlier drop in the route
1766                    rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd);
1767                    double saveCarMoves = rldSave.getCarMoves();
1768                    double saveRatio = saveCarMoves / rldSave.getMaxCarMoves();
1769                    double nextCarMoves = rld.getCarMoves();
1770                    double nextRatio = nextCarMoves / rld.getMaxCarMoves();
1771
1772                    // bias cars to the terminal
1773                    if (rld == _train.getTrainTerminatesRouteLocation()) {
1774                        nextRatio = nextRatio * nextRatio;
1775                        log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(),
1776                                Double.toString(nextRatio));
1777
1778                        // bias cars with default loads to a track with a
1779                        // schedule
1780                    } else if (!trackTemp.getScheduleId().equals(Track.NONE)) {
1781                        nextRatio = nextRatio * nextRatio;
1782                        log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(),
1783                                trackTemp.getScheduleName(), Double.toString(nextRatio));
1784                    }
1785                    // bias cars with default loads to saved track with a
1786                    // schedule
1787                    if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) {
1788                        saveRatio = saveRatio * saveRatio;
1789                        log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(),
1790                                trackSave.getScheduleName(), Double.toString(saveRatio));
1791                    }
1792                    log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(),
1793                            Double.toString(nextRatio));
1794                    if (saveRatio < nextRatio) {
1795                        // the saved is better than the last found
1796                        rld = rldSave;
1797                        trackTemp = trackSave;
1798                        finalDestinationTrackTemp = finalDestinationTrackSave;
1799                    }
1800                }
1801                // every time through, save the best route destination, and
1802                // track
1803                rldSave = rld;
1804                trackSave = trackTemp;
1805                finalDestinationTrackSave = finalDestinationTrackTemp;
1806            }
1807        }
1808        // did we find a destination?
1809        if (trackSave != null) {
1810            if (trackSave.isSpur()) {
1811                car.setScheduleItemId(trackSave.getScheduleItemId());
1812                trackSave.bumpSchedule();
1813                log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(),
1814                        trackSave.getName(), car.getScheduleItemId());
1815            }
1816            if (finalDestinationTrackSave != null) {
1817                car.setFinalDestination(finalDestinationTrackSave.getLocation());
1818                car.setFinalDestinationTrack(finalDestinationTrackSave);
1819                if (trackSave.isAlternate()) {
1820                    finalDestinationTrackSave.bumpMoves(); // bump move count
1821                }
1822            }
1823            addCarToTrain(car, rl, rldSave, trackSave);
1824            return true;
1825        }
1826        addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1827        addLine(_buildReport, FIVE, BLANK_LINE);
1828        return false; // no build errors, but car not given destination
1829    }
1830
1831    private final static Logger log = LoggerFactory.getLogger(TrainBuilderCars.class);
1832}