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, 2025
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 pulled from an interchange or spur?
510            if (!checkPickupInterchangeOrSpur(car)) {
511                _carList.remove(car);
512                _carIndex--; // removed car from list, so backup pointer
513                addLine(_buildReport, FIVE, BLANK_LINE);
514                continue; // no
515            }
516            // can this car be picked up?
517            if (!checkPickUpTrainDirection(car, rl)) {
518                addLine(_buildReport, FIVE, BLANK_LINE);
519                continue; // no
520            }
521            // do alternate track moves on the second pass (makes FIFO / LIFO work correctly)
522            if (Setup.isBuildAggressive() && !isSecondPass && car.getTrack().isAlternate() && _completedMoves != 0) {
523                continue;
524            }
525
526            showCarServiceOrder(car); // car on FIFO or LIFO track?
527
528            // is car departing staging and generate custom load?
529            if (!generateCarLoadFromStaging(car)) {
530                if (!generateCarLoadStagingToStaging(car) &&
531                        car.getTrack() == _departStageTrack &&
532                        !_departStageTrack.isLoadNameAndCarTypeShipped(car.getLoadName(), car.getTypeName())) {
533                    // report build failure car departing staging with a
534                    // restricted load
535                    addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageLoad", car.toString(),
536                            car.getLoadName(), _departStageTrack.getName()));
537                    addLine(_buildReport, FIVE, BLANK_LINE);
538                    continue; // keep going and see if there are other cars with
539                              // issues outs of staging
540                }
541            }
542            // check for quick service track timing
543            if (!checkQuickServiceDeparting(car, rl)) {
544                continue;
545            }
546            // If car been given a home division follow division rules for car
547            // movement.
548            if (!findDestinationsForCarsWithHomeDivision(car)) {
549                addLine(_buildReport, FIVE,
550                        Bundle.getMessage("buildNoDestForCar", car.toString()));
551                addLine(_buildReport, FIVE, BLANK_LINE);
552                continue; // hold car at current location
553            }
554            // does car have a custom load without a destination?
555            // if departing staging, a destination for this car is needed, so
556            // keep going
557            if (findFinalDestinationForCarLoad(car) &&
558                    car.getDestination() == null &&
559                    car.getTrack() != _departStageTrack) {
560                // done with this car, it has a custom load, and there are
561                // spurs/schedules, but no destination found
562                addLine(_buildReport, FIVE,
563                        Bundle.getMessage("buildNoDestForCar", car.toString()));
564                addLine(_buildReport, FIVE, BLANK_LINE);
565                continue;
566            }
567            // Check car for final destination, then an assigned destination, if
568            // neither, find a destination for the car
569            if (checkCarForFinalDestination(car)) {
570                log.debug("Car ({}) has a final desination that can't be serviced by train", car.toString());
571            } else if (checkCarForDestination(car, rl, _routeList.indexOf(rl))) {
572                // car had a destination, could have been added to the train.
573                log.debug("Car ({}) has desination ({}) using train ({})", car.toString(), car.getDestinationName(),
574                        car.getTrainName());
575            } else {
576                findDestinationAndTrack(car, rl, _routeList.indexOf(rl), _routeList.size());
577            }
578            if (_reqNumOfMoves <= 0) {
579                break; // done
580            }
581            // build failure if car departing staging without a destination and
582            // a train we'll just put out a warning message here so we can find
583            // out how many cars have issues
584            if (car.getTrack() == _departStageTrack &&
585                    (car.getDestination() == null || car.getDestinationTrack() == null || car.getTrain() == null)) {
586                addLine(_buildReport, ONE, Bundle.getMessage("buildWarningCarStageDest", car.toString()));
587                // does the car have a final destination to staging? If so we
588                // need to reset this car
589                if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack() == _terminateStageTrack) {
590                    addLine(_buildReport, THREE,
591                            Bundle.getMessage("buildStagingCarHasFinal", car.toString(), car.getFinalDestinationName(),
592                                    car.getFinalDestinationTrackName()));
593                    car.reset();
594                }
595                addLine(_buildReport, SEVEN, BLANK_LINE);
596            }
597        }
598        if (!foundCar && !isSecondPass) {
599            addLine(_buildReport, FIVE, Bundle.getMessage("buildNoCarsAtLocation", rl.getName()));
600            addLine(_buildReport, FIVE, BLANK_LINE);
601        }
602    }
603
604    private boolean generateCarLoadFromStaging(Car car) throws BuildFailedException {
605        return generateCarLoadFromStaging(car, null);
606    }
607
608    /**
609     * Used to generate a car's load from staging. Search for a spur with a
610     * schedule and load car if possible.
611     *
612     * @param car the car
613     * @param rld The route location destination for this car. Can be null.
614     * @return true if car given a custom load
615     * @throws BuildFailedException If code check fails
616     */
617    private boolean generateCarLoadFromStaging(Car car, RouteLocation rld) throws BuildFailedException {
618        // Code Check, car should have a track assignment
619        if (car.getTrack() == null) {
620            throw new BuildFailedException(
621                    Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName()));
622        }
623        if (!car.getTrack().isStaging() ||
624                (!car.getTrack().isAddCustomLoadsAnySpurEnabled() && !car.getTrack().isAddCustomLoadsEnabled()) ||
625                !car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
626                car.getDestination() != null ||
627                car.getFinalDestination() != null) {
628            log.debug(
629                    "No load generation for car ({}) isAddLoadsAnySpurEnabled: {}, car load ({}) destination ({}) final destination ({})",
630                    car.toString(), car.getTrack().isAddCustomLoadsAnySpurEnabled() ? "true" : "false",
631                    car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName());
632            // if car has a destination or final destination add "no load
633            // generated" message to report
634            if (car.getTrack().isStaging() &&
635                    car.getTrack().isAddCustomLoadsAnySpurEnabled() &&
636                    car.getLoadName().equals(carLoads.getDefaultEmptyName())) {
637                addLine(_buildReport, FIVE,
638                        Bundle.getMessage("buildCarNoLoadGenerated", car.toString(), car.getLoadName(),
639                                car.getDestinationName(), car.getFinalDestinationName()));
640            }
641            return false; // no load generated for this car
642        }
643        addLine(_buildReport, FIVE,
644                Bundle.getMessage("buildSearchTrackNewLoad", car.toString(), car.getTypeName(),
645                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getLocationName(), car.getTrackName(),
646                        rld != null ? rld.getLocation().getName() : ""));
647        // check to see if car type has custom loads
648        if (carLoads.getNames(car.getTypeName()).size() == 2) {
649            addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarNoCustomLoad", car.toString(), car.getTypeName()));
650            return false;
651        }
652        if (car.getKernel() != null) {
653            addLine(_buildReport, SEVEN,
654                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
655                            car.getKernel().getSize(), car.getKernel().getTotalLength(),
656                            Setup.getLengthUnit().toLowerCase()));
657        }
658        // save the car's load, should be the default empty
659        String oldCarLoad = car.getLoadName();
660        List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR);
661        log.debug("Found {} spurs", tracks.size());
662        // show locations not serviced by departure track once
663        List<Location> locationsNotServiced = new ArrayList<>();
664        for (Track track : tracks) {
665            if (locationsNotServiced.contains(track.getLocation())) {
666                continue;
667            }
668            if (rld != null && track.getLocation() != rld.getLocation()) {
669                locationsNotServiced.add(track.getLocation());
670                continue;
671            }
672            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
673                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
674                        track.getLocation().getName(), car.getTrackName()));
675                locationsNotServiced.add(track.getLocation());
676                continue;
677            }
678            // only use tracks serviced by this train?
679            if (car.getTrack().isAddCustomLoadsEnabled() &&
680                    !_train.getRoute().isLocationNameInRoute(track.getLocation().getName())) {
681                continue;
682            }
683            // only the first match in a schedule is used for a spur
684            ScheduleItem si = getScheduleItem(car, track);
685            if (si == null) {
686                continue; // no match
687            }
688            // need to set car load so testDestination will work properly
689            car.setLoadName(si.getReceiveLoadName());
690            car.setScheduleItemId(si.getId());
691            String status = car.checkDestination(track.getLocation(), track);
692            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
693                addLine(_buildReport, SEVEN,
694                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
695                                track.getLocation().getName(), track.getName(), car.toString(), si.getReceiveLoadName(),
696                                status));
697                continue;
698            }
699            addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrySpurLoad", track.getLocation().getName(),
700                    track.getName(), car.getLoadName()));
701            // does the car have a home division?
702            if (car.getDivision() != null) {
703                addLine(_buildReport, SEVEN,
704                        Bundle.getMessage("buildCarHasDivisionStaging", car.toString(), car.getTypeName(),
705                                car.getLoadType().toLowerCase(), car.getLoadName(), car.getDivisionName(),
706                                car.getLocationName(), car.getTrackName(), car.getTrack().getDivisionName()));
707                // load type empty must return to car's home division
708                // or load type load from foreign division must return to car's
709                // home division
710                if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY) && car.getDivision() != track.getDivision() ||
711                        car.getLoadType().equals(CarLoad.LOAD_TYPE_LOAD) &&
712                                car.getTrack().getDivision() != car.getDivision() &&
713                                car.getDivision() != track.getDivision()) {
714                    addLine(_buildReport, SEVEN,
715                            Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(),
716                                    track.getLocation().getName(), track.getName(), track.getDivisionName(),
717                                    car.toString(), car.getLoadType().toLowerCase(), car.getLoadName()));
718                    continue;
719                }
720            }
721            if (!track.isSpaceAvailable(car)) {
722                addLine(_buildReport, SEVEN,
723                        Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(),
724                                track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(),
725                                Setup.getLengthUnit().toLowerCase(), track.getReservationFactor()));
726                continue;
727            }
728            // try routing car
729            car.setFinalDestination(track.getLocation());
730            car.setFinalDestinationTrack(track);
731            if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) {
732                // return car with this custom load and destination
733                addLine(_buildReport, FIVE,
734                        Bundle.getMessage("buildCreateNewLoadForCar", car.toString(), si.getReceiveLoadName(),
735                                track.getLocation().getName(), track.getName()));
736                car.setLoadGeneratedFromStaging(true);
737                // is car part of kernel?
738                car.updateKernel();
739                track.bumpMoves();
740                track.bumpSchedule();
741                return true; // done, car now has a custom load
742            }
743            addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotRouteCar", car.toString(),
744                    si.getReceiveLoadName(), track.getLocation().getName(), track.getName()));
745            addLine(_buildReport, SEVEN, BLANK_LINE);
746            car.setDestination(null, null);
747            car.setFinalDestination(null);
748            car.setFinalDestinationTrack(null);
749        }
750        // restore car's load
751        car.setLoadName(oldCarLoad);
752        car.setScheduleItemId(Car.NONE);
753        addLine(_buildReport, FIVE, Bundle.getMessage("buildUnableNewLoad", car.toString()));
754        return false; // done, no load generated for this car
755    }
756
757    /**
758     * Tries to place a custom load in the car that is departing staging and
759     * attempts to find a destination for the car that is also staging.
760     *
761     * @param car the car
762     * @return True if custom load added to car
763     * @throws BuildFailedException If code check fails
764     */
765    private boolean generateCarLoadStagingToStaging(Car car) throws BuildFailedException {
766        // Code Check, car should have a track assignment
767        if (car.getTrack() == null) {
768            throw new BuildFailedException(
769                    Bundle.getMessage("buildWarningRsNoTrack", car.toString(), car.getLocationName()));
770        }
771        if (!car.getTrack().isStaging() ||
772                !car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ||
773                !car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
774                car.getDestination() != null ||
775                car.getFinalDestination() != null) {
776            log.debug(
777                    "No load generation for car ({}) isAddCustomLoadsAnyStagingTrackEnabled: {}, car load ({}) destination ({}) final destination ({})",
778                    car.toString(), car.getTrack().isAddCustomLoadsAnyStagingTrackEnabled() ? "true" : "false",
779                    car.getLoadName(), car.getDestinationName(), car.getFinalDestinationName());
780            return false;
781        }
782        // check to see if car type has custom loads
783        if (carLoads.getNames(car.getTypeName()).size() == 2) {
784            return false;
785        }
786        List<Track> tracks = locationManager.getTracks(Track.STAGING);
787        addLine(_buildReport, FIVE, Bundle.getMessage("buildTryStagingToStaging", car.toString(), tracks.size()));
788        if (Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED)) {
789            for (Track track : tracks) {
790                addLine(_buildReport, SEVEN,
791                        Bundle.getMessage("buildStagingLocationTrack", track.getLocation().getName(), track.getName()));
792            }
793        }
794        // list of locations that can't be reached by the router
795        List<Location> locationsNotServiced = new ArrayList<>();
796        if (_terminateStageTrack != null) {
797            addLine(_buildReport, SEVEN,
798                    Bundle.getMessage("buildIgnoreStagingFirstPass", _terminateStageTrack.getLocation().getName()));
799            locationsNotServiced.add(_terminateStageTrack.getLocation());
800        }
801        while (tracks.size() > 0) {
802            // pick a track randomly
803            int rnd = (int) (Math.random() * tracks.size());
804            Track track = tracks.get(rnd);
805            tracks.remove(track);
806            log.debug("Try staging track ({}, {})", track.getLocation().getName(), track.getName());
807            // find a staging track that isn't at the departure
808            if (track.getLocation() == _departLocation) {
809                log.debug("Can't use departure location ({})", track.getLocation().getName());
810                continue;
811            }
812            if (!_train.isAllowThroughCarsEnabled() && track.getLocation() == _terminateLocation) {
813                log.debug("Through cars to location ({}) not allowed", track.getLocation().getName());
814                continue;
815            }
816            if (locationsNotServiced.contains(track.getLocation())) {
817                log.debug("Location ({}) not reachable", track.getLocation().getName());
818                continue;
819            }
820            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
821                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
822                        track.getLocation().getName(), car.getTrackName()));
823                locationsNotServiced.add(track.getLocation());
824                continue;
825            }
826            // the following method sets the Car load generated from staging
827            // boolean
828            if (generateLoadCarDepartingAndTerminatingIntoStaging(car, track)) {
829                // test to see if destination is reachable by this train
830                if (router.setDestination(car, _train, _buildReport) && car.getDestination() != null) {
831                    return true; // done, car has a custom load and a final
832                                 // destination
833                }
834                addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingTrackNotReachable",
835                        track.getLocation().getName(), track.getName(), car.getLoadName()));
836                // return car to original state
837                car.setLoadName(carLoads.getDefaultEmptyName());
838                car.setLoadGeneratedFromStaging(false);
839                car.setFinalDestination(null);
840                car.updateKernel();
841                // couldn't route to this staging location
842                locationsNotServiced.add(track.getLocation());
843            }
844        }
845        // No staging tracks reachable, try the track the train is terminating
846        // to
847        if (_train.isAllowThroughCarsEnabled() &&
848                _terminateStageTrack != null &&
849                car.getTrack().isDestinationAccepted(_terminateStageTrack.getLocation()) &&
850                generateLoadCarDepartingAndTerminatingIntoStaging(car, _terminateStageTrack)) {
851            return true;
852        }
853
854        addLine(_buildReport, SEVEN,
855                Bundle.getMessage("buildNoStagingForCarCustom", car.toString()));
856        addLine(_buildReport, SEVEN, BLANK_LINE);
857        return false;
858    }
859
860    /**
861     * Check to see if car has been assigned a home division. If car has a home
862     * division the following rules are applied when assigning the car a
863     * destination:
864     * <p>
865     * If car load is type empty not at car's home division yard: Car is sent to
866     * a home division yard. If home division yard not available, then car is
867     * sent to home division staging, then spur (industry).
868     * <p>
869     * If car load is type empty at a yard at the car's home division: Car is
870     * sent to a home division spur, then home division staging.
871     * <p>
872     * If car load is type load not at car's home division: Car is sent to home
873     * division spur, and if spur not available then home division staging.
874     * <p>
875     * If car load is type load at car's home division: Car is sent to any
876     * division spur or staging.
877     * 
878     * @param car the car being checked for a home division
879     * @return false if destination track not found for this car
880     * @throws BuildFailedException
881     */
882    private boolean findDestinationsForCarsWithHomeDivision(Car car) throws BuildFailedException {
883        if (car.getDivision() == null || car.getDestination() != null || car.getFinalDestination() != null) {
884            return true;
885        }
886        if (car.getDivision() == car.getTrack().getDivision()) {
887            addLine(_buildReport, FIVE,
888                    Bundle.getMessage("buildCarDepartHomeDivision", car.toString(), car.getTypeName(),
889                            car.getLoadType().toLowerCase(),
890                            car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(),
891                            car.getLocationName(), car.getTrackName(),
892                            car.getTrack().getDivisionName()));
893        } else {
894            addLine(_buildReport, FIVE,
895                    Bundle.getMessage("buildCarDepartForeignDivision", car.toString(), car.getTypeName(),
896                            car.getLoadType().toLowerCase(),
897                            car.getLoadName(), car.getDivisionName(), car.getTrack().getTrackTypeName(),
898                            car.getLocationName(), car.getTrackName(),
899                            car.getTrack().getDivisionName()));
900        }
901        if (car.getKernel() != null) {
902            addLine(_buildReport, SEVEN,
903                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
904                            car.getKernel().getSize(),
905                            car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
906        }
907        // does train terminate into staging?
908        if (_terminateStageTrack != null) {
909            log.debug("Train terminates into staging track ({})", _terminateStageTrack.getName());
910            // bias cars to staging
911            if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
912                log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName());
913                if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) {
914                    log.debug("Car ({}) at it's home division yard", car.toString());
915                    if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
916                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
917                    }
918                }
919                // try to send to home division staging, then home division yard,
920                // then home division spur
921                else if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
922                    if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) {
923                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
924                    }
925                }
926            } else {
927                log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName());
928                // 1st send car to staging dependent of shipping track division, then
929                // try spur
930                if (!sendCarToHomeDivisionTrack(car, Track.STAGING,
931                        car.getTrack().getDivision() != car.getDivision())) {
932                    return sendCarToHomeDivisionTrack(car, Track.SPUR,
933                            car.getTrack().getDivision() != car.getDivision());
934                }
935            }
936        } else {
937            // train doesn't terminate into staging
938            if (car.getLoadType().equals(CarLoad.LOAD_TYPE_EMPTY)) {
939                log.debug("Car ({}) has home division ({}) and load type empty", car.toString(), car.getDivisionName());
940                if (car.getTrack().isYard() && car.getTrack().getDivision() == car.getDivision()) {
941                    log.debug("Car ({}) at it's home division yard", car.toString());
942                    if (!sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION)) {
943                        return sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION);
944                    }
945                }
946                // try to send to home division yard, then home division staging,
947                // then home division spur
948                else if (!sendCarToHomeDivisionTrack(car, Track.YARD, HOME_DIVISION)) {
949                    if (!sendCarToHomeDivisionTrack(car, Track.STAGING, HOME_DIVISION)) {
950                        return sendCarToHomeDivisionTrack(car, Track.SPUR, HOME_DIVISION);
951                    }
952                }
953            } else {
954                log.debug("Car ({}) has home division ({}) and load type load", car.toString(), car.getDivisionName());
955                // 1st send car to spur dependent of shipping track division, then
956                // try staging
957                if (!sendCarToHomeDivisionTrack(car, Track.SPUR, car.getTrack().getDivision() != car.getDivision())) {
958                    return sendCarToHomeDivisionTrack(car, Track.STAGING,
959                            car.getTrack().getDivision() != car.getDivision());
960                }
961            }
962        }
963        return true;
964    }
965
966    private static final boolean HOME_DIVISION = true;
967
968    /**
969     * Tries to set a final destination for the car with a home division.
970     * 
971     * @param car           the car
972     * @param trackType     One of three track types: Track.SPUR Track.YARD or
973     *                      Track.STAGING
974     * @param home_division If true track's division must match the car's
975     * @return true if car was given a final destination
976     */
977    private boolean sendCarToHomeDivisionTrack(Car car, String trackType, boolean home_division) {
978        // locations not reachable
979        List<Location> locationsNotServiced = new ArrayList<>();
980        List<Track> tracks = locationManager.getTracksByMoves(trackType);
981        log.debug("Found {} {} tracks", tracks.size(), trackType);
982        for (Track track : tracks) {
983            if (home_division && car.getDivision() != track.getDivision()) {
984                addLine(_buildReport, SEVEN,
985                        Bundle.getMessage("buildNoDivisionTrack", track.getTrackTypeName(),
986                                track.getLocation().getName(), track.getName(), track.getDivisionName(), car.toString(),
987                                car.getLoadType().toLowerCase(),
988                                car.getLoadName()));
989                continue;
990            }
991            if (locationsNotServiced.contains(track.getLocation())) {
992                continue;
993            }
994            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
995                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
996                        track.getLocation().getName(), car.getTrackName()));
997                // location not reachable
998                locationsNotServiced.add(track.getLocation());
999                continue;
1000            }
1001            // only use the termination staging track for this train
1002            if (trackType.equals(Track.STAGING) &&
1003                    _terminateStageTrack != null &&
1004                    track.getLocation() == _terminateLocation &&
1005                    track != _terminateStageTrack) {
1006                continue;
1007            }
1008            if (trackType.equals(Track.SPUR)) {
1009                if (sendCarToDestinationSpur(car, track)) {
1010                    return true;
1011                }
1012            } else {
1013                if (sendCarToDestinationTrack(car, track)) {
1014                    return true;
1015                }
1016            }
1017        }
1018        addLine(_buildReport, FIVE,
1019                Bundle.getMessage("buildCouldNotFindTrack", trackType.toLowerCase(), car.toString(),
1020                        car.getLoadType().toLowerCase(), car.getLoadName()));
1021        addLine(_buildReport, SEVEN, BLANK_LINE);
1022        return false;
1023    }
1024
1025    /**
1026     * Set the final destination and track for a car with a custom load. Car
1027     * must not have a destination or final destination. There's a check to see
1028     * if there's a spur/schedule for this car. Returns true if a schedule was
1029     * found. Will hold car at current location if any of the spurs checked has
1030     * the the option to "Hold cars with custom loads" enabled and the spur has
1031     * an alternate track assigned. Tries to sent the car to staging if there
1032     * aren't any spurs with schedules available.
1033     *
1034     * @param car the car with the load
1035     * @return true if there's a schedule that can be routed to for this car and
1036     *         load
1037     * @throws BuildFailedException
1038     */
1039    private boolean findFinalDestinationForCarLoad(Car car) throws BuildFailedException {
1040        if (car.getLoadName().equals(carLoads.getDefaultEmptyName()) ||
1041                car.getLoadName().equals(carLoads.getDefaultLoadName()) ||
1042                car.getDestination() != null ||
1043                car.getFinalDestination() != null) {
1044            return false; // car doesn't have a custom load, or already has a
1045                          // destination set
1046        }
1047        addLine(_buildReport, FIVE,
1048                Bundle.getMessage("buildSearchForSpur", car.toString(), car.getTypeName(),
1049                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(),
1050                        car.getTrackName()));
1051        if (car.getKernel() != null) {
1052            addLine(_buildReport, SEVEN,
1053                    Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1054                            car.getKernel().getSize(), car.getKernel().getTotalLength(),
1055                            Setup.getLengthUnit().toLowerCase()));
1056        }
1057        _routeToTrackFound = false;
1058        List<Track> tracks = locationManager.getTracksByMoves(Track.SPUR);
1059        log.debug("Found {} spurs", tracks.size());
1060        // locations not reachable
1061        List<Location> locationsNotServiced = new ArrayList<>();
1062        for (Track track : tracks) {
1063            if (car.getTrack() == track) {
1064                continue;
1065            }
1066            if (track.getSchedule() == null) {
1067                addLine(_buildReport, SEVEN, Bundle.getMessage("buildSpurNoSchedule",
1068                        track.getLocation().getName(), track.getName()));
1069                continue;
1070            }
1071            if (locationsNotServiced.contains(track.getLocation())) {
1072                continue;
1073            }
1074            if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1075                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1076                        track.getLocation().getName(), car.getTrackName()));
1077                // location not reachable
1078                locationsNotServiced.add(track.getLocation());
1079                continue;
1080            }
1081            if (sendCarToDestinationSpur(car, track)) {
1082                return true;
1083            }
1084        }
1085        addLine(_buildReport, SEVEN,
1086                Bundle.getMessage("buildCouldNotFindTrack", Track.getTrackTypeName(Track.SPUR).toLowerCase(),
1087                        car.toString(), car.getLoadType().toLowerCase(), car.getLoadName()));
1088        if (_routeToTrackFound &&
1089                !_train.isSendCarsWithCustomLoadsToStagingEnabled() &&
1090                !car.getLocation().isStaging()) {
1091            addLine(_buildReport, SEVEN, Bundle.getMessage("buildHoldCarValidRoute", car.toString(),
1092                    car.getLocationName(), car.getTrackName()));
1093        } else {
1094            // try and send car to staging
1095            addLine(_buildReport, SEVEN, BLANK_LINE);
1096            addLine(_buildReport, FIVE,
1097                    Bundle.getMessage("buildTrySendCarToStaging", car.toString(), car.getLoadName()));
1098            tracks = locationManager.getTracks(Track.STAGING);
1099            log.debug("Found {} staging tracks", tracks.size());
1100            while (tracks.size() > 0) {
1101                // pick a track randomly
1102                int rnd = (int) (Math.random() * tracks.size());
1103                Track track = tracks.get(rnd);
1104                tracks.remove(track);
1105                log.debug("Staging track ({}, {})", track.getLocation().getName(), track.getName());
1106                if (track.getLocation() == car.getLocation()) {
1107                    continue;
1108                }
1109                if (locationsNotServiced.contains(track.getLocation())) {
1110                    continue;
1111                }
1112                if (_terminateStageTrack != null &&
1113                        track.getLocation() == _terminateLocation &&
1114                        track != _terminateStageTrack) {
1115                    continue; // ignore other staging tracks at terminus
1116                }
1117                if (!car.getTrack().isDestinationAccepted(track.getLocation())) {
1118                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced",
1119                            track.getLocation().getName(), car.getTrackName()));
1120                    locationsNotServiced.add(track.getLocation());
1121                    continue;
1122                }
1123                String status = track.isRollingStockAccepted(car);
1124                if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1125                    log.debug("Staging track ({}) can't accept car ({})", track.getName(), car.toString());
1126                    continue;
1127                }
1128                addLine(_buildReport, SEVEN, Bundle.getMessage("buildStagingCanAcceptLoad", track.getLocation(),
1129                        track.getName(), car.getLoadName()));
1130                // try to send car to staging
1131                car.setFinalDestination(track.getLocation());
1132                // test to see if destination is reachable by this train
1133                if (router.setDestination(car, _train, _buildReport)) {
1134                    _routeToTrackFound = true; // found a route to staging
1135                }
1136                if (car.getDestination() != null) {
1137                    car.updateKernel(); // car part of kernel?
1138                    return true;
1139                }
1140                // couldn't route to this staging location
1141                locationsNotServiced.add(track.getLocation());
1142                car.setFinalDestination(null);
1143            }
1144            addLine(_buildReport, SEVEN,
1145                    Bundle.getMessage("buildNoStagingForCarLoad", car.toString(), car.getLoadName()));
1146            if (!_routeToTrackFound) {
1147                addLine(_buildReport, SEVEN, BLANK_LINE);
1148            }
1149        }
1150        log.debug("routeToSpurFound is {}", _routeToTrackFound);
1151        return _routeToTrackFound; // done
1152    }
1153
1154    boolean _routeToTrackFound;
1155
1156    /**
1157     * Used to determine if spur can accept car. Also will set routeToTrackFound
1158     * to true if there's a valid route available to the spur being tested. Sets
1159     * car's final destination to track if okay.
1160     * 
1161     * @param car   the car
1162     * @param track the spur
1163     * @return false if there's an issue with using the spur
1164     */
1165    private boolean sendCarToDestinationSpur(Car car, Track track) {
1166        if (!checkBasicMoves(car, track)) {
1167            addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(),
1168                    car.toString(), track.getLocation().getName(), track.getName()));
1169            return false;
1170        }
1171        String status = car.checkDestination(track.getLocation(), track);
1172        if (!status.equals(Track.OKAY)) {
1173            if (track.getScheduleMode() == Track.SEQUENTIAL && status.startsWith(Track.SCHEDULE)) {
1174                addLine(_buildReport, SEVEN, Bundle.getMessage("buildTrackSequentialMode",
1175                        track.getLocation().getName(), track.getName(), status));
1176            }
1177            // if the track has an alternate track don't abort if the issue was
1178            // space
1179            if (!status.startsWith(Track.LENGTH)) {
1180                addLine(_buildReport, SEVEN,
1181                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1182                                track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(),
1183                                status));
1184                return false;
1185            }
1186            String scheduleStatus = track.checkSchedule(car);
1187            if (!scheduleStatus.equals(Track.OKAY)) {
1188                addLine(_buildReport, SEVEN,
1189                        Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1190                                track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(),
1191                                scheduleStatus));
1192                return false;
1193            }
1194            if (track.getAlternateTrack() == null) {
1195                // report that the spur is full and no alternate
1196                addLine(_buildReport, SEVEN,
1197                        Bundle.getMessage("buildSpurFullNoAlternate", track.getLocation().getName(), track.getName()));
1198                return false;
1199            } else {
1200                addLine(_buildReport, SEVEN,
1201                        Bundle.getMessage("buildTrackFullHasAlternate", track.getLocation().getName(), track.getName(),
1202                                track.getAlternateTrack().getName()));
1203                // check to see if alternate and track are configured properly
1204                if (!_train.isLocalSwitcher() &&
1205                        (track.getTrainDirections() & track.getAlternateTrack().getTrainDirections()) == 0) {
1206                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropRsUsingTrain4", track.getName(),
1207                            formatStringToCommaSeparated(Setup.getDirectionStrings(track.getTrainDirections())),
1208                            track.getAlternateTrack().getName(), formatStringToCommaSeparated(
1209                                    Setup.getDirectionStrings(track.getAlternateTrack().getTrainDirections()))));
1210                    return false;
1211                }
1212            }
1213        }
1214        addLine(_buildReport, SEVEN, BLANK_LINE);
1215        addLine(_buildReport, SEVEN,
1216                Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(),
1217                        track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(),
1218                        car.getLoadName()));
1219
1220        // show if track is requesting cars with custom loads to only go to
1221        // spurs
1222        if (track.isHoldCarsWithCustomLoadsEnabled()) {
1223            addLine(_buildReport, SEVEN,
1224                    Bundle.getMessage("buildHoldCarsCustom", track.getLocation().getName(), track.getName()));
1225        }
1226        // check the number of in bound cars to this track
1227        if (!track.isSpaceAvailable(car)) {
1228            // Now determine if we should move the car or just leave it
1229            if (track.isHoldCarsWithCustomLoadsEnabled()) {
1230                // determine if this car can be routed to the spur
1231                String id = track.getScheduleItemId();
1232                if (router.isCarRouteable(car, _train, track, _buildReport)) {
1233                    // hold car if able to route to track
1234                    _routeToTrackFound = true;
1235                } else {
1236                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildRouteNotFound", car.toString(),
1237                            car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1238                }
1239                track.setScheduleItemId(id); // restore id
1240            }
1241            if (car.getTrack().isStaging()) {
1242                addLine(_buildReport, SEVEN,
1243                        Bundle.getMessage("buildNoDestTrackSpace", car.toString(), track.getLocation().getName(),
1244                                track.getName(), track.getNumberOfCarsInRoute(), track.getReservedInRoute(),
1245                                Setup.getLengthUnit().toLowerCase(), track.getReservationFactor()));
1246            } else {
1247                addLine(_buildReport, SEVEN,
1248                        Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(),
1249                                track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(),
1250                                track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase()));
1251            }
1252            return false;
1253        }
1254        // try to send car to this spur
1255        car.setFinalDestination(track.getLocation());
1256        car.setFinalDestinationTrack(track);
1257        // test to see if destination is reachable by this train
1258        if (router.setDestination(car, _train, _buildReport) && track.isHoldCarsWithCustomLoadsEnabled()) {
1259            _routeToTrackFound = true; // if we don't find another spur, don't
1260                                       // move car
1261        }
1262        if (car.getDestination() == null) {
1263            if (!router.getStatus().equals(Track.OKAY)) {
1264                addLine(_buildReport, SEVEN,
1265                        Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1266            }
1267            car.setFinalDestination(null);
1268            car.setFinalDestinationTrack(null);
1269            // don't move car if another train can
1270            if (router.getStatus().startsWith(Router.STATUS_NOT_THIS_TRAIN_PREFIX)) {
1271                _routeToTrackFound = true;
1272            }
1273            return false;
1274        }
1275        if (car.getDestinationTrack() != track) {
1276            track.bumpMoves();
1277            // car is being routed to this track
1278            if (track.getSchedule() != null) {
1279                car.setScheduleItemId(track.getCurrentScheduleItem().getId());
1280                track.bumpSchedule();
1281            }
1282        }
1283        car.updateKernel();
1284        return true; // done, car has a new destination
1285    }
1286
1287    /**
1288     * Destination track can be division yard or staging, NOT a spur.
1289     * 
1290     * @param car   the car
1291     * @param track the car's destination track
1292     * @return true if car given a new final destination
1293     */
1294    private boolean sendCarToDestinationTrack(Car car, Track track) {
1295        if (!checkBasicMoves(car, track)) {
1296            addLine(_buildReport, SEVEN, Bundle.getMessage("trainCanNotDeliverToDestination", _train.getName(),
1297                    car.toString(), track.getLocation().getName(), track.getName()));
1298            return false;
1299        }
1300        String status = car.checkDestination(track.getLocation(), track);
1301
1302        if (!status.equals(Track.OKAY)) {
1303            addLine(_buildReport, SEVEN,
1304                    Bundle.getMessage("buildNoDestTrackNewLoad", StringUtils.capitalize(track.getTrackTypeName()),
1305                            track.getLocation().getName(), track.getName(), car.toString(), car.getLoadName(), status));
1306            return false;
1307        }
1308        if (!track.isSpaceAvailable(car)) {
1309            addLine(_buildReport, SEVEN,
1310                    Bundle.getMessage("buildNoDestSpace", car.toString(), track.getTrackTypeName(),
1311                            track.getLocation().getName(), track.getName(), track.getNumberOfCarsInRoute(),
1312                            track.getReservedInRoute(), Setup.getLengthUnit().toLowerCase()));
1313            return false;
1314        }
1315        // try to send car to this division track
1316        addLine(_buildReport, SEVEN,
1317                Bundle.getMessage("buildSetFinalDestDiv", track.getTrackTypeName(), track.getLocation().getName(),
1318                        track.getName(), track.getDivisionName(), car.toString(), car.getLoadType().toLowerCase(),
1319                        car.getLoadName()));
1320        car.setFinalDestination(track.getLocation());
1321        car.setFinalDestinationTrack(track);
1322        // test to see if destination is reachable by this train
1323        if (router.setDestination(car, _train, _buildReport)) {
1324            log.debug("Can route car to destination ({}, {})", track.getLocation().getName(), track.getName());
1325        }
1326        if (car.getDestination() == null) {
1327            addLine(_buildReport, SEVEN,
1328                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1329            car.setFinalDestination(null);
1330            car.setFinalDestinationTrack(null);
1331            return false;
1332        }
1333        car.updateKernel();
1334        return true; // done, car has a new final destination
1335    }
1336
1337    /**
1338     * Checks for a car's final destination, and then after checking, tries to
1339     * route the car to that destination. Normal return from this routine is
1340     * false, with the car returning with a set destination. Returns true if car
1341     * has a final destination, but can't be used for this train.
1342     *
1343     * @param car
1344     * @return false if car needs destination processing (normal).
1345     */
1346    private boolean checkCarForFinalDestination(Car car) {
1347        if (car.getFinalDestination() == null || car.getDestination() != null) {
1348            return false;
1349        }
1350
1351        addLine(_buildReport, FIVE,
1352                Bundle.getMessage("buildCarRoutingBegins", car.toString(), car.getTypeName(),
1353                        car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(),
1354                        car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1355
1356        // no local moves for this train?
1357        if (!_train.isLocalSwitcher() &&
1358                !_train.isAllowLocalMovesEnabled() &&
1359                car.getSplitLocationName().equals(car.getSplitFinalDestinationName()) &&
1360                car.getTrack() != _departStageTrack) {
1361            addLine(_buildReport, FIVE,
1362                    Bundle.getMessage("buildCarHasFinalDestNoMove", car.toString(), car.getLocationName(),
1363                            car.getFinalDestinationName(), _train.getName()));
1364            addLine(_buildReport, FIVE, BLANK_LINE);
1365            log.debug("Removing car ({}) from list", car.toString());
1366            _carList.remove(car);
1367            _carIndex--;
1368            return true; // car has a final destination, but no local moves by
1369                         // this train
1370        }
1371        // is the car's destination the terminal and is that allowed?
1372        if (!checkThroughCarsAllowed(car, car.getFinalDestinationName())) {
1373            // don't remove car from list if departing staging
1374            if (car.getTrack() == _departStageTrack) {
1375                addLine(_buildReport, ONE, Bundle.getMessage("buildErrorCarStageDest", car.toString()));
1376            } else {
1377                log.debug("Removing car ({}) from list", car.toString());
1378                _carList.remove(car);
1379                _carIndex--;
1380            }
1381            return true; // car has a final destination, but through traffic not
1382                         // allowed by this train
1383        }
1384        // does the car have a final destination track that is willing to
1385        // service the car?
1386        // note the default mode for all track types is MATCH
1387        if (car.getFinalDestinationTrack() != null && car.getFinalDestinationTrack().getScheduleMode() == Track.MATCH) {
1388            String status = car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack());
1389            // keep going if the only issue was track length and the track
1390            // accepts the car's load
1391            if (!status.equals(Track.OKAY) &&
1392                    !status.startsWith(Track.LENGTH) &&
1393                    !(status.contains(Track.CUSTOM) && status.contains(Track.LOAD))) {
1394                addLine(_buildReport, SEVEN,
1395                        Bundle.getMessage("buildNoDestTrackNewLoad",
1396                                StringUtils.capitalize(car.getFinalDestinationTrack().getTrackTypeName()),
1397                                car.getFinalDestination().getName(), car.getFinalDestinationTrack().getName(),
1398                                car.toString(), car.getLoadName(), status));
1399                // is this car or kernel being sent to a track that is too
1400                // short?
1401                if (status.startsWith(Track.CAPACITY)) {
1402                    // track is too short for this car or kernel
1403                    addLine(_buildReport, SEVEN,
1404                            Bundle.getMessage("buildTrackTooShort", car.getFinalDestination().getName(),
1405                                    car.getFinalDestinationTrack().getName(), car.toString()));
1406                }
1407                _warnings++;
1408                addLine(_buildReport, SEVEN,
1409                        Bundle.getMessage("buildWarningRemovingFinalDest", car.getFinalDestination().getName(),
1410                                car.getFinalDestinationTrack().getName(), car.toString()));
1411                car.setFinalDestination(null);
1412                car.setFinalDestinationTrack(null);
1413                return false; // car no longer has a final destination
1414            }
1415        }
1416
1417        // now try and route the car
1418        if (!router.setDestination(car, _train, _buildReport)) {
1419            addLine(_buildReport, SEVEN,
1420                    Bundle.getMessage("buildNotAbleToSetDestination", car.toString(), router.getStatus()));
1421            // don't move car if routing issue was track space but not departing
1422            // staging
1423            if ((!router.getStatus().startsWith(Track.LENGTH) &&
1424                    !_train.isServiceAllCarsWithFinalDestinationsEnabled()) || (car.getTrack() == _departStageTrack)) {
1425                // add car to unable to route list
1426                if (!_notRoutable.contains(car)) {
1427                    _notRoutable.add(car);
1428                }
1429                addLine(_buildReport, FIVE, BLANK_LINE);
1430                addLine(_buildReport, FIVE,
1431                        Bundle.getMessage("buildWarningCarNotRoutable", car.toString(), car.getLocationName(),
1432                                car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
1433                addLine(_buildReport, FIVE, BLANK_LINE);
1434                return false; // move this car, routing failed!
1435            }
1436        } else {
1437            if (car.getDestination() != null) {
1438                return false; // routing successful process this car, normal
1439                              // exit from this routine
1440            }
1441            if (car.getTrack() == _departStageTrack) {
1442                log.debug("Car ({}) departing staging with final destination ({}) and no destination",
1443                        car.toString(), car.getFinalDestinationName());
1444                return false; // try and move this car out of staging
1445            }
1446        }
1447        addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1448        addLine(_buildReport, FIVE, BLANK_LINE);
1449        return true;
1450    }
1451
1452    /**
1453     * Checks to see if car has a destination and tries to add car to train.
1454     * Will find a track for the car if needed. Returns false if car doesn't
1455     * have a destination.
1456     *
1457     * @param rl         the car's route location
1458     * @param routeIndex where in the route to start search
1459     * @return true if car has a destination. Need to check if car given a train
1460     *         assignment.
1461     * @throws BuildFailedException if destination was staging and can't place
1462     *                              car there
1463     */
1464    private boolean checkCarForDestination(Car car, RouteLocation rl, int routeIndex) throws BuildFailedException {
1465        if (car.getDestination() == null) {
1466            return false; // the only false return
1467        }
1468        addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarHasAssignedDest", car.toString(), car.getLoadName(),
1469                car.getDestinationName(), car.getDestinationTrackName(), car.getFinalDestinationName(),
1470                car.getFinalDestinationTrackName()));
1471        RouteLocation rld = _train.getRoute().getLastLocationByName(car.getDestinationName());
1472        if (rld == null) {
1473            // code check, router doesn't set a car's destination if not carried
1474            // by train being built. Car has a destination that isn't serviced
1475            // by this train. Find buildExcludeCarDestNotPartRoute in
1476            // loadRemoveAndListCars()
1477            throw new BuildFailedException(Bundle.getMessage("buildExcludeCarDestNotPartRoute", car.toString(),
1478                    car.getDestinationName(), car.getDestinationTrackName(), _train.getRoute().getName()));
1479        }
1480        // now go through the route and try and find a location with
1481        // the correct destination name
1482        for (int k = routeIndex; k < _routeList.size(); k++) {
1483            rld = _routeList.get(k);
1484            // if car can be picked up later at same location, skip
1485            if (checkForLaterPickUp(car, rl, rld)) {
1486                addLine(_buildReport, SEVEN, BLANK_LINE);
1487                return true;
1488            }
1489            if (!rld.getName().equals(car.getDestinationName())) {
1490                continue;
1491            }
1492            // is the car's destination the terminal and is that allowed?
1493            if (!checkThroughCarsAllowed(car, car.getDestinationName())) {
1494                return true;
1495            }
1496            log.debug("Car ({}) found a destination in train's route", car.toString());
1497            // are drops allows at this location?
1498            if (!rld.isDropAllowed() && !car.isLocalMove()) {
1499                addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(),
1500                        rld.getId(), rld.getName()));
1501                continue;
1502            }
1503            // are local moves allows at this location?
1504            if (!rld.isLocalMovesAllowed() && car.isLocalMove()) {
1505                addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoLocalLocation", _train.getRoute().getName(),
1506                        rld.getId(), rld.getName()));
1507                continue;
1508            }
1509            if (_train.isLocationSkipped(rld)) {
1510                addLine(_buildReport, FIVE,
1511                        Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName()));
1512                continue;
1513            }
1514            // any moves left at this location?
1515            if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
1516                addLine(_buildReport, FIVE,
1517                        Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(),
1518                                _train.getRoute().getName(), rld.getId(), rld.getName()));
1519                continue;
1520            }
1521            // is the train length okay?
1522            if (!checkTrainLength(car, rl, rld)) {
1523                continue;
1524            }
1525            // check for valid destination track
1526            if (car.getDestinationTrack() == null) {
1527                addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDoesNotHaveDest", car.toString()));
1528                // is car going into staging?
1529                if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) {
1530                    String status = car.checkDestination(car.getDestination(), _terminateStageTrack);
1531                    if (status.equals(Track.OKAY)) {
1532                        addLine(_buildReport, FIVE, Bundle.getMessage("buildCarAssignedToStaging", car.toString(),
1533                                _terminateStageTrack.getName()));
1534                        addCarToTrain(car, rl, rld, _terminateStageTrack);
1535                        return true;
1536                    } else {
1537                        addLine(_buildReport, SEVEN,
1538                                Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
1539                                        _terminateStageTrack.getTrackTypeName(),
1540                                        _terminateStageTrack.getLocation().getName(), _terminateStageTrack.getName(),
1541                                        status));
1542                        continue;
1543                    }
1544                } else {
1545                    // no staging at this location, now find a destination track
1546                    // for this car
1547                    List<Track> tracks = getTracksAtDestination(car, rld);
1548                    if (tracks.size() > 0) {
1549                        if (tracks.get(1) != null) {
1550                            car.setFinalDestination(car.getDestination());
1551                            car.setFinalDestinationTrack(tracks.get(1));
1552                            tracks.get(1).bumpMoves();
1553                        }
1554                        addLine(_buildReport, FIVE,
1555                                Bundle.getMessage("buildCarCanDropMoves", car.toString(),
1556                                        tracks.get(0).getTrackTypeName(),
1557                                        tracks.get(0).getLocation().getName(), tracks.get(0).getName(),
1558                                        rld.getCarMoves(), rld.getMaxCarMoves()));
1559                        addCarToTrain(car, rl, rld, tracks.get(0));
1560                        return true;
1561                    }
1562                }
1563            } else {
1564                log.debug("Car ({}) has a destination track ({})", car.toString(), car.getDestinationTrack().getName());
1565                // going into the correct staging track?
1566                if (rld.equals(_train.getTrainTerminatesRouteLocation()) &&
1567                        _terminateStageTrack != null &&
1568                        _terminateStageTrack != car.getDestinationTrack()) {
1569                    // car going to wrong track in staging, change track
1570                    addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarDestinationStaging", car.toString(),
1571                            car.getDestinationName(), car.getDestinationTrackName()));
1572                    car.setDestination(_terminateStageTrack.getLocation(), _terminateStageTrack);
1573                }
1574                if (!rld.equals(_train.getTrainTerminatesRouteLocation()) ||
1575                        _terminateStageTrack == null ||
1576                        _terminateStageTrack == car.getDestinationTrack()) {
1577                    // is train direction correct? and drop to interchange or
1578                    // spur?
1579                    if (checkDropTrainDirection(car, rld, car.getDestinationTrack()) &&
1580                            checkTrainCanDrop(car, car.getDestinationTrack())) {
1581                        String status = car.checkDestination(car.getDestination(), car.getDestinationTrack());
1582                        if (status.equals(Track.OKAY) &&
1583                                (status = checkReserved(_train, rld, car, car.getDestinationTrack(), true))
1584                                        .equals(Track.OKAY)) {
1585                            Track destTrack = car.getDestinationTrack();
1586                            addCarToTrain(car, rl, rld, destTrack);
1587                            return true;
1588                        }
1589                        if (status.equals(TIMING) && checkForAlternate(car, car.getDestinationTrack())) {
1590                            // send car to alternate track) {
1591                            car.setFinalDestination(car.getDestination());
1592                            car.setFinalDestinationTrack(car.getDestinationTrack());
1593                            addCarToTrain(car, rl, rld, car.getDestinationTrack().getAlternateTrack());
1594                            return true;
1595                        }
1596                        addLine(_buildReport, SEVEN,
1597                                Bundle.getMessage("buildCanNotDropCarBecause", car.toString(),
1598                                        car.getDestinationTrack().getTrackTypeName(),
1599                                        car.getDestinationTrack().getLocation().getName(),
1600                                        car.getDestinationTrackName(), status));
1601
1602                    }
1603                } else {
1604                    // code check
1605                    throw new BuildFailedException(Bundle.getMessage("buildCarDestinationStaging", car.toString(),
1606                            car.getDestinationName(), car.getDestinationTrackName()));
1607                }
1608            }
1609            addLine(_buildReport, FIVE,
1610                    Bundle.getMessage("buildCanNotDropCar", car.toString(), car.getDestinationName(), rld.getId()));
1611            if (car.getDestinationTrack() == null) {
1612                log.debug("Could not find a destination track for location ({})", car.getDestinationName());
1613            }
1614        }
1615        log.debug("car ({}) not added to train", car.toString());
1616        addLine(_buildReport, FIVE,
1617                Bundle.getMessage("buildDestinationNotReachable", car.getDestinationName(), rl.getName(), rl.getId()));
1618        // remove destination and revert to final destination
1619        if (car.getDestinationTrack() != null) {
1620            // going to remove this destination from car
1621            car.getDestinationTrack().setMoves(car.getDestinationTrack().getMoves() - 1);
1622            Track destTrack = car.getDestinationTrack();
1623            // TODO should we leave the car's destination? The spur expects this
1624            // car!
1625            if (destTrack.getSchedule() != null && destTrack.getScheduleMode() == Track.SEQUENTIAL) {
1626                addLine(_buildReport, SEVEN, Bundle.getMessage("buildPickupCanceled",
1627                        destTrack.getLocation().getName(), destTrack.getName()));
1628            }
1629        }
1630        car.setFinalDestination(car.getPreviousFinalDestination());
1631        car.setFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
1632        car.setDestination(null, null);
1633        car.updateKernel();
1634
1635        addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1636        addLine(_buildReport, FIVE, BLANK_LINE);
1637        return true; // car no longer has a destination, but it had one.
1638    }
1639
1640    /**
1641     * Find a destination and track for a car at a route location.
1642     *
1643     * @param car the car!
1644     * @param rl  The car's route location
1645     * @param rld The car's route destination
1646     * @return true if successful.
1647     * @throws BuildFailedException if code check fails
1648     */
1649    private boolean findDestinationAndTrack(Car car, RouteLocation rl, RouteLocation rld) throws BuildFailedException {
1650        int index = _routeList.indexOf(rld);
1651        if (_train.isLocalSwitcher()) {
1652            return findDestinationAndTrack(car, rl, index, index + 1);
1653        }
1654        return findDestinationAndTrack(car, rl, index - 1, index + 1);
1655    }
1656
1657    /**
1658     * Find a destination and track for a car, and add the car to the train.
1659     *
1660     * @param car        The car that is looking for a destination and
1661     *                   destination track.
1662     * @param rl         The route location for this car.
1663     * @param routeIndex Where in the train's route to begin a search for a
1664     *                   destination for this car.
1665     * @param routeEnd   Where to stop looking for a destination.
1666     * @return true if successful, car has destination, track and a train.
1667     * @throws BuildFailedException if code check fails
1668     */
1669    private boolean findDestinationAndTrack(Car car, RouteLocation rl, int routeIndex, int routeEnd)
1670            throws BuildFailedException {
1671        if (routeIndex + 1 == routeEnd) {
1672            log.debug("Car ({}) is at the last location in the train's route", car.toString());
1673        }
1674        addLine(_buildReport, FIVE, Bundle.getMessage("buildFindDestinationForCar", car.toString(), car.getTypeName(),
1675                car.getLoadType().toLowerCase(), car.getLoadName(), car.getTrackType(), car.getLocationName(),
1676                car.getTrackName()));
1677        if (car.getKernel() != null) {
1678            addLine(_buildReport, SEVEN, Bundle.getMessage("buildCarLeadKernel", car.toString(), car.getKernelName(),
1679                    car.getKernel().getSize(), car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
1680        }
1681
1682        // normally start looking after car's route location
1683        int start = routeIndex;
1684        // the route location destination being checked for the car
1685        RouteLocation rld = null;
1686        // holds the best route location destination for the car
1687        RouteLocation rldSave = null;
1688        // holds the best track at destination for the car
1689        Track trackSave = null;
1690        // used when a spur has an alternate track and no schedule
1691        Track finalDestinationTrackSave = null;
1692        // true when car can be picked up from two or more locations in the
1693        // route
1694        boolean multiplePickup = false;
1695
1696        if (!_train.isLocalSwitcher()) {
1697            start++; // begin looking for tracks at the next location
1698        }
1699        // all pick ups to terminal?
1700        if (_train.isSendCarsToTerminalEnabled() &&
1701                !rl.getSplitName().equals(_departLocation.getSplitName()) &&
1702                routeEnd == _routeList.size()) {
1703            addLine(_buildReport, FIVE, Bundle.getMessage("buildSendToTerminal", _terminateLocation.getName()));
1704            // user could have specified several terminal locations with the
1705            // "same" name
1706            start = routeEnd - 1;
1707            while (start > routeIndex) {
1708                if (!_routeList.get(start - 1).getSplitName()
1709                        .equals(_terminateLocation.getSplitName())) {
1710                    break;
1711                }
1712                start--;
1713            }
1714        }
1715        // now search for a destination for this car
1716        for (int k = start; k < routeEnd; k++) {
1717            rld = _routeList.get(k);
1718            // if car can be picked up later at same location, set flag
1719            if (checkForLaterPickUp(car, rl, rld)) {
1720                multiplePickup = true;
1721            }
1722            if (rld.isDropAllowed() || car.hasFred() || car.isCaboose()) {
1723                addLine(_buildReport, FIVE, Bundle.getMessage("buildSearchingLocation", rld.getName(), rld.getId()));
1724            } else {
1725                addLine(_buildReport, FIVE, Bundle.getMessage("buildRouteNoDropLocation", _train.getRoute().getName(),
1726                        rld.getId(), rld.getName()));
1727                continue;
1728            }
1729            if (_train.isLocationSkipped(rld)) {
1730                addLine(_buildReport, FIVE,
1731                        Bundle.getMessage("buildLocSkipped", rld.getName(), rld.getId(), _train.getName()));
1732                continue;
1733            }
1734            // any moves left at this location?
1735            if (rld.getCarMoves() >= rld.getMaxCarMoves()) {
1736                addLine(_buildReport, FIVE,
1737                        Bundle.getMessage("buildNoAvailableMovesDest", rld.getCarMoves(), rld.getMaxCarMoves(),
1738                                _train.getRoute().getName(), rld.getId(), rld.getName()));
1739                continue;
1740            }
1741            // get the destination
1742            Location testDestination = rld.getLocation();
1743            // code check, all locations in the route have been already checked
1744            if (testDestination == null) {
1745                throw new BuildFailedException(
1746                        Bundle.getMessage("buildErrorRouteLoc", _train.getRoute().getName(), rld.getName()));
1747            }
1748            // don't move car to same location unless the train is a switcher
1749            // (local moves) or is passenger, caboose or car with FRED
1750            if (rl.getSplitName().equals(rld.getSplitName()) &&
1751                    !_train.isLocalSwitcher() &&
1752                    !car.isPassenger() &&
1753                    !car.isCaboose() &&
1754                    !car.hasFred()) {
1755                // allow cars to return to the same staging location if no other
1756                // options (tracks) are available
1757                if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) &&
1758                        testDestination.isStaging() &&
1759                        trackSave == null) {
1760                    addLine(_buildReport, SEVEN,
1761                            Bundle.getMessage("buildReturnCarToStaging", car.toString(), rld.getName()));
1762                } else {
1763                    addLine(_buildReport, SEVEN,
1764                            Bundle.getMessage("buildCarLocEqualDestination", car.toString(), rld.getName()));
1765                    continue;
1766                }
1767            }
1768
1769            // check to see if departure track has any restrictions
1770            if (!car.getTrack().isDestinationAccepted(testDestination)) {
1771                addLine(_buildReport, SEVEN, Bundle.getMessage("buildDestinationNotServiced", testDestination.getName(),
1772                        car.getTrackName()));
1773                continue;
1774            }
1775
1776            if (!testDestination.acceptsTypeName(car.getTypeName())) {
1777                addLine(_buildReport, SEVEN, Bundle.getMessage("buildCanNotDropLocation", car.toString(),
1778                        car.getTypeName(), testDestination.getName()));
1779                continue;
1780            }
1781            // can this location service this train's direction
1782            if (!checkDropTrainDirection(rld)) {
1783                continue;
1784            }
1785            // is the train length okay?
1786            if (!checkTrainLength(car, rl, rld)) {
1787                break; // no, done with this car
1788            }
1789            // is the car's destination the terminal and is that allowed?
1790            if (!checkThroughCarsAllowed(car, rld.getName())) {
1791                continue; // not allowed
1792            }
1793
1794            Track trackTemp = null;
1795            // used when alternate track selected
1796            Track finalDestinationTrackTemp = null;
1797
1798            // is there a track assigned for staging cars?
1799            if (rld == _train.getTrainTerminatesRouteLocation() && _terminateStageTrack != null) {
1800                trackTemp = tryStaging(car, rldSave);
1801                if (trackTemp == null) {
1802                    continue; // no
1803                }
1804            } else {
1805                // not staging, start track search
1806                List<Track> tracks = getTracksAtDestination(car, rld);
1807                if (tracks.size() > 0) {
1808                    trackTemp = tracks.get(0);
1809                    finalDestinationTrackTemp = tracks.get(1);
1810                }
1811            }
1812            // did we find a new destination?
1813            if (trackTemp == null) {
1814                addLine(_buildReport, FIVE,
1815                        Bundle.getMessage("buildCouldNotFindDestForCar", car.toString(), rld.getName()));
1816            } else {
1817                addLine(_buildReport, FIVE,
1818                        Bundle.getMessage("buildCarCanDropMoves", car.toString(), trackTemp.getTrackTypeName(),
1819                                trackTemp.getLocation().getName(), trackTemp.getName(), +rld.getCarMoves(),
1820                                rld.getMaxCarMoves()));
1821                if (multiplePickup) {
1822                    if (rldSave != null) {
1823                        addLine(_buildReport, FIVE,
1824                                Bundle.getMessage("buildTrackServicedLater", car.getLocationName(),
1825                                        trackTemp.getTrackTypeName(), trackTemp.getLocation().getName(),
1826                                        trackTemp.getName(), car.getLocationName()));
1827                    } else {
1828                        addLine(_buildReport, FIVE,
1829                                Bundle.getMessage("buildCarHasSecond", car.toString(), car.getLocationName()));
1830                        trackSave = null;
1831                    }
1832                    break; // done
1833                }
1834                // if there's more than one available destination use the lowest
1835                // ratio
1836                if (rldSave != null) {
1837                    // check for an earlier drop in the route
1838                    rld = checkForEarlierDrop(car, trackTemp, rld, start, routeEnd);
1839                    double saveCarMoves = rldSave.getCarMoves();
1840                    double saveRatio = saveCarMoves / rldSave.getMaxCarMoves();
1841                    double nextCarMoves = rld.getCarMoves();
1842                    double nextRatio = nextCarMoves / rld.getMaxCarMoves();
1843
1844                    // bias cars to the terminal
1845                    if (rld == _train.getTrainTerminatesRouteLocation()) {
1846                        nextRatio = nextRatio * nextRatio;
1847                        log.debug("Location ({}) is terminate location, adjusted nextRatio {}", rld.getName(),
1848                                Double.toString(nextRatio));
1849
1850                        // bias cars with default loads to a track with a
1851                        // schedule
1852                    } else if (!trackTemp.getScheduleId().equals(Track.NONE)) {
1853                        nextRatio = nextRatio * nextRatio;
1854                        log.debug("Track ({}) has schedule ({}), adjusted nextRatio {}", trackTemp.getName(),
1855                                trackTemp.getScheduleName(), Double.toString(nextRatio));
1856                    }
1857                    // bias cars with default loads to saved track with a
1858                    // schedule
1859                    if (trackSave != null && !trackSave.getScheduleId().equals(Track.NONE)) {
1860                        saveRatio = saveRatio * saveRatio;
1861                        log.debug("Saved track ({}) has schedule ({}), adjusted nextRatio {}", trackSave.getName(),
1862                                trackSave.getScheduleName(), Double.toString(saveRatio));
1863                    }
1864                    log.debug("Saved {} = {}, {} = {}", rldSave.getName(), Double.toString(saveRatio), rld.getName(),
1865                            Double.toString(nextRatio));
1866                    if (saveRatio < nextRatio) {
1867                        // the saved is better than the last found
1868                        rld = rldSave;
1869                        trackTemp = trackSave;
1870                        finalDestinationTrackTemp = finalDestinationTrackSave;
1871                    }
1872                }
1873                // every time through, save the best route destination, and
1874                // track
1875                rldSave = rld;
1876                trackSave = trackTemp;
1877                finalDestinationTrackSave = finalDestinationTrackTemp;
1878            }
1879        }
1880        // did we find a destination?
1881        if (trackSave != null && rldSave != null) {
1882            // determine if local staging move is allowed (leaves car in staging)
1883            if ((_train.isAllowReturnToStagingEnabled() || Setup.isStagingAllowReturnEnabled()) &&
1884                    rl.isDropAllowed() &&
1885                    rl.getLocation().isStaging() &&
1886                    trackSave.isStaging() &&
1887                    rl.getLocation() == rldSave.getLocation() &&
1888                    !_train.isLocalSwitcher() &&
1889                    !car.isPassenger() &&
1890                    !car.isCaboose() &&
1891                    !car.hasFred()) {
1892                addLine(_buildReport, SEVEN,
1893                        Bundle.getMessage("buildLeaveCarInStaging", car.toString(), car.getLocationName(),
1894                                car.getTrackName()));
1895                rldSave = rl; // make local move
1896            } else if (trackSave.isSpur()) {
1897                car.setScheduleItemId(trackSave.getScheduleItemId());
1898                trackSave.bumpSchedule();
1899                log.debug("Sending car to spur ({}, {}) with car schedule id ({}))", trackSave.getLocation().getName(),
1900                        trackSave.getName(), car.getScheduleItemId());
1901            } else {
1902                car.setScheduleItemId(Car.NONE);
1903            }
1904            if (finalDestinationTrackSave != null) {
1905                car.setFinalDestination(finalDestinationTrackSave.getLocation());
1906                car.setFinalDestinationTrack(finalDestinationTrackSave);
1907                if (trackSave.isAlternate()) {
1908                    finalDestinationTrackSave.bumpMoves(); // bump move count
1909                }
1910            }
1911            addCarToTrain(car, rl, rldSave, trackSave);
1912            return true;
1913        }
1914        addLine(_buildReport, FIVE, Bundle.getMessage("buildNoDestForCar", car.toString()));
1915        addLine(_buildReport, FIVE, BLANK_LINE);
1916        return false; // no build errors, but car not given destination
1917    }
1918
1919    /**
1920     * Add car to train, and adjust train length and weight
1921     *
1922     * @param car   the car being added to the train
1923     * @param rl    the departure route location for this car
1924     * @param rld   the destination route location for this car
1925     * @param track the destination track for this car
1926     */
1927    protected void addCarToTrain(Car car, RouteLocation rl, RouteLocation rld, Track track) {
1928        car = checkQuickServiceArrival(car, rld, track);
1929        addLine(_buildReport, THREE,
1930                Bundle.getMessage("buildCarAssignedDest", car.toString(), rld.getName(), track.getName()));
1931        car.setDestination(track.getLocation(), track, Car.FORCE);
1932        int length = car.getTotalLength();
1933        int weightTons = car.getAdjustedWeightTons();
1934        // car could be part of a kernel
1935        if (car.getKernel() != null) {
1936            length = car.getKernel().getTotalLength(); // includes couplers
1937            weightTons = car.getKernel().getAdjustedWeightTons();
1938            List<Car> kCars = car.getKernel().getCars();
1939            addLine(_buildReport, THREE,
1940                    Bundle.getMessage("buildCarPartOfKernel", car.toString(), car.getKernelName(), kCars.size(),
1941                            car.getKernel().getTotalLength(), Setup.getLengthUnit().toLowerCase()));
1942            for (Car kCar : kCars) {
1943                if (kCar != car) {
1944                    addLine(_buildReport, THREE, Bundle.getMessage("buildCarKernelAssignedDest", kCar.toString(),
1945                            kCar.getKernelName(), rld.getName(), track.getName()));
1946                    kCar.setTrain(_train);
1947                    kCar.setRouteLocation(rl);
1948                    kCar.setRouteDestination(rld);
1949                    kCar.setDestination(track.getLocation(), track, Car.FORCE); // force destination
1950                    // save final destination and track values in case of train reset
1951                    kCar.setPreviousFinalDestination(car.getPreviousFinalDestination());
1952                    kCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
1953                }
1954            }
1955            car.updateKernel();
1956        }
1957        // warn if car's load wasn't generated out of staging
1958        if (!_train.isLoadNameAccepted(car.getLoadName(), car.getTypeName())) {
1959            _warnings++;
1960            addLine(_buildReport, SEVEN,
1961                    Bundle.getMessage("buildWarnCarDepartStaging", car.toString(), car.getLoadName()));
1962        }
1963        addLine(_buildReport, THREE, BLANK_LINE);
1964        _numberCars++; // bump number of cars moved by this train
1965        _completedMoves++; // bump number of car pick up moves for the location
1966        _reqNumOfMoves--; // decrement number of moves left for the location
1967
1968        if (_carList.remove(car)) {
1969            _carIndex--; // removed car from list, so backup pointer
1970        }
1971
1972        rl.setCarMoves(rl.getCarMoves() + 1);
1973        if (rl != rld) {
1974            rld.setCarMoves(rld.getCarMoves() + 1);
1975        }
1976        // now adjust train length and weight for each location that car is in
1977        // the train
1978        finishAddRsToTrain(car, rl, rld, length, weightTons);
1979    }
1980
1981    /**
1982     * Checks to see if cars that are already in the train can be redirected
1983     * from the alternate track to the spur that really wants the car. Fixes the
1984     * issue of having cars placed at the alternate when the spur's cars get
1985     * pulled by this train, but cars were sent to the alternate because the
1986     * spur was full at the time it was tested.
1987     *
1988     * @return true if one or more cars were redirected
1989     * @throws BuildFailedException if coding issue
1990     */
1991    protected boolean redirectCarsFromAlternateTrack() throws BuildFailedException {
1992        // code check, should be aggressive
1993        if (!Setup.isBuildAggressive()) {
1994            throw new BuildFailedException("ERROR coding issue, should be using aggressive mode");
1995        }
1996        boolean redirected = false;
1997        List<Car> cars = carManager.getByTrainList(_train);
1998        for (Car car : cars) {
1999            // does the car have a final destination and the destination is this
2000            // one?
2001            if (car.getFinalDestination() == null ||
2002                    car.getFinalDestinationTrack() == null ||
2003                    !car.getFinalDestinationName().equals(car.getDestinationName())) {
2004                continue;
2005            }
2006            Track alternate = car.getFinalDestinationTrack().getAlternateTrack();
2007            if (alternate == null || car.getDestinationTrack() != alternate) {
2008                continue;
2009            }
2010            // is the car in a kernel?
2011            if (car.getKernel() != null && !car.isLead()) {
2012                continue;
2013            }
2014            log.debug("Car ({}) alternate track ({}) has final destination track ({}) location ({})", car.toString(),
2015                    car.getDestinationTrackName(), car.getFinalDestinationTrackName(), car.getDestinationName()); // NOI18N
2016            if ((alternate.isYard() || alternate.isInterchange()) &&
2017                    car.checkDestination(car.getFinalDestination(), car.getFinalDestinationTrack())
2018                            .equals(Track.OKAY) &&
2019                    checkReserved(_train, car.getRouteDestination(), car, car.getFinalDestinationTrack(), false)
2020                            .equals(Track.OKAY) &&
2021                    checkDropTrainDirection(car, car.getRouteDestination(), car.getFinalDestinationTrack()) &&
2022                    checkTrainCanDrop(car, car.getFinalDestinationTrack())) {
2023                log.debug("Car ({}) alternate track ({}) can be redirected to final destination track ({})",
2024                        car.toString(), car.getDestinationTrackName(), car.getFinalDestinationTrackName());
2025                if (car.getKernel() != null) {
2026                    for (Car k : car.getKernel().getCars()) {
2027                        if (k.isLead()) {
2028                            continue;
2029                        }
2030                        addLine(_buildReport, FIVE,
2031                                Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(),
2032                                        car.getFinalDestinationTrackName(), k.toString(),
2033                                        car.getDestinationTrackName()));
2034                        // force car to track
2035                        k.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE);
2036                    }
2037                }
2038                addLine(_buildReport, FIVE,
2039                        Bundle.getMessage("buildRedirectFromAlternate", car.getFinalDestinationName(),
2040                                car.getFinalDestinationTrackName(),
2041                                car.toString(), car.getDestinationTrackName()));
2042                car.setDestination(car.getFinalDestination(), car.getFinalDestinationTrack(), Car.FORCE);
2043                // check for quick service
2044                checkQuickServiceRedirected(car);
2045                redirected = true;
2046            }
2047        }
2048        return redirected;
2049    }
2050
2051    /*
2052     * Checks to see if the redirected car is going to a track with quick
2053     * service. The car in this case has already been assigned to the train.
2054     * This routine will create clones if needed, and allow the car to be
2055     * reassigned to the same train. Only lead car in a kernel is allowed.
2056     */
2057    private void checkQuickServiceRedirected(Car car) {
2058        if (car.getDestinationTrack().isQuickServiceEnabled()) {
2059            RouteLocation rl = car.getRouteLocation();
2060            RouteLocation rld = car.getRouteDestination();
2061            Track track = car.getDestinationTrack();
2062            // remove cars from train
2063            if (car.getKernel() != null) {
2064                for (Car kar : car.getKernel().getCars())
2065                    kar.reset();
2066            } else {
2067                car.reset();
2068            }
2069            _carList.add(0, car);
2070            addCarToTrain(car, rl, rld, track);
2071        }
2072    }
2073
2074    /**
2075     * Checks to see if spur/industry is requesting a quick load service, which
2076     * means that on the outbound side of the turn a car or set of cars in a
2077     * kernel are set out, and on the return side of the turn the same cars are
2078     * pulled. Since it isn't possible for a car to be pulled and set out twice,
2079     * this code creates a second "clone" car to create the requested Manifest.
2080     * A car could have multiple clones, therefore each clone has a creation
2081     * order number. The first clone is used to restore a car's location and
2082     * load in the case of reset.
2083     * <p>
2084     * Also works with an interchange track to make the car immediately
2085     * available to be pulled by the next train being built.
2086     * 
2087     * @param car   the car possibly needing a quick turn
2088     * @param track the destination track
2089     * @return the car if not a quick turn, or a clone if quick turn
2090     */
2091    private Car checkQuickServiceArrival(Car car, RouteLocation rld, Track track) {
2092        if (!track.isQuickServiceEnabled()) {
2093            return car;
2094        }
2095        addLine(_buildReport, FIVE,
2096                Bundle.getMessage("buildTrackQuickService", StringUtils.capitalize(track.getTrackTypeName()),
2097                        track.getLocation().getName(), track.getName()));
2098        // quick service enabled, create clones
2099        int cloneCreationOrder = carManager.getCloneCreationOrder();
2100        Car cloneCar = car.copy();
2101        cloneCar.setNumber(car.getNumber() + Car.CLONE + cloneCreationOrder);
2102        cloneCar.setClone(true);
2103        // register car before setting location so the car gets logged
2104        carManager.register(cloneCar);
2105        cloneCar.setLocation(car.getLocation(), car.getTrack(), RollingStock.FORCE);
2106        // for reset
2107        cloneCar.setPreviousFinalDestination(car.getPreviousFinalDestination());
2108        cloneCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
2109        cloneCar.setPreviousScheduleId(car.getScheduleItemId());
2110        cloneCar.setLastRouteId(car.getLastRouteId());
2111        // for timing, use arrival times for the train that is building
2112        // other trains will use their departure time, loaded when creating the Manifest
2113        String expectedArrivalTime = _train.getExpectedArrivalTime(rld, true);
2114        cloneCar.setSetoutTime(expectedArrivalTime);
2115        if (car.getKernel() != null) {
2116            String kernelName = car.getKernelName() + Car.CLONE + cloneCreationOrder;
2117            Kernel kernel = InstanceManager.getDefault(KernelManager.class).newKernel(kernelName);
2118            cloneCar.setKernel(kernel);
2119            for (Car kar : car.getKernel().getCars()) {
2120                if (kar != car) {
2121                    Car nCar = kar.copy();
2122                    nCar.setNumber(kar.getNumber() + Car.CLONE + cloneCreationOrder);
2123                    nCar.setClone(true);
2124                    nCar.setKernel(kernel);
2125                    carManager.register(nCar);
2126                    nCar.setLocation(car.getLocation(), car.getTrack(), RollingStock.FORCE);
2127                    // for reset
2128                    nCar.setPreviousFinalDestination(car.getPreviousFinalDestination());
2129                    nCar.setPreviousFinalDestinationTrack(car.getPreviousFinalDestinationTrack());
2130                    // move car to new location for later pick up
2131                    kar.setLocation(track.getLocation(), track, RollingStock.FORCE);
2132                    kar.setLastTrain(_train);
2133                    kar.setLastLocationId(car.getLocationId());
2134                    kar.setLastTrackId(car.getTrackId());
2135                    kar.setLastDate(_startTime);
2136                    kar.setCloneOrder(cloneCreationOrder); // for reset
2137                }
2138            }
2139        }
2140        // move car to new location for later pick up
2141        car.setLocation(track.getLocation(), track, RollingStock.FORCE);
2142        car.setLastTrain(_train);
2143        car.setLastLocationId(cloneCar.getLocationId());
2144        car.setLastTrackId(cloneCar.getTrackId());
2145        car.setLastRouteId(_train.getRoute().getId());
2146        // this car was moved during the build process
2147        car.setLastDate(_startTime);
2148        car.setCloneOrder(cloneCreationOrder); // for reset
2149        car.setDestination(null, null);
2150        track.scheduleNext(car); // apply schedule to car
2151        car.loadNext(track); // update load, wait count
2152        if (car.getWait() > 0) {
2153            _carList.remove(car); // available for next train
2154            addLine(_buildReport, FIVE, Bundle.getMessage("buildExcludeCarWait", car.toString(),
2155                    car.getTypeName(), car.getLocationName(), car.getTrackName(), car.getWait()));
2156            car.setWait(car.getWait() - 1);
2157            car.updateLoad(track);
2158        }
2159        // remember where in the route the car was delivered
2160        car.setRouteDestination(rld);
2161        car.updateKernel();
2162        return cloneCar; // return clone
2163    }
2164
2165    /*
2166     * Checks to see if car is departing a quick service track and is allowed to
2167     * be pulled by this train. Only one pull or move from a location with quick
2168     * service tracks is allowed per route location. To service the car, the
2169     * train must arrive after the car's clone is set out by this train or by
2170     * another train.
2171     */
2172    private boolean checkQuickServiceDeparting(Car car, RouteLocation rl) {
2173        if (car.getTrack().isQuickServiceEnabled()) {
2174            Car clone = getClone(car);
2175            if (clone != null) {
2176                // was the car delivered using this route location?
2177                if (car.getRouteDestination() == rl) {
2178                    addLine(_buildReport, FIVE,
2179                            Bundle.getMessage("buildCarRouteLocation", car.toString(),
2180                                    car.getTrack().getTrackTypeName(),
2181                                    car.getLocationName(), car.getTrackName(), _train.getName(), rl.getName(),
2182                                    rl.getId()));
2183                    addLine(_buildReport, FIVE, BLANK_LINE);
2184                    return false;
2185                }
2186                
2187                // determine when the clone is going to be delivered
2188                String trainExpectedArrival = _train.getExpectedArrivalTime(rl, true);
2189                int trainArrivalTimeMinutes = convertStringTime(trainExpectedArrival);
2190                int cloneSetoutTimeMinutes = convertStringTime(clone.getSetoutTime());
2191                if (cloneSetoutTimeMinutes > trainArrivalTimeMinutes) {
2192                    addLine(_buildReport, FIVE, Bundle.getMessage("buildCarDeliveryTiming", car.toString(),
2193                            clone.getSetoutTime(), car.getTrack().getTrackTypeName(), car.getLocationName(),
2194                            car.getTrackName(), clone.getTrainName(), _train.getName(), trainExpectedArrival));
2195                    addLine(_buildReport, FIVE, BLANK_LINE);
2196                    return false;
2197                }
2198            }
2199        }
2200        return true;
2201    }
2202
2203    /*
2204     * Return null if there isn't a clone car. Returns the car's last clone car
2205     * if there's one.
2206     */
2207    private Car getClone(Car car) {
2208        for (Car kar : carManager.getList()) {
2209            if (kar.isClone() &&
2210                    kar.getDestinationTrack() == car.getTrack() &&
2211                    kar.getRoadName().equals(car.getRoadName()) &&
2212                    kar.getNumber().split(Car.CLONE_REGEX)[0].equals(car.getNumber())) {
2213                return kar;
2214            }
2215        }
2216        return null; // no clone for this car
2217    }
2218
2219    private final static Logger log = LoggerFactory.getLogger(TrainBuilderCars.class);
2220}