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