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