001package jmri.jmrit.operations.router;
002
003import java.io.PrintWriter;
004import java.text.MessageFormat;
005import java.util.*;
006
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.InstanceManager;
011import jmri.InstanceManagerAutoDefault;
012import jmri.jmrit.operations.locations.Location;
013import jmri.jmrit.operations.locations.Track;
014import jmri.jmrit.operations.rollingstock.RollingStock;
015import jmri.jmrit.operations.rollingstock.cars.Car;
016import jmri.jmrit.operations.setup.Setup;
017import jmri.jmrit.operations.trains.Train;
018import jmri.jmrit.operations.trains.TrainManager;
019import jmri.jmrit.operations.trains.trainbuilder.TrainCommon;
020
021/**
022 * Router for car movement. This code attempts to find a way (a route) to move a
023 * car to its final destination through the use of two or more trains. First the
024 * code tries to move car using a single train. If that fails, attempts are made
025 * using two trains via a classification/interchange (C/I) tracks, then yard
026 * tracks if enabled. Next attempts are made using three or more trains using
027 * any combination of C/I and yard tracks. If that fails and routing via staging
028 * is enabled, the code tries two trains using staging tracks, then multiple
029 * trains using a combination of C/I, yards, and staging tracks. Currently the
030 * router is limited to seven trains.
031 *
032 * @author Daniel Boudreau Copyright (C) 2010, 2011, 2012, 2013, 2015, 2021,
033 *         2022, 2024, 2026
034 */
035public class Router extends TrainCommon implements InstanceManagerAutoDefault {
036
037    TrainManager trainManager = InstanceManager.getDefault(TrainManager.class);
038
039    protected final List<Track> _nextLocationTracks = new ArrayList<>();
040    protected final List<Track> _lastLocationTracks = new ArrayList<>();
041    private final List<Track> _otherLocationTracks = new ArrayList<>();
042
043    protected final List<Track> _next2ndLocationTracks = new ArrayList<>();
044    protected final List<Track> _next3rdLocationTracks = new ArrayList<>();
045    protected final List<Track> _next4thLocationTracks = new ArrayList<>();
046
047    protected final List<Train> _nextLocationTrains = new ArrayList<>();
048    protected final List<Train> _lastLocationTrains = new ArrayList<>();
049    protected List<Train> _excludeTrains;
050
051    protected Hashtable<String, Train> _listTrains = new Hashtable<>();
052
053    protected static final String STATUS_NOT_THIS_TRAIN = Bundle.getMessage("RouterTrain");
054    public static final String STATUS_NOT_THIS_TRAIN_PREFIX =
055            STATUS_NOT_THIS_TRAIN.substring(0, STATUS_NOT_THIS_TRAIN.indexOf('('));
056    protected static final String STATUS_NOT_ABLE = Bundle.getMessage("RouterNotAble");
057    protected static final String STATUS_ROUTER_DISABLED = Bundle.getMessage("RouterDisabled");
058
059    private String _status = "";
060    private Train _train = null;
061    PrintWriter _buildReport = null; // build report
062    Date _startTime; // when routing started
063
064    private static final String SEVEN = Setup.BUILD_REPORT_VERY_DETAILED;
065    private boolean _addtoReport = false;
066    private boolean _addtoReportVeryDetailed = false;
067
068    /**
069     * Returns the status of the router when using the setDestination() for a
070     * car.
071     *
072     * @return Track.OKAY, STATUS_NOT_THIS_TRAIN, STATUS_NOT_ABLE,
073     *         STATUS_ROUTER_DISABLED, or the destination track status is
074     *         there's an issue.
075     */
076    public String getStatus() {
077        return _status;
078    }
079
080    /**
081     * Determines if car can be routed to the destination track
082     * 
083     * @param car         the car being tested
084     * @param train       the first train servicing the car, can be null
085     * @param track       the destination track, can not be null
086     * @param buildReport the report, can be null
087     * @return true if the car can be routed to the track
088     */
089    public boolean isCarRouteable(Car car, Train train, Track track, PrintWriter buildReport) {
090        addLine(buildReport, SEVEN, Bundle.getMessage("RouterIsCarRoutable",
091                car.toString(), car.getLocationName(), car.getTrackName(), car.getLoadName(),
092                track.getLocation().getName(), track.getName()));
093        return isCarRouteable(car, train, track.getLocation(), track, buildReport);
094    }
095
096    public boolean isCarRouteable(Car car, Train train, Location destination, Track track, PrintWriter buildReport) {
097        Car c = car.copy();
098        c.setTrack(car.getTrack());
099        c.setFinalDestination(destination);
100        c.setFinalDestinationTrack(track);
101        c.setScheduleItemId(car.getScheduleItemId());
102        boolean results = setDestination(c, train, buildReport);
103        c.setDestination(null, null); // clear router car destinations
104        c.setFinalDestinationTrack(null);
105        // transfer route path info
106        car.setRoutePath(c.getRoutePath());
107        return results;
108    }
109
110    /**
111     * Attempts to set the car's destination if a final destination exists. Only
112     * sets the car's destination if the train is part of the car's route.
113     *
114     * @param car         the car to route
115     * @param train       the first train to carry this car, can be null
116     * @param buildReport PrintWriter for build report, and can be null
117     * @return true if car can be routed.
118     */
119    public boolean setDestination(Car car, Train train, PrintWriter buildReport) {
120        if (car.getTrack() == null || car.getFinalDestination() == null) {
121            return false;
122        }
123        _startTime = new Date();
124        _status = Track.OKAY;
125        _train = train;
126        _buildReport = buildReport;
127        _addtoReport = Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED) ||
128                Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED);
129        _addtoReportVeryDetailed = Setup.getRouterBuildReportLevel().equals(Setup.BUILD_REPORT_VERY_DETAILED);
130        log.debug("Car ({}) at location ({}, {}) final destination ({}, {}) car routing begins", car,
131                car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
132                car.getFinalDestinationTrackName());
133        if (_train != null) {
134            log.debug("Routing using train ({})", train.getName());
135        }
136        // is car part of kernel?
137        if (car.getKernel() != null && !car.isLead()) {
138            return false;
139        }
140        // note clone car has the car's "final destination" as its destination
141        Car clone = clone(car);
142        // Note the following test doesn't check for car length which is what we
143        // want.
144        // Also ignores spur schedule since the car's destination is already
145        // set.
146        _status = clone.checkDestination(clone.getDestination(), clone.getDestinationTrack());
147        if (!_status.equals(Track.OKAY)) {
148            addLine(Bundle.getMessage("RouterCanNotDeliverCar",
149                    car.toString(), car.getFinalDestinationName(), car.getFinalDestinationTrackName(),
150                    _status, (car.getFinalDestinationTrack() == null ? Bundle.getMessage("RouterDestination")
151                            : car.getFinalDestinationTrack().getTrackTypeName())));
152            return false;
153        }
154        // check to see if car has a destination track or one is available
155        if (!checkForDestinationTrack(clone)) {
156            return false; // no destination track found
157        }
158        // check to see if car will move to destination using a single train
159        if (checkForSingleTrain(car, clone)) {
160            return true; // a single train can service this car
161        }
162        if (!Setup.isCarRoutingEnabled()) {
163            log.debug("Car ({}) final destination ({}) is not served directly by any train", car,
164                    car.getFinalDestinationName()); // NOI18N
165            _status = STATUS_ROUTER_DISABLED;
166            car.setFinalDestination(null);
167            car.setFinalDestinationTrack(null);
168            return false;
169        }
170        log.debug("Car ({}) final destination ({}) is not served by a single train", car,
171                car.getFinalDestinationName());
172        // was the request for a local move? Try multiple trains to move car
173        if (car.getLocationName().equals(car.getFinalDestinationName())) {
174            addLine(Bundle.getMessage("RouterCouldNotFindTrain",
175                    car.getLocationName(), car.getTrackName(), car.getFinalDestinationName(),
176                    car.getFinalDestinationTrackName()));
177        }
178        if (_addtoReport) {
179            addLine(Bundle.getMessage("RouterBeginTwoTrain",
180                    car.toString(), car.getLocationName(), car.getFinalDestinationName()));
181        }
182
183        setupLists();
184        excludeTrains(car);
185        excludeTracks();
186
187        // first try using 2 trains and an interchange track to route the car
188        if (setCarDestinationTwoTrainsInterchange(car)) {
189            if (car.getDestination() == null) {
190                log.debug(
191                        "Was able to find a route via classification/interchange track, but not using specified train" +
192                                " or car destination not set, try again using yard tracks"); // NOI18N
193                if (setCarDestinationTwoTrainsYard(car)) {
194                    log.debug("Was able to find route via yard ({}, {}) for car ({})", car.getDestinationName(),
195                            car.getDestinationTrackName(), car);
196                }
197            } else {
198                log.debug("Was able to find route via interchange ({}, {}) for car ({})", car.getDestinationName(),
199                        car.getDestinationTrackName(), car);
200            }
201            if (_addtoReportVeryDetailed) {
202                addLine(Bundle.getMessage("RouterTwoTrainsSuccess", car.toString()));
203            }
204            // now try 2 trains using a yard track
205        } else if (setCarDestinationTwoTrainsYard(car)) {
206            log.debug("Was able to find route via yard ({}, {}) for car ({}) using two trains",
207                    car.getDestinationName(), car.getDestinationTrackName(), car);
208            if (_addtoReportVeryDetailed) {
209                addLine(Bundle.getMessage("RouterTwoTrainsSuccess", car.toString()));
210            }
211            // now try 3 or more trains to route car, but not through staging
212        } else if (setCarDestinationMultipleTrains(car, false)) {
213            log.debug("Was able to find multiple train route for car ({})", car);
214            // now try 2 trains using a staging track to connect
215        } else if (setCarDestinationTwoTrainsStaging(car)) {
216            log.debug("Was able to find route via staging ({}, {}) for car ({}) using two trains",
217                    car.getDestinationName(), car.getDestinationTrackName(), car);
218            // now try 3 or more trains to route car, include staging if enabled
219        } else if (setCarDestinationMultipleTrains(car, true)) {
220            log.debug("Was able to find multiple train route for car ({}) through staging", car);
221        } else {
222            log.debug("Wasn't able to set route for car ({}) took {} mSec", car,
223                    new Date().getTime() - _startTime.getTime());
224            _status = STATUS_NOT_ABLE;
225            return false; // maybe next time
226        }
227        return true; // car's destination has been set
228    }
229
230    /*
231     * Checks to see if the car has a destination track, no destination track,
232     * searches for one. returns true if the car has a destination track or if
233     * there's one available.
234     */
235    private boolean checkForDestinationTrack(Car clone) {
236        if (clone.getDestination() != null && clone.getDestinationTrack() == null) {
237            // determine if there's a track that can service the car
238            String status = "";
239            for (Track track : clone.getDestination().getTracksList()) {
240                status = track.isRollingStockAccepted(clone);
241                if (status.equals(Track.OKAY) || status.startsWith(Track.LENGTH)) {
242                    log.debug("Track ({}) will accept car ({})", track.getName(), clone.toString());
243                    break;
244                }
245            }
246            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
247                addLine(_status = Bundle.getMessage("RouterNoTracks",
248                        clone.getDestinationName(), clone.toString()));
249                return false;
250            }
251        }
252        return true;
253    }
254
255    /**
256     * Checks to see if a single train can transport car to its final
257     * destination. Special case if car is departing staging.
258     *
259     * @return true if single train can transport car to its final destination.
260     */
261    private boolean checkForSingleTrain(Car car, Car clone) {
262        boolean trainServicesCar = false; // true the specified train can service the car
263        Train testTrain = null;
264        if (_train != null) {
265            trainServicesCar = _train.isServiceable(_buildReport, clone);
266        }
267        if (trainServicesCar) {
268            testTrain = _train; // use the specified train
269            log.debug("Train ({}) can service car ({})", _train.getName(), car.toString());
270        } else if (_train != null && !_train.getServiceStatus().equals(Train.NONE)) {
271            // _train isn't able to service car
272            // determine if car was attempting to go to the train's termination staging
273            String trackName = car.getFinalDestinationTrackName();
274            if (car.getFinalDestinationTrack() == null &&
275                    car.getFinalDestinationName().equals(_train.getTrainTerminatesName()) &&
276                    _train.getTerminationTrack() != null) {
277                trackName = _train.getTerminationTrack().getName(); // use staging track
278            }
279            // report that train can't service car
280            addLine(Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(),
281                    car.getFinalDestinationName(), trackName, _train.getServiceStatus()));
282            if (!car.getTrack().isStaging() &&
283                    !_train.isServiceAllCarsWithFinalDestinationsEnabled()) {
284                _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
285                return true; // temporary issue with train moves, length, or destination track length
286            }
287        }
288        // Determines if specified train can service car out of staging.
289        // Note that the router code will try to route the car using
290        // two or more trains just to get the car out of staging.
291        if (car.getTrack().isStaging() && _train != null && !trainServicesCar) {
292            addLine(Bundle.getMessage("RouterTrainCanNotStaging",
293                    _train.getName(), car.toString(), car.getLocationName(),
294                    clone.getDestinationName(), clone.getDestinationTrackName()));
295            if (!_train.getServiceStatus().equals(Train.NONE)) {
296                addLine(_train.getServiceStatus());
297            }
298            addLine(Bundle.getMessage("RouterStagingTryRouting", car.toString(), clone.getLocationName(),
299                    clone.getDestinationName(), clone.getDestinationTrackName()));
300            // note that testTrain = null, return false
301        } else if (!trainServicesCar) {
302            List<Train> excludeTrains = new ArrayList<>(Arrays.asList(_train));
303            testTrain = trainManager.getTrainForCar(clone, excludeTrains, _buildReport, true);
304        }
305        // report that another train could transport the car
306        if (testTrain != null &&
307                _train != null &&
308                !trainServicesCar &&
309                _train.isServiceAllCarsWithFinalDestinationsEnabled()) {
310            // log.debug("Option to service all cars with a final destination is enabled");
311            addLine(Bundle.getMessage("RouterOptionToCarry",
312                    _train.getName(), testTrain.getName(), car.toString(),
313                    clone.getDestinationName(), clone.getDestinationTrackName()));
314            testTrain = null; // return false
315        }
316        if (testTrain != null) {
317            return finishRouteUsingOneTrain(testTrain, car, clone);
318        }
319        return false;
320    }
321
322    /**
323     * A single train can service the car. Provide various messages to build
324     * report detailing which train can service the car. Also checks to see if
325     * the needs to go the alternate track or yard track if the car's final
326     * destination track is full. Returns false if car is stuck in staging. Sets
327     * the car's destination if specified _train is available
328     *
329     * @return true for all cases except if car is departing staging and is
330     *         stuck there.
331     */
332    private boolean finishRouteUsingOneTrain(Train testTrain, Car car, Car clone) {
333        addLine(Bundle.getMessage("RouterTrainCanTransport", testTrain.getName(), car.toString(),
334                car.getTrack().getTrackTypeName(), car.getLocationName(), car.getTrackName(),
335                clone.getDestinationName(), clone.getDestinationTrackName()));
336        showRoute(car, new ArrayList<>(Arrays.asList(testTrain)),
337                new ArrayList<>(Arrays.asList(car.getFinalDestinationTrack())));
338        // don't modify car if a train wasn't specified
339        if (_train == null) {
340            return true; // done, car can be routed
341        }
342        // now check to see if specified train can service car directly
343        else if (_train != testTrain) {
344            addLine(Bundle.getMessage("TrainDoesNotServiceCar", _train.getName(), car.toString(),
345                    clone.getDestinationName(), clone.getDestinationTrackName()));
346            _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{testTrain.getName()});
347            return true; // car can be routed, but not by this train!
348        }
349        _status = car.setDestination(clone.getDestination(), clone.getDestinationTrack());
350        if (_status.equals(Track.OKAY)) {
351            return true; // done, car has new destination
352        }
353        addLine(Bundle.getMessage("RouterCanNotDeliverCar", car.toString(), clone.getDestinationName(),
354                clone.getDestinationTrackName(), _status,
355                (clone.getDestinationTrack() == null ? Bundle.getMessage("RouterDestination")
356                        : clone.getDestinationTrack().getTrackTypeName())));
357        // check to see if an alternative track was specified
358        if ((_status.startsWith(Track.LENGTH) || _status.startsWith(Track.SCHEDULE)) &&
359                clone.getDestinationTrack() != null &&
360                clone.getDestinationTrack().getAlternateTrack() != null &&
361                clone.getDestinationTrack().getAlternateTrack() != car.getTrack()) {
362            String status = car.setDestination(clone.getDestination(), clone.getDestinationTrack().getAlternateTrack());
363            if (status.equals(Track.OKAY)) {
364                if (_train.isServiceable(car)) {
365                    addLine(Bundle.getMessage("RouterSendCarToAlternative",
366                            car.toString(), clone.getDestinationTrack().getAlternateTrack().getName(),
367                            clone.getDestination().getName()));
368                    return true; // car is going to alternate track
369                }
370                addLine(Bundle.getMessage("RouterNotSendCarToAlternative", _train.getName(), car.toString(),
371                        clone.getDestinationTrack().getAlternateTrack().getName(),
372                        clone.getDestination().getName()));
373            } else {
374                addLine(Bundle.getMessage("RouterAlternateFailed",
375                        clone.getDestinationTrack().getAlternateTrack().getName(), status));
376            }
377        } else if (clone.getDestinationTrack() != null &&
378                clone.getDestinationTrack().getAlternateTrack() != null &&
379                clone.getDestinationTrack().getAlternateTrack() == car.getTrack()) {
380            // state that car is spotted at the alternative track
381            addLine(Bundle.getMessage("RouterAtAlternate",
382                    car.toString(), clone.getDestinationTrack().getAlternateTrack().getName(),
383                    clone.getLocationName(), clone.getDestinationTrackName()));
384        } else if (car.getLocation() == clone.getDestination()) {
385            // state that alternative and yard track options are not available
386            // if car is at final destination
387            addLine(Bundle.getMessage("RouterIgnoreAlternate", car.toString(), car.getLocationName()));
388        }
389        // check to see if spur was full, if so, forward to yard if possible
390        if (Setup.isForwardToYardEnabled() &&
391                _status.startsWith(Track.LENGTH) &&
392                car.getLocation() != clone.getDestination()) {
393            addLine(Bundle.getMessage("RouterSpurFull",
394                    clone.getDestinationName(), clone.getDestinationTrackName(), clone.getDestinationName()));
395            Location dest = clone.getDestination();
396            List<Track> yards = dest.getTracksByMoves(Track.YARD);
397            log.debug("Found {} yard(s) at destination ({})", yards.size(), clone.getDestinationName());
398            for (Track track : yards) {
399                String status = car.setDestination(dest, track);
400                if (status.equals(Track.OKAY)) {
401                    if (!_train.isServiceable(car)) {
402                        log.debug("Train ({}) can not deliver car ({}) to yard ({})", _train.getName(), car,
403                                track.getName());
404                        continue;
405                    }
406                    addLine(Bundle.getMessage("RouterSendCarToYard",
407                            car.toString(), dest.getName(), track.getName(), dest.getName()));
408                    return true; // car is going to a yard
409                } else {
410                    addLine(Bundle.getMessage("RouterCanNotUseYard",
411                            track.getLocation().getName(), track.getName(), status));
412                }
413            }
414            addLine(Bundle.getMessage("RouterNoYardTracks",
415                    dest.getName(), car.toString()));
416        }
417        car.setDestination(null, null);
418        if (car.getTrack().isStaging()) {
419            addLine(Bundle.getMessage("RouterStagingTryRouting", car.toString(), clone.getLocationName(),
420                    clone.getDestinationName(), clone.getDestinationTrackName()));
421            return false; // try 2 or more trains
422        }
423        return true; // able to route, but unable to set the car's destination
424    }
425
426    private void setupLists() {
427        _nextLocationTracks.clear();
428        _next2ndLocationTracks.clear();
429        _next3rdLocationTracks.clear();
430        _next4thLocationTracks.clear();
431        _lastLocationTracks.clear();
432        _otherLocationTracks.clear();
433        _nextLocationTrains.clear();
434        _lastLocationTrains.clear();
435        _listTrains.clear();
436    }
437
438    private void excludeTrains(Car car) {
439        if (_addtoReportVeryDetailed) {
440            addLine(BLANK_LINE);
441            addLine(Bundle.getMessage("RouterExcludeTrains", car.toString(),
442                    car.getTypeName(), car.getLoadType().toLowerCase(), car.getLoadName(), car.getRoadName(),
443                    car.getBuilt(), car.getOwnerName()));
444        }
445        _excludeTrains = trainManager.getExcludeTrainListForCar(car, _buildReport);
446    }
447    
448    /*
449     * No routing through alternate tracks.  List them.
450     */
451    private void excludeTracks() {
452        if (_addtoReportVeryDetailed) {
453            addLine(BLANK_LINE);
454            addLine(Bundle.getMessage("RouterExcludeAltTracks"));
455            List<Track> tracks = locationManager.getTracks(null);
456            for (Track track : tracks) {
457                if (track.isAlternate() &&
458                        (track.getTrackType().equals(Track.INTERCHANGE) ||
459                                track.getTrackType().equals(Track.YARD) && Setup.isCarRoutingViaYardsEnabled())) {
460                    addLine(Bundle.getMessage("RouterExcludeAltTrack", track.getTrackTypeName(),
461                            track.getLocation().getName(), track.getName()));
462                }
463            }
464        }
465    }
466
467    /**
468     * Sets a car's destination to an interchange track if two trains can route
469     * the car.
470     *
471     * @param car the car to be routed
472     * @return true if car's destination has been modified to an interchange.
473     *         False if an interchange track wasn't found that could service the
474     *         car's final destination.
475     */
476    private boolean setCarDestinationTwoTrainsInterchange(Car car) {
477        return setCarDestinationTwoTrains(car, Track.INTERCHANGE);
478    }
479
480    /**
481     * Sets a car's destination to a yard track if two trains can route the car.
482     *
483     * @param car the car to be routed
484     * @return true if car's destination has been modified to a yard. False if a
485     *         yard track wasn't found that could service the car's final
486     *         destination.
487     */
488    private boolean setCarDestinationTwoTrainsYard(Car car) {
489        if (Setup.isCarRoutingViaYardsEnabled()) {
490            return setCarDestinationTwoTrains(car, Track.YARD);
491        }
492        return false;
493    }
494
495    /**
496     * Sets a car's destination to a staging track if two trains can route the
497     * car.
498     *
499     * @param car the car to be routed
500     * @return true if car's destination has been modified to a staging track.
501     *         False if a staging track wasn't found that could service the
502     *         car's final destination.
503     */
504    private boolean setCarDestinationTwoTrainsStaging(Car car) {
505        if (Setup.isCarRoutingViaStagingEnabled()) {
506            addLine(BLANK_LINE);
507            addLine(Bundle.getMessage("RouterAttemptStaging", car.toString(),
508                    car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
509            return setCarDestinationTwoTrains(car, Track.STAGING);
510        }
511        return false;
512    }
513
514    /*
515     * Note that this routine loads the last set of tracks and trains that can
516     * service the car to its final location. This routine attempts to find a
517     * "two" train route by cycling through various interchange, yard, and
518     * staging tracks searching for a second train that can pull the car from
519     * the track and deliver the car to the its destination. Then the program
520     * determines if the train being built or another train (first) can deliver
521     * the car to the track from its current location. If successful, a two
522     * train route was found, and returns true.
523     */
524    private boolean setCarDestinationTwoTrains(Car car, String trackType) {
525        Car testCar = clone(car); // reload
526        log.debug("Two train routing, find {} track for car ({}) final destination ({}, {})", trackType, car,
527                testCar.getDestinationName(), testCar.getDestinationTrackName());
528        if (_addtoReportVeryDetailed) {
529            addLine(BLANK_LINE);
530            addLine(Bundle.getMessage("RouterFindTrack", Track.getTrackTypeName(trackType), car.toString(),
531                    testCar.getDestinationName(), testCar.getDestinationTrackName()));
532        }
533        boolean foundRoute = false;
534        // now search for a yard or interchange that a train can pick up and
535        // deliver the car to its destination
536        List<Track> tracks = getTracks(car, testCar, trackType);
537        for (Track track : tracks) {
538            if (_addtoReportVeryDetailed) {
539                addLine(BLANK_LINE);
540                addLine(Bundle.getMessage("RouterFoundTrack",
541                        Track.getTrackTypeName(trackType), track.getLocation().getName(),
542                        track.getName(), car.toString()));
543            }
544            // test to see if there's a train that can deliver the car to its
545            // final location
546            testCar.setTrack(track);
547            testCar.setDestination(car.getFinalDestination());
548            // note that destination track can be null
549            testCar.setDestinationTrack(car.getFinalDestinationTrack());
550            Train secondTrain = trainManager.getTrainForCar(testCar, _excludeTrains, _buildReport, false);
551            if (secondTrain == null) {
552                // maybe the train being built can service the car?
553                String specified = canSpecifiedTrainService(testCar);
554                if (specified.equals(NOT_NOW)) {
555                    secondTrain = _train;
556                } else {
557                    if (_addtoReportVeryDetailed) {
558                        addLine(Bundle.getMessage("RouterNotFindTrain", testCar.toString(),
559                                Track.getTrackTypeName(trackType), track.getLocation().getName(), track.getName(),
560                                testCar.getDestinationName(), testCar.getDestinationTrackName()));
561                    }
562                    continue;
563                }
564            }
565            if (_addtoReportVeryDetailed) {
566                addLine(Bundle.getMessage("RouterTrainCanTransport",
567                        secondTrain.getName(), car.toString(), testCar.getTrack().getTrackTypeName(),
568                        testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(),
569                        testCar.getDestinationTrackName()));
570            }
571            // Save the "last" tracks for later use if needed
572            _lastLocationTracks.add(track);
573            _lastLocationTrains.add(secondTrain);
574            // now try to forward car to this track
575            testCar.setTrack(car.getTrack()); // restore car origin
576            testCar.setDestination(track.getLocation());
577            testCar.setDestinationTrack(track);
578            // determine if car can be transported from current location to this
579            // interchange, yard, or staging track
580            // Now find a train that will transport the car to this track
581            Train firstTrain = null;
582            String specified = canSpecifiedTrainService(testCar);
583            if (specified.equals(YES)) {
584                firstTrain = _train;
585            } else if (specified.equals(NOT_NOW)) {
586                // found a two train route for this car, show the car's route
587                List<Train> trains = new ArrayList<>(Arrays.asList(_train, secondTrain));
588                tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack()));
589                showRoute(car, trains, tracks);
590
591                addLine(Bundle.getMessage("RouterTrainCanNotDueTo",
592                        _train.getName(), car.toString(), track.getLocation().getName(), track.getName(),
593                        _train.getServiceStatus()));
594                foundRoute = true; // issue is route moves or train length
595            } else {
596                firstTrain = trainManager.getTrainForCar(testCar, _excludeTrains, _buildReport, false);
597            }
598            // check to see if a train or trains with the same route is delivering and pulling the car to an interchange track
599            if (firstTrain != null &&
600                    firstTrain.getRoute() == secondTrain.getRoute() &&
601                    track.isInterchange() &&
602                    track.getPickupOption().equals(Track.ANY)) {
603                if (_addtoReportVeryDetailed) {
604                    addLine(Bundle.getMessage("RouterSameInterchange", firstTrain.getName(),
605                            track.getLocation().getName(), track.getName()));
606                }
607                List<Train> excludeTrains = new ArrayList<>(Arrays.asList(firstTrain));
608                firstTrain = trainManager.getTrainForCar(testCar, excludeTrains, _buildReport, true);
609            }
610            if (firstTrain == null && _addtoReportVeryDetailed) {
611                addLine(Bundle.getMessage("RouterNotFindTrain", testCar.toString(),
612                        testCar.getTrack().getTrackTypeName(), testCar.getTrack().getLocation().getName(),
613                        testCar.getTrack().getName(), testCar.getDestinationName(), testCar.getDestinationTrackName()));
614            }
615            // Can the specified train carry this car out of staging?
616            if (_train != null && car.getTrack().isStaging() && !specified.equals(YES)) {
617                if (_addtoReport) {
618                    addLine(Bundle.getMessage("RouterTrainCanNot",
619                            _train.getName(), car.toString(), car.getLocationName(),
620                            car.getTrackName(), track.getLocation().getName(), track.getName()));
621                }
622                continue; // can't use this train
623            }
624            // Is the option for the specified train carry this car?
625            if (firstTrain != null &&
626                    _train != null &&
627                    _train.isServiceAllCarsWithFinalDestinationsEnabled() &&
628                    !specified.equals(YES)) {
629                if (_addtoReport) {
630                    addLine(Bundle.getMessage("RouterOptionToCarry",
631                            _train.getName(), firstTrain.getName(), car.toString(),
632                            track.getLocation().getName(), track.getName()));
633                }
634                continue; // can't use this train
635            }
636            if (firstTrain != null) {
637                foundRoute = true; // found a route
638                if (_addtoReportVeryDetailed) {
639                    addLine(Bundle.getMessage("RouterTrainCanTransport", firstTrain.getName(), car.toString(),
640                            testCar.getTrack().getTrackTypeName(),
641                            testCar.getLocationName(), testCar.getTrackName(), testCar.getDestinationName(),
642                            testCar.getDestinationTrackName()));
643                }
644                // found a two train route for this car, show the car's route
645                List<Train> trains = new ArrayList<>(Arrays.asList(firstTrain, secondTrain));
646                tracks = new ArrayList<>(Arrays.asList(track, car.getFinalDestinationTrack()));
647                showRoute(car, trains, tracks);
648
649                _status = car.checkDestination(track.getLocation(), track);
650                if (_status.startsWith(Track.LENGTH)) {
651                    // if the issue is length at the interim track, add message
652                    // to build report
653                    addLine(Bundle.getMessage("RouterCanNotDeliverCar",
654                            car.toString(), track.getLocation().getName(), track.getName(),
655                            _status, track.getTrackTypeName()));
656                    continue;
657                }
658                if (_status.equals(Track.OKAY)) {
659                    // only set car's destination if specified train can service
660                    // car
661                    if (_train != null && _train != firstTrain) {
662                        addLine(Bundle.getMessage("TrainDoesNotServiceCar",
663                                _train.getName(), car.toString(), testCar.getDestinationName(),
664                                testCar.getDestinationTrackName()));
665                        _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{firstTrain.getName()});
666                        continue;// found a route but it doesn't start with the
667                                 // specified train
668                    }
669                    // is this the staging track assigned to the specified
670                    // train?
671                    if (track.isStaging() &&
672                            firstTrain.getTerminationTrack() != null &&
673                            firstTrain.getTerminationTrack() != track) {
674                        addLine(Bundle.getMessage("RouterTrainIntoStaging", firstTrain.getName(),
675                                firstTrain.getTerminationTrack().getLocation().getName(),
676                                firstTrain.getTerminationTrack().getName()));
677                        continue;
678                    }
679                    _status = car.setDestination(track.getLocation(), track);
680                    if (_addtoReport) {
681                        addLine(Bundle.getMessage("RouterTrainCanService",
682                                firstTrain.getName(), car.toString(), car.getLocationName(), car.getTrackName(),
683                                Track.getTrackTypeName(trackType), track.getLocation().getName(), track.getName()));
684                    }
685                    return true; // the specified train and another train can
686                                 // carry the car to its destination
687                }
688            }
689        }
690        if (foundRoute) {
691            if (_train != null) {
692                _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
693            } else {
694                _status = STATUS_NOT_ABLE;
695            }
696        }
697        return foundRoute;
698    }
699
700    /**
701     * This routine builds a set of tracks that could be used for routing. It
702     * also lists all of the tracks that can't be used.
703     * 
704     * @param car       The car being routed
705     * @param testCar   the test car
706     * @param trackType the type of track used for routing
707     * @return list of usable tracks
708     */
709    private List<Track> getTracks(Car car, Car testCar, String trackType) {
710        List<Track> inTracks = locationManager.getTracksByMoves(trackType);
711        List<Track> tracks = new ArrayList<Track>();
712        for (Track track : inTracks) {
713            if (car.getTrack() == track || car.getFinalDestinationTrack() == track) {
714                continue; // don't use car's current track
715            }
716            // can't use staging if car's load can be modified
717            if (trackType.equals(Track.STAGING) && track.isModifyLoadsEnabled()) {
718                if (_addtoReportVeryDetailed) {
719                    addLine(Bundle.getMessage("RouterStagingExcluded",
720                            track.getLocation().getName(), track.getName()));
721                }
722                continue;
723            }
724            String status = track.isRollingStockAccepted(testCar);
725            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
726                if (_addtoReportVeryDetailed) {
727                    addLine(Bundle.getMessage("RouterCanNotDeliverCar",
728                            car.toString(), track.getLocation().getName(), track.getName(),
729                            status, track.getTrackTypeName()));
730                }
731                continue;
732            }
733            tracks.add(track);
734        }
735        return tracks;
736    }
737
738    /*
739     * Note that "last" set of location/tracks (_lastLocationTracks) was loaded
740     * by setCarDestinationTwoTrains. The following code builds two additional
741     * sets of location/tracks called "next" (_nextLocationTracks) and "other"
742     * (_otherLocationTracks). "next" is the next set of location/tracks that
743     * the car can reach by a single train. "last" is the last set of
744     * location/tracks that services the cars final destination. And "other" is
745     * the remaining sets of location/tracks that are not "next" or "last". The
746     * code then tries to connect the "next" and "last" location/track sets with
747     * a train that can service the car. If successful, that would be a three
748     * train route for the car. If not successful, the code than tries
749     * combinations of "next", "other" and "last" location/tracks to create a
750     * route for the car.
751     */
752    private boolean setCarDestinationMultipleTrains(Car car, boolean useStaging) {
753        if (useStaging && !Setup.isCarRoutingViaStagingEnabled())
754            return false; // routing via staging is disabled
755
756        if (_addtoReportVeryDetailed) {
757            addLine(BLANK_LINE);
758        }
759        if (_lastLocationTracks.isEmpty()) {
760            if (useStaging) {
761                addLine(Bundle.getMessage("RouterCouldNotFindStaging",
762                        car.getFinalDestinationName()));
763            } else {
764                addLine(Bundle.getMessage("RouterCouldNotFindLast",
765                        car.getFinalDestinationName()));
766            }
767            return false;
768        }
769
770        Car testCar = clone(car); // reload
771        // build the "next" and "other" location/tracks
772        if (_nextLocationTracks.isEmpty() && _otherLocationTracks.isEmpty()) {
773            loadInterchangeAndYards(car, testCar);
774        }
775        // add staging if requested
776        if (useStaging) {
777            loadStaging(car, testCar);
778        }
779
780        if (_nextLocationTracks.isEmpty()) {
781            addLine(Bundle.getMessage("RouterCouldNotFindLoc",
782                    car.getLocationName()));
783            return false;
784        }
785
786        addLine(Bundle.getMessage("RouterTwoTrainsFailed", car));
787
788        if (_addtoReport) {
789            // tracks that could be the very next destination for the car
790            for (Track t : _nextLocationTracks) {
791                addLine(Bundle.getMessage("RouterNextTrack", t.getTrackTypeName(), t.getLocation().getName(),
792                        t.getName(), car, car.getLocationName(), car.getTrackName(),
793                        _nextLocationTrains.get(_nextLocationTracks.indexOf(t))));
794            }
795            // tracks that could be the next to last destination for the car
796            for (Track t : _lastLocationTracks) {
797                addLine(Bundle.getMessage("RouterLastTrack",
798                        t.getTrackTypeName(), t.getLocation().getName(), t.getName(), car,
799                        car.getFinalDestinationName(), car.getFinalDestinationTrackName(),
800                        _lastLocationTrains.get(_lastLocationTracks.indexOf(t))));
801            }
802        }
803        if (_addtoReportVeryDetailed) {
804            // tracks that are not the next or the last list
805            for (Track t : _otherLocationTracks) {
806                addLine(Bundle.getMessage("RouterOtherTrack", t.getTrackTypeName(), t.getLocation().getName(),
807                        t.getName(), car));
808            }
809            addLine(BLANK_LINE);
810        }
811        boolean foundRoute = routeUsing3Trains(car);
812        if (!foundRoute) {
813            log.debug("Using 3 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
814            foundRoute = routeUsing4Trains(car);
815        }
816        if (!foundRoute) {
817            log.debug("Using 4 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
818            foundRoute = routeUsing5Trains(car);
819        }
820        if (!foundRoute) {
821            log.debug("Using 5 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
822            foundRoute = routeUsing6Trains(car);
823        }
824        if (!foundRoute) {
825            log.debug("Using 6 trains to route car to ({}) was unsuccessful", car.getFinalDestinationName());
826            foundRoute = routeUsing7Trains(car);
827        }
828        if (!foundRoute) {
829            addLine(Bundle.getMessage("RouterNotAbleToRoute", car.toString(), car.getLocationName(),
830                    car.getTrackName(), car.getFinalDestinationName(), car.getFinalDestinationTrackName()));
831        }
832        return foundRoute;
833    }
834
835    private void loadInterchangeAndYards(Car car, Car testCar) {
836        List<Track> tracks;
837        // start with interchanges
838        tracks = locationManager.getTracksByMoves(Track.INTERCHANGE);
839        loadTracksAndTrains(car, testCar, tracks);
840        // next load yards if enabled
841        if (Setup.isCarRoutingViaYardsEnabled()) {
842            tracks = locationManager.getTracksByMoves(Track.YARD);
843            loadTracksAndTrains(car, testCar, tracks);
844        }
845    }
846
847    private void loadStaging(Car car, Car testCar) {
848        // add staging if requested
849        List<Track> stagingTracks = locationManager.getTracksByMoves(Track.STAGING);
850        List<Track> tracks = new ArrayList<Track>();
851        for (Track staging : stagingTracks) {
852            if (!staging.isModifyLoadsEnabled()) {
853                tracks.add(staging);
854            }
855        }
856        loadTracksAndTrains(car, testCar, tracks);
857    }
858
859    private boolean routeUsing3Trains(Car car) {
860        addLine(Bundle.getMessage("RouterNTrains", "3", car.getFinalDestinationName(),
861                car.getFinalDestinationTrackName()));
862        Car testCar = clone(car); // reload
863        boolean foundRoute = false;
864        for (Track nlt : _nextLocationTracks) {
865            for (Track llt : _lastLocationTracks) {
866                // does a train service these two locations?
867                Train middleTrain =
868                        getTrainForCar(testCar, nlt, llt, _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
869                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
870                if (middleTrain != null) {
871                    log.debug("Found 3 train route, setting car destination ({}, {})", nlt.getLocation().getName(),
872                            nlt.getName());
873                    foundRoute = true;
874                    // show the car's route by building an ordered list of
875                    // trains and tracks
876                    List<Train> trains = new ArrayList<>(
877                            Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain,
878                                    _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
879                    List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, llt, car.getFinalDestinationTrack()));
880                    showRoute(car, trains, tracks);
881                    if (finshSettingRouteFor(car, nlt)) {
882                        return true; // done 3 train routing
883                    }
884                    break; // there was an issue with the first stop in the
885                           // route
886                }
887            }
888        }
889        return foundRoute;
890    }
891
892    private boolean routeUsing4Trains(Car car) {
893        addLine(Bundle.getMessage("RouterNTrains", "4", car.getFinalDestinationName(),
894                car.getFinalDestinationTrackName()));
895        Car testCar = clone(car); // reload
896        boolean foundRoute = false;
897        for (Track nlt : _nextLocationTracks) {
898            otherloop: for (Track mlt : _otherLocationTracks) {
899                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt,
900                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
901                if (middleTrain2 == null) {
902                    continue;
903                }
904                // build a list of tracks that are reachable from the 1st
905                // interchange
906                if (!_next2ndLocationTracks.contains(mlt)) {
907                    _next2ndLocationTracks.add(mlt);
908                    if (_addtoReport) {
909                        addLine(Bundle.getMessage("RouterNextHop", mlt.getTrackTypeName(), mlt.getLocation().getName(),
910                                mlt.getName(), car, nlt.getLocation().getName(), nlt.getName(),
911                                middleTrain2.getName()));
912                    }
913                }
914                for (Track llt : _lastLocationTracks) {
915                    Train middleTrain3 = getTrainForCar(testCar, mlt, llt, middleTrain2,
916                            _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
917                    if (middleTrain3 == null) {
918                        continue;
919                    }
920                    log.debug("Found 4 train route, setting car destination ({}, {})", nlt.getLocation().getName(),
921                            nlt.getName());
922                    foundRoute = true;
923                    // show the car's route by building an ordered list of
924                    // trains and tracks
925                    List<Train> trains = new ArrayList<>(
926                            Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2,
927                                    middleTrain3, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
928                    List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt, llt, car.getFinalDestinationTrack()));
929                    showRoute(car, trains, tracks);
930                    if (finshSettingRouteFor(car, nlt)) {
931                        return true; // done 4 train routing
932                    }
933                    break otherloop; // there was an issue with the first
934                                     // stop in the route
935                }
936            }
937        }
938        return foundRoute;
939    }
940
941    private boolean routeUsing5Trains(Car car) {
942        addLine(Bundle.getMessage("RouterNTrains", "5", car.getFinalDestinationName(),
943                car.getFinalDestinationTrackName()));
944        Car testCar = clone(car); // reload
945        boolean foundRoute = false;
946        for (Track nlt : _nextLocationTracks) {
947            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
948                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
949                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
950                if (middleTrain2 == null) {
951                    continue;
952                }
953                for (Track mlt2 : _otherLocationTracks) {
954                    if (_next2ndLocationTracks.contains(mlt2)) {
955                        continue;
956                    }
957                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
958                    if (middleTrain3 == null) {
959                        continue;
960                    }
961                    // build a list of tracks that are reachable from the 2nd
962                    // interchange
963                    if (!_next3rdLocationTracks.contains(mlt2)) {
964                        _next3rdLocationTracks.add(mlt2);
965                        if (_addtoReport) {
966                            addLine(Bundle.getMessage("RouterNextHop", mlt2.getTrackTypeName(),
967                                    mlt2.getLocation().getName(),
968                                    mlt2.getName(), car, mlt1.getLocation().getName(), mlt1.getName(),
969                                    middleTrain3.getName()));
970                        }
971                    }
972                    for (Track llt : _lastLocationTracks) {
973                        Train middleTrain4 = getTrainForCar(testCar, mlt2, llt, middleTrain3,
974                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
975                        if (middleTrain4 == null) {
976                            continue;
977                        }
978                        log.debug("Found 5 train route, setting car destination ({}, {})",
979                                nlt.getLocation().getName(),
980                                nlt.getName());
981                        foundRoute = true;
982                        // show the car's route by building an ordered list
983                        // of trains and tracks
984                        List<Train> trains = new ArrayList<>(Arrays.asList(
985                                _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), middleTrain2, middleTrain3,
986                                middleTrain4, _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
987                        List<Track> tracks =
988                                new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, llt, car.getFinalDestinationTrack()));
989                        showRoute(car, trains, tracks);
990                        if (finshSettingRouteFor(car, nlt)) {
991                            return true; // done 5 train routing
992                        }
993                        break otherloop; // there was an issue with the
994                                         // first stop in the route
995                    }
996                }
997            }
998        }
999        return foundRoute;
1000    }
1001
1002    private boolean routeUsing6Trains(Car car) {
1003        addLine(Bundle.getMessage("RouterNTrains", "6", car.getFinalDestinationName(),
1004                car.getFinalDestinationTrackName()));
1005        Car testCar = clone(car); // reload
1006        boolean foundRoute = false;
1007        for (Track nlt : _nextLocationTracks) {
1008            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
1009                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
1010                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
1011                if (middleTrain2 == null) {
1012                    continue;
1013                }
1014                for (Track mlt2 : _next3rdLocationTracks) {
1015                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
1016                    if (middleTrain3 == null) {
1017                        continue;
1018                    }
1019                    for (Track mlt3 : _otherLocationTracks) {
1020                        if (_next2ndLocationTracks.contains(mlt3) || _next3rdLocationTracks.contains(mlt3)) {
1021                            continue;
1022                        }
1023                        Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null);
1024                        if (middleTrain4 == null) {
1025                            continue;
1026                        }
1027                        if (!_next4thLocationTracks.contains(mlt3)) {
1028                            _next4thLocationTracks.add(mlt3);
1029                            if (_addtoReport) {
1030                                addLine(Bundle.getMessage("RouterNextHop", mlt3.getTrackTypeName(),
1031                                        mlt3.getLocation().getName(), mlt3.getName(), car, mlt2.getLocation().getName(),
1032                                        mlt2.getName(), middleTrain4.getName()));
1033                            }
1034                        }
1035                        for (Track llt : _lastLocationTracks) {
1036                            Train middleTrain5 = getTrainForCar(testCar, mlt3, llt, middleTrain4,
1037                                    _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
1038                            if (middleTrain5 == null) {
1039                                continue;
1040                            }
1041                            log.debug("Found 6 train route, setting car destination ({}, {})",
1042                                    nlt.getLocation().getName(), nlt.getName());
1043                            foundRoute = true;
1044                            // show the car's route by building an ordered
1045                            // list of trains and tracks
1046                            List<Train> trains = new ArrayList<>(
1047                                    Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
1048                                            middleTrain2, middleTrain3, middleTrain4, middleTrain5,
1049                                            _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
1050                            List<Track> tracks = new ArrayList<>(
1051                                    Arrays.asList(nlt, mlt1, mlt2, mlt3, llt, car.getFinalDestinationTrack()));
1052                            showRoute(car, trains, tracks);
1053                            // only set car's destination if specified train
1054                            // can service car
1055                            if (finshSettingRouteFor(car, nlt)) {
1056                                return true; // done 6 train routing
1057                            }
1058                            break otherloop; // there was an issue with the
1059                                             // first stop in the route
1060                        }
1061                    }
1062                }
1063            }
1064        }
1065        return foundRoute;
1066    }
1067
1068    private boolean routeUsing7Trains(Car car) {
1069        addLine(Bundle.getMessage("RouterNTrains", "7", car.getFinalDestinationName(),
1070                car.getFinalDestinationTrackName()));
1071        Car testCar = clone(car); // reload
1072        boolean foundRoute = false;
1073        for (Track nlt : _nextLocationTracks) {
1074            otherloop: for (Track mlt1 : _next2ndLocationTracks) {
1075                Train middleTrain2 = getTrainForCar(testCar, nlt, mlt1,
1076                        _nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)), null);
1077                if (middleTrain2 == null) {
1078                    continue;
1079                }
1080                for (Track mlt2 : _next3rdLocationTracks) {
1081                    Train middleTrain3 = getTrainForCar(testCar, mlt1, mlt2, middleTrain2, null);
1082                    if (middleTrain3 == null) {
1083                        continue;
1084                    }
1085                    for (Track mlt3 : _next4thLocationTracks) {
1086                        Train middleTrain4 = getTrainForCar(testCar, mlt2, mlt3, middleTrain3, null);
1087                        if (middleTrain4 == null) {
1088                            continue;
1089                        }
1090                        for (Track mlt4 : _otherLocationTracks) {
1091                            if (_next2ndLocationTracks.contains(mlt4) ||
1092                                    _next3rdLocationTracks.contains(mlt4) ||
1093                                    _next4thLocationTracks.contains(mlt4)) {
1094                                continue;
1095                            }
1096                            Train middleTrain5 = getTrainForCar(testCar, mlt3, mlt4, middleTrain4, null);
1097                            if (middleTrain5 == null) {
1098                                continue;
1099                            }
1100                            for (Track llt : _lastLocationTracks) {
1101                                Train middleTrain6 = getTrainForCar(testCar, mlt4, llt, middleTrain5,
1102                                        _lastLocationTrains.get(_lastLocationTracks.indexOf(llt)));
1103                                if (middleTrain6 == null) {
1104                                    continue;
1105                                }
1106                                log.debug("Found 7 train route, setting car destination ({}, {})",
1107                                        nlt.getLocation().getName(), nlt.getName());
1108                                foundRoute = true;
1109                                // show the car's route by building an ordered
1110                                // list of trains and tracks
1111                                List<Train> trains = new ArrayList<>(
1112                                        Arrays.asList(_nextLocationTrains.get(_nextLocationTracks.indexOf(nlt)),
1113                                                middleTrain2, middleTrain3, middleTrain4, middleTrain5, middleTrain6,
1114                                                _lastLocationTrains.get(_lastLocationTracks.indexOf(llt))));
1115                                List<Track> tracks = new ArrayList<>(Arrays.asList(nlt, mlt1, mlt2, mlt3, mlt4, llt,
1116                                        car.getFinalDestinationTrack()));
1117                                showRoute(car, trains, tracks);
1118                                // only set car's destination if specified train
1119                                // can service car
1120                                if (finshSettingRouteFor(car, nlt)) {
1121                                    return true; // done 7 train routing
1122                                }
1123                                break otherloop; // there was an issue with the
1124                                                 // first stop in the route
1125                            }
1126                        }
1127                    }
1128                }
1129            }
1130        }
1131        return foundRoute;
1132    }
1133
1134    /**
1135     * This method returns a train that is able to move the test car between the
1136     * fromTrack and the toTrack. The default for an interchange track is to not
1137     * allow the same train to spot and pull a car.
1138     * 
1139     * @param testCar   test car
1140     * @param fromTrack departure track
1141     * @param toTrack   arrival track
1142     * @param fromTrain train servicing fromTrack (previous drop to fromTrack)
1143     * @param toTrain   train servicing toTrack (pulls from the toTrack)
1144     * @return null if no train found, else a train able to move test car
1145     *         between fromTrack and toTrack.
1146     */
1147    private Train getTrainForCar(Car testCar, Track fromTrack, Track toTrack, Train fromTrain, Train toTrain) {
1148        testCar.setTrack(fromTrack); // car to this location and track
1149        testCar.setDestinationTrack(toTrack); // car to this destination & track
1150        List<Train> excludeTrains = new ArrayList<>();
1151        if (fromTrack.isInterchange() && fromTrack.getPickupOption().equals(Track.ANY)) {
1152            excludeTrains.add(fromTrain);
1153        }
1154        if (toTrack.isInterchange() && toTrack.getPickupOption().equals(Track.ANY)) {
1155            excludeTrains.add(toTrain);
1156        }
1157        // does a train service these two locations? 
1158        String key = fromTrack.getId() + toTrack.getId();
1159        Train train = _listTrains.get(key);
1160        if (train == null) {
1161            train = trainManager.getTrainForCar(testCar, excludeTrains, null, true);
1162            if (train != null) {
1163                _listTrains.put(key, train);
1164            } else {
1165                _listTrains.put(key, new Train("null", "null"));
1166            }
1167        } else if (train.getId().equals("null")) {
1168            return null;
1169        }
1170        return train;
1171
1172    }
1173
1174    private void showRoute(Car car, List<Train> trains, List<Track> tracks) {
1175        StringBuffer buf = new StringBuffer(
1176                Bundle.getMessage("RouterRouteForCar", car.toString(), car.getLocationName(), car.getTrackName()));
1177        StringBuffer bufRp = new StringBuffer(
1178                Bundle.getMessage("RouterRoutePath", car.getLocationName(), car.getTrackName()));
1179        for (Track track : tracks) {
1180            if (_addtoReport) {
1181                buf.append(Bundle.getMessage("RouterRouteTrain", trains.get(tracks.indexOf(track)).getName()));
1182            }
1183            bufRp.append(Bundle.getMessage("RouterRoutePathTrain", trains.get(tracks.indexOf(track)).getName()));
1184            if (track != null) {
1185                buf.append(Bundle.getMessage("RouterRouteTrack", track.getLocation().getName(), track.getName()));
1186                bufRp.append(
1187                        Bundle.getMessage("RouterRoutePathTrack", track.getLocation().getName(), track.getName()));
1188            } else {
1189                buf.append(Bundle.getMessage("RouterRouteTrack", car.getFinalDestinationName(),
1190                        car.getFinalDestinationTrackName()));
1191                bufRp.append(Bundle.getMessage("RouterRoutePathTrack", car.getFinalDestinationName(),
1192                        car.getFinalDestinationTrackName()));
1193            }
1194        }
1195        car.setRoutePath(bufRp.toString());
1196        addLine(buf.toString());
1197    }
1198
1199    /**
1200     * @param car   The car to which the destination (track) is going to be
1201     *              applied. Will set car's destination if specified train can
1202     *              service car
1203     * @param track The destination track for car
1204     * @return false if there's an issue with the destination track length or
1205     *         wrong track into staging, otherwise true.
1206     */
1207    private boolean finshSettingRouteFor(Car car, Track track) {
1208        // only set car's destination if specified train can service car
1209        Car ts2 = clone(car);
1210        ts2.setDestinationTrack(track);
1211        String specified = canSpecifiedTrainService(ts2);
1212        if (specified.equals(NO)) {
1213            addLine(Bundle.getMessage("TrainDoesNotServiceCar",
1214                    _train.getName(), car.toString(), track.getLocation().getName(), track.getName()));
1215            _status = MessageFormat.format(STATUS_NOT_THIS_TRAIN, new Object[]{_train.getName()});
1216            return false;
1217        } else if (specified.equals(NOT_NOW)) {
1218            addLine(Bundle.getMessage("RouterTrainCanNotDueTo", _train.getName(), car.toString(),
1219                    track.getLocation().getName(), track.getName(), _train.getServiceStatus()));
1220            return false; // the issue is route moves or train length
1221        }
1222        // check to see if track is staging
1223        if (track.isStaging() &&
1224                _train != null &&
1225                _train.getTerminationTrack() != null &&
1226                _train.getTerminationTrack() != track) {
1227            addLine(Bundle.getMessage("RouterTrainIntoStaging",
1228                    _train.getName(), _train.getTerminationTrack().getLocation().getName(),
1229                    _train.getTerminationTrack().getName()));
1230            return false; // wrong track into staging
1231        }
1232        _status = car.setDestination(track.getLocation(), track);
1233        if (!_status.equals(Track.OKAY)) {
1234            addLine(Bundle.getMessage("RouterCanNotDeliverCar", car.toString(),
1235                    track.getLocation().getName(), track.getName(), _status, track.getTrackTypeName()));
1236            if (_status.startsWith(Track.LENGTH) && !redirectToAlternate(car, track)) {
1237                return false;
1238            }
1239        }
1240        return true;
1241    }
1242
1243    /**
1244     * Used when the 1st hop interchanges and yards are full. Will attempt to
1245     * use a spur's alternate track when pulling a car from the spur. This will
1246     * create a local move. Code checks to see if local move by the train being
1247     * used is allowed. Will only use the alternate track if all possible 1st
1248     * hop tracks were tested.
1249     * 
1250     * @param car the car being redirected
1251     * @return true if car's destination was set to alternate track
1252     */
1253    private boolean redirectToAlternate(Car car, Track track) {
1254        if (car.getTrack().isSpur() &&
1255                car.getTrack().getAlternateTrack() != null &&
1256                _nextLocationTracks.indexOf(track) == _nextLocationTracks.size() - 1) {
1257            // try redirecting car to the alternate track
1258            Car ts = clone(car);
1259            ts.setDestinationTrack(car.getTrack().getAlternateTrack());
1260            String specified = canSpecifiedTrainService(ts);
1261            if (specified.equals(YES)) {
1262                _status = car.setDestination(car.getTrack().getAlternateTrack().getLocation(),
1263                        car.getTrack().getAlternateTrack());
1264                if (_status.equals(Track.OKAY)) {
1265                    addLine(Bundle.getMessage("RouterSendCarToAlternative",
1266                            car.toString(), car.getTrack().getAlternateTrack().getName(),
1267                            car.getTrack().getAlternateTrack().getLocation().getName()));
1268                    return true;
1269                }
1270            }
1271        }
1272        return false;
1273    }
1274
1275    // sets clone car destination to final destination and track
1276    private Car clone(Car car) {
1277        Car clone = car.copy();
1278        // modify clone car length if car is part of kernel
1279        if (car.getKernel() != null) {
1280            clone.setLength(Integer.toString(car.getKernel().getTotalLength() - RollingStock.COUPLERS));
1281        }
1282        clone.setTrack(car.getTrack());
1283        clone.setFinalDestination(car.getFinalDestination());
1284        // don't set the clone's final destination track, that will record the
1285        // car as being inbound
1286        // next two items is where the clone is different
1287        clone.setDestination(car.getFinalDestination());
1288        // note that final destination track can be null
1289        clone.setDestinationTrack(car.getFinalDestinationTrack());
1290        return clone;
1291    }
1292
1293    /*
1294     * Creates two sets of tracks when routing. 1st set (_nextLocationTracks) is
1295     * one hop away from car's current location. 2nd set is all other tracks
1296     * (_otherLocationTracks) that aren't one hop away from car's current
1297     * location or destination. Also creates the list of trains used to service
1298     * _nextLocationTracks.
1299     */
1300    private void loadTracksAndTrains(Car car, Car testCar, List<Track> tracks) {
1301        for (Track track : tracks) {
1302            if (track == car.getTrack()) {
1303                continue; // don't use car's current track
1304            }
1305            // note that last could equal next if this routine was used for two
1306            // train routing
1307            if (_lastLocationTracks.contains(track)) {
1308                continue;
1309            }
1310            String status = track.isRollingStockAccepted(testCar);
1311            if (!status.equals(Track.OKAY) && !status.startsWith(Track.LENGTH)) {
1312                continue; // track doesn't accept this car
1313            }
1314            // test to see if there's a train that can deliver the car to this
1315            // destination
1316            testCar.setDestinationTrack(track);
1317            Train train = null;
1318            String specified = canSpecifiedTrainService(testCar);
1319            if (specified.equals(YES) || specified.equals(NOT_NOW)) {
1320                train = _train;
1321            } else {
1322                train = trainManager.getTrainForCar(testCar, null);
1323            }
1324            // Can specified train carry this car out of staging?
1325            if (car.getTrack().isStaging() && specified.equals(NO)) {
1326                train = null;
1327            }
1328            // is the option carry all cars with a final destination enabled?
1329            if (train != null &&
1330                    _train != null &&
1331                    _train != train &&
1332                    _train.isServiceAllCarsWithFinalDestinationsEnabled() &&
1333                    !specified.equals(YES)) {
1334                addLine(Bundle.getMessage("RouterOptionToCarry", _train.getName(),
1335                        train.getName(), car.toString(), track.getLocation().getName(), track.getName()));
1336                train = null;
1337            }
1338            if (train != null) {
1339                _nextLocationTracks.add(track);
1340                _nextLocationTrains.add(train);
1341            } else {
1342                _otherLocationTracks.add(track);
1343            }
1344        }
1345    }
1346
1347    private static final String NO = "no"; // NOI18N
1348    private static final String YES = "yes"; // NOI18N
1349    private static final String NOT_NOW = "not now"; // NOI18N
1350    private static final String NO_SPECIFIED_TRAIN = "no specified train"; // NOI18N
1351
1352    private String canSpecifiedTrainService(Car car) {
1353        if (_train == null) {
1354            return NO_SPECIFIED_TRAIN;
1355        }
1356        if (_train.isServiceable(car)) {
1357            return YES;
1358        } // is the reason this train can't service route moves or train length?
1359        else if (!_train.getServiceStatus().equals(Train.NONE)) {
1360            return NOT_NOW; // the issue is route moves or train length
1361        }
1362        return NO;
1363    }
1364
1365    private static final Logger log = LoggerFactory.getLogger(Router.class);
1366
1367    // all router build report messages are at level seven
1368    protected void addLine(String string) {
1369        addLine(_buildReport, SEVEN, string);
1370    }
1371
1372}