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