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