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