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